Structure

Server - serves both as server and database which stores messages of all clients. Client - connects to server and can send private messages on arbitrary address, corresponding to node address in given network. Also it serves for showing and receiving messages which were sent by other clients on this address.

Writing your own python service using cellframe-sdk (t-dApp)

Server Part

The first thing is to define a constant which contains number UID (Unique-ID) of the service. It must be unique and not lower than 100. Values from 0 to 100 are reserved for cellframe-network services. In this example we chose number 477 because we like it and it’s unique. It is denoted by SERVICE-UID variable.

There is a list of imported functions from our SDK and default libraries. Below there is chosen UID.

from DAP.Core import logIt
from CellFrame.Network import Service, ServiceUID
from CellFrame.Chain import GlobalDB
from datetime import datetime
import json
 
SERVICE_UID = 477

logIt - function which writes textual data in main log cellframe-node

Service, ServiceUID - modules which encapsulate work with internal service representation and UID

GlobalDB - module for working with cellframe-node built-in distributed key-value database Global-DB

To simpify forward work we will describe python wrapper above C-wrappers which work with database. In this example it calls GDBTable and includes main functions for working with tables in Global-DB.

class GDBTable:
    def __init__(self, table):
        self.table = table
    def set(self, key, value):
        GlobalDB.set(key, self.table, value.encode("utf-8"))
    def get(self, key, default):
        res =  GlobalDB.get(key, self.table)
        if not res:
            return default
        return res.decode("utf-8")
    def delete(self, key):
        GlobalDB.delete(key, self.table)
    def group_list(self):
        return GlobalDB.grLoad(self.table) or []

set - inserts string value (previously converted into bytes) in table using key

get - takes data from table by key and convert it into string value

delete - deletes key from table

group_list - returnes list of all objects-records in specified table

Pay attention that key is always a string while data is arbitrary set of bytes. Because in our service we work with textual messages and textual data, we implicitly convert them into bytes inside this wrapper.

List of support functions

safe_json_loads - function which safely downloads json file. If data is correct it returns dict, otherwise - None

def safe_json_loads(data):
    try:
        return json.loads(data.decode("utf-8"))
    except:
        return None

to_bytes - decoding strings into bytes

def to_bytes(data):
    return data.encode("utf-8")

json_dump - serialization of JSON object into string

def json_dump(data):
    return json.dumps(data)

Function init - entering point into plugin code

init - is the first function which is called when cellframe-node is starting and python plugins (t-dApps) are downloading.

Service object is being initialized in our case. Constructor receives UID, service name and set of callback-functions, which will be called by this service if needed. For proper functionality of service we need to decrive several callbacks-functions. At this moment they are empty (related to payment methods) but one main custom_data is active.

def init():
 
    logIt.notice("=== Start mail-service plugin ===")
    srv_object = Service(
        ServiceUID(SERVICE_UID),
        "mail_service",
        requested,
        response_success,
        response_error,
        next_success,
        custom_data
    )
    return 0

List of callback-functions for service working

requested - payment callback for signing the first check

def requested(srv, usage_id, client_remote, data):
    print("=== requested ==")
    logIt.info("[server] func requested")
    return 0

response_success - signature is completed successfully

def response_success(srv, usage_id, client_remote, data):
    print("=== success ==")
    logIt.notice("[server] func response success")
    return 0

response_error - mistake has emerged in signing process

def response_error(srv, usage_id, client_remote, data):
    print("=== error custom_data input ==")
    logIt.warning("[server] func response error")

next_success - payment callback for signing next checks

def next_success(srv, usage_id, client_remote, data):
    print("=== next_success ==")
    logIt.notice("[server] func next success")
    return 0

In this service we don’t use payment system for simplify, but in the next cases payment callbacks also will be meanful.

custom_data - is a callback with handler of arbitrary data which were sent by encrypted connection. As in other callbacks, the first parameters being transmitted are service object, client identificator for payment, structure for gaining information about client and byte array of received data. In this case we use only variable with data.

Our service protocol assumes exchange in JSON format, that’s why we make an attempt to deserializate and return error to client if deserialization wasn’t successfull. If format is correct - we extract “method” from clients request which containts something client needs from service, and then we check can our service accomplish such “method”. In case everything went well, the list of handlers is formed, and we request specified handler from list depends on chosen method.

