Plutonication 1.0.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package Plutonication --version 1.0.3
NuGet\Install-Package Plutonication -Version 1.0.3
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Plutonication" Version="1.0.3" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Plutonication --version 1.0.3
#r "nuget: Plutonication, 1.0.3"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Plutonication as a Cake Addin
#addin nuget:?package=Plutonication&version=1.0.3

// Install Plutonication as a Cake Tool
#tool nuget:?package=Plutonication&version=1.0.3

Plutonication

c# .NET 6 class class library for network TCP communication. Originaly designed for PlutoWallet project. There is also javascript version in development.

Plutonication is now available for use via NuGet package

Installation

Install Plutonication package in LTS from NuGet. See NuGet.

What it does?

How it is done?

Communication is established via TCP protocol.

  • Wallet acts as client (e.g. PlutoWallet)
  • dApp acts as server
  • Completely P2P. No third party server involved.
  • Note that connection may not be completely secure. Keep that please in mind and don't transfer any sensitive information via Plutonication. We hope that with smart design choices of Plutonication and PlutoWallet there is no need to transfer any sensitive data.

Usecases

Projects

  1. You want to implement dApp (that communicate via TCP with Wallet)
    • Send transactions into cryptowallet where you can sign them.
    • Share publickey with dApp.
    • Send responses.
    • Also possible to send other data, see sending.
  2. You want to implement cryptowallet (that communicate via TCP) with dApps.
  3. You want to implement general app that has on one hand high level of abstraction of TCP communication but on the other hand is extensible.

Why this solution

I. Event driven architecture that is easy to use
  1. Setup methods that handle MessageReceived event which will pop up every time you receive message.
  2. Establish connection with just one call and you are ready to go.
  3. To send message just use SendMessage(PlutoMessage msg).
  4. To close connection just use CloseConnection() method.
II. Connection with authentification by default
  • AcccessCredentials class enable you to convert information (that are must for connection) to URI. That way, it's easy to share. E.g. PlutoWallet use QR code scanning to access dApp's URI.
III. Send data easily
  • Each message has id header MessageCode (e.g. MessageCode.PublicKey). This way you determinate, how to interpret the received data.
  • Sending messages (byte[], string or transaction (see Method class in Ajuna.NetApi)). Optionaly you can serialize any data of your choice and send them as byte[].
  • SendMethod(Method m) makes very convinient to send methods.
IV. Receive & process data
  • As mentioned in above: network stream receiving loop is setup automaticaly.
  • Handle event OnMessageReceived with you custom function, which process the incoming message.
V. Multiplatform solution
  • Plutonication can be implemented on any device that run .NET. This include desktop programs (Windows, MacOS, Linux), mobile apps (android, IOS), smart watches, smart TVs, IOT devices and other.
VI. Adjustable
  • To implement custom solution you can adjust and implement your own variations.
  • If PlutoEventManager is not general or specific enought for your usecase: Extend class PlutoManager the way YOU like it. PlutoManager contains methods that designed to build custom abstraction.

Usage by pure code examples

If you like learn&play with prepared demo:

  1. Create 2 .NET C# cmd apps from our examples bellow
  2. Run Server code
  3. Run Client code
  4. Learn, play & develop

Server (dApp) Demo

Create custom dApp using PlutoEventManager. Example of usecase: PlutoWallet send its publickey to dApp. App then can read information about Wallet. DApp send custom transaction to PlutoWallet. In PlutoWallet project you can sign this transaction with privatekey (or omit).

using Plutonication;

/*
    Serveside (dApp) code example
*/

// PART 1: Instanciate manager ------------------------------------------------
PlutoEventManager manager = new PlutoEventManager();

AccessCredentials ac = new AccessCredentials(
    address: PlutoManager.GetMyIpAddress(),
    port: 8080,
    key: "samplePassword"
);

// PART 2: Listen for incoming connections ------------------------------------
Console.WriteLine("... waiting for connection ...");
await manager.ListenSafeAsync(
    key: ac.Key, 
    port: ac.Port
);

// PART 3: Setup Events -------------------------------------------------------

// bind individual events to a function
manager.ConnectionEstablished += () =>
{
    Console.WriteLine("Connection Established! :)");
};

manager.ConnectionRefused += () =>
{
    Console.WriteLine("Connection Refused! :(");
    return;
};

