/** ** ============================== ** O O O OOOO ** O O O O O O ** O O O O O O ** OOOO OOOO O OOO OOOO ** O O O O O O O ** O O O O O O O ** OOOO OOOO O O OOOO ** ============================== ** Dr. Stefan Bosse http://www.bsslab.de ** ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED ** BY THE AUTHOR(S). ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, ** MODIFIED, OR OTHERWISE USED IN A CONTEXT ** OUTSIDE OF THE SOFTWARE SYSTEM. ** ** $AUTHORS: Stefan Bosse ** $INITIAL: (C) 2006-2016 bLAB ** $CREATED: 31-3-15 by sbosse. ** $VERSION: 1.4.4 ** ** $INFO: ** ** DOS: Remote Procedure Call Interface ** ** $ENDOFINFO */ "use strict"; var log = 0; var util = Require('util'); var Io = Require('com/io'); var Comp = Require('com/compat'); var String = Comp.string; var Array = Comp.array; var Perv = Comp.pervasives; var Buf = Require('dos/buf'); var Net = Require('dos/network'); var Sch = Require('dos/scheduler'); var Status = Net.Status; // Transaction identifier number tracking. Must be shared by all Rpc_int instances! var _transaction_id = 0; function transaction_id() { var tid=_transaction_id; _transaction_id=(_transaction_id+1)%65536; return tid; } /** RPC Operation Type * * @enum {number} */ var Operation = { SEND:1, RECV:2, TRANSREQ:3, TRANSREP:4, TRANSAWAIT:5, TRANS:6, GETREQ:7, PUTREP:8, LOOKUP:9, // Search the host port that has a RPC server IAMHERE:10, IAMNOTHERE:11, WHOIS:12, WHEREIS:13, // Search a connection to a host HEREIS:14, // Reply of host print:function(op) { switch (op) { case Operation.SEND: return "SEND"; case Operation.RECV: return "RECV"; case Operation.TRANSREQ: return "TRANSREQ"; case Operation.TRANSREP: return "TRANSREP"; case Operation.TRANSAWAIT: return "TRANSAWAIT"; case Operation.TRANS: return "TRANS"; case Operation.GETREQ: return "GETREQ"; case Operation.PUTREP: return "PUTREP"; case Operation.IAMHERE: return "IAMHERE"; case Operation.IAMNOTHERE: return "IAMNOTHERE"; case Operation.WHOIS: return "WHOIS"; case Operation.WHEREIS: return "WHEREIS"; case Operation.HEREIS: return "HEREIS"; case Operation.LOOKUP: return "LOOKUP"; default: return "Rpc.Operation?"; } } }; function getData(data) { if (!String.equal(data.val,'')) return data.val; else return data.children.toString(); } /** RPCIO Packet Object (message context handle) ** Each transaction, each request and reply get an RPCIO handle. ** Additionally, server localization uses RPCIO packets. ** * * @param {(Operation.SEND|*)} [operation] * @param {header} [hdr] * @param {Buffer|string} [data] * @param {taskcontext} [context] * @param {function} [callback] * @constructor * @typedef {{operation,pubport,header,data,pos,context:context,callback,host,hop,hop_max,tid,timeout,status,index, * }} rpcio~obj * @see rpcio~obj * @see rpcio~meth */ var rpcio = function(operation,hdr,data,context,callback) { if (operation==undefined) { // Create empty rpcio with headers this.operation=undefined; /* ** Public server port (GETREQ only) */ this.pubport=undefined; /* ** Connection Link Port Identifier */ this.connport=undefined; this.header=Net.Header(); this.data=undefined; this.pos=0; this.context=Sch.GetCurrent(); // TODO Should be undefined? this.callback=undefined; /* ** Source host port (initiator) */ this.hostport=undefined; /* ** Destination host port (executor). */ this.sendport=undefined; this.hop=0; this.hop_max=Net.DEF_RPC_MAX_HOP; this.tid=0; this.timeout=-1; this.status=undefined; this.index=-1; this.cache=[]; } else { this.operation = operation; /* ** Public server port (GETREQ only) */ this.pubport=Net.Port(); /* ** Connection Link Port Identifier */ this.connport=undefined; this.header = hdr; this.data = Buf.Buffer(data).data; this.pos=0; this.context = context; this.callback = callback; /* ** Source host port (initiator) */ this.hostport=undefined; /* ** Destination host port (executor) */ this.sendport=undefined; this.hop=0; this.hop_max=Net.DEF_RPC_MAX_HOP; this.tid=0; this.timeout=-1; this.status=undefined; this.index=-1; this.cache=[]; } }; /** * * @param [operation] * @param {header} [hdr] * @param {Buffer|string} [data] * @param {taskcontext} [context] * @returns {rpcio} */ function Rpcio(operation,hdr,data,context) { var obj = new rpcio(operation,hdr,data,context); Object.preventExtensions(obj); return obj; } /** 1. Return a (weak) copy of this rpcio packet ** 2. Copy source content to this rpcio (weak, only first level copy) * */ rpcio.prototype.copy = function (src,callback) { if (src) { this.operation=src.operation; this.pubport=src.pubport; this.connport=src.connport; this.header=src.header; this.pos=src.pos; this.data=src.data; this.hostport=src.hostport; this.sendport=src.sendport; this.tid=src.tid; //this.timeout=src.timeout; this.timeout=-1; this.hop=src.hop; this.hop_max=src.hop_max; this.status=src.status; this.cache=[]; this.context=undefined; this.callback=callback; } } /** ** Initialize a rpcio object * * @param {(Operation.SEND|*)} [operation] * @param {header} [hdr] * @param {Buffer|string} [data] * @param [context] * @param {function} [callback] */ rpcio.prototype.init = function(operation,hdr,data,context,callback) { // Create empty rpcio with headers this.operation=operation; this.pubport=undefined; this.connport=undefined; // if (hdr!=undefined && !(hdr==this.header)) // Net.Copy.header(hdr,this.header); if (hdr!=undefined) this.header=Net.Header(hdr.h_port,hdr.h_priv,hdr.h_command,hdr.h_status); else if (hdr == undefined) { this.header=Net.Header(); // start with a clean unreferenced header structure - may be relinked // this.header.h_status=undefined; // this.header.h_command=undefined; // this.header.h_port=Net.Port(); // this.header.h_priv.prv_obj=0; // this.header.h_priv.prv_rights=0; // this.header.h_priv.prv_rand=Net.Port(); } if (data) this.data=Buf.Buffer(data).data; else this.data=new Buffer(''); //if (context) this.context=context; //if (callback) this.callback=undefined; this.pos=0; this.hostport=undefined; this.sendport=undefined; this.tid=-1; this.timeout=-1; this.hop=0; this.hop_max=Net.DEF_RPC_MAX_HOP; this.status=undefined; this.cache=[]; this.context=context; this.callback=callback; }; // TODO? HOP/HOPMAX fields????? /** Encode RPCIO packet to XML * * @param {string} wrap xml/rpc * @returns {string} */ rpcio.prototype.to_xml=function(wrap) { var self=this, body,buf; buf = Buf.Buffer(); Buf.buf_put_hdr(buf, this.header); String.match(wrap,[ ['xml',function() { body = '
' + Buf.buf_to_hex(buf) + '
'; body = body + '' + Buf.buf_to_hex(self) + '
'; }], ['rpc',function(){ body = ''; body = body + '
' + Buf.buf_to_hex(buf) + '
'; body = body + '' + Buf.buf_to_hex(self) + '
'; }] ]); return body; }; /** Decode XML to RPCIO * * .. xml = new xmldoc.XmlDocument(body) .. */ rpcio.prototype.of_xml=function(xml) { var self=this, header,data,buf, hdr = Net.Header(), rpc,tid,hostport,sendport,operation; if (String.equal(xml.name,'rpc')) { /* ** Wrapped RPCIO
*/ rpc = xml; tid = rpc.attr.tid; hostport = rpc.attr.hostport; sendport = rpc.attr.sendport; operation = rpc.attr.operation; header = rpc.childNamed('header'); data = rpc.childNamed('data'); } else if (String.equal(xml.name,'xml')) { /* ** Plain RPCIO
*/ header = xml.childNamed('header'); data = xml.childNamed('data'); } if (header != undefined) { buf = Buf.Buffer(); Buf.buf_of_hex(buf, getData(header)); Buf.buf_get_hdr(buf, hdr); } switch (operation) { case 'TRANSREQ': self.init(Operation.TRANSREQ, hdr, getData(data)); self.tid = Perv.int_of_string(tid); self.hostport = Net.port_of_param(hostport); self.sendport = Net.port_of_param(sendport); return 1; break; case 'TRANSREP': // TRANSREP for a client on this host self.init(Operation.TRANSREP, hdr, getData(data)); self.hostport = Net.port_of_param(hostport); self.sendport = Net.port_of_param(sendport); self.tid = Perv.int_of_string(tid); return 1; break; case 'LOOKUP': buf = Buf.Buffer(getData(xml)); hdr.h_port = Buf.buf_get_port(buf); self.init(Operation.LOOKUP, hdr); return 1; break; case 'IAMHERE': buf = Buf.Buffer(getData(xml)); hdr.h_port = Buf.buf_get_port(buf); self.init(Operation.IAMHERE, hdr); self.hostport = Net.port_of_param(hostport); self.sendport = Net.port_of_param(sendport); return 1; break; case 'WHEREIS': case 'HEREIS': self.init(Operation.IAMHERE, hdr); self.hostport = Net.port_of_param(hostport); self.sendport = Net.port_of_param(sendport); return 1; break; default: return 0; } }; /** Encode RPCIO packet to JSON object * * @param {string} format simple/extended * @returns {string} */ rpcio.prototype.to_json=function(format) { var self=this; var obj={},buf; buf = Buf.Buffer(); Buf.buf_put_hdr(buf, this.header); String.match(format,[ ['simple',function() { obj = {header:Buf.buf_to_hex(buf),data:Buf.buf_to_hex(self)}; }], ['extended',function(){ obj = { operation: Operation.print(self.operation), hostport: self.hostport, sendport: self.sendport, tid: self.tid, header: Buf.buf_to_hex(buf), data: Buf.buf_to_hex(self) } }] ]); return obj; }; /** Decode JSON object to RPCIO * */ rpcio.prototype.of_json=function(obj,format) { var self=this, header=obj.header, data=obj.data, buf, hostport = obj.hostport, sendport = obj.sendport, operation = obj.operation||'', tid = obj.tid, hdr = Net.Header(); // console.log(obj) if (header != undefined) { buf = Buf.Buffer(); Buf.buf_of_hex(buf, header); Buf.buf_get_hdr(buf, hdr); } switch (operation) { case 'TRANSREQ': self.init(Operation.TRANSREQ, hdr, data); self.tid = Perv.int_of_string(tid); self.hostport = hostport; self.sendport = sendport; return 1; break; case 'TRANSREP': // TRANSREP for a client on this host self.init(Operation.TRANSREP, hdr, data); self.hostport = hostport; self.sendport = sendport; self.tid = Perv.int_of_string(tid); return 1; break; case 'LOOKUP': buf = Buf.Buffer(data); hdr.h_port = Buf.buf_get_port(buf); self.init(Operation.LOOKUP, hdr); self.hostport = hostport; return 1; break; case 'IAMHERE': buf = Buf.Buffer(data); hdr.h_port = Buf.buf_get_port(buf); self.init(Operation.IAMHERE, hdr); self.hostport = hostport; self.sendport = sendport; return 1; break; case 'WHEREIS': case 'HEREIS': self.init(Operation.IAMHERE, hdr); self.hostport = hostport; self.sendport = sendport; return 1; break; default: return 0; } }; /** A RPC connection. Used by the router only. * * @param {port} port * @param {function(rpcio)|undefined} [send] * @param {function()} [alive] * @constructor */ var rpcconn = function (port,send,alive) { this.port=port; // function(rpcio,callback?) // type callback is function (stat,rpcio) this.send=send; this.alive=alive||function() {return false;}; this.multiport=false; this.stats = { op_ask:0, op_brokerrep:0, op_brokerreq:0, op_get:0, op_messages:0, op_notify:0, op_put:0, op_broadcast:0, op_forward:0, op_schedule:0, op_alive:0, op_send:0, op_link:0, op_unlink:0, op_receive:0, op_ping:0, op_pong:0, op_error:0, op_noentr:0 } }; /** ** RPC Transaction Object * * * @param {rpcrouter} router * @constructor * @typedef {{router:rpcrouter}} rpcint~obj * @see rpcint~obj * @see rpcint~meth */ // function (router:rpcrouter) -> rpcint template var rpcint = function (router) { // RPC message router this.router=router; }; /** * @typedef {{trans:rpcint.trans,getreq:rpcint.getreq,putrep:rpcint.putrep}} rpcint~meth */ /** ** Client Interface ** Optional callback: function(rpcio) * * @param {rpcio} rpcio * @param {function} [callback] */ rpcint.prototype.trans = function(rpcio,callback) { var rpcio=rpcio; var hdr=rpcio.header; Sch.Suspend(); rpcio.operation=Operation.TRANSREQ; rpcio.context=Sch.GetCurrent(); rpcio.callback=callback; rpcio.tid=transaction_id(); rpcio.hostport=this.router.hostport; this.router.route(rpcio); }; /** ** Server interface * * * @param {rpcio} rpcio */ rpcint.prototype.getreq = function (rpcio) { rpcio.pubport=Net.prv2pub(rpcio.header.h_port); rpcio.operation=Operation.GETREQ; Sch.Suspend(); rpcio.context=Sch.GetCurrent(); this.router.route(rpcio) }; /** * * @param {rpcio} rpcio */ rpcint.prototype.putrep = function (rpcio) { rpcio.operation=Operation.PUTREP; this.router.route(rpcio); }; module.exports = { Rpcio: Rpcio, /** * * @param {rpcrouter} router * @returns {rpcint} */ RpcInt: function(router) { var obj = new rpcint(router); Object.preventExtensions(obj); return obj; }, /** * @param {port} port * @param {function(rpcio)} [send] * @param {function()} [alive] * @returns {rpcconn} */ RpcConn: function(port,send,alive) { var obj = new rpcconn(port,send,alive); Object.preventExtensions(obj); return obj; }, Print: { rpcio: function(rpcio) { if (rpcio) { var str = '{'; str = str + 'header: ' + Net.Print.header(rpcio.header) + ', '; if (rpcio.data!=undefined) str = str + 'data[' + rpcio.data.length + '], '; str = str + 'hostport: ' + Net.Print.port(rpcio.hostport); str = str + ' connport: ' + Net.Print.port(rpcio.connport); str = str + ' sendport: ' + Net.Print.port(rpcio.sendport); if (rpcio.tid>=0) str = str + ', tid: ' + rpcio.tid; if (rpcio.operation!=undefined) str = str + ', operation: ' + Operation.print(rpcio.operation); if (rpcio.timeout>0) str=str + ', timeout: '+rpcio.timeout; if (rpcio.status!=undefined) str=str + ', status: '+Net.Status.print(rpcio.status); if (rpcio.index>=0) str=str + ', index: '+rpcio.index; if (rpcio.context!=undefined) str=str + ', context: '+rpcio.context.id; str = str + ' hop='+rpcio.hop+' hopmax='+rpcio.hop_max; str = str +(rpcio.callback!=undefined?' CB':'')+(rpcio.context!=undefined?' CT':'')+'}'; return str; } else return "undefined"; } }, Operation:Operation, transaction_id:transaction_id };