As a result we send that which specified handler has returned. It must be a bytes object which is being sent back to client.

def custom_data(srv, usage_id, client_remote, data):
 
   print("=== SRV custom_data input ==")
 
   request  = safe_json_loads(data)
   if not request:
       return to_bytes(json_dump({"status":"invalid_request"}))
   method = request.get("method")
   if not method:
       return to_bytes(json_dump({"status":"no_method"}))
 
   handlers = {"hello": method_hello,
               "store": method_store,
               "list": method_list,
               "get": method_get}
   if method not in handlers:
       return to_bytes(json_dump({"method":method,
                                  "status":"unknown_method"}))
   return handlers[method](request)

List of handlers for different requests

method_hello - method which reports to client that connection was established successfully

def method_hello(req):
    return to_bytes(json_dump({"method":"hello",
                                "status":"success"}))

method list - this method receives JSON object from client, from which is extracted field “me”. This field contains client node address, if there is no such field - the error is being returned. If everything is good then begins creation of copy of previously described class for working with GDB where table for specified user is opened. After this there occurs reading of all records and returning of the list to client. It is supposed that in database we store properly serialized messages.

def method_list(req):
    me = req.get("me", None)
    if not me:
        return to_bytes(json_dump({"method":"store",
                                    "status":"no_sender"}))
    MAILBOX = GDBTable(f"local.mailserver.mailbox.{me}")
    items = [safe_json_loads(i.value) for i in MAILBOX.group_list()]   
    return to_bytes(json_dump({"method":"list",
                                "status":"success", "items": items}))

method_get - performs almost the same function as “method_list”, but also affords to get content by using specified index

def method_get(req):
    me = req.get("me",None)
    if not me:
        return to_bytes(json_dump({"method":"get",
                                    "status":"no_sender"}))
    mid = req.get("id", None)
    if mid == None:
        return to_bytes(json_dump({"method":"get",
                                    "status":"no_id"}))
    MAILBOX = GDBTable(f"local.mailserver.mailbox.{me}")
    msg = MAILBOX.get(str(mid), None)
    if not msg:
        return to_bytes(json_dump({"method":"get",
                                    "status":"no_msg"}))
    try:
        msg = safe_json_loads(msg.encode("utf-8"))
        return to_bytes(json_dump({"method":"get",
                                   "status":"success", **msg}))
    except:
        traceback.print_exc()

method_store - saves message in database. It alsi extracts the same field “me” as a sender, the very message and reciever rcpt. During message sending there adds new record in GDB table of receiver with new index, message, receiver and sending time.

def method_store(req):
    sender = req.get("me",None)
    if not sender:
        return to_bytes(json_dump({"method":"store",
                                    "status":"no_sender"}))
    message = req.get("message",None)
    if not message:
        return to_bytes(json_dump({"method":"store",
                                    "status":"no_message"}))
    rcpt = req.get("rcpt",None)
    if not rcpt:
        return to_bytes(json_dump({"method":"store",
                                    "status":"no_rcpt"}))
    try:
        MAILBOX = GDBTable(f"local.mailserver.mailbox.{rcpt}")
        new_id = len(MAILBOX.group_list())
        MAILBOX.set(str(new_id), json_dump( {"id": new_id,
                                        "message" :  message,
                                        "timestamp": int(datetime.timestamp(datetime.now())),
                                        "from": sender}))
    except:
        traceback.print_exc()
    return to_bytes(json_dump({"method":"store",
                                "status":"success"}))

Client part

In client as in service the first thing to do is import set of modules from cellframe SDK and specify UID of the service.

There we have a list of necessary imported library functions from our SDK.

from DAP.Core import logIt
from CellFrame import AppCliServer
from CellFrame.Network import Net, ServiceUID, ServiceClient
from CellFrame.Chain import GlobalDB
from DAP.Core import logIt
from threading import Event
from datetime import datetime
import json
 
SERVICE_UID = 477

logIt - function which writes textual data in main log cellframe-node

AppCliServer - module for working with cellframe-node-cli on the client side