manager.MessageReceived += () => {
    Console.WriteLine("message received!");

    // Pop oldest message from message queue
    PlutoMessage msg = manager.IncomingMessages.Dequeue();
    
    // Based on MessageCode process you message
    switch (msg.Identifier) {
        case MessageCode.PublicKey:
            Console.WriteLine("Public key received: " + msg.CustomDataToString());
            // send response:
            Task sendingSuccess = manager.SendMessageAsync(MessageCode.Success);
            break;
        // handle other message code as you wish
        // case MessageCode.XXX
        // ... process message object ...   
        // break;

        default:
            Console.WriteLine("Message with unknown code received: " + msg.Identifier);
            Task sendingRefused = manager.SendMessageAsync(MessageCode.Refused);
            break;
    }
};


// PART 4-5: Start receiving messages -----------------------------------------
Task receiving = manager.SetupReceiveLoopAsync();

// PART 4-5: Send messages of your choice -------------------------------------
// For more options and details read documentation
Task sendingMsg = manager.SendMessageAsync(new PlutoMessage(MessageCode.PublicKey, "publicKeySample"));


// ... app is comunicating ... ------------------------------------------------
Console.WriteLine("[After you type connection will stop]");
Console.ReadKey(); // after you press key on your keyboard code goes on ...
// ... app is going to close connection ... -----------------------------------

// PART 4.5 (optional): stop receiving messages -------------------------------
manager.StopReceiveLoop();

// STEP 5: end connection -----------------------------------------------------
manager.CloseConnection();

/* 
Notes 
- If you strugle with events see handling and raising events C#.
- If you strugle with connection establishment check firewall rules (can be problem 
on server side - this code). This case is specific for LAN. On localhost it should
works just fine.
- for more details see documentation on github: www.github.com/cisar2218/Plutonication
*/

Client (CryptoWallet) Demo

Use this code to test serverside behaviour or implement apps that support comunication between Wallet and dApp. PlutoWallet project uses similar code to pair itself with dApps (by scanning QR code - generated by dApp - where URI of AccessCredential is encoded). PlutoWallet at first share its publickey to give dApp information abou itself.

using System.Net;
using Plutonication;

/*
    Clientside (Wallet) code example
*/

// PART 1: Instanciate manager ------------------------------------------------
PlutoEventManager manager = new PlutoEventManager();


// PART 1.5: Create AccessCredentials object
/* 
    ! Credentials params HAVE TO MATCH server side
    - give Uri object generated from Serverside dApp; accessCredentials.ToUri();
    - or manualy set properties (ip address 127.0.0.1 for localhost testing)
*/
// A. Variant with URI
/* AccessCredentials ac = new AccessCredentials( uri ); */

// B. Manualy set credentials
AccessCredentials ac = new AccessCredentials(
    address: IPAddress.Parse("127.0.0.1"),
    port: 8080,
    key: "samplePassword" 
);

// PART 2: Connect to remote server (dApp) ------------------------------------
await manager.ConnectSafeAsync(ac);

// PART 3: Setup Events -------------------------------------------------------

// bind individual events to a function
manager.ConnectionEstablished += () =>
{
    Console.WriteLine("Connection Established! :)");
};

manager.ConnectionRefused += () =>
{
    Console.WriteLine("Connection Refused! :(");
    return;
};

manager.MessageReceived += () => {
    Console.WriteLine("message received!");

    // Pop oldest message from message queue
    PlutoMessage msg = manager.IncomingMessages.Dequeue();
    
    // Based on MessageCode process you message
    switch (msg.Identifier) {
        case MessageCode.Success:
            Console.WriteLine("Code: '{0}'. public key delivered!", msg.Identifier);
            break;
        // handle other message code as you wish
        // case MessageCode.XXX
        // ... process message object ...   
        // break;

        default:
            Console.WriteLine("Message with unknown code received: " + msg.Identifier);
            break;
    }
};


// PART 4-5: Start receiving messages -----------------------------------------
Task receiving = manager.SetupReceiveLoopAsync();

// PART 4-5: Send messages of your choice -------------------------------------
/* Now you are able to send messages of your choice. For details see documentation.
    manager.SendMessage(...);
    await manager.SendMessageAsync(...);
    manager.SendMethod(...);
    await manager.SendMethodAsync(...);
    ...
*/

// ... app is comunicating ... ------------------------------------------------
Console.WriteLine("[After you type connection will stop]");
Console.ReadKey(); // after you press key on your board code goes on ...
// ... app is going to close connection ... -----------------------------------

// PART 4.5 (optional): stop receiving messages -------------------------------
manager.StopReceiveLoop();

// STEP 5: end connection -----------------------------------------------------
manager.CloseConnection();

/* 
Notes 
- if you strugle with events see handling and raising events c#
- if you strugle with connection establishment check firewall rules (can be problem 
on server side). This case is specific for LAN. On localhost it should
work just fine.
- for more details see documentation on github: www.github.com/cisar2218/Plutonication
*/

Usage by object

PlutoEventManager class

PlutoEventManager class will be your main class used from Plutonication package if you want to build simple dApp that communicate with PlutoWallet. We recommend run and play with code examples and then study additional details in documentation.

It has the highest level of abstraction, if you want more flexible solution, see more flexible objects.

Try to establish connection

To establish connection instantiate PlutoEventManager and then make call specific for your usecase (client (wallet) / server (dApp)).

PlutoEventManager manager = new PlutoEventManager();
// ... make your connection call bellow ...
Server (dApp)

To start listening as server use async method bellow:

string key = "samplePassword";
int port = 8080;
await manager.ListenSafeAsync( key: key, port: port );

Server will be started at port that you have specified. Client can connect with password (which matches key). Optionaly you can specify timeout param which default value is 1 minute.

int timeoutMiliseconds = 3*60_000; // 3 minutes
await manager.ListenSafeAsync("samplePassword", 8080, timeoutMiliseconds);
Client (Wallet)
  • Make sure server is running before you attempt connection.
  • Make sure that address, port and key is matching.
  • To connect with server use async method bellow.
string key = "samplePassword";
int port = 8080;
await manager.ConnectSafeAsync( key: key, port: port );

ConnectionEstablished event is raised when connection established. ConnectionRefused event is raised when connection refused.

Sending
  • Make sure connection is established. And other side is ready to receive messages.
  • To send messages 2 main methods are used: sendMessage(), sendMethod(). See they variations bellow to find your usecase.
Overview

Data that you are sending are stored in object called PlutoMessage. Requirment for sending messages is that connection is established. That means PlutoEventManager is instanciated like so:

PlutoEventManager manager = new PlutoEventManager();
Sending PlutoMessage object with publickey
  1. Create PlutoMessage object like so (see PlutoMessage variations).
MessageCode id = MessageCode.PublicKey; // identifier for your key
string publickey = "PublicKeySample"; // here goes your key
PlutoMessage msg = new PlutoMessage(id, publickey);
  1. Send created PlutoMessage.
manager.SendMessage(msg);
Sending MessageCode object alone

MessageCode is convient way to send response to certain message (see MessageCode overview table to more variants):

MessageCode resCode = MessageCode.Success; // your response code
PlutoMessage msg = new PlutoMessage(resCode);
  1. Send created PlutoMessage.
manager.SendMessage(msg);
Sending Ajuna.NetApi Method
Sending Async
  • all 'SendSomething' methods implement their async version
Receiving

Receiving messages is based on event handeling. On each incoming message:

  1. Incoming message is added to IncomingMessages queue
  2. MessageReceived event is triggered
Common setup

I supose you have instanciated PlutoEventManager and established connection.

  1. Set which function will execute on message received event.
  2. Deque and process message (inside your custom method which handles incoming messages)
  3. Start receiving messages by calling SetupReceiveLoopAsync() method.
// PlutoEventManager manager = new PlutoEventManager();
// 
// ...

manager.MessageReceived += () => {
    Console.WriteLine("message received!");

    // Pop oldest message from message queue
    PlutoMessage msg = manager.IncomingMessages.Dequeue();
    
    // Based on MessageCode process you message
    switch (msg.Identifier) {
        case MessageCode.Success:
            Console.WriteLine("Code: '{0}'. public key delivered!", msg.Identifier);
            break;
        // handle other message code as you wish
        // case MessageCode.XXX
        // ... process message object ...   
        // break;

        default:
            Console.WriteLine("Unknown code: " + msg.Identifier);
            break;
    }
};

Task receiving = manager.SetupReceiveLoopAsync();

Messages are stored in queue. By calling the event bellow, put the oldest message out of the queue.

PlutoMessage msg = manager.IncomingMessages.Dequeue();
Processing of incoming messages

Messages are in form of PlutoMessage object.

To interpret data in form of byte[], you need to read the Identifier member of message. Based on msg.Indentifier process message like so:

PlutoMessage msg = manager.IncomingMessages.Dequeue();
switch (msg.Identifier) {
    // handle short responses
    case MessageCode.Success:
    case MessageCode.Refused:
    case MessageCode.FilledOut:
        Console.WriteLine("Code: '{0}'!", msg.Identifier);
        // ... process message here ...
        break;
    // handle incoming string data
    case MessageCode.PublicKey:
        Console.WriteLine("Code: '{0}'. public key delivered!", msg.Identifier);
        string publicKey = msg.CustomDataToString();
        // ... process publiKey here ...
        break;
    // handle incoming methods to be sign
    case MessageCode.Method:
        Console.WriteLine("Code: '{0}'. Method delivered!", msg.Identifier);
        Method m = msg.GetMethod();
        // ... process method here ...
        break;
    //...

    // handle unknown message codes
    dafault:
        Console.Writeline("Unknown message code + ", msg.Indetifier);
        break;
}
List of events
ConnectionEstablished event and ConnectionRefused event

Before you call method which tries to establish connection with other app setup these events.

// bind individual events to a function
// this event will fire when apps are connected
manager.ConnectionEstablished += () =>
{
    Console.WriteLine("Connection Established! :)");
    // ... execude any code here ...
};

// this event will fire when other side refuse connection
manager.ConnectionRefused += () =>
{
    Console.WriteLine("Connection Refused! :(");
    // ... execude any code here ...
};

You don't have to use lambda function ofc.:

manager.ConnectionRefused += MyHandleFunction;

// ... 

public void MyHandleFunction() {
    Console.WriteLine("Connection Refused! :(");
    //... handle here ...
}

Don't know about events? Check official documentation about events

SetupReceiveLoopAsync() method
MessageReceived event

Before you call method which receives messages setup this event:

manager.MessageReceived += () => {
    // ... handle message here ...
};

See Common setup and complete examples for complete examples.

Don't know about events? Check official documentation about events

Closing connection

To close connection manually call PlutoEventManager's method CloseConnection. From now on connection will be closed.

PlutoEventManager manager = new PlutoEventManager();
// ... 
manager.CloseConnection();

PlutoMessage class

  • Class that make abstraction over data that are send and received.
  • Properties:
    • MessageCode Identifier - header which is used to identify type of message received.
    • Byte[] CustomData - message itself serialized to bytes
  • Methods:
    • string CustomDataToString() - used to convert received data to string (e.g. publickey).
    • byte[] ToByteArray() - used to align Identifier and CustomData together into one byte array before sending. User typicaly don't need to use this method.
    • Method GetMethod() - When received CustomData are serialized Method, you can convert them with this method. E.g.:
      if (incomingMessage.Identifier == MessageCode.Method) {
          Method m = incomingMessage.GetMethod();
          // ...
      }
      
      Parameters
    1. MessageCode Identifier
    2. byte[] CustomData
How to create
PlutoMessage msgToSend = new PlutoMessage(MessageCode.XXX, dataToSend);

Now you are able to send message with PlutoEventManager. To receive see receiving messages section.

Variations

You can use different types of pluto messages.

  1. Method
// define method with your values
byte moduleIndex = 0;
byte callIndex = 1;
byte[] parameters = new byte[3] {4,8,1};

Method method = new Method(moduleIndex, callIndex, parameters);
// method with empty params also possible like this: 
Method methodEmptyParams = new Method(moduleIndex, callIndex, new byte[0]);

// send with PlutoEventManager or PlutoManager class
manager.SendMethod(method);

Where manager is PlutoEventManager.

  • Unpack method:
// incomingMessage is message you have received earlier
PlutoMessage incomingMessage;
// is incomingMessage method?
if (incomingMessage.Identifier == MessageCode.Method) {
    // YES, ITS METHOD
    Method m = incomingMessage.GetMethod();
    // ... process method ...
} else { /* IS NOT METHOD */ }
  1. String (publickey)
  • typicaly used to send publickey from wallet to dApp, but can send any string data.
  • Create PlutoMessage with containing publickey
string publickey = "YourKeyGoesHere";
var keyMsg = new PlutoMessage(MessageCode.PublicKey, publickey);
// to send with PlutoEventManager / PlutoManager
manager.SendMessage(keyMsg);
  • Unpack PlutoMessage with publickey