Net, Service, ServiceUID - modules which work with network and UID

GlobalDB - module for working with database

Because of client interaction with server performs asynchronously while using callbacks which server uses too, it is necessary to import synchronization mechanisms.

As we did on the server side we need to describe wrapper-class for working with global database.

class GDBTable:
    def __init__(self, table):
        self.table = table
    def set(self, key, value):
        GlobalDB.set(key, self.table, value.encode("utf-8"))
    def get(self, key, default):
        res =  GlobalDB.get(key, self.table)
        if not res:
            return default
        return res.decode("utf-8")
    def delete(self, key):
        GlobalDB.delete(key, self.table)
    def group_list(self):
        return GlobalDB.grLoad(self.table) or []

set - inserts string value (previously converted into bytes) in table using key

get - takes data from table by key and convert it into string value

delete - deletes key from table

group_list - returnes list of all objects-records in specified table

Let’s describe two tables in global-DB with names config and aliases for storing connection parameters and list of aliases.

CONFIG = GDBTable("local.mailclient.config")
ALIASES = GDBTable("local.mailclient.aliases")

List of additional functions

safe_json_loads - function for safe downloading of JSON file. If data is correct it returns pyObject in string format

def safe_json_loads(data):
    try:
        return json.loads(data.decode("utf-8"))
    except:
        return None

to_bytes - decoding string into bytes

def to_bytes(data):
    return data.encode("utf-8")

json_rump - receiving string from JSON object

def json_dump(data):
    return json.dumps(data)

find_arg - parsing of command line args: realizes searching by argv parameters of some option val, in case of success returnes true

def find_arg(val, argv):
    for i, arg in enumerate(argv):
        if arg == val:
            return True
    return False

find_arg_value - parsing of command line args: also realizes searching by argv of some option val and returnes it’s value

def find_arg_value(val, argv):
    for i, arg in enumerate(argv):
        if arg == val:
            if len(argv) > i+1:
                return argv[i+1]
    return None

Mechanism of forming and sending requests on server

This mechanism consists of 2 functions: make_service_requests_sync_cfg и make_service_request_sync.

make_service_requests_sync_cfg - receives dictionary as input with data, which is required to send to service (request,data) and ID of cli-client (indexStrReply). There could be many requests to node simultaneously so it’s necessary to know which is to asnwer.

This function reads configuration parameters for connection to service (IP, port, network name) from “CONFIG” table in GDB. After data extraction there occurs validity check, and then if everything is good, is requested the second function which related with request sending and answer receiving.

def make_service_request_sync_cfg(data, indexStrReply):
    addr    =       CONFIG.get("RELAY_ADDRESS", None)
    port    =   int(CONFIG.get("RELAY_PORT",    None))
    netname =       CONFIG.get("NET_NAME",      None)
    if None in [addr, port, netname]:
        AppCliServer.setReplyText("Relay not configured!", indexStrReply)
        return
    return make_service_request_sync(data, addr, port, netname, indexStrReply)

make_service_request_sync - is a function which realizes blocking performation of commands by service. Gets all the parameters which previous function also get (data, addr, port, network name, indexStrReply)

This function is synchronous because service operates with set of callbacks which are requested by different events. To synchronize and put in order sequence request-answer, we will use mechanism of multi-threaded synchronization “Event”. This primitive allows us to atomically check flag state, by which we are going to define was there any received data from server or not.

In the very function on the first stage we create network object using name transmitted to function. Then we check is there network in the node in which client want to interact. After this we extract address of local node from this object. Local node address - is “me” in JSON-request by which server-provider defines from where he is supposed to read messages and who is the sender.

def make_service_request_sync(data, ip, port, netname, indexStrReply):
net = Net.byName(netname)
    if not net:
        AppCliServer.setReplyText(f"No such network [{netname}]!", indexStrReply)   
        return 
    #tell server who are we
    data["me"] =  str(net.getCurAddr())

After this we will create class with data CallbackPayload which object is going to be transmitted in every callback of our service-client. In this class we will store fields-properties which are necessary inside callbacks we are going to call in future.

    class CallbackPayload:
        def __init__(self, request, netname, ip, port, evt, closed_evt):
 
            self.request = request
            self.netname = netname
            self.ip = ip
            self.port = port
            self.indexStrReply = indexStrReply
            self.response = "",
            self.has_response = evt
            self.closed = closed_evt

request - dictionary with request to server (after this it will be serialized in byte array)

netname - string name of network in which request was sent

ip - address where request was sent

port - port where request was sent

indexStrReply - number of cli-client in case if callback would like to give it message

response - future answer from service

has_response - event that says about answer appearance

closed - were there connections break from the server side

Then create the very object payload in which we will transmit all functions parameters were given in input.

payload = CallbackPayload(data, netname, ip, port, Event(), 
Event())

The next step is creating object of our SDK - ServiceClient, name it client. This object works with address and specified port and it will call specified callback on every event. Into this object we transmit previously created payload which will be used in every callback.

client = ServiceClient(net, ip, port,    callback_connected,
                                        callback_disconnected,
                                        callback_check,
                                        callback_sign,
                                        callback_success,
                                        callback_error,
                                        callback_data,
                                        payload)

At the moment of client creation cellframe-node initializes opening of stream with service node using given IP address. If connection was successfull, node will use callback connected. This all occurs in cellframe-node stream, not in the stream of python-plugin, that’s why after client creation we wait for event has_response which will be created in one of callbacks.

payload.has_response.wait()

Waiting answer from server and it’s following handling realizes this way.

The very process of request sending is built in the following way: during installation of encrypted P2P connection between client and server is requested connected callback inside which is occured transmition request on server. As soon as server handled request and sent answer - there will be requested callback_data from the client, in which will be established response receiving flag has_response and wrote the very answer in corresponding field of structure payload.

As answer is gained, method wait turns off blockage and then will be requested method which closes stream for client.

client.close()

After this is requested callback disconnected from the service side and the network session can be considerate ended. Then given answer on request will return back in command handler which has initiated request sending.

return safe_json_loads(payload.response)

List of callbacks for client’s work

callback_connected - in this callback is formed request into service with data from client including service UID. In case service and client UID are not the same then the procedure will be done with mistake.

def callback_connected(serviceClient, payload):
    logIt.notice("Python client connected")
    ch_uid = ServiceUID(SERVICE_UID)
    data = json_dump(payload.request).encode("utf-8")
    print(data)
    serviceClient.write(ch_uid, data)

callback_disconnected - is requested in case if provider has closed session and broken connection, it establishes answer receiving flag and closes connection stream with service.

def callback_disconnected(serviceClient, payload):
    logIt.notice("Python client disconnected")
    payload.has_response.set()
    payload.closed.set()

callback_check - callback for checking connection with server “a la” KeepAlive (not in usage, will be reworked)

def callback_check(serviceClient, payload):
    logIt.notice("Python client successfully checked the service")

callback_sign - callback which is requested when server uses service, if not then error occurs (also not in usage)

def callback_sign(serviceClient, txCondRec, payload):
    logIt.notice("Siging receipt by python client")

callback_success - callback for signing check of payment serivce (not in usage)

def callback_success(serviceClient, txCondHash, payload):
    logIt.notice("Python client successfully requested the service")

callback_error - callback which messages error code if such occurs, also establishes answer receiving flag

def callback_error(serviceClient, errorNum, payload):
    logIt.warning(f"Python client got error {errorNum:#x}")
    payload.has_response.set()

callback_data - callback for receiving answer from service with following data recording in payload and establishing answer receiving flag

def callback_data(serviceClient, data, payload):
    logIt.notice(f"Python client custom data readback!")
    payload.response = data
    payload.has_response.set()

init function

Function init performs client initialization. Unlike from server, this client includes only task for command line handlers through AppCliServer, with it’s help it’s easy to inject new service commands in default request handler cellframe-node-cli.

def init():
    logIt.notice("Init demoClient")
    userProfile = None
    AppCliServer.cmdItemCreate("mail", cmd_message_srv_client, "mail-server client commands",
"""
 
    Set up a mail-server node. Your node should be in the same net!
        mail relay -ip <ip> -port <port> -net <net>
    Send a message
        mail send -m MESSAGE -rcpt NODE_ADDRESS | ALIAS
    Create alias:
        mail alias NODE_ADDRESS ALIAS
    Working with messages.
        mail list
        mail get ID
""")
    return 0