// incomingMessage is message you have received earlier
PlutoMessage incomingMessage;
// is incomingMessage method?
if (incomingMessage.Identifier == MessageCode.PublicKey) {
    // YES, ITS PUBLICKEY
    string publickey = incomingMessage.CustomDataToString();
    // ... process publickey here ...
} else { /* IS NOT PUBLICKEY */ }
  1. Byte[]
  • you can also serialize and send any type of data. Give it propper header you can process it when received
var byteMsg = new PlutoMessage(MessageCode.Method, new byte[] {4,8,1});
  1. MessageCode
  • You can send MessageCode alone like so. This is typical for response messages.
var respose = new PlutoMessage(MessageCode.Success);
  • Handle responses like so
// incomingMessage is message you have received earlier
PlutoMessage incomingMessage;

if (incomingMessage.Identifier == MessageCode.Success) {
    // ... handle response here ...
} else if (incomingMessage.Identifier == MessageCode.Refused){
    // ... handle response here ...
} else if ( /* other codes */ ){
    // ... handle response here ...
}

AccessCredentials class

  • This class is used to store information about dApp that are needed for connection.
  • Properties:
    • string Address - LAN ip address in string format (where to connect).
    • int Port - Port number in int format (where to connect).
    • string Key - Acts like password or auth token. Has to match on server/client side. Can be generated or setted manualy.
    • string Name (optional) - dApp name. This way wallet with dApp's AccessCredentials object know name of dApp without connecting.
    • string Icon (optional) - Url that leads to image of dApp. E.g icon.
  • Methods:
    • static string GenerateKey(int keyLen=30) - generates key for you. THe keyLen argument is optional (default value is 30).
    • Uri ToUri() - Converts properties of AccessCredentials object to URI address.
      • Uri address can be (and in case of PlutoWallet is) encoded to QR code.
      • AccessCredentials has constructor that accepts Uri object:
        var credentials = AccessCredentials(Uri uri);
        

MessageCode enum

MessageCode class serves as a header of messages. When receiving message we have to interpret incoming bytes that why header convention is essential in this type of network communication.

  • We plan to add more codes in the future.
  • You can implement your own new code.
  • See table of basics MessageCodes bellow.
MessageCode Value Convinient use
PublicKey 0 This message contains (string) public key.
Success 1 Response that confirms succesful operation.
Refused 2 Response that your message was refused.
Method 3 This message contains Ajuna Method.
Auth 4 This message contains key.
FilledOut 5 Other side is overloaded. Try send message later.

See PlutoMessage class for common message format.

More flexible objects

PlutoManager
  • This part of documentation is brief. Can be extended in the future.
  • For custom implementation of Plutonication, extend PlutoManager to your needs. E.g.:
    • Custom receving loop
    • Custom authentification
  • Properties
    • protected const int DEFAULT_READSTREAM_TIMEOUT = 1000; // miliseconds
    • protected TcpClient Client { get; set; }
    • protected int Port { get; set; }
    • protected IPAddress ServerAddress { get; set; }
  • Methods
    • public abstract void CloseConnection();
    • public PlutoMessage ReceiveMessage(int timeoutMiliseconds = DEFAULT_READSTREAM_TIMEOUT)
    • public async Task<PlutoMessage> ReceiveMessageAsync(int timeoutMiliseconds = DEFAULT_READSTREAM_TIMEOUT)
    • public void SendMethod(Method transaction)
    • public async Task SendMethodAsync(Method transaction)
    • public void SendMessage(MessageCode code)
    • public void SendMessage(PlutoMessage message)
    • public async Task SendMessageAsync(MessageCode code)
    • public async Task SendMessageAsync(PlutoMessage message)
    • public static IPAddress GetMyIpAddress()
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.3.6 87 3/28/2024
2.3.5 81 3/21/2024
2.3.4 86 3/21/2024
2.3.3 93 3/19/2024
2.3.2 92 3/19/2024
2.3.1 94 2/20/2024
2.3.0 108 2/20/2024
2.2.1 106 2/9/2024
2.2.0 88 2/4/2024
2.1.4 83 2/3/2024
2.1.3 76 2/2/2024
2.1.1 86 1/28/2024
2.1.0 185 11/4/2023
2.0.6 123 8/19/2023
2.0.5 142 8/19/2023
2.0.4 121 8/19/2023
2.0.3 139 8/19/2023
2.0.2 129 8/18/2023
2.0.1 132 8/18/2023
2.0.0 107 8/18/2023
1.0.4 210 3/17/2023
1.0.3 198 3/17/2023
1.0.2 226 2/17/2023
1.0.1 230 2/16/2023
1.0.0 230 2/13/2023