AppCliServer.cmdItemCreate - allows to register new method for cli.

  • The first argument is name of the new method - “mail”
  • The second argument is callback which handles all requests from user, requested for “mail”
  • The third arguments is full name of the new method
  • The fourth argument is “help” of the new method

List of available cli commands

help or ? - shows commands help relay - request on client configuration (connection to server with IP and port in denitire cellframe network)

relay -ip <ip> -port <port> -net <net>

send - to send messsage on definite address or alias

send -m MESSAGE -rcpt NODE_ADDRESS | ALIAS

alias - to create alias, for example, name “Test” corresponds to address ABSC:ASSF:XXXX:XXXX, in this case instead of specify node address every time it’s enough just specify the very alias “Test”

mail alias NODE_ADDRESS ALIAS

list - returnes list with messages ID’s

list

get - opens content of chosen message usin ID from the list

get ID

If request “help mail” comes then client receives “help” which is decribed in AppCliServer.cmdItemCreate in init function. If request ”? mail” comes then client receives “help” from callback.

Callback for handling request from client

Callback for request handling takes as input set of parameters argv, which are returned by node after dealing with cli-request and it’s ID indexStrReply

def cmd_message_srv_client(argv, indexStrReply):
    hlp = """
    Set up a mail-server node. Your node should be in the same net!
        mail relay -ip <ip> -port <port> -net <net>
    Send a message
        mail send -m MESSAGE -rcpt NODE_ADDRESS | ALIAS
    Create alias:
        mail alias NODE_ADDRESS ALIAS
    Working with messages.
        mail list
        mail get ID
"""
    if find_arg("?",argv):
        AppCliServer.setReplyText(hlp, indexStrReply)
        return
    commands = {"relay": command_relay,
                "send": command_send,
                "alias": command_alias,
                "list": command_list,
                "get": command_get }
    if len(argv) < 2 or argv[1] not in commands:
        AppCliServer.setReplyText("Invalid command", indexStrReply)
        return
    commands[argv[1]](argv[2:], indexStrReply)

In the very callback on the first stage is checked help request in condition if find_arg(“?”,argv) where is searched symbol ”?” among accepted arguments. If there were found concidence specified command AppCliServer.setReplyTest() returnes to node corresponding text and serial number of request. The following text then is transmitted as answer to cli command.

If there wasn’t any help request then we will cerate object with requests and their handlers.

commands = {"relay": command_relay,
                "send": command_send,
                "alias": command_alias,
                "list": command_list,
                "get": command_get }

After this all is checked validity of transmitted arguments and availability of commands from “commands” dictionary among them, in case of mistake client receives corresponding message.

AppCliServer.setReplyText("Invalid command", indexStrReply)

If everything is good, then is performed request to “commands” dictionary and calling of corresponding handler.

commands[argv[1]](argv[2:], indexStrReply)

Firstly corresponding command (relay,send, etc)argv[1] is extracted, then all arguments (-ip,-port, etc) argv[2:] for this command are transmitted together with ID of cli-client indexStrReply.

Command handlers

command_relay - configuration method for connection to service. Receives as input list of arguments (which user has transmitted to cli) and extracts next parameters ip, port, network name. All parameters are checked on validity before they will be used. In case of mistake - user will get corresponding answer in terminal.

After all checks request on connection to service is fromed using method make_service_request_sync which returns answer about successfull operation. If connection request came with error or didn’t come at all then corresponding message is output, otherwise, we message that everything is good and write network settings in GDB table which we called “CONFIG”.

def command_relay(args, indexStrReply):
    ip = find_arg_value("-ip",args)
    if not ip:
        AppCliServer.setReplyText("IP param not found",indexStrReply)
        return
    port = find_arg_value("-port", args)
    if not port:
        AppCliServer.setReplyText("port param not found", indexStrReply)
        return
    netname = find_arg_value("-net", args)
    if not netname:
        AppCliServer.setReplyText("net param not found", indexStrReply)
        return
    if not Net.byName(netname):
        AppCliServer.setReplyText(f"No such network [{netname}]!", indexStrReply)   
        return
    net = Net.byName(netname)
    if not net:
        AppCliServer.setReplyText(f"Unknown network {netname}", indexStrReply)
        return
    res = make_service_request_sync({
                                        "method":"hello",
                                    },
                                    ip,
                                    int(port),
                                    netname,
                                    indexStrReply)
    if not res:
        AppCliServer.setReplyText(f"Relay  {ip}:{port} [{netname}] not responding.", indexStrReply)   
        return
    status = res.get("status", None)
    if not status or status != "success":
        AppCliServer.setReplyText(f"Relay  {ip}:{port} [{netname}****] respondes to hello with [{status}] status.", indexStrReply)   
        return
    CONFIG.set("RELAY_ADDRESS", ip)
    CONFIG.set("RELAY_PORT", str(port))
    CONFIG.set("NET_NAME",netname)
    AppCliServer.setReplyText(f"Relay service configured as{ip}:{port}[{netname}], your address is:{str(net.getCurAddr())},indexStrReply)

command_send sends message to node address or alias. Extracts fields with message text -m and address/alias of receiver -rcpt (all parameters are also checked on validity) from set of parameters args. In case there was specified “alias” then we take address from ALIAS table by name, otherwise we take value from rcpt field as address.

rcpt =  ALIASES.get(rcpt, None) or rcpt

After all preparations we form request in service with help of the command make_service_request_sync, read the answer and return result in terminal.

def command_send(args, indexStrReply):
    msg = find_arg_value("-m", args)
    if not msg:
        AppCliServer.setReplyText("-m param not found", indexStrReply)
        return
    rcpt = find_arg_value("-rcpt", args)
    if not rcpt:
        AppCliServer.setReplyText("-rcpt param not found", indexStrReply)
        return
    rcpt =  ALIASES.get(rcpt, None) or rcpt
    res = make_service_request_sync_cfg({
                                        "method":"store",
                                        "message": msg,
                                        "rcpt": rcpt
                                    },
                                    indexStrReply)
    status = res.get("status", None)
    if not status or status != "success":
        AppCliServer.setReplyText(f"Relay respondes with [{status}] status.", indexStrReply)   
        return
    AppCliServer.setReplyText(f"Success", indexStrReply)

command_alias allows to create couple “NODE_ADDRESS - NAME”. On the first stage it checks validity of trasmitted parameters, and if everything is good then writes to table ALIASES new value or replaces oln one.

def command_alias(args, indexStrReply):
    if len(args) < 2:
        AppCliServer.setReplyText("No alias or node address specified", indexStrReply)
        return
    ALIASES.set(args[0], args[1])
    AppCliServer.setReplyText(f"Recepient {args[1]} is now known as {args[0]}", indexStrReply)

command_list - makes request in service to get list of messages for local client (you). At this moment request make_service_request_sync is formed and then both it’s status and content is checked. If list is empty then client gets message “Mailbox is empty”. If it is not empty then corresponding messages are output in string format with next content: ID (ordered number of message), from, timestamp. These messages user then receives.

def command_list(args, indexStrReply):
    res = make_service_request_sync_cfg({
                                        "method":"list",
                                    },
                                    indexStrReply)
    status = res.get("status", None)
    if not status or status != "success":
        AppCliServer.setReplyText(f"Relay respondes with [{status}] status.", indexStrReply)   
        return
    resp_str = ""
    if not res["items"]:
        resp_str = "Mailbox is empty"
    for item in res["items"]:
        resp_str += f"{item['id']}: from {item['from']} at {datetime.fromtimestamp(item['timestamp'])}\n"
    AppCliServer.setReplyText(resp_str, indexStrReply)

command_get - command for reading definite message by ID. On the first stage is checked validity of arguments, and then is formed request into service make_service_request_sync, status is extracted. If everything is good then user receives message in next format - From: <node addr or alias> at <timestamp> <message>

def command_get(args, indexStrReply):
    if len(args) < 1:
        AppCliServer.setReplyText(f"No message-id provided", indexStrReply)   
        return
    res = make_service_request_sync_cfg({
                                        "method":"get",
                                        "id": int(args[0])
                                    },
                                    indexStrReply)
    status = res.get("status", None)
    if not status or status != "success":
        AppCliServer.setReplyText(f"Relay respondes with [{status}] status.", indexStrReply)   
        return
    resp_str = f"From: {res['from']} at {datetime.fromtimestamp(res['timestamp'])}\n{res['message']}"
    AppCliServer.setReplyText(resp_str, indexStrReply)

Manifest, structure and installation

File “manifest” is intended to store support information about version, name, developer, programm description etc.

manifest.json for client

{
        "name": "cmsClient",
        "version": "1.0",
        "author": "DEMLABS (C) 2023",
        "dependencies": [],
        "description": "Client plugin for messaging."
}

manifest.json for service

{
        "name": "cmsService",
        "version": "1.0",
        "author": "DEMLABS (C) 2023",
        "dependencies": [],
        "description": "Service plugin for messaging."
}

The most important part in this file is name name of our plagin. For availability of initialization from the node side the name of the plugin should be the same with name of the file with code and the very directory where file is situated. This file must lie near the main plugin installation file.

Installation from the service side

(Installation on Linux, pay attention that insallation on other OS’s can be different)

Plugin repository: https://dapps.cellframe.net/node/t-dapp-cms.zip

We must have node with “white” IP address. Then you should download plugin from repository and put it in corresponding folder ” /opt//cellframe-node/var/lib/plugins/” (showed on screen). After this you must uncomment next strings in the end of the file “/opt/cellframe-node/etc/cellframe-node.cfg”:

# Plugins
[plugins]
enabled=true
# Load Python-based plugins
py_load=true  
# Path to Pyhon-based plugins
py_path=/opt/cellframe-node/var/lib/plugins

After this all it is enough to reset node and wait it’s initialization and that’s all, the service is active. To verify that plugin has loaded, you can call command in terminal, which will show list of node plugins cellframe-node-cli plugins list.

denis@denis-virtual-machine:~$ cellframe-node-cli plugins list

|           Name plugin    |           Version           |           Author(s)         |

|           cmsService     |           1.0       |           DEMLABS (C) 2023    |

Also during successfull plugin initialization in the node logs must be appear message which our service sends in process of initialization.

logIt.notice(”= Start mail-service plugin =“)

Logs are located in “/opt/cellframe-node/var/log/cellframe-node.log

Installation from the client side

(Installation on Linux, pay attention that insallation on other OS’s can be different)

Client setup is like service setup except we don’t need node with “white” IP address and instead of cmsService we have to use cmsClient

Example of usage

After service and two different client setup we can start interaction. Below there is example of work in terminal from one of the clients.

-        Check node version

denis@denis-virtual-machine:~$ cellframe-node-cli version

cellframe-node version 5.2-312

-        Let’s check has our plugin downloaded in the node

denis@denis-virtual-machine:~$ cellframe-node-cli plugins list

|           Name plugin    |           Version           |           Author(s)         |

|           cmsClient        |           1.0       |           DEMLABS (C) 2023    |

-        Perform request on configuration of our client and make first request into service

denis@denis-virtual-machine:~$ cellframe-node-cli mail relay -ip 208.85.17.16 -port 8079 -net raiden

Relay service configured as 208.85.17.16:8079 [raiden], your address is: 3234::D530::87C0::3471

-        Check “mailbox” if there is any messages

denis@denis-virtual-machine:~$ cellframe-node-cli mail list

0: from 05D0::A2E3::7A23::492E at 2023-10-10 11:08:43

-        Read received message

denis@denis-virtual-machine:~$ cellframe-node-cli mail get 0

From: 05D0::A2E3::7A23::492E at 2023-10-10 11:08:43

Hello from cellframe-mail

-        Let’s create alias for more comfortable interaction with the second client

denis@denis-virtual-machine:~$ cellframe-node-cli mail alias Dima 05D0::A2E3::7A23::492E

Recepient 05D0::A2E3::7A23::492E is now known as Dima

-        Send message to our second client

denis@denis-virtual-machine:~$ cellframe-node-cli mail send -m “Hi, Dima” -rcpt Dima Success