From 92693b532594abdc410a7813dc8104b3e005cda2 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 12 Dec 2017 05:35:28 -0500 Subject: [PATCH 001/162] QualifiedNode and QualifiedParameter support --- README.md | 18 +++- ber.js | 42 +++++++- client.js | 13 +-- device.js | 130 ++++++++++++++++++++--- ember.js | 294 ++++++++++++++++++++++++++++++++++++++++++++++++--- package.json | 10 +- 6 files changed, 471 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index d8463af..9ff6953 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ Basic trees of parameters should work. Streams aren't there yet, but shouldn't be too far off. Everything else is probably missing at this point (e.g. the newer matrix stuff). +QualifiedNode and QualifiedParameter have been added in release 1.1.0. +This version should be able to retrieve an EVS XT4k tree. +We intend to implement the server version with Matrix support pretty soon. + ## Example usage ```javascript @@ -27,8 +31,18 @@ const DeviceTree = require('emberplus').DeviceTree; var tree = new DeviceTree("localhost", 9998); tree.on('ready', () => { - tree.getNodeByPath("EmberDevice/Sources/Monitor/Amplification").then((node) => { - + // When device returns a qualified node, you don't have a complete tree + // Collect entire tree with the expand function. + // The function returns a Promise + tree.expand(tree.root.elements[0]).then((node) => { + console.log(tree.root.elements[0]); + }); + + // You can also get one qualified parameter or node at a time. + tree.getDirectory(root.elements[0]).then((node) => { + console.log(tree.root.elements[0]); + }); + // Subscribe to parameter changes tree.subscribe(node, (node) => { console.log("Volume changed: %d", node.contents.value); diff --git a/ber.js b/ber.js index e1454e7..110a028 100644 --- a/ber.js +++ b/ber.js @@ -30,14 +30,39 @@ var APPLICATION = function(x) { return x | 0x60; }; var CONTEXT = function(x) { return x | 0xa0; }; var UNIVERSAL = function(x) { return x; }; -const EMBER_SET = 0x20 | 17; -const EMBER_STRING = 12; +const EMBER_BOOLEAN = 1; +const EMBER_INTEGER = 2; +const EMBER_BITSTRING = 3; +const EMBER_OCTETSTRING = 4; +const EMBER_NULL = 5; +const EMBER_OBJECTIDENTIFIER = 6; +const EMBER_OBJECTDESCRIPTOR = 7; +const EMBER_EXTERNAL = 8; +const EMBER_REAL = 9; +const EMBER_ENUMERATED = 10; +const EMBER_EMBEDDED = 11; +const EMBER_STRING = 12; + +const EMBER_SEQUENCE = 0x20 | 16; +const EMBER_SET = 0x20 | 17; module.exports.APPLICATION = APPLICATION; module.exports.CONTEXT = CONTEXT; module.exports.UNIVERSAL = UNIVERSAL; module.exports.EMBER_SET = EMBER_SET; +module.exports.EMBER_BOOLEAN = EMBER_BOOLEAN; +module.exports.EMBER_INTEGER = EMBER_INTEGER; +module.exports.EMBER_BITSTRING = EMBER_BITSTRING; +module.exports.EMBER_OCTETSTRING = EMBER_OCTETSTRING; +module.exports.EMBER_NULL = EMBER_NULL; +module.exports.EMBER_OBJECTIDENTIFIER = EMBER_OBJECTIDENTIFIER; +module.exports.EMBER_OBJECTDESCRIPTOR = EMBER_OBJECTDESCRIPTOR; +module.exports.EMBER_EXTERNAL = EMBER_EXTERNAL; +module.exports.EMBER_REAL = EMBER_REAL; +module.exports.EMBER_ENUMERATED = EMBER_ENUMERATED; +module.exports.EMBER_EMBEDDED = EMBER_EMBEDDED; + module.exports.EMBER_STRING = EMBER_STRING; function ExtendedReader(data) { @@ -69,6 +94,7 @@ ExtendedReader.prototype.readValue = function() { } } + ExtendedReader.prototype.readReal = function(tag) { if(tag !== undefined) { tag = UNIVERSAL(9); @@ -242,14 +268,26 @@ ExtendedWriter.prototype.writeReal = function(value, tag) { ExtendedWriter.prototype.writeValue = function(value, tag) { if(Number.isInteger(value)) { + if (tag === undefined) { + tag = EMBER_INTEGER; + } this.writeInt(value, tag); } else if(typeof value == 'boolean') { + if (tag === undefined) { + tag = EMBER_BOOLEAN; + } this.writeBoolean(value, tag); } else if(typeof value == 'number') { + if (tag === undefined) { + tag = EMBER_REAL; + } this.writeReal(value, tag); } else if(Buffer.isBuffer(value)) { this.writeBuffer(value, tag); } else { + if (tag === undefined) { + tag = EMBER_STRING; + } this.writeString(value.toString(), tag); } } diff --git a/client.js b/client.js index 995522a..1061c4f 100644 --- a/client.js +++ b/client.js @@ -32,7 +32,9 @@ function S101Client(address, port) { var ber = new BER.Reader(packet); try { var root = ember.Root.decode(ber); - self.emit('emberTree', root); + if (root !== undefined) { + self.emit('emberTree', root); + } } catch(e) { console.log(e); } @@ -44,7 +46,7 @@ util.inherits(S101Client, EventEmitter); S101Client.prototype.connect = function() { var self = this; self.emit('connecting'); - console.log("socket connecting"); + //console.log("socket connecting"); self.socket = net.createConnection(self.port, self.address, () => { winston.debug('socket connected'); @@ -63,7 +65,7 @@ S101Client.prototype.connect = function() { self.socket.on('error', (e) => { //self.emit('disconnected'); //self.socket = null; - console.log("Socket error", e); + //console.log("Socket error", e); }); } @@ -109,10 +111,9 @@ S101Client.prototype.sendBERNode = function(node) { var writer = new BER.Writer(); node.encode(writer); self.sendBER(writer.buffer); - - //var reader = new BER.Reader(writer.buffer); - //console.log(util.inspect(ember.Root.decode(reader), {depth:null})); } + + module.exports = S101Client; diff --git a/device.js b/device.js index 02282d5..f44bc8d 100644 --- a/device.js +++ b/device.js @@ -5,15 +5,18 @@ const S101Client = require('./client.js'); const ember = require('./ember.js'); const errors = require('./errors.js'); + function DeviceTree(host, port) { DeviceTree.super_.call(this); var self = this; + self.timeoutValue = 2000; self.client = new S101Client(host, port); self.root = new ember.Root(); self.pendingRequests = []; self.activeRequest = null; self.timeout = null; self.connectTimeout = null; + self.callback = undefined; self.client.on('connecting', () => { self.emit('connecting'); @@ -33,13 +36,69 @@ function DeviceTree(host, port) { }, 10000); }); + self.client.on("error", (e) => { + self.emit("error", e); + }); + self.client.on('emberTree', (root) => { self.handleRoot(root); + if (self.callback) { + self.callback(undefined, root); + } }); } util.inherits(DeviceTree, EventEmitter); + +DeviceTree.prototype.expand = function(node) +{ + let self = this; + return new Promise((resolve, reject) => { + //console.log("Getting directory for node", node); + self.getDirectory(node).then((res) => { + if ((res === undefined) || (node.children === undefined)) { + //console.log("No more children for ", node); + return resolve(); + } + let p = []; + for(let child of node.children) { + //console.log("Expanding child", child); + p.push(self.expand(child)); + } + return Promise.all(p) + }).then(() => {resolve();}); + }); +} + +DeviceTree.prototype.getDirectory = function(qnode) { + var self = this; + return new Promise((resolve, reject) => { + self.addRequest((error) => { + if (error) { + self.finishRequest(); + reject(error); + return; + } + + let cb = (error, node) => { + self.finishRequest(); + if (error) { + reject(error); + } + else { + //console.log("Received getDirectory response", node); + resolve(node); + } + }; + + //console.log("Sending getDirectory"); + self.callback = cb; + self.client.sendBERNode(qnode.getDirectory()); + }); + }); +} + DeviceTree.prototype.disconnect = function() { this.client.disconnect(); } @@ -51,7 +110,7 @@ DeviceTree.prototype.makeRequest = function() { self.activeRequest(); self.timeout = setTimeout(() => { self.timeoutRequest(); - }, 300); + }, self.timeoutValue); } }; @@ -63,6 +122,7 @@ DeviceTree.prototype.addRequest = function(cb) { DeviceTree.prototype.finishRequest = function() { var self=this; + self.callback = undefined; if(self.timeout != null) { clearTimeout(self.timeout); self.timeout = null; @@ -80,11 +140,16 @@ DeviceTree.prototype.timeoutRequest = function() { DeviceTree.prototype.handleRoot = function(root) { var self=this; + //console.log("handling root", JSON.stringify(root)); var callbacks = self.root.update(root); - if(root.elements !== undefined) { for(var i=0; i { - if(!(node instanceof ember.Parameter)) { + if((!(node instanceof ember.Parameter)) && + (!(node instanceof ember.QualifiedParameter))) + { reject(new errors.EmberAccessError('not a property')); - } else if(node.contents !== undefined && node.contents.value == value) { + } + else if(node.contents !== undefined && node.contents.value == value) { resolve(node); } else { - console.log('setValue', node.getPath(), value); + //console.log('setValue', node.getPath(), value); self.addRequest((error) => { if(error) { reject(error); self.finishRequest(); return; } - - self.client.sendBERNode(node.setValue(value, (error, node) => { - if(error) { + let cb = (error, node) => { + self.finishRequest(); + if (error) { reject(error); - } else { + } + else { resolve(node); } - self.finishRequest(); - })); + }; + + self.callback = cb; + self.client.sendBERNode(node.setValue(value)); }); } }); diff --git a/ember.js b/ember.js index 8aeb03b..47b273b 100644 --- a/ember.js +++ b/ember.js @@ -17,17 +17,33 @@ function Root() { util.inherits(Root, TreeNode); Root.decode = function(ber) { - var r = new Root(); - ber.readSequence(BER.APPLICATION(0)); - var tag = ber.readSequence(); + let r = new Root(); + let tag = undefined; + let tmp, len; + //console.log(ber); + try { + ber.readSequence(BER.APPLICATION(0)); + tag = ber.readSequence(); + } + catch (e) { + return; + } + if(tag == BER.APPLICATION(11)) { r.elements = []; - var seq = ber.getSequence(BER.CONTEXT(0)); - while(seq.remain > 0) { - r.addElement(RootElement.decode(seq)); + while(ber.remain > 0) { + try { + var seq = ber.getSequence(BER.CONTEXT(0)); + + while (seq.remain > 0) { + r.addElement(RootElement.decode(seq)); + } + } + catch(e) { + return r; + } } - } else { // StreamCollection BER.APPLICATION(6) // InvocationResult BER.APPLICATION(23) @@ -157,6 +173,48 @@ TreeNode.prototype.getChildren = function() { return null; } + +_getElementByPath = function(children, pathArray, path) { + if ((children === null)||(children === undefined)||(pathArray.length < 1)) { + return null; + } + var currPath = pathArray.join("."); + var number = pathArray[pathArray.length - 1]; + //console.log(`looking for path ${currPath} or number ${number}`); + + for (var i = 0; i < children.length; i++) { + //console.log("looking at child", JSON.stringify(children[i])); + + if ((children[i].path === currPath)|| + (children[i].number === number)){ + if (path.length === 0) { + return children[i]; + } + pathArray.push(path.splice(0,1)); + return _getElementByPath(children[i].getChildren(), pathArray, path); + } + } + + return null; +} + +TreeNode.prototype.getElementByPath = function(path) { + var children = this.getChildren(); + if ((children === null)||(children === undefined)) { + return null; + } + path = path.split("."); + + if (path.length > 2) { + pathArray = path.splice(0,2); + } + else { + pathArray = path; + path = []; + } + return _getElementByPath(children, pathArray, path); +} + TreeNode.prototype.getElementByNumber = function(index) { var children = this.getChildren(); if(children === null) return null; @@ -196,7 +254,7 @@ TreeNode.prototype.update = function(other) { while(self._directoryCallbacks.length > 0) { (function(cb) { callbacks.push(() => { - console.log(this.constructor.name, "dir cb", self.getPath()); + //console.log(this.constructor.name, "dir cb", self.getPath()); cb(null, self) }); })(self._directoryCallbacks.shift()); @@ -206,7 +264,7 @@ TreeNode.prototype.update = function(other) { for(var i=0; i { - console.log(self.constructor.name, "cb", self.getPath()); + //console.log(self.constructor.name, "cb", self.getPath()); cb(self) }); })(self._callbacks[i]); @@ -234,7 +292,7 @@ TreeNode.prototype.getNodeByPath = function(client, path, callback) { } child = node.getElement(path[0]); if(child === null) { - console.log("inv:", path[0], self); + //console.log("inv:", path[0], self); callback('invalid path'); return; } else { @@ -248,6 +306,9 @@ TreeNode.prototype.getNodeByPath = function(client, path, callback) { } TreeNode.prototype.getPath = function() { + if (this.path !== undefined) { + return this.path; + } if(this._parent === null) { if(this.number === undefined) { return ""; @@ -292,6 +353,10 @@ Element.decode = function(ber) { } else if(tag == BER.APPLICATION(2)) { // Command return Command.decode(ber); + } else if(tag == BER.APPLICATION(9)) { + return QualifiedParameter.decode(ber); + } else if(tag == BER.APPLICATION(10)) { + return QualifiedNode.decode(ber); } else if(tag == BER.APPLICATION(13)) { // Matrix throw new errors.UnimplementedEmberTypeError(tag); @@ -312,6 +377,99 @@ Element.decode = function(ber) { +/**************************************************************************** + * QualifiedNode + ***************************************************************************/ + +function QualifiedNode(path) { + QualifiedNode.super_.call(this); + if (path != undefined) { + this.path = path; + } +} + +util.inherits(QualifiedNode, TreeNode); + + +QualifiedNode.decode = function(ber) { + var qn = new QualifiedNode(); + ber = ber.getSequence(BER.APPLICATION(10)); + while(ber.remain > 0) { + var tag = ber.readSequence(); + if(tag == BER.CONTEXT(0)) { + qn.path = ber.readOID(13); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qn.contents = NodeContents.decode(ber); + } else if(tag == BER.CONTEXT(2)) { + qn.children = []; + var seq = ber.getSequence(BER.APPLICATION(4)); + while(seq.remain > 0) { + seq.readSequence(BER.CONTEXT(0)); + qn.addChild(Element.decode(seq)); + } + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return qn; +} + +QualifiedNode.prototype.update = function(other) { + callbacks = QualifiedNode.super_.prototype.update.apply(this); + if ((other !== undefined) && (other.contents !== undefined)) { + this.contents = other.contents; + } + return callbacks; +} + +QualifiedNode.prototype.getDirectory = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + //console.log("Generating getDirectory command for node", this); + var r = new Root(); + var qn = new QualifiedNode(); + qn.path = this.path; + r.addElement(qn); + qn.addChild(new Command(32)); + if(callback !== undefined) { + this._directoryCallbacks.push((error, node) => { callback(error, node) }); + } + return r; +} + + +QualifiedNode.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(10)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeOID(this.path, 13); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.children !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.APPLICATION(4)); + for(var i=0; i 0) { + var tag = ber.readSequence(); + if(tag == BER.CONTEXT(0)) { + qp.path = ber.readOID(13); // 13 => relative OID + //console.log("Decoded path",qp.path); + } + else if(tag == BER.CONTEXT(1)) { + //console.log("Decoding content"); + qp.contents = ParameterContents.decode(ber); + //console.log("Decoded content",qp.contents); + } else if(tag == BER.CONTEXT(2)) { + qp.children = []; + //console.log("Decoding children"); + var seq = ber.getSequence(BER.APPLICATION(4)); + while(seq.remain > 0) { + seq.readSequence(BER.CONTEXT(0)); + qp.addChild(Element.decode(seq)); + } + } else { + return qp; + //throw new errors.UnimplementedEmberTypeError(tag); + } + } + return qp; +} + +QualifiedParameter.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(9)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeOID(this.path, 13); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.children !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.APPLICATION(4)); + for(var i=0; i { callback(error, node) }); + } + return r; +} + +QualifiedParameter.prototype.setValue = function(value, callback) { + if(callback !== undefined) { + this._directoryCallbacks.push(callback); + } + + let r = new Root(); + let qp = new QualifiedParameter(); + r.addElement(qp); + qp.path = this.path; + qp.contents = new ParameterContents(value); + //console.log(JSON.stringify(r)); + return r; +} + /**************************************************************************** * Parameter ***************************************************************************/ @@ -662,11 +929,14 @@ ParameterContents.decode = function(ber) { pc.streamDescriptor = StreamDescription.decode(ber); } else if(tag == BER.CONTEXT(17)) { pc.schemaIdentifiers = ber.readString(BER.EMBER_STRING); - } else { + } else if (tag == null) { + break; + } + else { throw new errors.UnimplementedEmberTypeError(tag); } } - + //console.log("decoded content", pc); return pc; } diff --git a/package.json b/package.json index 57b1ffb..70796f9 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,25 @@ { "name": "emberplus", - "version": "1.0.0", + "version": "1.1.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Brian Mayton (http://bdm.cc)", + "contributors": [ + { + "name": "Gilles Dufour", + "email": "dufour.gilles@gmail.com" + } + ], "repository": { "type": "git", "url": "https://github.com/bmayton/node-emberplus" }, "license": "MIT", "dependencies": { - "asn1": "^0.2.3", + "asn1": "evs-broadcast/node-asn1", "bluebird": "^3.5.0", "enum": "^2.4.0", "long": "^3.2.0", From 10e1615df4a6e58b9b4b3bf2d9c9e0528b72d9c8 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 19 Dec 2017 04:58:22 -0500 Subject: [PATCH 002/162] Adding support for matrix and qualifiedmatrix --- ber.js | 23 +- device.js | 7 +- ember.js | 848 +++++++++++++++++++++++++++++++++++++++++++++------ index.js | 6 +- package.json | 2 +- 5 files changed, 775 insertions(+), 111 deletions(-) mode change 100644 => 100755 ber.js mode change 100644 => 100755 device.js mode change 100644 => 100755 ember.js diff --git a/ber.js b/ber.js old mode 100644 new mode 100755 index 110a028..d889f93 --- a/ber.js +++ b/ber.js @@ -43,6 +43,7 @@ const EMBER_REAL = 9; const EMBER_ENUMERATED = 10; const EMBER_EMBEDDED = 11; const EMBER_STRING = 12; +const EMBER_RELATIVE_OID = 13; const EMBER_SEQUENCE = 0x20 | 16; const EMBER_SET = 0x20 | 17; @@ -51,6 +52,7 @@ module.exports.APPLICATION = APPLICATION; module.exports.CONTEXT = CONTEXT; module.exports.UNIVERSAL = UNIVERSAL; module.exports.EMBER_SET = EMBER_SET; +module.exports.EMBER_SEQUENCE = EMBER_SEQUENCE; module.exports.EMBER_BOOLEAN = EMBER_BOOLEAN; module.exports.EMBER_INTEGER = EMBER_INTEGER; module.exports.EMBER_BITSTRING = EMBER_BITSTRING; @@ -62,8 +64,8 @@ module.exports.EMBER_EXTERNAL = EMBER_EXTERNAL; module.exports.EMBER_REAL = EMBER_REAL; module.exports.EMBER_ENUMERATED = EMBER_ENUMERATED; module.exports.EMBER_EMBEDDED = EMBER_EMBEDDED; - module.exports.EMBER_STRING = EMBER_STRING; +module.exports.EMBER_RELATIVE_OID = EMBER_RELATIVE_OID; function ExtendedReader(data) { ExtendedReader.super_.call(this, data); @@ -72,23 +74,32 @@ function ExtendedReader(data) { util.inherits(ExtendedReader, BER.Reader); module.exports.Reader = ExtendedReader; + +readBlock = function(ber) { + +} + + ExtendedReader.prototype.getSequence = function(tag) { var buf = this.readString(tag, true); return new ExtendedReader(buf); } ExtendedReader.prototype.readValue = function() { - var tag = this.peek(tag); + var tag = this.peek(); + if(tag == EMBER_STRING) { return this.readString(EMBER_STRING); - } else if(tag == UNIVERSAL(2)) { + } else if(tag == EMBER_INTEGER) { return this.readInt(); - } else if(tag == UNIVERSAL(9)) { + } else if(tag == EMBER_REAL) { return this.readReal(); - } else if(tag == UNIVERSAL(1)) { + } else if(tag == EMBER_BOOLEAN) { return this.readBoolean(); - } else if(tag == UNIVERSAL(4)) { + } else if(tag == EMBER_OCTETSTRING) { return this.readString(UNIVERSAL(4), true); + } else if (tag === EMBER_RELATIVE_OID) { + return this.readOID(EMBER_RELATIVE_OID); } else { throw new errors.UnimplementedEmberTypeError(tag); } diff --git a/device.js b/device.js old mode 100644 new mode 100755 index f44bc8d..d5134f3 --- a/device.js +++ b/device.js @@ -3,6 +3,7 @@ const util = require('util'); const Promise = require('bluebird'); const S101Client = require('./client.js'); const ember = require('./ember.js'); +const BER = require('./ber.js'); const errors = require('./errors.js'); @@ -50,6 +51,10 @@ function DeviceTree(host, port) { util.inherits(DeviceTree, EventEmitter); +DecodeBuffer = function(packet) { + var ber = new BER.Reader(packet); + return ember.Root.decode(ber); +} DeviceTree.prototype.expand = function(node) { @@ -306,4 +311,4 @@ function TreePath(path) { } -module.exports = DeviceTree; +module.exports = {DeviceTree, DecodeBuffer}; diff --git a/ember.js b/ember.js old mode 100644 new mode 100755 index 47b273b..b0a83a4 --- a/ember.js +++ b/ember.js @@ -3,7 +3,15 @@ const errors = require('./errors.js'); const util = require('util'); const Enum = require('enum'); -module.exports.GetDirectory = 32; + +const COMMAND_SUBSCRIBE = 30; +const COMMAND_UNSUBSCRIBE = 31; +const COMMAND_GETDIRECTORY = 32; +module.exports.Subscribe = COMMAND_SUBSCRIBE; +module.exports.Unsubscribe = COMMAND_UNSUBSCRIBE; +module.exports.GetDirectory = COMMAND_GETDIRECTORY; + + /**************************************************************************** * Root @@ -16,38 +24,43 @@ function Root() { util.inherits(Root, TreeNode); + + Root.decode = function(ber) { let r = new Root(); let tag = undefined; - let tmp, len; - //console.log(ber); - try { - ber.readSequence(BER.APPLICATION(0)); - tag = ber.readSequence(); - } - catch (e) { - return; - } + while(ber.remain > 0) { + try { + ber = ber.getSequence(BER.APPLICATION(0)); + tag = ber.peek(); + } + catch (e) { + console.log(e.stack); + break; + } - if(tag == BER.APPLICATION(11)) { - r.elements = []; - while(ber.remain > 0) { - try { - var seq = ber.getSequence(BER.CONTEXT(0)); - while (seq.remain > 0) { - r.addElement(RootElement.decode(seq)); + if (tag == BER.APPLICATION(11)) { + var seq = ber.getSequence(BER.APPLICATION(11)); + r.elements = []; + while (seq.remain > 0) { + try { + var rootReader = seq.getSequence(BER.CONTEXT(0)); + while (rootReader.remain > 0) { + r.addElement(RootElement.decode(rootReader)); + } + } + catch (e) { + console.log(e.stack); + return r; } } - catch(e) { - return r; - } + } else { + // StreamCollection BER.APPLICATION(6) + // InvocationResult BER.APPLICATION(23) + throw new errors.UnimplementedEmberTypeError(tag); } - } else { - // StreamCollection BER.APPLICATION(6) - // InvocationResult BER.APPLICATION(23) - throw new errors.UnimplementedEmberTypeError(tag); } return r; } @@ -135,7 +148,6 @@ TreeNode.prototype.getMinimal = function() { TreeNode.prototype.getTreeBranch = function(child, modifier) { var m = this.getMinimal(); if(child !== undefined) { - //console.log('addChild', child); m.addChild(child); } @@ -163,7 +175,7 @@ TreeNode.prototype.getDirectory = function(callback) { if(callback !== undefined) { this._directoryCallbacks.push((error, node) => { callback(error, node) }); } - return this.getTreeBranch(new Command(32)); + return this.getTreeBranch(new Command(COMMAND_GETDIRECTORY)); } TreeNode.prototype.getChildren = function() { @@ -345,22 +357,22 @@ function Element() {}; Element.decode = function(ber) { var tag = ber.peek(); if(tag == BER.APPLICATION(1)) { - // Parameter return Parameter.decode(ber); } else if(tag == BER.APPLICATION(3)) { - // Node return Node.decode(ber); } else if(tag == BER.APPLICATION(2)) { - // Command return Command.decode(ber); } else if(tag == BER.APPLICATION(9)) { return QualifiedParameter.decode(ber); } else if(tag == BER.APPLICATION(10)) { - return QualifiedNode.decode(ber); + return QualifiedNode.decode(ber); } else if(tag == BER.APPLICATION(13)) { - // Matrix - throw new errors.UnimplementedEmberTypeError(tag); - } else if(tag == BER.APPLICATION(19)) { + return MatrixNode.decode(ber); + } + else if(tag == BER.APPLICATION(17)) { + return QualifiedMatrix.decode(ber); + } + else if(tag == BER.APPLICATION(19)) { // Function throw new errors.UnimplementedEmberTypeError(tag); } else if(tag == BER.APPLICATION(24)) { @@ -395,18 +407,19 @@ QualifiedNode.decode = function(ber) { var qn = new QualifiedNode(); ber = ber.getSequence(BER.APPLICATION(10)); while(ber.remain > 0) { - var tag = ber.readSequence(); + var tag = ber.peek(); + var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - qn.path = ber.readOID(13); // 13 => relative OID + qn.path = seq.readOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID } else if(tag == BER.CONTEXT(1)) { - qn.contents = NodeContents.decode(ber); + qn.contents = NodeContents.decode(seq); } else if(tag == BER.CONTEXT(2)) { qn.children = []; - var seq = ber.getSequence(BER.APPLICATION(4)); + seq = seq.getSequence(BER.APPLICATION(4)); while(seq.remain > 0) { - seq.readSequence(BER.CONTEXT(0)); - qn.addChild(Element.decode(seq)); + var nodeSeq = seq.getSequence(BER.CONTEXT(0)); + qn.addChild(Element.decode(nodeSeq)); } } else { throw new errors.UnimplementedEmberTypeError(tag); @@ -432,7 +445,7 @@ QualifiedNode.prototype.getDirectory = function(callback) { var qn = new QualifiedNode(); qn.path = this.path; r.addElement(qn); - qn.addChild(new Command(32)); + qn.addChild(new Command(COMMAND_GETDIRECTORY)); if(callback !== undefined) { this._directoryCallbacks.push((error, node) => { callback(error, node) }); } @@ -444,7 +457,7 @@ QualifiedNode.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(10)); ber.startSequence(BER.CONTEXT(0)); - ber.writeOID(this.path, 13); + ber.writeOID(this.path, BER.EMBER_RELATIVE_OID); ber.endSequence(); // BER.CONTEXT(0) if(this.contents !== undefined) { @@ -487,17 +500,18 @@ Node.decode = function(ber) { ber = ber.getSequence(BER.APPLICATION(3)); while(ber.remain > 0) { - var tag = ber.readSequence(); + var tag = ber.peek(); + var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - n.number = ber.readInt(); + n.number = seq.readInt(); } else if(tag == BER.CONTEXT(1)) { - n.contents = NodeContents.decode(ber); + n.contents = NodeContents.decode(seq); } else if(tag == BER.CONTEXT(2)) { + seq = seq.getSequence(BER.APPLICATION(4)); n.children = []; - var seq = ber.getSequence(BER.APPLICATION(4)); while(seq.remain > 0) { - seq.readSequence(BER.CONTEXT(0)); - n.addChild(Element.decode(seq)); + var nodeSeq = seq.getSequence(BER.CONTEXT(0)); + n.addChild(Element.decode(nodeSeq)); } } else { throw new errors.UnimplementedEmberTypeError(tag); @@ -550,6 +564,636 @@ Node.prototype.subscribe = function(callback) { module.exports.Node = Node; +/****************************** + * MATRIX + ******************************/ + +function MatrixNode(number) { + MatrixNode.super_.call(this); + if(number !== undefined) + this.number = number; +} + + +MatrixNode.decode = function(ber) { + var m = new MatrixNode(); + ber = ber.getSequence(BER.APPLICATION(13)); + while (ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if (tag == BER.CONTEXT(0)) { + m.number = seq.readInt(); + } + else if (tag == BER.CONTEXT(1)) { + m.contents = MatrixContents.decode(seq); + + } else if (tag == BER.CONTEXT(2)) { + m.children = []; + seq = seq.getSequence(BER.APPLICATION(4)); + while (seq.remain > 0) { + var childSeq = seq.getSequence(BER.CONTEXT(0)); + m.addChild(Element.decode(childSeq)); + } + } else if (tag == BER.CONTEXT(3)) { + m.targets = decodeTargets(seq); + } else if (tag == BER.CONTEXT(4)) { + m.sources = decodeSources(seq); + } else if (tag == BER.CONTEXT(5)) { + m.connections = []; + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + var conSeq = seq.getSequence(BER.CONTEXT(0)); + m.connections.push(MatrixConnection.decode(conSeq)); + } + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return m; +}; + +MatrixNode.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(13)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.children !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.APPLICATION(4)); + for(var i=0; i 0) { + var tag = ber.peek(); + //console.log("Next tag", tag, ber.buffer); + var seq = ber.getSequence(tag); + + if(tag == BER.CONTEXT(0)) { + mc.identifier = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + mc.description = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(2)) { + mc.type = MatrixType.decode(seq); + } else if(tag == BER.CONTEXT(3)) { + mc.mode = MatrixMode.decode(seq); + } else if(tag == BER.CONTEXT(4)) { + mc.targetCount = seq.readInt(); + } else if(tag == BER.CONTEXT(5)) { + mc.sourceCount = seq.readInt(); + } else if(tag == BER.CONTEXT(6)) { + mc.maximumTotalConnects = seq.readInt(); + } else if(tag == BER.CONTEXT(7)) { + mc.maximumConnectsPerTarget = seq.readInt(); + } else if(tag == BER.CONTEXT(8)) { + mc.parametersLocation = seq.readInt(); + } else if(tag == BER.CONTEXT(9)) { + mc.gainParameterNumber = seq.readInt(); + } else if(tag == BER.CONTEXT(10)) { + mc.labels = []; + //console.log("\n\nLABEL\n\n",seq.buffer); + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + var lSeq = seq.getSequence(BER.CONTEXT(0)); + mc.labels.push(Label.decode(lSeq)); + } + //console.log(mc); + } else if(tag == BER.CONTEXT(11)) { + mc.schemaIdentifiers = seq.readInt(); + } else if(tag == BER.CONTEXT(12)) { + mc.templateReference = seq.readOID(BER.EMBER_RELATIVE_OID); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + //console.log("end of matrix contents"); + return mc; +}; + +MatrixContents.prototype.encode = function(ber) { + ber.startSequence(BER.EMBER_SET); + if (this.identifier !== undefined) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.identifier); + ber.endSequence(); + } + if (this.description !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description); + ber.endSequence(); + } + if (this.type !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + this.type.encode(ber); + ber.endSequence(); + } + if (this.mode !== undefined) { + ber.startSequence(BER.CONTEXT(3)); + this.mode.encode(ber); + ber.endSequence(); + } + if (this.targetCount !== undefined) { + ber.startSequence(BER.CONTEXT(4)); + ber.writeInt(this.targetCount); + ber.endSequence(); + } + if (this.sourceCount !== undefined) { + ber.startSequence(BER.CONTEXT(5)); + ber.writeInt(this.sourceCount); + ber.endSequence(); + } + if (this.maximumTotalConnects !== undefined) { + ber.startSequence(BER.CONTEXT(6)); + ber.writeInt(this.maximumTotalConnects); + ber.endSequence(); + } + if (this.maximumConnectsPerTarget !== undefined) { + ber.startSequence(BER.CONTEXT(7)); + ber.writeInt(this.maximumConnectsPerTarget); + ber.endSequence(); + } + if (this.parametersLocation !== undefined) { + ber.startSequence(BER.CONTEXT(8)); + ber.writeInt(this.parametersLocation); + ber.endSequence(); + } + if (this.gainParameterNumber !== undefined) { + ber.startSequence(BER.CONTEXT(9)); + ber.writeInt(this.gainParameterNumber); + ber.endSequence(); + } + if (this.labels !== undefined) { + ber.startSequence(BER.CONTEXT(10)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i =0; i < this.labels.length; i++) { + this.labels[i].encode(ber); + } + ber.endSequence(); + ber.endSequence(); + } + if (this.schemaIdentifiers !== undefined) { + ber.startSequence(BER.CONTEXT(11)); + ber.writeInt(this.schemaIdentifiers); + ber.endSequence(); + } + if (this.templateReference !== undefined) { + ber.startSequence(BER.CONTEXT(12)); + ber.writeOID(this.templateReference, BER.EMBER_RELATIVE_OID); + ber.endSequence(); + } + ber.endSequence(); +} + +decodeTargets = function(ber) { + let targets = []; + + ber = ber.getSequence(BER.EMBER_SEQUENCE); + + + while(ber.remain > 0) { + var seq = ber.getSequence(BER.CONTEXT(0)); + seq = seq.getSequence(BER.APPLICATION(14)); + seq = seq.getSequence(BER.CONTEXT(0)); + targets.push(seq.readInt()); + } + + return targets; +} + +decodeSources = function(ber) { + let sources = []; + + ber = ber.getSequence(BER.EMBER_SEQUENCE); + + while(ber.remain > 0) { + var seq = ber.getSequence(BER.CONTEXT(0)); + seq = seq.getSequence(BER.APPLICATION(15)); + seq = seq.getSequence(BER.CONTEXT(0)); + sources.push(seq.readInt()); + } + + return sources; +}; + +module.exports.MatrixContents = MatrixContents; + + + +function MatrixConnection() { + +} + +MatrixConnection.decode = function(ber) { + var c = new MatrixConnection(); + ber = ber.getSequence(BER.APPLICATION(16)); + ber = ber.getSequence(BER.EMBER_SEQUENCE); + while (ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if (tag == BER.CONTEXT(0)) { + c.target = seq.readInt(); + } + else if (tag == BER.CONTEXT(1)) { + //sources + var sources = seq.readOID(BER.EMBER_RELATIVE_OID); + c.sources = sources.split("."); + } else if (tag == BER.CONTEXT(2)) { + c.operation = seq.readInt(); + // ConnectionOperation ::= + // INTEGER { + // absolute (0), -- default. sources contains absolute information + // connect (1), -- nToN only. sources contains sources to add to connection + // disconnect (2) -- nToN only. sources contains sources to remove from + // connection + // } + } else if (tag == BER.CONTEXT(3)) { + m.disposition = seq.readInt(); + // ConnectionDisposition ::= + // INTEGER { + // tally (0), -- default + // modified (1), -- sources contains new current state + // pending (2), -- sources contains future state + // locked (3) -- error: target locked. sources contains current state + // -- more tbd. + // } + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return m; +} + +MatrixConnection.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(16)); + ber.startSequence(BER.EMBER_SEQUENCE); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.target); + ber.endSequence(); + + if (this.sources !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); + ber.endSequence(); + } + if (this.operation !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.writeInt(this.operation); + ber.endSequence(); + } + if (this.disposition !== undefined) { + ber.startSequence(BER.CONTEXT(3)); + ber.writeInt(this.disposition); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); +} + +module.exports.MatrixConnection = MatrixConnection; + +function Label() { +} + +Label.decode = function(ber) { + var l = {}; + + ber = ber.getSequence(BER.APPLICATION(18)); + + while (ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if (tag == BER.CONTEXT(0)) { + l.basePath = seq.readOID(BER.EMBER_RELATIVE_OID); + } else if (tag == BER.CONTEXT(1)) { + l.description = seq.readString(BER.EMBER_STRING); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return l; +}; + +Label.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(18)); + if (this.basePath !== undefined) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeOID(this.basePath, BER.EMBER_RELATIVE_OID); + } + if (this.description !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.basePath); + } + ber.endSequence(); +} + +module.exports.Label = Label; + + +function ParametersLocation() { +} + +ParametersLocation.decode = function(ber) { + var tag = ber.peek(); + ber = ber.getSequence(tag); + this.value = ber.readValue(); +} + +module.exports.ParametersLocation = ParametersLocation; + +function MatrixType() { + this.value = "oneToN"; +} + +MatrixType.decode = function(ber) { + var mt = new MatrixType(); + var type = ber.readInt(); + + if (type === 1) { + mt.value = "oneToOne"; + } else if (type === 2) { + mt.value = "nToN"; + } + return mt; +} + +MatrixType.prototype.encode = function(ber) { + if (this.type === "oneToOne") { + ber.writeInt(1); + } + else if (this.type === "nToN") { + ber.writeInt(2); + } + else { + ber.writeInt(0); + } +} + +module.exports.MatrixType = MatrixType; + +function MatrixMode() { + this.value = "linear"; +} + +MatrixMode.decode = function(ber) { + var mt = new MatrixMode(); + var mode = ber.readInt(); + + if (mode === 1) { + mt.value = "nonLinear"; + } + return mt; +} + +MatrixMode.prototype.encode = function(ber) { + if (this.type === "nonLinear") { + ber.writeInt(1); + } + else { + ber.writeInt(0); + } +} + +module.exports.MatrixMode = MatrixMode; + + +/**************************************************************************** + * QualifiedMatrix + ***************************************************************************/ + +function QualifiedMatrix(path) { + QualifiedMatrix.super_.call(this); + if (path != undefined) { + this.path = path; + } +} + +util.inherits(QualifiedMatrix, TreeNode); + + +QualifiedNoQualifiedMatrixde.decode = function(ber) { + var qm = new QualifiedMatrix(); + ber = ber.getSequence(BER.APPLICATION(17)); + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qm.path = seq.readOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qm.contents = MatrixContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qm.children = []; + seq = seq.getSequence(BER.APPLICATION(4)); + while(seq.remain > 0) { + var nodeSeq = seq.getSequence(BER.CONTEXT(0)); + qm.addChild(Element.decode(nodeSeq)); + } + } else if (tag == BER.CONTEXT(3)) { + qm.targets = decodeTargets(seq); + } else if (tag == BER.CONTEXT(4)) { + qm.sources = decodeSources(seq); + } else if (tag == BER.CONTEXT(5)) { + qm.connections = []; + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + var conSeq = seq.getSequence(BER.CONTEXT(0)); + qm.connections.push(MatrixConnection.decode(conSeq)); + } + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return qm; +} + +QualifiedMatrix.prototype.update = function(other) { + callbacks = QualifiedMatrix.super_.prototype.update.apply(this); + if (other !== undefined) { + if (other.contents !== undefined) { + this.contents = other.contents; + } + if (other.targets !== undefined) { + this.targets = other.targets; + } + if (other.sources !== undefined) { + this.sources = other.sources; + } + if (other.connections !== undefined) { + this.connections = other.connections; + } + } + + return callbacks; +} + +QualifiedMatrix.prototype.getDirectory = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + //console.log("Generating getDirectory command for node", this); + var r = new Root(); + var qn = new QualifiedMatrix(); + qn.path = this.path; + r.addElement(qn); + qn.addChild(new Command(COMMAND_GETDIRECTORY)); + if(callback !== undefined) { + this._directoryCallbacks.push((error, node) => { callback(error, node) }); + } + return r; +} + + +QualifiedMatrix.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(17)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.children !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.APPLICATION(4)); + for(var i=0; i 0) { - var tag = ber.readSequence(); + var tag = ber.peek(); + var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - nc.identifier = ber.readString(BER.EMBER_STRING); + nc.identifier = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(1)) { - nc.description = ber.readString(BER.EMBER_STRING); + nc.description = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(2)) { - nc.isRoot = ber.readBoolean(); + nc.isRoot = seq.readBoolean(); } else if(tag == BER.CONTEXT(3)) { - nc.isOnline = ber.readBoolean(); + nc.isOnline = seq.readBoolean(); } else if(tag == BER.CONTEXT(4)) { - nc.schemaIdentifiers = ber.readString(BER.EMBER_STRING); + nc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); } else { throw new errors.UnimplementedEmberTypeError(tag); } @@ -634,9 +1280,10 @@ Command.decode = function(ber) { ber = ber.getSequence(BER.APPLICATION(2)); while(ber.remain > 0) { - var tag = ber.readSequence(); + var tag = ber.peek(); + var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - c.number = ber.readInt(); + c.number = seq.readInt(); } else { // TODO: options throw new errors.UnimplementedEmberTypeError(tag); @@ -679,22 +1326,23 @@ QualifiedParameter.decode = function(ber) { var qp = new QualifiedParameter(); ber = ber.getSequence(BER.APPLICATION(9)); while(ber.remain > 0) { - var tag = ber.readSequence(); + var tag = ber.peek(); + var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - qp.path = ber.readOID(13); // 13 => relative OID + qp.path = seq.readOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID //console.log("Decoded path",qp.path); } else if(tag == BER.CONTEXT(1)) { //console.log("Decoding content"); - qp.contents = ParameterContents.decode(ber); + qp.contents = ParameterContents.decode(seq); //console.log("Decoded content",qp.contents); } else if(tag == BER.CONTEXT(2)) { qp.children = []; //console.log("Decoding children"); - var seq = ber.getSequence(BER.APPLICATION(4)); + seq = seq.getSequence(BER.APPLICATION(4)); while(seq.remain > 0) { - seq.readSequence(BER.CONTEXT(0)); - qp.addChild(Element.decode(seq)); + var nodeSeq = seq.getSequence(BER.CONTEXT(0)); + qp.addChild(Element.decode(nodeSeq)); } } else { return qp; @@ -708,7 +1356,7 @@ QualifiedParameter.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(9)); ber.startSequence(BER.CONTEXT(0)); - ber.writeOID(this.path, 13); + ber.writeOID(this.path, BER.EMBER_RELATIVE_OID); ber.endSequence(); // BER.CONTEXT(0) if(this.contents !== undefined) { @@ -748,7 +1396,7 @@ QualifiedParameter.prototype.getDirectory = function(callback) { let qp = new QualifiedParameter(); qp.path = this.path; r.addElement(qp); - qp.addChild(new Command(32)); + qp.addChild(new Command(COMMAND_GETDIRECTORY)); if(callback !== undefined) { this._directoryCallbacks.push((error, node) => { callback(error, node) }); } @@ -765,7 +1413,6 @@ QualifiedParameter.prototype.setValue = function(value, callback) { r.addElement(qp); qp.path = this.path; qp.contents = new ParameterContents(value); - //console.log(JSON.stringify(r)); return r; } @@ -787,17 +1434,18 @@ Parameter.decode = function(ber) { ber = ber.getSequence(BER.APPLICATION(1)); while(ber.remain > 0) { - var tag = ber.readSequence(); + var tag = ber.peek(); + var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - p.number = ber.readInt(); + p.number = seq.readInt(); } else if(tag == BER.CONTEXT(1)) { - p.contents = ParameterContents.decode(ber); + p.contents = ParameterContents.decode(seq); } else if(tag == BER.CONTEXT(2)) { + seq = seq.getSequence(BER.APPLICATION(4)); p.children = []; - var seq = ber.getSequence(BER.APPLICATION(4)); while(seq.remain > 0) { - seq.readSequence(BER.CONTEXT(0)); - p.addChild(Element.decode(seq)); + var paramSeq = seq.getSequence(BER.CONTEXT(0)); + p.addChild(Element.decode(paramSeq)); } } else { throw new errors.UnimplementedEmberTypeError(tag); @@ -892,43 +1540,44 @@ ParameterContents.decode = function(ber) { ber = ber.getSequence(BER.EMBER_SET); while(ber.remain > 0) { - var tag = ber.readSequence(); + var tag = ber.peek(); + var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - pc.identifier = ber.readString(BER.EMBER_STRING); + pc.identifier = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(1)) { - pc.description = ber.readString(BER.EMBER_STRING); + pc.description = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(2)) { - pc.value = ber.readValue(); + pc.value = seq.readValue(); } else if(tag == BER.CONTEXT(3)) { - pc.minimum = ber.readValue(); + pc.minimum = seq.readValue(); } else if(tag == BER.CONTEXT(4)) { - pc.maximum = ber.readValue(); + pc.maximum = seq.readValue(); } else if(tag == BER.CONTEXT(5)) { - pc.access = ParameterAccess.get(ber.readInt()); + pc.access = ParameterAccess.get(seq.readInt()); } else if(tag == BER.CONTEXT(6)) { - pc.format = ber.readString(BER.EMBER_STRING); + pc.format = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(7)) { - pc.enumeration = ber.readString(BER.EMBER_STRING); + pc.enumeration = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(8)) { - pc.factor = ber.readInt(); + pc.factor = seq.readInt(); } else if(tag == BER.CONTEXT(9)) { - pc.isOnline = ber.readBoolean(); + pc.isOnline = seq.readBoolean(); } else if(tag == BER.CONTEXT(10)) { - pc.formula = ber.readString(BER.EMBER_STRING); + pc.formula = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(11)) { - pc.step = ber.readInt(); + pc.step = seq.readInt(); } else if(tag == BER.CONTEXT(12)) { - pc.default = ber.readValue(); + pc.default = seq.readValue(); } else if(tag == BER.CONTEXT(13)) { - pc.type = ParameterType.get(ber.readInt()); + pc.type = ParameterType.get(seq.readInt()); } else if(tag == BER.CONTEXT(14)) { - pc.streamIdentifier = ber.readInt(); + pc.streamIdentifier = seq.readInt(); } else if(tag == BER.CONTEXT(15)) { - pc.enumMap = StringIntegerCollection.decode(ber); + pc.enumMap = StringIntegerCollection.decode(seq); } else if(tag == BER.CONTEXT(16)) { - pc.streamDescriptor = StreamDescription.decode(ber); + pc.streamDescriptor = StreamDescription.decode(seq); } else if(tag == BER.CONTEXT(17)) { - pc.schemaIdentifiers = ber.readString(BER.EMBER_STRING); + pc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); } else if (tag == null) { break; } @@ -936,7 +1585,6 @@ ParameterContents.decode = function(ber) { throw new errors.UnimplementedEmberTypeError(tag); } } - //console.log("decoded content", pc); return pc; } @@ -988,15 +1636,16 @@ StringIntegerCollection.decode = function(ber) { var enumMap = {}; ber = ber.getSequence(BER.APPLICATION(8)); while(ber.remain > 0) { - ber.readSequence(BER.CONTEXT(0)); - var seq = ber.getSequence(BER.APPLICATION(7)); + var seq = ber.getSequence(BER.CONTEXT(0)); + seq = seq.getSequence(BER.APPLICATION(7)); var entryString, entryInteger; while(seq.remain > 0) { - var tag = seq.readSequence(); + var tag = seq.peek(); + var dataSeq = seq.getSequence(tag); if(tag == BER.CONTEXT(0)) { - entryString = seq.readString(BER.EMBER_STRING); + entryString = dataSeq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(1)) { - entryInteger = seq.readInt(); + entryInteger = dataSeq.readInt(); } else { throw new errors.UnimplementedEmberTypeError(tag); } @@ -1057,11 +1706,12 @@ StreamDescription.decode = function(ber) { ber = ber.getSequence(BER.APPLICATION(12)); while(ber.remain > 0) { - var tag = ber.readSequence(); + var tag = ber.peek(); + var seq =ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - sd.format = StreamFormat.get(ber.readInt()); + sd.format = StreamFormat.get(seq.readInt()); } else if(tag == BER.CONTEXT(1)) { - sd.offset = ber.readInt(); + sd.offset = seq.readInt(); } else { throw new errors.UnimplementedEmberTypeError(tag); } diff --git a/index.js b/index.js index e5c946d..bbaf3ee 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,3 @@ -const DeviceTree = require('./device.js'); +const EMBERPLUS = require('./device.js'); -module.exports = { - DeviceTree -} +module.exports = EMBERPLUS; diff --git a/package.json b/package.json index 70796f9..860b816 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.1.0", + "version": "1.2.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 79084faffbd8cca3fcedce53206e29e7a92e4b25 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 19 Dec 2017 05:30:28 -0500 Subject: [PATCH 003/162] fixing qualifiedMatrix --- client.js | 52 +++++++++++++++++++++++++++++----------------------- device.js | 18 ++++++++++++++---- ember.js | 2 +- package.json | 2 +- 4 files changed, 45 insertions(+), 29 deletions(-) mode change 100644 => 100755 client.js diff --git a/client.js b/client.js old mode 100644 new mode 100755 index 1061c4f..5954c21 --- a/client.js +++ b/client.js @@ -14,42 +14,48 @@ function S101Client(address, port) { self.address = address; self.port = port; self.socket = null; - + self.keepaliveInterval = 10; self.codec = new S101Codec(); - self.connect(); - - setInterval(() => { - self.sendKeepaliveRequest(); - }, 10000); - - self.codec.on('keepaliveReq', () => { - self.sendKeepaliveResponse(); - }); - - self.codec.on('emberPacket', (packet) => { - self.emit('emberPacket', packet); + self.status = "disconnected"; - var ber = new BER.Reader(packet); - try { - var root = ember.Root.decode(ber); - if (root !== undefined) { - self.emit('emberTree', root); - } - } catch(e) { - console.log(e); - } - }); } util.inherits(S101Client, EventEmitter); S101Client.prototype.connect = function() { var self = this; + if (self.status !== "disconnected") { + return; + } + self.emit('connecting'); //console.log("socket connecting"); self.socket = net.createConnection(self.port, self.address, () => { winston.debug('socket connected'); + + setInterval(() => { + self.sendKeepaliveRequest(); + }, 1000 * self.keepaliveInterval ); + + self.codec.on('keepaliveReq', () => { + self.sendKeepaliveResponse(); + }); + + self.codec.on('emberPacket', (packet) => { + self.emit('emberPacket', packet); + + var ber = new BER.Reader(packet); + try { + var root = ember.Root.decode(ber); + if (root !== undefined) { + self.emit('emberTree', root); + } + } catch(e) { + self.emit("error", e); + } + }); + self.emit('connected'); }); diff --git a/device.js b/device.js index d5134f3..a00a374 100755 --- a/device.js +++ b/device.js @@ -24,10 +24,11 @@ function DeviceTree(host, port) { }); self.client.on('connected', () => { - self.root.clear(); - self.client.sendBERNode(self.root.getDirectory((node) => { - self.emit('ready'); - })); + // self.root.clear(); + // self.client.sendBERNode(self.root.getDirectory((node) => { + // self.emit('ready'); + // })); + self.emit('connected'); }); self.client.on('disconnected', () => { @@ -51,11 +52,16 @@ function DeviceTree(host, port) { util.inherits(DeviceTree, EventEmitter); + DecodeBuffer = function(packet) { var ber = new BER.Reader(packet); return ember.Root.decode(ber); } +DeviceTree.prototype.connect = function() { + this.client.connect(); +} + DeviceTree.prototype.expand = function(node) { let self = this; @@ -78,6 +84,10 @@ DeviceTree.prototype.expand = function(node) DeviceTree.prototype.getDirectory = function(qnode) { var self = this; + if (qnode === undefined) { + self.root.clear(); + qnode = self.root; + } return new Promise((resolve, reject) => { self.addRequest((error) => { if (error) { diff --git a/ember.js b/ember.js index b0a83a4..e8b07bf 100755 --- a/ember.js +++ b/ember.js @@ -1048,7 +1048,7 @@ function QualifiedMatrix(path) { util.inherits(QualifiedMatrix, TreeNode); -QualifiedNoQualifiedMatrixde.decode = function(ber) { +QualifiedMatrix.decode = function(ber) { var qm = new QualifiedMatrix(); ber = ber.getSequence(BER.APPLICATION(17)); while(ber.remain > 0) { diff --git a/package.json b/package.json index 860b816..e39f172 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.2.0", + "version": "1.3.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 3316acf07a6da9aaf8edd243e8b89b0ac1e1a051 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 19 Dec 2017 08:36:50 -0500 Subject: [PATCH 004/162] Fixing s101 multi packet message. Adding support for qualified functions --- ber.js | 3 +- ember.js | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- s101.js | 11 +-- 3 files changed, 231 insertions(+), 10 deletions(-) mode change 100644 => 100755 s101.js diff --git a/ber.js b/ber.js index d889f93..5cf5f5c 100755 --- a/ber.js +++ b/ber.js @@ -100,7 +100,8 @@ ExtendedReader.prototype.readValue = function() { return this.readString(UNIVERSAL(4), true); } else if (tag === EMBER_RELATIVE_OID) { return this.readOID(EMBER_RELATIVE_OID); - } else { + } + else { throw new errors.UnimplementedEmberTypeError(tag); } } diff --git a/ember.js b/ember.js index e8b07bf..72ce4a7 100755 --- a/ember.js +++ b/ember.js @@ -3,7 +3,6 @@ const errors = require('./errors.js'); const util = require('util'); const Enum = require('enum'); - const COMMAND_SUBSCRIBE = 30; const COMMAND_UNSUBSCRIBE = 31; const COMMAND_GETDIRECTORY = 32; @@ -25,7 +24,6 @@ function Root() { util.inherits(Root, TreeNode); - Root.decode = function(ber) { let r = new Root(); let tag = undefined; @@ -36,7 +34,6 @@ Root.decode = function(ber) { tag = ber.peek(); } catch (e) { - console.log(e.stack); break; } @@ -375,7 +372,10 @@ Element.decode = function(ber) { else if(tag == BER.APPLICATION(19)) { // Function throw new errors.UnimplementedEmberTypeError(tag); - } else if(tag == BER.APPLICATION(24)) { + } else if (tag == BER.APPLICATION(20)) { + return QualifiedFunction.decode(ber); + } + else if(tag == BER.APPLICATION(24)) { // Template throw new errors.UnimplementedEmberTypeError(tag); } else { @@ -1194,6 +1194,225 @@ QualifiedMatrix.prototype.encode = function(ber) { module.exports.QualifiedMatrix = QualifiedMatrix; +/**************************************************************************** + * FunctionContent + ***************************************************************************/ + +function FunctionContent() { +} + +decodeTupleDescription = function(ber) { + var tuple = {}; + ber = ber.getSequence(BER.APPLICATION(21)); + while(ber.remain > 0) { + tag = ber.peek(); + var seq = ber.getSequence(tag); + if (tag === BER.CONTEXT(0)) { + tuple.type = seq.readInt(); + } + else if (tag === BER.CONTEXT(1)) { + tuple.name = seq.readString(BER.EMBER_STRING); + } + } + return tuple; +}; + +encodeTupleDescription = function(tuple, ber) { + ber.startSequence(BER.APPLICATION(21)); + if (tuple.type !== undefined) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(tuple.type); + ber.endSequence(); + } + if (tuple.name !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(tuple.name); + ber.endSequence(); + } + ber.endSequence(); +} + +FunctionContent.decode = function(ber) { + var fc = new FunctionContent(); + ber = ber.getSequence(BER.EMBER_SET); + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + fc.identifier = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + fc.description = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(2)) { + fc.arguments = []; + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + tag = seq.peek(); + var dataSeq = seq.getSequence(BER.CONTEXT(0)); + if (tag === BER.CONTEXT(0)) { + fc.arguments.push(decodeTupleDescription(dataSeq)); + } + } + } else if(tag == BER.CONTEXT(3)) { + fc.result = []; + while(seq.remain > 0) { + tag = seq.peek(); + var dataSeq = seq.getSequence(tag); + if (tag === BER.CONTEXT(0)) { + fc.result.push(decodeTupleDescription(dataSeq)); + } + } + } else if(tag == BER.CONTEXT(4)) { + fc.templateReference = seq.readOID(BER.EMBER_RELATIVE_OID); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return fc; +} + +FunctionContent.prototype.encode = function(ber) { + ber.startSequence(BER.EMBER_SET); + + if(this.identifier !== undefined) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.identifier, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(0) + } + + if(this.description !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.arguments !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i =0; i < this.arguments; i++) { + ber.startSequence(BER.CONTEXT(0)); + encodeTupleDescription(this.arguments[i], ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); // BER.CONTEXT(2) + } + + if(this.result !== undefined) { + ber.startSequence(BER.CONTEXT(3)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i =0; i < this.result; i++) { + ber.startSequence(BER.CONTEXT(0)); + encodeTupleDescription(this.result[i], ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); // BER.CONTEXT(3) + } + + ber.endSequence(); // BER.EMBER_SET +} + +module.exports.FunctionContent = FunctionContent; + +/**************************************************************************** + * QualifiedFunction + ***************************************************************************/ + +function QualifiedFunction(path) { + QualifiedFunction.super_.call(this); + if (path != undefined) { + this.path = path; + } +} + +util.inherits(QualifiedFunction, TreeNode); + + +QualifiedFunction.decode = function(ber) { + var qf = new QualifiedFunction(); + ber = ber.getSequence(BER.APPLICATION(20)); + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qf.path = seq.readOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qf.contents = FunctionContent.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qf.children = []; + seq = seq.getSequence(BER.APPLICATION(4)); + while(seq.remain > 0) { + var nodeSeq = seq.getSequence(BER.CONTEXT(0)); + qf.addChild(Element.decode(nodeSeq)); + } + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return qf; +} + +QualifiedFunction.prototype.update = function(other) { + callbacks = QualifiedFunction.super_.prototype.update.apply(this); + if (other !== undefined) { + if (other.contents !== undefined) { + this.contents = other.contents; + } + } + + return callbacks; +} + +QualifiedFunction.prototype.getDirectory = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + //console.log("Generating getDirectory command for node", this); + var r = new Root(); + var qf = new QualifiedFunction(); + qf.path = this.path; + r.addElement(qf); + qf.addChild(new Command(COMMAND_GETDIRECTORY)); + if(callback !== undefined) { + this._directoryCallbacks.push((error, node) => { callback(error, node) }); + } + return r; +} + + +QualifiedFunction.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(20)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.children !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.APPLICATION(4)); + for(var i=0; i Date: Tue, 19 Dec 2017 08:37:06 -0500 Subject: [PATCH 005/162] version 1.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e39f172..ecc5050 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.3.0", + "version": "1.4.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From c6768e3e258d798b5034c9d70a39bb935204f625 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 20 Dec 2017 03:23:00 -0500 Subject: [PATCH 006/162] Make connect() return a Promise --- README.md | 69 +++++++++++++++++++++------------------------------- ber.js | 2 +- client.js | 6 ++--- device.js | 23 ++++++++++++------ package.json | 2 +- 5 files changed, 48 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 9ff6953..5e35fb5 100644 --- a/README.md +++ b/README.md @@ -7,53 +7,40 @@ communication protocols and user interfaces; this module allows those to be integrated with Ember+ somewhat more easily than the reference libember C++ implementation. -It is, at this point in time, still very much a work in progress. I am -developing it primarily to control our instance of [Virtual Patch -Bay](http://www.r3lay.com/product/vpb-virtual-patch-bay/), which seems to make -use of only a subset of of the Glow DTD. As such, I'm focusing on -implementing the parts of the DTD that are necessary for my use case. Please -consider this highly experimental code; any use in production environments is -entirely at your own risk. - -Basic trees of parameters should work. Streams aren't there yet, but -shouldn't be too far off. Everything else is probably missing at this point -(e.g. the newer matrix stuff). - -QualifiedNode and QualifiedParameter have been added in release 1.1.0. -This version should be able to retrieve an EVS XT4k tree. -We intend to implement the server version with Matrix support pretty soon. +This version support following ember objects : Node, Parameter, Matrix, QualifiedNode, +QualifiedParameter, QualifiedMatrix, QualifiedFunction. + +It has been tested with EVS XT4k and Embrionix IP solutions. + +The current version has added new features to the initial commit but it also modified +the way the lib is used so that now it uses Promise ## Example usage ```javascript const DeviceTree = require('emberplus').DeviceTree; +var root; +var tree = new DeviceTree("10.9.8.7", 9000); +tree.connect() +.then(() => { + return tree.getDirectory(); +}) +.then((r) => { + root = r ; + return tree.expand(r.elements[0]); +}) +.then(() => { + console.log("done"); +}) +.catch((e) => { + console.log(e.stack); +}); -var tree = new DeviceTree("localhost", 9998); - -tree.on('ready', () => { - // When device returns a qualified node, you don't have a complete tree - // Collect entire tree with the expand function. - // The function returns a Promise - tree.expand(tree.root.elements[0]).then((node) => { - console.log(tree.root.elements[0]); - }); - - // You can also get one qualified parameter or node at a time. - tree.getDirectory(root.elements[0]).then((node) => { - console.log(tree.root.elements[0]); - }); - - // Subscribe to parameter changes - tree.subscribe(node, (node) => { - console.log("Volume changed: %d", node.contents.value); - }); - // Change parameter value - tree.setValue(node, -20.0); +// Simple packet decoder +const Decoder = require('emberplus').DecodeBuffer; +const fs = require("fs"); - }).catch((e) => { - console.log("Failed to resolve node:", e); - }); +fs.readFile("tree.ember", (e,data) => { + var root = Decoder(data); }); - -``` diff --git a/ber.js b/ber.js index 5cf5f5c..e293ee9 100755 --- a/ber.js +++ b/ber.js @@ -100,7 +100,7 @@ ExtendedReader.prototype.readValue = function() { return this.readString(UNIVERSAL(4), true); } else if (tag === EMBER_RELATIVE_OID) { return this.readOID(EMBER_RELATIVE_OID); - } + } else { throw new errors.UnimplementedEmberTypeError(tag); } diff --git a/client.js b/client.js index 5954c21..5581574 100755 --- a/client.js +++ b/client.js @@ -65,13 +65,12 @@ S101Client.prototype.connect = function() { self.socket.on('close', () => { self.emit('disconnected'); + self.status = "disconnected"; self.socket = null; }); self.socket.on('error', (e) => { - //self.emit('disconnected'); - //self.socket = null; - //console.log("Socket error", e); + self.emit("error", e); }); } @@ -80,6 +79,7 @@ S101Client.prototype.disconnect = function() { if(self.socket !== null) { self.socket.destroy(); self.socket = null; + self.status = "disconnected"; } } diff --git a/device.js b/device.js index a00a374..e4a2a60 100755 --- a/device.js +++ b/device.js @@ -24,22 +24,21 @@ function DeviceTree(host, port) { }); self.client.on('connected', () => { - // self.root.clear(); - // self.client.sendBERNode(self.root.getDirectory((node) => { - // self.emit('ready'); - // })); self.emit('connected'); + if (self.callback !== undefined) { + self.callback(); + } }); self.client.on('disconnected', () => { self.emit('disconnected'); - self.connectTimeout = setTimeout(() => { - self.client.connect(); - }, 10000); }); self.client.on("error", (e) => { self.emit("error", e); + if (self.callback !== undefined) { + self.callback(e); + } }); self.client.on('emberTree', (root) => { @@ -59,7 +58,15 @@ DecodeBuffer = function(packet) { } DeviceTree.prototype.connect = function() { - this.client.connect(); + return new Promise((resolve, reject) => { + this.callback = (e) => { + if (e === undefined) { + return resolve(); + } + return reject(e); + }; + this.client.connect(); + }); } DeviceTree.prototype.expand = function(node) diff --git a/package.json b/package.json index ecc5050..363c6b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.4.0", + "version": "1.5.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 9d1f4b764d9c01b6907beeea0365bc3852fd29c8 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 21 Dec 2017 03:50:03 -0500 Subject: [PATCH 007/162] Adding isParameter, isMatrix, isFunction to TreeNode --- ember.js | 14 ++++++++++++++ index.js | 7 ++++--- package.json | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) mode change 100644 => 100755 index.js diff --git a/ember.js b/ember.js index 72ce4a7..4fa53f9 100755 --- a/ember.js +++ b/ember.js @@ -70,6 +70,8 @@ Root.prototype.addElement = function(ele) { this.elements.push(ele); } + + Root.prototype.addChild = function(child) { this.addElement(child); } @@ -121,6 +123,18 @@ TreeNode.prototype.addChild = function(child) { this.children.push(child); } +TreeNode.prototype.isParameter = function() { + return ((this instanceof MatrixNode) || (this instanceof QualifiedMatrix)); +} + +TreeNode.prototype.isMatrix = function() { + return ((this instanceof Parameter) || (this instanceof QualifiedParameter)); +} + +TreeNode.prototype.isFunction = function() { + return (this instanceof QualifiedFunction); +} + TreeNode.prototype.addCallback = function(callback) { if(this._callbacks.indexOf(callback) < 0) { this._callbacks.push(callback); diff --git a/index.js b/index.js old mode 100644 new mode 100755 index bbaf3ee..2ae669c --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ -const EMBERPLUS = require('./device.js'); - -module.exports = EMBERPLUS; +const DeviceTree = require('./device.js').DeviceTree; +const Decoder = require('./device.js').DecodeBuffer; +const Ember = require("./ember.js"); +module.exports = {DeviceTree, Decoder, Ember}; diff --git a/package.json b/package.json index 363c6b5..a55dc1b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.5.0", + "version": "1.5.1", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 7546c8b4f47a9b9363a4b960252a91e4033f71e0 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 22 Dec 2017 03:19:58 -0500 Subject: [PATCH 008/162] Fixing isParameter --- ember.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ember.js b/ember.js index 4fa53f9..44af23e 100755 --- a/ember.js +++ b/ember.js @@ -123,11 +123,11 @@ TreeNode.prototype.addChild = function(child) { this.children.push(child); } -TreeNode.prototype.isParameter = function() { +TreeNode.prototype.isMatrix = function() { return ((this instanceof MatrixNode) || (this instanceof QualifiedMatrix)); } -TreeNode.prototype.isMatrix = function() { +TreeNode.prototype.isParameter = function() { return ((this instanceof Parameter) || (this instanceof QualifiedParameter)); } diff --git a/package.json b/package.json index a55dc1b..1ce41e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.5.1", + "version": "1.5.2", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 4ccde9756703f674f82acecd863e11dde0663c4d Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 27 Dec 2017 07:02:57 -0500 Subject: [PATCH 009/162] Fixing QualifiedParameter - initial parameter is a path not a number --- ember.js | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ember.js b/ember.js index 44af23e..fc219b0 100755 --- a/ember.js +++ b/ember.js @@ -1545,10 +1545,10 @@ module.exports.Command = Command; * QualifiedParameter ***************************************************************************/ -function QualifiedParameter(number) { +function QualifiedParameter(path) { QualifiedParameter.super_.call(this); - if(number !== undefined) - this.number = number; + if(path !== undefined) + this.path = path; } util.inherits(QualifiedParameter, TreeNode); diff --git a/package.json b/package.json index 1ce41e0..cd9afcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.5.2", + "version": "1.5.3", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 8ddfc388b36df5d75127005d6a8c1ddf144260d5 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 28 Dec 2017 09:29:22 -0500 Subject: [PATCH 010/162] Adding connection timeout --- client.js | 20 +++++++++++++++----- device.js | 9 +++++---- ember.js | 3 +-- package.json | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/client.js b/client.js index 5581574..e6cc485 100755 --- a/client.js +++ b/client.js @@ -22,7 +22,7 @@ function S101Client(address, port) { util.inherits(S101Client, EventEmitter); -S101Client.prototype.connect = function() { +S101Client.prototype.connect = function(timeout = 2) { var self = this; if (self.status !== "disconnected") { return; @@ -31,9 +31,22 @@ S101Client.prototype.connect = function() { self.emit('connecting'); //console.log("socket connecting"); + if (timeout > 0) { + self._timeout = timeout; + self._timer = setTimeout(() => { + self.socket = undefined; + self.emit("error", new Error("connection timeout")); + }, 1000 * timeout); + } + self.socket = net.createConnection(self.port, self.address, () => { winston.debug('socket connected'); + if (self._timer) { + clearTimeout(self._timer); + } + + setInterval(() => { self.sendKeepaliveRequest(); }, 1000 * self.keepaliveInterval ); @@ -59,6 +72,7 @@ S101Client.prototype.connect = function() { self.emit('connected'); }); + self.socket.on('data', (data) => { self.codec.dataIn(data); }); @@ -103,11 +117,7 @@ S101Client.prototype.sendBER = function(data) { var self = this; var frames = self.codec.encodeBER(data); for(var i=0; i { @@ -57,7 +56,7 @@ DecodeBuffer = function(packet) { return ember.Root.decode(ber); } -DeviceTree.prototype.connect = function() { +DeviceTree.prototype.connect = function(timeout = 2) { return new Promise((resolve, reject) => { this.callback = (e) => { if (e === undefined) { @@ -65,7 +64,7 @@ DeviceTree.prototype.connect = function() { } return reject(e); }; - this.client.connect(); + this.client.connect(timeout); }); } @@ -129,10 +128,12 @@ DeviceTree.prototype.makeRequest = function() { var self=this; if(self.activeRequest === null && self.pendingRequests.length > 0) { self.activeRequest = self.pendingRequests.shift(); - self.activeRequest(); + self.timeout = setTimeout(() => { self.timeoutRequest(); }, self.timeoutValue); + + self.activeRequest(); } }; diff --git a/ember.js b/ember.js index fc219b0..78dfb9f 100755 --- a/ember.js +++ b/ember.js @@ -1642,9 +1642,8 @@ QualifiedParameter.prototype.setValue = function(value, callback) { } let r = new Root(); - let qp = new QualifiedParameter(); + let qp = new QualifiedParameter(this.path); r.addElement(qp); - qp.path = this.path; qp.contents = new ParameterContents(value); return r; } diff --git a/package.json b/package.json index cd9afcd..bc00eb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.5.3", + "version": "1.5.4", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 854c96c7d186dcbbbb232cd4931176fe1237ae09 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 29 Dec 2017 09:12:06 -0500 Subject: [PATCH 011/162] Fixed several matrix content encoding errors. Added server part --- README.md | 7 +++ ber.js | 2 +- client.js | 123 ++++++++++++++++++++++++++++++++++++++++++++---- device.js | 2 +- ember.js | 36 ++++++++++---- embrionix.ember | Bin 0 -> 41743 bytes index.js | 3 +- package.json | 2 +- 8 files changed, 153 insertions(+), 22 deletions(-) create mode 100644 embrionix.ember diff --git a/README.md b/README.md index 5e35fb5..e989b14 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ It has been tested with EVS XT4k and Embrionix IP solutions. The current version has added new features to the initial commit but it also modified the way the lib is used so that now it uses Promise +Server has been added in version 1.6.0. + ## Example usage ```javascript @@ -44,3 +46,8 @@ const fs = require("fs"); fs.readFile("tree.ember", (e,data) => { var root = Decoder(data); }); + +// Server +const TreeServer = require("emberplus").TreeServer; +const server = new TreeServer("127.0.0.1", 9000, root); +server.listen().then(() => { console.log("listening"); }).catch((e) => { console.log(e.stack); }); diff --git a/ber.js b/ber.js index e293ee9..47a0b74 100755 --- a/ber.js +++ b/ber.js @@ -314,7 +314,7 @@ ExtendedWriter.prototype.writeIfDefined = function(property, writer, outer, inne ExtendedWriter.prototype.writeIfDefinedEnum = function(property, type, writer, outer, inner) { if(property !== undefined) { - this.startSequence(BER.CONTEXT(outer)); + this.startSequence(CONTEXT(outer)); if(property.value !== undefined) { writer.call(this, property.value, inner); } else { diff --git a/client.js b/client.js index e6cc485..12b22da 100755 --- a/client.js +++ b/client.js @@ -7,9 +7,10 @@ const ember = require('./ember.js'); const S101Codec = require('./s101.js'); -function S101Client(address, port) { + +function S101Socket(address, port) { var self = this; - S101Client.super_.call(this); + S101Socket.super_.call(this); self.address = address; self.port = port; @@ -20,9 +21,111 @@ function S101Client(address, port) { } -util.inherits(S101Client, EventEmitter); +util.inherits(S101Socket, EventEmitter); + + +function S101Client(socket, server) { + var self = this; + S101Client.super_.call(this); + + self.server = server; + self.socket = socket; + + self.status = "connected"; + + self.codec.on('keepaliveReq', () => { + self.sendKeepaliveResponse(); + }); + + self.codec.on('emberPacket', (packet) => { + self.emit('emberPacket', packet); + + var ber = new BER.Reader(packet); + try { + var root = ember.Root.decode(ber); + if (root !== undefined) { + self.emit('emberTree', root); + } + } catch(e) { + self.emit("error", e); + } + }); + + if (socket !== undefined) { + self.socket.on('data', (data) => { + self.codec.dataIn(data); + }); + + self.socket.on('close', () => { + self.emit('disconnected'); + self.status = "disconnected"; + self.socket = null; + }); + + self.socket.on('error', (e) => { + self.emit("error", e); + }); + } +} + +util.inherits(S101Client, S101Socket); + + +/********************************************** + * SERVER + **********************************************/ + +function S101Server(address, port) { + var self = this; + S101Server.super_.call(this); + + self.address = address; + self.port = port; + self.server = null; + self.status = "disconnected"; +} + +util.inherits(S101Server, EventEmitter); + +S101Server.prototype.listen = function() { + var self = this; + if (self.status !== "disconnected") { + return; + } + + self.server = net.createServer((socket) => { + self.addClient(socket); + }); + + self.server.on("error", (e) => { + self.emit("error", e); + }); + + self.server.on("listening", () => { + self.emit("listening"); + }); + + self.server.listen(self.port, self.address); +} + + +S101Server.prototype.addClient = function(socket) { + var client = new S101Client(socket, this); + this.emit("connection", client); +} + + +/***************************************************** + * Client + *****************************************************/ + + + +/***************************************************** + * Socket + *****************************************************/ -S101Client.prototype.connect = function(timeout = 2) { +S101Socket.prototype.connect = function(timeout = 2) { var self = this; if (self.status !== "disconnected") { return; @@ -88,7 +191,7 @@ S101Client.prototype.connect = function(timeout = 2) { }); } -S101Client.prototype.disconnect = function() { +S101Socket.prototype.disconnect = function() { var self = this; if(self.socket !== null) { self.socket.destroy(); @@ -97,7 +200,7 @@ S101Client.prototype.disconnect = function() { } } -S101Client.prototype.sendKeepaliveRequest = function() { +S101Socket.prototype.sendKeepaliveRequest = function() { var self = this; if(self.socket !== null) { self.socket.write(self.codec.keepAliveRequest()); @@ -105,7 +208,7 @@ S101Client.prototype.sendKeepaliveRequest = function() { } } -S101Client.prototype.sendKeepaliveResponse = function() { +S101Socket.prototype.sendKeepaliveResponse = function() { var self = this; if(self.socket !== null) { self.socket.write(self.codec.keepAliveResponse()); @@ -113,7 +216,7 @@ S101Client.prototype.sendKeepaliveResponse = function() { } } -S101Client.prototype.sendBER = function(data) { +S101Socket.prototype.sendBER = function(data) { var self = this; var frames = self.codec.encodeBER(data); for(var i=0; i^Lju%!m0K z4#|9(bLPz7ntw6#R8>6bm7pXTV`y7_jyb++T}f5-?t8Cm(|7B)*6*#?*Y6!39zJyF zk9A}H-m%P)Y3Fvc?i_;8#(D$3x5M9`%8XyO-Nqf;b+kFV?Q9&)j9#4mZ0_Pe{A&#T z%l|v#bNvr;huF0r%bcF?E_;l7-EsTPZpWva6Z(X%qz^MX+l?#6KD zkdijSXycg^yhQwYPiIa#?f!BvYwKA(pS5gui4pn1MWq~Om&jzsX6=r>;V;R&39?W8d94 znaPA6#!x=WQFak%w!UY3&HDCbiP(my^JDA+P=0X%Wl^@f$Z_c+s*-GXjWKqOEzpQ18-t)}(d zX4`h}YJI2P?KI$j<74gW?PkO2YNie!?dn49-1+M@ty~x%t5?OHOwfZqVNICl3YLGp zes|5SJK5!C3rb&UcIqunJ`-E}puTm~(u6^fR*}s>6c)9JdgMGW*Yc)G7 z)tggGQ*PF(m=$wUyVGoVt5qncKB=ub&6QQJYUm{Z&X0E8TXw;_xS*G+HmNn7o_{KQ zw=gwdou9rssnxr!E)>`C+`H8a3q`#=snuqyehpo!%}#3b^K;d+^9w%CntH|5jpA8g z*Vq{gwYf>H*@5Dv*KRf{qVjiC<+;RFo=dAdTIC@-kClPF)@Vj#P~1WWR^m1ZL@LRks9iCwb5;pt zaBgek0-b3Lwu6?W<_O)hcq@`;NFUP%PZeJb9bG;bks`3p(P51tw1FZ+)D zYu;kw;uZJ1cze>krNreg?RWV@0VA<`%ZZCu-tXe=N%I@*K?EE(hDsLWdlBsGEGd z(GJp0c_HnlOuH!~ULbkGx^2GtDEAD9^q6*2ChUbxom#b z`Qgl+lc&xDnK>sD>^`itn=)wPZPUKNZsN3?GHBvN zX;#`zxtBGttDflOnO53OnRZjMDJy$v!#nx0w1XbIw3|{rVQ;$}Y!JFB`J7>xsw`$x z-f4RERc!%gOeL5=$|n%R#Ar^wbZusOum~fkRtNvdT zZbP9Y!7ztVqCdcqDlwgh%_rG!_EDrtd>~5XksMb41YtN|a`L+EhvCMK!mwCr!NWVO zZJB4QBD9+8^kKmdOAJOr0etA+>#MLtY-Rl(+z$_l|s4aaWqW9+f; z80U`i0zzf_x46M>aU^`~0zY;Xk3m&U&&*G;GmrU4xBB1;KVEQXlJjiNeuCU;*o53_ zIQxKetHj;G$@`HK&34<%!K~f%F%0Hd-Oh4`)WTr7S-NN?ei;}>i}5j~g+*hbk;X!? zF#FMsg_t(*rNgk$dTQ0~bevY7r8Gm~ltzxB7(Y1|_ipKA2+?D3 zkS0SYy&K<1A@rL&wXpq+pUDS-2FAjo@zYG>XBt1(MaRI_@pCv){Ny`sVEoJteEdvj zn~C^&kY<}bRPi&lutBr1X#C8j@iUE|_alDt4W}@ES_$H3!SBB{4r>2xI-U1`PUqcW z3{9=f-!yFBdT|GJZwsZBMdPTI#?ds6-j6uS_gus{njic)ny#4$Ce$CKH4{n?7wyYE z;BhpyvH`TRXdKO_aWsvi_alztzQcSzQDN}oXz~d~>Hb*{a!Jd>6-QGm3*zWOTK=fC zvS=JFq;WKjqxU0@^3C}1_h@nO<7o1UMd>DmX&j~RRY*Mh+eob}h@%5Mzo?kT(KL?U zk2uO-PlIu^H285e`Sg!;i-k0f?lz95Ru;t3ftkq`{XJSr<7gU3??)UR-+ay6wC&m3 zzJ@Kl%;j5un$gV%Jk2QG#zwq#4mO+FYK!qlYPaq1l9<#Q)4kvC_lEboUw&Fn!}22^ zmi=F%;SVk;<#Ryb#i)D;b=LOW=8yiFd|hcf{OV=tEWSzCpKQO4zea~)lZkaUfN}bt z>pE=7;ljI|$B!Hu9s2t!zW&v}@Sp74j<~RYTUZ5mY1SbAqlCG*8e1 zLDvYnPS6(w-5}^DL0=N|6+vGU^bJAZ60}IrcLdo4EfG{Fs6miJ&@w?Q1g#R(BXw?DzN-4(|R zU4gu#9jn-dK^#8jIMpEnUOjSy39G<>8eV45MO+H82=SisDM zm}_XS|O&|bWTake2A$wp3@Q&t;dW};~9yF=3{2nHT#%EMEfzb>WY0_B9=D| z&&;ap^$AKe(c_p|b+tYz5z*zCS#_;GB@xl*m|3+ZpO%Q|bj++;l+Q>+^g3o%t;%O5 zBAN~ufLfN%NzB5g(E(E}%jYF#F~n3G&kGW>6k@8a=S7K$?gyS$Z9XqiW)9sCFxB?+ zvcyF915C96X%Z6+C>JBkS0o}DP%cK6XC)#UP%cK6uS!IWTe%onz9tdT_2gn?`MN|z z*OQBpwL+4X6+!%c~L*4X6+!%Xx{2v8xax%LR#uuBQ+q%WD!5T~8rKme(aBx}HLeEWeP5 z=z0nnM2ua< z7+E$L5#Hdi*=2~nrx>G4M`oh)DaHtMS!Sa5DaI&sMP{P=DaJ^1Rc509DaL5CDKpW4 zzzo#l{9dMF00XL8owsBvMlhhN<=K*{7{Y+6)@NI$q9X#TTA&??8gxDbRjtsjOhs1& zRJBBVG8Ij!6r<4}WG0$YDMqBO%tTWv#i+C|GclHxVr1&cO!P&i7@4lgOms%27@6Ld zndps5F*3a)GtnKDVr2TG#0KoEz!KCl{Yj=`Km)2;ra#M6jA%er%k&qS ziXjcCYMK5jQ_&>>RV~xsWGcEOpsHp1yG%uw1k^2M>W@nLADp9wBaHp-np<}g40*yJ z4-9=>Y{MH6mA{B0W1ay~-3<6hJOFMsy?;k_BSc0$gFC95ATsP35Y-JZ${HXv?imob ztbQC2jtqPTMD-|3WaKj-s(+wHhCTzLdXyzH_6e!UrU9r`93A`&QPsv19sLYZ)#ei& k{tQvo1{59t3{ll46deE!QPoBi9RUqdw=^TJ;hp{cAFtSbSpWb4 literal 0 HcmV?d00001 diff --git a/index.js b/index.js index 2ae669c..2b6570d 100755 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ const DeviceTree = require('./device.js').DeviceTree; const Decoder = require('./device.js').DecodeBuffer; const Ember = require("./ember.js"); -module.exports = {DeviceTree, Decoder, Ember}; +const TreeServer = require("./server"); +module.exports = {DeviceTree, Decoder, Ember, TreeServer}; diff --git a/package.json b/package.json index bc00eb8..366ce3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.5.4", + "version": "1.6.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 0faeb9d8202a79762a449925a9b5183a171da6f4 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 29 Dec 2017 11:01:34 -0500 Subject: [PATCH 012/162] Fixed getElementByPath --- ember.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ember.js b/ember.js index 93011ce..8763a59 100755 --- a/ember.js +++ b/ember.js @@ -215,8 +215,8 @@ _getElementByPath = function(children, pathArray, path) { for (var i = 0; i < children.length; i++) { //console.log("looking at child", JSON.stringify(children[i])); - if ((children[i].path === currPath)|| - (children[i].number === number)){ + if ((children[i].path == currPath)|| + (children[i].number == number)){ if (path.length === 0) { return children[i]; } diff --git a/package.json b/package.json index 366ce3d..ea4f8f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.6.0", + "version": "1.6.1", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 9c57d6cb6e7e939915e2d653cddb873af2d23fe4 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 2 Jan 2018 11:29:55 -0500 Subject: [PATCH 013/162] Fixed relativeOID - require ASN 0.5.1 -, Fixed Matrix connect/disconnect and parameter setvalue on server --- client.js | 24 +++++++- device.js | 6 ++ ember.js | 164 +++++++++++++++++++++++++++++++++++---------------- package.json | 2 +- 4 files changed, 142 insertions(+), 54 deletions(-) diff --git a/client.js b/client.js index 12b22da..143ff0e 100755 --- a/client.js +++ b/client.js @@ -31,6 +31,9 @@ function S101Client(socket, server) { self.server = server; self.socket = socket; + self.pendingRequests = []; + self.activeRequest = null; + self.status = "connected"; self.codec.on('keepaliveReq', () => { @@ -103,6 +106,7 @@ S101Server.prototype.listen = function() { self.server.on("listening", () => { self.emit("listening"); + self.status = "listening"; }); self.server.listen(self.port, self.address); @@ -119,6 +123,25 @@ S101Server.prototype.addClient = function(socket) { * Client *****************************************************/ +S101Client.prototype.queueMessage = function(node) { + const self = this; + this.addRequest(() => { + self.sendBERNode(node); + }); +} + +S101Client.prototype.makeRequest = function() { + if(this.activeRequest === null && this.pendingRequests.length > 0) { + this.activeRequest = this.pendingRequests.shift(); + this.activeRequest(); + this.activeRequest = null; + } +}; + +S101Client.prototype.addRequest = function(cb) { + this.pendingRequests.push(cb); + this.makeRequest(); +} /***************************************************** @@ -132,7 +155,6 @@ S101Socket.prototype.connect = function(timeout = 2) { } self.emit('connecting'); - //console.log("socket connecting"); if (timeout > 0) { self._timeout = timeout; diff --git a/device.js b/device.js index 5499bf7..aeffa34 100755 --- a/device.js +++ b/device.js @@ -56,6 +56,12 @@ DecodeBuffer = function(packet) { return ember.Root.decode(ber); } +DeviceTree.prototype.saveTree = function(f) { + var writer = new BER.Writer(); + this.root.encode(writer); + f(writer.buffer); +} + DeviceTree.prototype.connect = function(timeout = 2) { return new Promise((resolve, reject) => { this.callback = (e) => { diff --git a/ember.js b/ember.js index 8763a59..d53cce5 100755 --- a/ember.js +++ b/ember.js @@ -233,14 +233,27 @@ TreeNode.prototype.getElementByPath = function(path) { if ((children === null)||(children === undefined)) { return null; } + + var myPath = this.getPath(); + if (path == myPath) { + return this; + } + var myPathArray = []; + if (this._parent) { + myPathArray = myPath.split("."); + } path = path.split("."); - if (path.length > 2) { - pathArray = path.splice(0,2); + if (path.length > myPathArray.length) { + pathArray = path.splice(0, myPath.length + 1); + for(var i = 0; i < pathArray.length - 1; i++) { + if (pathArray[i] != myPathArray[i]) { + return null; + } + } } else { - pathArray = path; - path = []; + return null; } return _getElementByPath(children, pathArray, path); } @@ -431,7 +444,7 @@ QualifiedNode.decode = function(ber) { var tag = ber.peek(); var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - qn.path = seq.readOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + qn.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID } else if(tag == BER.CONTEXT(1)) { qn.contents = NodeContents.decode(seq); @@ -478,7 +491,7 @@ QualifiedNode.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(10)); ber.startSequence(BER.CONTEXT(0)); - ber.writeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); ber.endSequence(); // BER.CONTEXT(0) if(this.contents !== undefined) { @@ -620,11 +633,14 @@ MatrixNode.decode = function(ber) { } else if (tag == BER.CONTEXT(4)) { m.sources = decodeSources(seq); } else if (tag == BER.CONTEXT(5)) { - m.connections = []; + m.connections = {}; seq = seq.getSequence(BER.EMBER_SEQUENCE); while(seq.remain > 0) { var conSeq = seq.getSequence(BER.CONTEXT(0)); - m.connections.push(MatrixConnection.decode(conSeq)); + var con = MatrixConnection.decode(conSeq); + if (con.target !== undefined) { + m.connections[con.target] = con; + } } } else { @@ -698,9 +714,9 @@ MatrixNode.prototype.encode = function(ber) { if (this.connections !== undefined) { ber.startSequence(BER.CONTEXT(5)); - for(var i=0; i 0) { var tag = ber.peek(); var seq = ber.getSequence(tag); @@ -897,38 +956,23 @@ MatrixConnection.decode = function(ber) { } else if (tag == BER.CONTEXT(1)) { //sources - var sources = seq.readOID(BER.EMBER_RELATIVE_OID); + var sources = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); c.sources = sources.split("."); } else if (tag == BER.CONTEXT(2)) { - c.operation = seq.readInt(); - // ConnectionOperation ::= - // INTEGER { - // absolute (0), -- default. sources contains absolute information - // connect (1), -- nToN only. sources contains sources to add to connection - // disconnect (2) -- nToN only. sources contains sources to remove from - // connection - // } + c.operation = MatrixOperation.get(seq.readInt()); + } else if (tag == BER.CONTEXT(3)) { - m.disposition = seq.readInt(); - // ConnectionDisposition ::= - // INTEGER { - // tally (0), -- default - // modified (1), -- sources contains new current state - // pending (2), -- sources contains future state - // locked (3) -- error: target locked. sources contains current state - // -- more tbd. - // } + c.disposition = MatrixDisposition.get(seq.readInt()); } else { throw new errors.UnimplementedEmberTypeError(tag); } } - return m; + return c; } MatrixConnection.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(16)); - ber.startSequence(BER.EMBER_SEQUENCE); ber.startSequence(BER.CONTEXT(0)); ber.writeInt(this.target); @@ -936,21 +980,20 @@ MatrixConnection.prototype.encode = function(ber) { if (this.sources !== undefined) { ber.startSequence(BER.CONTEXT(1)); - ber.writeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); + ber.writeRelativeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); ber.endSequence(); } if (this.operation !== undefined) { ber.startSequence(BER.CONTEXT(2)); - ber.writeInt(this.operation); + ber.writeInt(this.operation.value); ber.endSequence(); } if (this.disposition !== undefined) { ber.startSequence(BER.CONTEXT(3)); - ber.writeInt(this.disposition); + ber.writeInt(this.disposition.value); ber.endSequence(); } ber.endSequence(); - ber.endSequence(); } module.exports.MatrixConnection = MatrixConnection; @@ -967,7 +1010,7 @@ Label.decode = function(ber) { var tag = ber.peek(); var seq = ber.getSequence(tag); if (tag == BER.CONTEXT(0)) { - l.basePath = seq.readOID(BER.EMBER_RELATIVE_OID); + l.basePath = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); } else if (tag == BER.CONTEXT(1)) { l.description = seq.readString(BER.EMBER_STRING); } @@ -982,12 +1025,12 @@ Label.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(18)); if (this.basePath !== undefined) { ber.startSequence(BER.CONTEXT(0)); - ber.writeOID(this.basePath, BER.EMBER_RELATIVE_OID); + ber.writeRelativeOID(this.basePath, BER.EMBER_RELATIVE_OID); ber.endSequence(); } if (this.description !== undefined) { ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.basePath); + ber.writeString(this.description, BER.EMBER_STRING); ber.endSequence(); } ber.endSequence(); @@ -1084,7 +1127,7 @@ QualifiedMatrix.decode = function(ber) { var tag = ber.peek(); var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - qm.path = seq.readOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + qm.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID } else if(tag == BER.CONTEXT(1)) { qm.contents = MatrixContents.decode(seq); @@ -1100,11 +1143,14 @@ QualifiedMatrix.decode = function(ber) { } else if (tag == BER.CONTEXT(4)) { qm.sources = decodeSources(seq); } else if (tag == BER.CONTEXT(5)) { - qm.connections = []; + qm.connections = {}; seq = seq.getSequence(BER.EMBER_SEQUENCE); while(seq.remain > 0) { var conSeq = seq.getSequence(BER.CONTEXT(0)); - qm.connections.push(MatrixConnection.decode(conSeq)); + var con = MatrixConnection.decode(conSeq); + if (con.target !== undefined) { + qm.connections[con.target] = con; + } } } else { @@ -1150,12 +1196,24 @@ QualifiedMatrix.prototype.getDirectory = function(callback) { return r; } +QualifiedMatrix.prototype.connect = function(connections) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + //console.log("Generating getDirectory command for node", this); + var r = new Root(); + var qn = new QualifiedMatrix(); + qn.path = this.path; + r.addElement(qn); + qn.connections = connections; + return r; +} QualifiedMatrix.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(17)); ber.startSequence(BER.CONTEXT(0)); - ber.writeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); ber.endSequence(); // BER.CONTEXT(0) if(this.contents !== undefined) { @@ -1210,12 +1268,14 @@ QualifiedMatrix.prototype.encode = function(ber) { if (this.connections !== undefined) { ber.startSequence(BER.CONTEXT(5)); - for(var i=0; i relative OID + qf.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID } else if(tag == BER.CONTEXT(1)) { qf.contents = FunctionContent.decode(seq); @@ -1416,7 +1476,7 @@ QualifiedFunction.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(20)); ber.startSequence(BER.CONTEXT(0)); - ber.writeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); ber.endSequence(); // BER.CONTEXT(0) if(this.contents !== undefined) { @@ -1577,7 +1637,7 @@ QualifiedParameter.decode = function(ber) { var tag = ber.peek(); var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - qp.path = seq.readOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + qp.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID //console.log("Decoded path",qp.path); } else if(tag == BER.CONTEXT(1)) { @@ -1604,7 +1664,7 @@ QualifiedParameter.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(9)); ber.startSequence(BER.CONTEXT(0)); - ber.writeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); ber.endSequence(); // BER.CONTEXT(0) if(this.contents !== undefined) { diff --git a/package.json b/package.json index ea4f8f2..4bb3a66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.6.1", + "version": "1.6.2", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 19daadd931cceb5ac8abb6da94bdc0f51d530998 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 3 Jan 2018 03:25:59 -0500 Subject: [PATCH 014/162] Added missing server.js file --- ember.js | 10 +- server.js | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 337 insertions(+), 2 deletions(-) create mode 100755 server.js diff --git a/ember.js b/ember.js index d53cce5..192e9dd 100755 --- a/ember.js +++ b/ember.js @@ -160,7 +160,12 @@ TreeNode.prototype.cancelCallbacks = function() { } TreeNode.prototype.getMinimal = function() { - return new this.constructor(this.number); + if (this.isQualified()) { + return new this.constructor(this.path); + } + else { + return new this.constructor(this.number); + } } TreeNode.prototype.getTreeBranch = function(child, modifier) { @@ -175,7 +180,8 @@ TreeNode.prototype.getTreeBranch = function(child, modifier) { if(this._parent === null) { return m; - } else { + } + else { var p = this._parent.getTreeBranch(m); return p; } diff --git a/server.js b/server.js new file mode 100755 index 0000000..e7ae4a2 --- /dev/null +++ b/server.js @@ -0,0 +1,329 @@ +const EventEmitter = require('events').EventEmitter; +const util = require('util'); +const S101Server = require('./client.js').S101Server; +const ember = require('./ember.js'); + +function TreeServer(host, port, tree) { + TreeServer.super_.call(this); + var self = this; + + self.callback = undefined; + self.timeoutValue = 2000; + self.server = new S101Server(host, port); + self.tree = tree; + self.clients = new Set(); + self.subscribers = {}; + + self.server.on('listening', () => { + self.emit('listening'); + if (self.callback !== undefined) { + self.callback(); + self.callback = undefined; + } + }); + + self.server.on('connection', (client) => { + self.clients.add(client); + client.on("emberTree", (root) => { + // Queue the action to make sure responses are sent in order. + client.addRequest(() => { + self.handleRoot(client, root); + }); + }); + client.on("disconnected", () => { + self.clients.delete(client); + }); + self.emit('connection', client); + }); + + self.server.on('disconnected', () => { + self.emit('disconnected'); + }); + + self.server.on("error", (e) => { + self.emit("error", e); + if (self.callback !== undefined) { + self.callback(e); + } + }); + +} + +util.inherits(TreeServer, EventEmitter); + + +TreeServer.prototype.listen = function() { + return new Promise((resolve, reject) => { + this.callback = (e) => { + if (e === undefined) { + return resolve(); + } + return reject(e); + }; + this.server.listen(); + }); +}; + +TreeServer.prototype.handleRoot = function(client, root) { + if ((root === undefined) || (root.elements === undefined) || (root.elements < 1)) { + this.emit("error", new Error("invalid request")); + return; + } + + const node = root.elements[0]; + if (node.path !== undefined) { + this.handleQualifiedNode(client, node); + } + else if (node instanceof ember.Command) { + // Command on root element + this.handleCommand(client, this.tree, node.number); + } + else { + this.handleNode(client, node); + } +} + +TreeServer.prototype.handleQualifiedNode = function(client, node) { + const path = node.path; + // Find this element in our tree + const element = this.tree.getElementByPath(path); + + if ((element === null) || (element === undefined)) { + this.emit("error", new Error(`unknown element at path ${path}`)); + return; + } + + if ((node.children !== undefined) && (node.children.length === 1) && + (node.children[0] instanceof ember.Command)) { + this.handleCommand(client, element, node.children[0].number); + } + else { + if (node instanceof ember.QualifiedMatrix) { + this.handleQualifiedMatrix(client, element, node); + } + else if (node instanceof ember.QualifiedParameter) { + this.handleQualifiedParameter(client, element, node); + } + } +} + + +TreeServer.prototype.handleNode = function(client, node) { + // traverse the tree + let element = node; + let path = [0]; + while(element !== undefined) { + if (element.number === undefined) { + this.emit("error", "invalid request"); + return; + } + if (element instanceof ember.Command) { + break; + } + let children = element.getChildren(); + if ((children === undefined) || (children.length === 0)) { + break; + } + path.push(element.number); + element = element.children[0]; + } + let cmd = element; + + if (cmd === undefined) { + this.emit("error", "invalid request"); + return; + } + + element = this.tree.getElementByPath(path.join(".")); + + if ((element === null) || (element === undefined)) { + this.emit("error", new Error(`unknown element at path ${path}`)); + return; + } + + if (cmd instanceof ember.Command) { + this.handleCommand(client, element, cmd.number); + } + else if ((cmd instanceof ember.MatrixNode) && (cmd.connections !== undefined)) { + this.handleMatrixConnections(client, element, cmd.connections); + } + else if ((cmd instanceof ember.Parameter) && + (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { + // New value Received. + this.setValue(element, cmd.contents.value, client); + } + else { + this.emit("error", new Error(`invalid request format`)); + } +} + +TreeServer.prototype.handleQualifiedMatrix = function(client, element, matrix) +{ + this.handleMatrixConnections(client, element, matrix.connections); +} + +TreeServer.prototype.handleQualifiedParameter = function(client, element, parameter) +{ + if (parameter.contents.value !== undefined) { + this.setValue(element, parameter.contents.value, client); + } +} + + +TreeServer.prototype.handleMatrixConnections = function(client, matrix, connections) { + var res; + var root; + if (matrix.isQualified()) { + root = new ember.Root(); + res = new ember.QualifiedMatrix(matrix.path); + root.addChild(res); + } + else { + res = new ember.MatrixNode(matrix.number); + root = matrix._parent.getTreeBranch(res); + } + res.connections = {}; + for(let target in connections) { + let connection = connections[target]; + var conResult = new ember.MatrixConnection(connection.target); + res.connections[connection.target] = conResult; + if ((connection.operation === undefined) || + (connection.operation.value == ember.MatrixOperation.absolute)) { + conResult.sources = connection.sources; + this.emit("matrix-change", {target: target, sources: connection.sources}); + } + else if (connection.operation == ember.MatrixOperation.connect) { + if ((connection.sources == undefined) ||(connection.sources.length < 1)) { + conResult.sources = matrix.connections[connection.target].sources; + continue; + } + let sources = matrix.connections[connection.target].sources; + if (sources === undefined) { + sources = []; + } + var j = 0; + var newSources = []; + for(var i = 0; i < sources.length; i++) { + while((j < connection.sources.length) && + (connection.sources[j] < sources[i])) + { + newSources.push(connection.sources[j++]); + } + newSources.push(sources[i]); + } + while(j < connection.sources.length) { + newSources.push(connection.sources[j++]); + } + conResult.sources = newSources; + this.emit("matrix-connect", {target: target, sources: connection.sources}); + } + else { // Disconnect + let sources = matrix.connections[connection.target].sources; + if (sources === undefined) { + sources = []; + } + var j = 0; + var newSources = []; + + for(var i = 0; i < sources.length; i++) { + if ((j < connection.sources.length) && (sources[i] == connection.sources[j])) { + j++; + continue; + } + newSources.push(sources[i]); + } + conResult.sources = newSources; + this.emit("matrix-disconnect", {target: target, sources: connection.sources}); + } + matrix.connections[target].sources = conResult.sources; + conResult.disposition = ember.MatrixDisposition.modified; + } + client.sendBERNode(root); + this.updateSubscribers(matrix.getPath(), root, client); +} + +TreeServer.prototype.handleCommand = function(client, element, cmd) { + if (cmd === ember.GetDirectory) { + this.handleGetDirectory(client, element); + } + else if (cmd === ember.Subscribe) { + this.handleSubscribe(client, element); + } + else if (cmd === ember.Unsubscribe) { + this.handleUnSubscribe(client, element); + } + else { + this.emit("error", new Error(`invalid command ${cmd}`)); + } +} + + +TreeServer.prototype.handleGetDirectory = function(client, element) { + if (client !== undefined) { + client.sendBERNode(element); + } +} + +TreeServer.prototype.handleSubscribe = function(client, element) { + this.subscribe(client, element); +} + +TreeServer.prototype.handleUnSubscribe = function(client, element) { + this.unsubscribe(client, element); +} + + +TreeServer.prototype.subscribe = function(client, element) { + const path = element.getPath(); + if (this.subscribers[path] === undefined) { + this.subscribers[path] = new Set(); + } + this.subscribers[path].add(client); +} + +TreeServer.prototype.unsubscribe = function(client, element) { + const path = element.getPath(); + if (this.subscribers[path] === undefined) { + return; + } + this.subscribers[path].delete(client); +} + +TreeServer.prototype.setValue = function(element, value, origin) { + + // Change the element value if write access permitted. + if ((element.contents !== undefined) && + (element.contents.access !== undefined) && + (element.contents.access.value > 1)) { + element.contents.value = value; + this.emit("value-change", element); + } + + // Get the element branch to be returned and send response + let res = this.handleGetDirectory(origin, element); + // Update the subscribers + this.updateSubscribers(element.getPath(), res, origin); + return res; +} + +TreeServer.prototype.updateSubscribers = function(path, response, origin) { + if (this.subscribers[path] === undefined) { + return; + } + + for (let client of this.subscribers[path]) { + if (client === origin) { + continue; // already sent the response to origin + } + if (this.clients.has(client)) { + client.queueMessage(response); + } + else { + // clean up subscribers - client is gone + this.subscribers[path].delete(client); + } + } +} + + +module.exports = TreeServer; From bf717cec4d5d472b187fa0f53fbbf7497a007178 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 3 Jan 2018 03:26:20 -0500 Subject: [PATCH 015/162] Changed version to 1.6.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4bb3a66..8913490 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.6.2", + "version": "1.6.3", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From cde43fa65b86e8f7e523019d78666333fb2ff1fc Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 3 Jan 2018 04:47:26 -0500 Subject: [PATCH 016/162] Fixed expand function --- device.js | 24 ++++++++++++++++-------- package.json | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/device.js b/device.js index aeffa34..3a89f07 100755 --- a/device.js +++ b/device.js @@ -173,7 +173,7 @@ DeviceTree.prototype.handleRoot = function(root) { var callbacks = self.root.update(root); if(root.elements !== undefined) { for(var i=0; i Date: Wed, 3 Jan 2018 06:31:18 -0500 Subject: [PATCH 017/162] fixed error handling during connection --- client.js | 7 +++---- device.js | 2 +- package.json | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/client.js b/client.js index 143ff0e..23524b6 100755 --- a/client.js +++ b/client.js @@ -195,9 +195,10 @@ S101Socket.prototype.connect = function(timeout = 2) { }); self.emit('connected'); + }).on('error', (e) => { + self.emit("error", e); }); - self.socket.on('data', (data) => { self.codec.dataIn(data); }); @@ -208,9 +209,7 @@ S101Socket.prototype.connect = function(timeout = 2) { self.socket = null; }); - self.socket.on('error', (e) => { - self.emit("error", e); - }); + } S101Socket.prototype.disconnect = function() { diff --git a/device.js b/device.js index 3a89f07..a713798 100755 --- a/device.js +++ b/device.js @@ -34,10 +34,10 @@ function DeviceTree(host, port) { }); self.client.on("error", (e) => { - self.emit("error", e); if (self.callback !== undefined) { self.callback(e); } + self.emit("error", e); }); self.client.on('emberTree', (root) => { diff --git a/package.json b/package.json index cba38ac..7f1a388 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.6.4", + "version": "1.6.5", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 3aeea76cf1658630549e3efb06d087ad280af8c7 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 5 Jan 2018 05:49:07 -0500 Subject: [PATCH 018/162] Fixed expand timeout issue --- device.js | 78 +++++++++++++++++++++++++++++++--------------------- package.json | 2 +- s101.js | 1 - server.js | 27 +++++++++--------- 4 files changed, 61 insertions(+), 47 deletions(-) diff --git a/device.js b/device.js index a713798..74cfda3 100755 --- a/device.js +++ b/device.js @@ -1,22 +1,23 @@ const EventEmitter = require('events').EventEmitter; const util = require('util'); -const Promise = require('bluebird'); const S101Client = require('./client.js').S101Socket; const ember = require('./ember.js'); const BER = require('./ber.js'); const errors = require('./errors.js'); +const DEBUG = false; function DeviceTree(host, port) { DeviceTree.super_.call(this); var self = this; - self.timeoutValue = 2000; + self.timeoutValue = 3000; self.client = new S101Client(host, port); self.root = new ember.Root(); self.pendingRequests = []; self.activeRequest = null; self.timeout = null; self.callback = undefined; + self.requestID = 0; self.client.on('connecting', () => { self.emit('connecting'); @@ -77,20 +78,24 @@ DeviceTree.prototype.connect = function(timeout = 2) { DeviceTree.prototype.expand = function(node) { let self = this; - return new Promise((resolve, reject) => { - //console.log("Getting directory for node", node); - self.getDirectory(node).then((res) => { - if ((res === undefined) || (node.children === undefined)) { - //console.log("No more children for ", node); - return resolve(); - } - let p = []; - for(let child of node.children) { - //console.log("Expanding child", child); - p.push(self.expand(child)); - } - return Promise.all(p) - }).then(() => {resolve();}); + return self.getDirectory(node).then((res) => { + let children = node.getChildren(); + if ((res === undefined) || (children === undefined) || (children === null)) { + if (DEBUG) {console.log("No more children for ", node);} + return; + } + let p = []; + for (let child of children) { + if (DEBUG) {console.log("Expanding child", child);} + p.push( + self.expand(child).catch((e) => { + // We had an error on some expansion + // let's save it on the child itself + child.error = e; + }) + ); + } + return Promise.all(p); }); } @@ -103,23 +108,24 @@ DeviceTree.prototype.getDirectory = function(qnode) { return new Promise((resolve, reject) => { self.addRequest((error) => { if (error) { - self.finishRequest(); reject(error); + self.finishRequest(); return; } let cb = (error, node) => { - self.finishRequest(); + self.clearTimeout(); // clear the timeout now. The resolve below may take a while. if (error) { reject(error); } else { - //console.log("Received getDirectory response", node); - resolve(node); + if (DEBUG) {console.log("Received getDirectory response", node);} + resolve(node); // make sure the info is treated before going to next request. } + self.finishRequest(); }; - //console.log("Sending getDirectory"); + if (DEBUG) {console.log("Sending getDirectory", qnode);} self.callback = cb; self.client.sendBERNode(qnode.getDirectory()); }); @@ -135,10 +141,14 @@ DeviceTree.prototype.makeRequest = function() { if(self.activeRequest === null && self.pendingRequests.length > 0) { self.activeRequest = self.pendingRequests.shift(); - self.timeout = setTimeout(() => { - self.timeoutRequest(); - }, self.timeoutValue); + const t = function(id) { + if (DEBUG) {console.log(`Making request ${id}`,Date.now());} + self.timeout = setTimeout(() => { + self.timeoutRequest(id); + }, self.timeoutValue); + }; + t(self.requestID++); self.activeRequest(); } }; @@ -149,21 +159,25 @@ DeviceTree.prototype.addRequest = function(cb) { self.makeRequest(); } +DeviceTree.prototype.clearTimeout = function() { + if(this.timeout != null) { + clearTimeout(this.timeout); + this.timeout = null; + } +} + DeviceTree.prototype.finishRequest = function() { var self=this; self.callback = undefined; - if(self.timeout != null) { - clearTimeout(self.timeout); - self.timeout = null; - } + self.clearTimeout(); self.activeRequest = null; self.makeRequest(); } -DeviceTree.prototype.timeoutRequest = function() { +DeviceTree.prototype.timeoutRequest = function(id) { var self = this; self.root.cancelCallbacks(); - self.activeRequest(new errors.EmberTimeoutError('Request timed out')); + self.activeRequest(new errors.EmberTimeoutError(`Request ${id !== undefined ? id : ""} timed out`)); } DeviceTree.prototype.handleRoot = function(root) { @@ -303,12 +317,13 @@ DeviceTree.prototype.setValue = function(node, value) { //console.log('setValue', node.getPath(), value); self.addRequest((error) => { if(error) { - reject(error); self.finishRequest(); + reject(error); return; } let cb = (error, node) => { + //console.log('setValue complete...', node.getPath(), value); self.finishRequest(); if (error) { reject(error); @@ -319,6 +334,7 @@ DeviceTree.prototype.setValue = function(node, value) { }; self.callback = cb; + //console.log('setValue sending ...', node.getPath(), value); self.client.sendBERNode(node.setValue(value)); }); } diff --git a/package.json b/package.json index 7f1a388..0e7779c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.6.5", + "version": "1.6.6", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/s101.js b/s101.js index 354f9eb..fa467ce 100755 --- a/s101.js +++ b/s101.js @@ -2,7 +2,6 @@ const EventEmitter = require('events').EventEmitter; const util = require('util'); const SmartBuffer = require('smart-buffer').SmartBuffer; const winston = require('winston'); -const BER = require('./ber.js'); const S101_BOF = 0xFE; const S101_EOF = 0xFF; diff --git a/server.js b/server.js index e7ae4a2..81bea7f 100755 --- a/server.js +++ b/server.js @@ -111,7 +111,7 @@ TreeServer.prototype.handleQualifiedNode = function(client, node) { TreeServer.prototype.handleNode = function(client, node) { // traverse the tree let element = node; - let path = [0]; + let path = []; while(element !== undefined) { if (element.number === undefined) { this.emit("error", "invalid request"); @@ -290,20 +290,19 @@ TreeServer.prototype.unsubscribe = function(client, element) { } TreeServer.prototype.setValue = function(element, value, origin) { + return new Promise((resolve, reject) => { + // Change the element value if write access permitted. + if ((element.contents !== undefined) && + (element.contents.access !== undefined) && + (element.contents.access.value > 1)) { + element.contents.value = value; + this.emit("value-change", element); + } - // Change the element value if write access permitted. - if ((element.contents !== undefined) && - (element.contents.access !== undefined) && - (element.contents.access.value > 1)) { - element.contents.value = value; - this.emit("value-change", element); - } - - // Get the element branch to be returned and send response - let res = this.handleGetDirectory(origin, element); - // Update the subscribers - this.updateSubscribers(element.getPath(), res, origin); - return res; + let res = this.handleGetDirectory(origin, element); + // Update the subscribers + this.updateSubscribers(element.getPath(), res, origin); + }); } TreeServer.prototype.updateSubscribers = function(path, response, origin) { From 3304affde61e3ec1168fb125dc61959d467799ec Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 5 Jan 2018 05:53:19 -0500 Subject: [PATCH 019/162] Removed bluebird. Let's use Node 6 and above Promise --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 0e7779c..bc3cfe7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.6.6", + "version": "1.6.7", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { @@ -20,7 +20,6 @@ "license": "MIT", "dependencies": { "asn1": "evs-broadcast/node-asn1", - "bluebird": "^3.5.0", "enum": "^2.4.0", "long": "^3.2.0", "smart-buffer": "^3.0.3", From e1d426e8916bf0fd84947bb8db057a6fa7ac786a Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 9 Jan 2018 02:33:40 -0500 Subject: [PATCH 020/162] Added function to convert json to ember tree --- ember.js | 28 +++++++++ package.json | 2 +- server.js | 169 +++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 152 insertions(+), 47 deletions(-) diff --git a/ember.js b/ember.js index 192e9dd..1ac6cbe 100755 --- a/ember.js +++ b/ember.js @@ -950,6 +950,34 @@ var MatrixDisposition = new Enum({ module.exports.MatrixOperation = MatrixOperation; module.exports.MatrixDisposition = MatrixDisposition; +MatrixConnection.prototype.setSources = function(sources) { + if (sources === undefined) { + return; + } + this.sources = sources; // sources should be an array +} + +MatrixConnection.prototype.connectSources = function(sources) { + if (sources === undefined) { + return; + } + let s = new Set(this.sources); + for(let item of sources) { + s.add(item); + } + this.sources = [...s].sort(); +} + +MatrixConnection.prototype.disconnectSources = function(sources) { + if (sources === undefined) { + return; + } + let s = new Set(this.sources); + for(let item of sources) { + s.delete(item); + } + this.sources = [...s].sort(); +} MatrixConnection.decode = function(ber) { var c = new MatrixConnection(); diff --git a/package.json b/package.json index bc3cfe7..60f2769 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.6.7", + "version": "1.7.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 81bea7f..a7868f2 100755 --- a/server.js +++ b/server.js @@ -170,9 +170,9 @@ TreeServer.prototype.handleQualifiedParameter = function(client, element, parame } -TreeServer.prototype.handleMatrixConnections = function(client, matrix, connections) { +TreeServer.prototype.handleMatrixConnections = function(client, matrix, connections, response = true) { var res; - var root; + var root; // ember message root if (matrix.isQualified()) { root = new ember.Root(); res = new ember.QualifiedMatrix(matrix.path); @@ -187,61 +187,83 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti let connection = connections[target]; var conResult = new ember.MatrixConnection(connection.target); res.connections[connection.target] = conResult; - if ((connection.operation === undefined) || - (connection.operation.value == ember.MatrixOperation.absolute)) { - conResult.sources = connection.sources; - this.emit("matrix-change", {target: target, sources: connection.sources}); + + if (connection.sources === undefined) { + conResult.sources = matrix.connections[connection.target].sources; + continue; } - else if (connection.operation == ember.MatrixOperation.connect) { - if ((connection.sources == undefined) ||(connection.sources.length < 1)) { - conResult.sources = matrix.connections[connection.target].sources; - continue; - } - let sources = matrix.connections[connection.target].sources; - if (sources === undefined) { - sources = []; + else { + if ((connection.operation === undefined) || + (connection.operation.value == ember.MatrixOperation.absolute)) { + matrix.connections[connection.target].setSources(connection.sources); + this.emit("matrix-change", {target: target, sources: connection.sources}); } - var j = 0; - var newSources = []; - for(var i = 0; i < sources.length; i++) { - while((j < connection.sources.length) && - (connection.sources[j] < sources[i])) - { - newSources.push(connection.sources[j++]); - } - newSources.push(sources[i]); + else if (connection.operation == ember.MatrixOperation.connect) { + matrix.connections[connection.target].connectSources(connection.sources); + conResult.sources = matrix.connections[connection.target].sources; + this.emit("matrix-connect", {target: target, sources: connection.sources}); } - while(j < connection.sources.length) { - newSources.push(connection.sources[j++]); + else { // Disconnect + matrix.connections[connection.target].disconnectSources(connection.sources); + conResult.sources = matrix.connections[connection.target].sources; + this.emit("matrix-disconnect", {target: target, sources: connection.sources}); } - conResult.sources = newSources; - this.emit("matrix-connect", {target: target, sources: connection.sources}); - } - else { // Disconnect - let sources = matrix.connections[connection.target].sources; - if (sources === undefined) { - sources = []; + if (response) { + conResult.sources = matrix.connections[connection.target].sources; + conResult.disposition = ember.MatrixDisposition.modified; } - var j = 0; - var newSources = []; - - for(var i = 0; i < sources.length; i++) { - if ((j < connection.sources.length) && (sources[i] == connection.sources[j])) { - j++; - continue; - } - newSources.push(sources[i]); + else { + conResult.operation = connection.operation; } - conResult.sources = newSources; - this.emit("matrix-disconnect", {target: target, sources: connection.sources}); } - matrix.connections[target].sources = conResult.sources; - conResult.disposition = ember.MatrixDisposition.modified; } - client.sendBERNode(root); + if (client !== undefined) { + client.sendBERNode(root); + } this.updateSubscribers(matrix.getPath(), root, client); } +const validateMatrixOperation = function(matrix, target, sources) { + if (matrix === undefined) { + throw new Error(`matrix not found with path ${path}`); + } + if (matrix.contents === undefined) { + throw new Error(`invalid matrix at ${path} : no contents`); + } + if (matrix.contents.targetCount === undefined) { + throw new Error(`invalid matrix at ${path} : no targetCount`); + } + if ((target < 0) || (target >= matrix.contents.targetCount)) { + throw new Error(`invalid target id at ${target}`); + } + if (sources.length === undefined) { + throw new Error("invalid sources format"); + } +} + +const doMatrixOperation = function(server, path, target, sources, operation) { + let matrix = server.tree.getElementByPath(path); + + validateMatrixOperation(matrix, target, sources); + + let connections = new ember.MatrixConnection(target); + connections.sources = sources; + connections.operation = operation; + server.handleMatrixConnections(undefined, matrix, connections, false); +} + +TreeServer.prototype.matrixConnect = function(path, target, sources) { + doMatrixOperation(this, path, target, sources, ember.MatrixOperation.connect); +} + +TreeServer.prototype.matrixDisConnect = function(path, target, sources) { + doMatrixOperation(this, path, target, sources, ember.MatrixOperation.disconnect); +} + +TreeServer.prototype.matrixSet = function(path, target, sources) { + doMatrixOperation(this, path, target, sources, ember.MatrixOperation.absolute); +} + TreeServer.prototype.handleCommand = function(client, element, cmd) { if (cmd === ember.GetDirectory) { this.handleGetDirectory(client, element); @@ -324,5 +346,60 @@ TreeServer.prototype.updateSubscribers = function(path, response, origin) { } } +const parseObj = function(parent, obj, isQualified) { + let path = parent.getPath(); + for(let number = 0; number < obj.length; number++) { + let emberElement; + let content = obj[number]; + //console.log(`parsing obj at number ${number}`, content); + if (content.value !== undefined) { + //console.log("new parameter"); + // this is a parameter + if (isQualified) { + emberElement = new ember.QualifiedParameter(`${path}${path !== "" ? "." : ""}${number}`); + } + else { + emberElement = new ember.Parameter(number); + } + emberElement.contents = new ember.ParameterContents(content.value); + } + else if (content.targetCount !== undefined) { + //console.log("new matrix"); + if (isQualified) { + emberElement = new ember.QualifiedMatrix(`${path}${path !== "" ? "." : ""}${number}`); + } + else { + emberElement = new ember.MatrixNode(number); + } + emberElement.contents = new ember.MatrixContents(); + } + else { + //console.log("new node"); + if (isQualified) { + emberElement = new ember.QualifiedNode(`${path}${path !== "" ? "." : ""}${number}`); + } + else { + emberElement = new ember.Node(number); + } + emberElement.contents = new ember.NodeContents(); + } + for(let id in content) { + if ((id !== "children") && (content.hasOwnProperty(id))) { + //console.log(`adding contents ${id}`); + emberElement.contents[id] = content[id]; + } + else { + parseObj(emberElement, content.children, isQualified); + } + } + parent.addChild(emberElement); + } +} + +TreeServer.JSONtoTree = function(obj, isQualified = true) { + let tree = new ember.Root(); + parseObj(tree, obj, isQualified); + return tree; +} module.exports = TreeServer; From 092b2383e0e124f53c3886ece7f02c744bbc0cee Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 9 Jan 2018 05:24:13 -0500 Subject: [PATCH 021/162] Changed MatrixType and MatrixMode to enum. Completed JSONtoTree function --- ember.js | 68 ++++++++++++++++--------------------------------------- server.js | 32 +++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 52 deletions(-) diff --git a/ember.js b/ember.js index 1ac6cbe..076d578 100755 --- a/ember.js +++ b/ember.js @@ -743,6 +743,8 @@ util.inherits(MatrixNode, TreeNode); module.exports.MatrixNode = MatrixNode; function MatrixContents() { + this.type = MatrixType.oneToOne; + this.mode = MatrixMode.linear; } MatrixContents.decode = function(ber) { @@ -762,9 +764,9 @@ MatrixContents.decode = function(ber) { } else if(tag == BER.CONTEXT(1)) { mc.description = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(2)) { - mc.type = MatrixType.decode(seq); + mc.type = MatrixType.get(seq.readInt()); } else if(tag == BER.CONTEXT(3)) { - mc.mode = MatrixMode.decode(seq); + mc.mode = MatrixMode.get(seq.readInt()); } else if(tag == BER.CONTEXT(4)) { mc.targetCount = seq.readInt(); } else if(tag == BER.CONTEXT(5)) { @@ -813,12 +815,12 @@ MatrixContents.prototype.encode = function(ber) { } if (this.type !== undefined) { ber.startSequence(BER.CONTEXT(2)); - this.type.encode(ber); + ber.writeInt(this.type.value); ber.endSequence(); } if (this.mode !== undefined) { ber.startSequence(BER.CONTEXT(3)); - this.mode.encode(ber); + ber.writeInt(this.mode.value); ber.endSequence(); } if (this.targetCount !== undefined) { @@ -1032,7 +1034,10 @@ MatrixConnection.prototype.encode = function(ber) { module.exports.MatrixConnection = MatrixConnection; -function Label() { +function Label(path) { + if (path) { + this.basePath = path; + } } Label.decode = function(ber) { @@ -1084,58 +1089,23 @@ ParametersLocation.decode = function(ber) { module.exports.ParametersLocation = ParametersLocation; -function MatrixType() { - this.value = "oneToN"; -} -MatrixType.decode = function(ber) { - var mt = new MatrixType(); - var type = ber.readInt(); - if (type === 1) { - mt.value = "oneToOne"; - } else if (type === 2) { - mt.value = "nToN"; - } - return mt; -} +var MatrixType = new Enum({ + oneToN: 0, + oneToOne: 1, + nToN: 2 +}); -MatrixType.prototype.encode = function(ber) { - if (this.type === "oneToOne") { - ber.writeInt(1); - } - else if (this.type === "nToN") { - ber.writeInt(2); - } - else { - ber.writeInt(0); - } -} module.exports.MatrixType = MatrixType; -function MatrixMode() { - this.value = "linear"; -} - -MatrixMode.decode = function(ber) { - var mt = new MatrixMode(); - var mode = ber.readInt(); - if (mode === 1) { - mt.value = "nonLinear"; - } - return mt; -} +var MatrixMode = new Enum({ + linear: 0, + nonLinear: 1 +}); -MatrixMode.prototype.encode = function(ber) { - if (this.type === "nonLinear") { - ber.writeInt(1); - } - else { - ber.writeInt(0); - } -} module.exports.MatrixMode = MatrixMode; diff --git a/server.js b/server.js index a7868f2..82ba13e 100755 --- a/server.js +++ b/server.js @@ -256,7 +256,7 @@ TreeServer.prototype.matrixConnect = function(path, target, sources) { doMatrixOperation(this, path, target, sources, ember.MatrixOperation.connect); } -TreeServer.prototype.matrixDisConnect = function(path, target, sources) { +TreeServer.prototype.matrixDisconnect = function(path, target, sources) { doMatrixOperation(this, path, target, sources, ember.MatrixOperation.disconnect); } @@ -348,9 +348,11 @@ TreeServer.prototype.updateSubscribers = function(path, response, origin) { const parseObj = function(parent, obj, isQualified) { let path = parent.getPath(); - for(let number = 0; number < obj.length; number++) { + for(let i = 0; i < obj.length; i++) { let emberElement; - let content = obj[number]; + let content = obj[i]; + let number = content.number !== undefined ? content.number : i; + delete content.number; //console.log(`parsing obj at number ${number}`, content); if (content.value !== undefined) { //console.log("new parameter"); @@ -362,6 +364,20 @@ const parseObj = function(parent, obj, isQualified) { emberElement = new ember.Parameter(number); } emberElement.contents = new ember.ParameterContents(content.value); + if (content.type) { + emberElement.contents.type = ember.ParameterType.get(content.type); + delete content.type; + } + else { + emberElement.contents.type = ember.ParameterType.string; + } + if (content.access) { + emberElement.contents.access = ember.ParameterAccess.get(content.access); + delete content.access; + } + else { + emberElement.contents.access = ember.ParameterAccess.read; + } } else if (content.targetCount !== undefined) { //console.log("new matrix"); @@ -372,6 +388,16 @@ const parseObj = function(parent, obj, isQualified) { emberElement = new ember.MatrixNode(number); } emberElement.contents = new ember.MatrixContents(); + + if (content.labels) { + emberElement.contents.labels = []; + for(let l = 0; l < content.labels.length; l++) { + emberElement.contents.labels.push( + new ember.Label(content.labels[l]) + ); + } + delete content.labels; + } } else { //console.log("new node"); From 40fb970e6ab8a60a5a760b8bf400a3bddb4ba3e0 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 11 Jan 2018 04:54:13 -0500 Subject: [PATCH 022/162] Fixed matrix connect/disconnect operation on server and client --- client.js | 7 ++ device.js | 8 ++ ember.js | 225 +++++++++++++++++++++++++++++++++++++-------------- package.json | 2 +- server.js | 125 +++++++++++++++++++--------- 5 files changed, 270 insertions(+), 97 deletions(-) diff --git a/client.js b/client.js index 23524b6..4c3b71b 100755 --- a/client.js +++ b/client.js @@ -123,6 +123,13 @@ S101Server.prototype.addClient = function(socket) { * Client *****************************************************/ +S101Client.prototype.remoteAddress = function() { + if (this.socket === undefined) { + return; + } + return `${this.socket.remoteAddress}:${this.socket.remotePort}` +} + S101Client.prototype.queueMessage = function(node) { const self = this; this.addRequest(() => { diff --git a/device.js b/device.js index 74cfda3..4da0ef9 100755 --- a/device.js +++ b/device.js @@ -303,6 +303,14 @@ DeviceTree.prototype.subscribe = function(node, callback) { } } +DeviceTree.prototype.unsubscribe = function(node, callback) { + if(node instanceof ember.Parameter && node.isStream()) { + // TODO: implement + } else { + node.addCallback(callback); + } +} + DeviceTree.prototype.setValue = function(node, value) { var self=this; return new Promise((resolve, reject) => { diff --git a/ember.js b/ember.js index 076d578..a2ec3b1 100755 --- a/ember.js +++ b/ember.js @@ -142,6 +142,11 @@ TreeNode.prototype.isQualified = function() { (this instanceof QualifiedFunction)); } +TreeNode.prototype.isStream = function() { + return this.contents !== undefined && + this.contents.streamDescriptor !== undefined; +} + TreeNode.prototype.addCallback = function(callback) { if(this._callbacks.indexOf(callback) < 0) { this._callbacks.push(callback); @@ -202,6 +207,20 @@ TreeNode.prototype.getDirectory = function(callback) { return this.getTreeBranch(new Command(COMMAND_GETDIRECTORY)); } +TreeNode.prototype.subscribe = function(callback) { + if(callback !== undefined) { + this._directoryCallbacks.push((error, node) => { callback(error, node) }); + } + return this.getTreeBranch(new Command(COMMAND_SUBSCRIBE)); +} + +TreeNode.prototype.unsubscribe = function(callback) { + if(callback !== undefined) { + this._directoryCallbacks.push((error, node) => { callback(error, node) }); + } + return this.getTreeBranch(new Command(COMMAND_UNSUBSCRIBE)); +} + TreeNode.prototype.getChildren = function() { if(this.children !== undefined) { return this.children; @@ -476,22 +495,38 @@ QualifiedNode.prototype.update = function(other) { return callbacks; } -QualifiedNode.prototype.getDirectory = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - //console.log("Generating getDirectory command for node", this); +function QualifiedNodeCommand(self, cmd, callback) { var r = new Root(); var qn = new QualifiedNode(); - qn.path = this.path; + qn.path = self.path; r.addElement(qn); - qn.addChild(new Command(COMMAND_GETDIRECTORY)); + qn.addChild(new Command(cmd)); if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); + self._directoryCallbacks.push((error, node) => { callback(error, node) }); } return r; } +QualifiedNode.prototype.getDirectory = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedNodeCommand(this, COMMAND_GETDIRECTORY, callback) +} + +QualifiedNode.prototype.subscribe = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedNodeCommand(this, COMMAND_SUBSCRIBE, callback) +} + +QualifiedNode.prototype.unsubscribe = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedNodeCommand(this, COMMAND_UNSUBSCRIBE, callback) +} QualifiedNode.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(10)); @@ -645,7 +680,7 @@ MatrixNode.decode = function(ber) { var conSeq = seq.getSequence(BER.CONTEXT(0)); var con = MatrixConnection.decode(conSeq); if (con.target !== undefined) { - m.connections[con.target] = con; + m.connections[con.target] = (con); } } } @@ -721,9 +756,11 @@ MatrixNode.prototype.encode = function(ber) { if (this.connections !== undefined) { ber.startSequence(BER.CONTEXT(5)); for(var id in this.connections) { - ber.startSequence(BER.CONTEXT(0)); - this.connections[id].encode(ber); - ber.endSequence(); + if (this.connections.hasOwnProperty(id)) { + ber.startSequence(BER.CONTEXT(0)); + this.connections[id].encode(ber); + ber.endSequence(); + } } ber.endSequence(); } @@ -731,6 +768,12 @@ MatrixNode.prototype.encode = function(ber) { ber.endSequence(); // BER.APPLICATION(3) } +MatrixNode.prototype.update = function(other) { + callbacks = MatrixNode.super_.prototype.update.apply(this); + MatrixUpdate(this, other); + return callbacks; +} + MatrixNode.prototype.connect = function(connections) { let r = this.getTreeBranch(); let m = r.getElementByPath(this.getPath()); @@ -915,10 +958,13 @@ module.exports.MatrixContents = MatrixContents; function MatrixConnection(target) { if (target) { + target = Number(target); + if (isNaN(target)) { target = 0; } this.target = target; } - - + else { + this.target = 0; + } } // ConnectionOperation ::= @@ -954,9 +1000,11 @@ module.exports.MatrixDisposition = MatrixDisposition; MatrixConnection.prototype.setSources = function(sources) { if (sources === undefined) { + delete this.sources; return; } - this.sources = sources; // sources should be an array + let s = new Set(sources); + this.sources = [...s].sort(); // sources should be an array } MatrixConnection.prototype.connectSources = function(sources) { @@ -1014,7 +1062,7 @@ MatrixConnection.prototype.encode = function(ber) { ber.writeInt(this.target); ber.endSequence(); - if (this.sources !== undefined) { + if ((this.sources !== undefined)&& (this.sources.length > 0)) { ber.startSequence(BER.CONTEXT(1)); ber.writeRelativeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); ber.endSequence(); @@ -1164,47 +1212,74 @@ QualifiedMatrix.decode = function(ber) { return qm; } -QualifiedMatrix.prototype.update = function(other) { - callbacks = QualifiedMatrix.super_.prototype.update.apply(this); - if (other !== undefined) { - if (other.contents !== undefined) { - this.contents = other.contents; +function MatrixUpdate(matrix, newMatrix) { + if (newMatrix !== undefined) { + if (newMatrix.contents !== undefined) { + matrix.contents = newMatrix.contents; } - if (other.targets !== undefined) { - this.targets = other.targets; + if (newMatrix.targets !== undefined) { + matrix.targets = newMatrix.targets; } - if (other.sources !== undefined) { - this.sources = other.sources; + if (newMatrix.sources !== undefined) { + matrix.sources = newMatrix.sources; } - if (other.connections !== undefined) { - this.connections = other.connections; + if (newMatrix.connections !== undefined) { + for(let id in newMatrix.connections) { + if (newMatrix.connections.hasOwnProperty(id)) { + let connection = newMatrix.connections[id]; + if ((connection.target < matrix.contents.targetCount) && + (connection.target >= 0)) { + matrix.connections[connection.target].setSources(connection.sources); + } + } + } } } +} +QualifiedMatrix.prototype.update = function(other) { + callbacks = QualifiedMatrix.super_.prototype.update.apply(this); + MatrixUpdate(this, other); return callbacks; } -QualifiedMatrix.prototype.getDirectory = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - //console.log("Generating getDirectory command for node", this); +function QualifiedMatrixCommand(self, cmd, callback) { var r = new Root(); var qn = new QualifiedMatrix(); - qn.path = this.path; + qn.path = self.path; r.addElement(qn); - qn.addChild(new Command(COMMAND_GETDIRECTORY)); + qn.addChild(new Command(cmd)); if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); + self._directoryCallbacks.push((error, node) => { callback(error, node) }); } return r; } +QualifiedMatrix.prototype.getDirectory = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedMatrixCommand(this, COMMAND_GETDIRECTORY, callback); +} + +QualifiedMatrix.prototype.subscribe = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedMatrixCommand(this, COMMAND_SUBSCRIBE, callback); +} + +QualifiedMatrix.prototype.unsubscribe = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedMatrixCommand(this, COMMAND_UNSUBSCRIBE, callback); +} + QualifiedMatrix.prototype.connect = function(connections) { if (this.path === undefined) { throw new Error("Invalid path"); } - //console.log("Generating getDirectory command for node", this); var r = new Root(); var qn = new QualifiedMatrix(); qn.path = this.path; @@ -1273,10 +1348,12 @@ QualifiedMatrix.prototype.encode = function(ber) { if (this.connections !== undefined) { ber.startSequence(BER.CONTEXT(5)); ber.startSequence(BER.EMBER_SEQUENCE); - for(var id in this.connections) { - ber.startSequence(BER.CONTEXT(0)); - this.connections[id].encode(ber); - ber.endSequence(); + for(var id in this.connections) { + if (this.connections.hasOwnProperty(id)) { + ber.startSequence(BER.CONTEXT(0)); + this.connections[id].encode(ber); + ber.endSequence(); + } } ber.endSequence(); ber.endSequence(); @@ -1459,22 +1536,38 @@ QualifiedFunction.prototype.update = function(other) { return callbacks; } -QualifiedFunction.prototype.getDirectory = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - //console.log("Generating getDirectory command for node", this); +function QualifiedFunctionCommand(self, cmd, callback) { var r = new Root(); var qf = new QualifiedFunction(); - qf.path = this.path; + qf.path = self.path; r.addElement(qf); - qf.addChild(new Command(COMMAND_GETDIRECTORY)); + qf.addChild(new Command(cmd)); if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); + self._directoryCallbacks.push((error, node) => { callback(error, node) }); } return r; } +QualifiedFunction.prototype.getDirectory = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedFunctionCommand(this, COMMAND_GETDIRECTORY, callback); +} + +QualifiedFunction.prototype.subscribe = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedFunctionCommand(this, COMMAND_SUBSCRIBE, callback); +} + +QualifiedFunction.prototype.unsubscribe = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedFunctionCommand(this, COMMAND_UNSUBSCRIBE, callback); +} QualifiedFunction.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(20)); @@ -1700,21 +1793,39 @@ QualifiedParameter.prototype.update = function(other) { return callbacks; } -QualifiedParameter.prototype.getDirectory = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } +function QualifiedParameterCommand(self, cmd, callback) { let r = new Root(); let qp = new QualifiedParameter(); - qp.path = this.path; + qp.path = self.path; r.addElement(qp); - qp.addChild(new Command(COMMAND_GETDIRECTORY)); + qp.addChild(new Command(cmd)); if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); + self._directoryCallbacks.push((error, node) => { callback(error, node) }); } return r; } +QualifiedParameter.prototype.getDirectory = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedParameterCommand(this, COMMAND_GETDIRECTORY, callback); +} + +QualifiedParameter.prototype.subscribe = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedParameterCommand(this, COMMAND_SUBSCRIBE, callback); +} + +QualifiedParameter.prototype.unsubscribe = function(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + return QualifiedParameterCommand(this, COMMAND_UNSUBSCRIBE, callback); +} + QualifiedParameter.prototype.setValue = function(value, callback) { if(callback !== undefined) { this._directoryCallbacks.push(callback); @@ -1818,10 +1929,6 @@ Parameter.prototype.update = function(other) { return callbacks; } -Parameter.prototype.isStream = function() { - return this.contents !== undefined && - this.contents.streamDescriptor !== undefined; -} var ParameterAccess = new Enum({ none: 0, diff --git a/package.json b/package.json index 60f2769..cb54a50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.0", + "version": "1.7.1", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 82ba13e..baf57f3 100755 --- a/server.js +++ b/server.js @@ -27,17 +27,24 @@ function TreeServer(host, port, tree) { client.on("emberTree", (root) => { // Queue the action to make sure responses are sent in order. client.addRequest(() => { - self.handleRoot(client, root); + try { + let path = self.handleRoot(client, root); + self.emit("request", {client: client.remoteAddress(), root: root, path: path}); + } + catch(e) { + self.emit("error", e); + } }); }); client.on("disconnected", () => { self.clients.delete(client); + self.emit('disconnect', client.remoteAddress()); }); - self.emit('connection', client); + self.emit('connection', client.remoteAddress()); }); self.server.on('disconnected', () => { - self.emit('disconnected'); + self.emit('disconnected', client.remoteAddress()); }); self.server.on("error", (e) => { @@ -72,14 +79,15 @@ TreeServer.prototype.handleRoot = function(client, root) { const node = root.elements[0]; if (node.path !== undefined) { - this.handleQualifiedNode(client, node); + return this.handleQualifiedNode(client, node); } else if (node instanceof ember.Command) { // Command on root element this.handleCommand(client, this.tree, node.number); + return "root"; } else { - this.handleNode(client, node); + return this.handleNode(client, node); } } @@ -105,6 +113,7 @@ TreeServer.prototype.handleQualifiedNode = function(client, node) { this.handleQualifiedParameter(client, element, node); } } + return path; } @@ -155,6 +164,7 @@ TreeServer.prototype.handleNode = function(client, node) { else { this.emit("error", new Error(`invalid request format`)); } + return path; } TreeServer.prototype.handleQualifiedMatrix = function(client, element, matrix) @@ -183,44 +193,57 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti root = matrix._parent.getTreeBranch(res); } res.connections = {}; - for(let target in connections) { - let connection = connections[target]; - var conResult = new ember.MatrixConnection(connection.target); + for(let id in connections) { + if (!connections.hasOwnProperty(id)) { + continue; + } + let connection = connections[id]; + let conResult = new ember.MatrixConnection(connection.target); + let emitType; res.connections[connection.target] = conResult; - if (connection.sources === undefined) { + + // Apply changes + + if ((connection.operation === undefined) || + (connection.operation.value == ember.MatrixOperation.absolute)) { + matrix.connections[connection.target].setSources(connection.sources); + emitType = "matrix-change"; + } + else if (connection.operation == ember.MatrixOperation.connect) { + matrix.connections[connection.target].connectSources(connection.sources); + emitType = "matrix-connect"; + } + else { // Disconnect + matrix.connections[connection.target].disconnectSources(connection.sources); + emitType = "matrix-disconnect"; + } + + // Send response or update subscribers. + + if (response) { conResult.sources = matrix.connections[connection.target].sources; - continue; + conResult.disposition = ember.MatrixDisposition.modified; + // We got a request so emit something. + this.emit(emitType, { + target: connection.target, + sources: connection.sources, + client: client.remoteAddress() + }); } else { - if ((connection.operation === undefined) || - (connection.operation.value == ember.MatrixOperation.absolute)) { - matrix.connections[connection.target].setSources(connection.sources); - this.emit("matrix-change", {target: target, sources: connection.sources}); - } - else if (connection.operation == ember.MatrixOperation.connect) { - matrix.connections[connection.target].connectSources(connection.sources); - conResult.sources = matrix.connections[connection.target].sources; - this.emit("matrix-connect", {target: target, sources: connection.sources}); - } - else { // Disconnect - matrix.connections[connection.target].disconnectSources(connection.sources); - conResult.sources = matrix.connections[connection.target].sources; - this.emit("matrix-disconnect", {target: target, sources: connection.sources}); - } - if (response) { - conResult.sources = matrix.connections[connection.target].sources; - conResult.disposition = ember.MatrixDisposition.modified; - } - else { - conResult.operation = connection.operation; - } + // the action has been applied. So we should either send the current state (absolute) + // or send the action itself (connection.sources) + conResult.sources = matrix.connections[connection.target].sources; + conResult.operation = ember.MatrixOperation.absolute; } } if (client !== undefined) { client.sendBERNode(root); } - this.updateSubscribers(matrix.getPath(), root, client); + else { + this.updateSubscribers(matrix.getPath(), root, client); + } } const validateMatrixOperation = function(matrix, target, sources) { @@ -246,10 +269,10 @@ const doMatrixOperation = function(server, path, target, sources, operation) { validateMatrixOperation(matrix, target, sources); - let connections = new ember.MatrixConnection(target); - connections.sources = sources; - connections.operation = operation; - server.handleMatrixConnections(undefined, matrix, connections, false); + let connection = new ember.MatrixConnection(target); + connection.sources = sources; + connection.operation = operation; + server.handleMatrixConnections(undefined, matrix, [connection], false); } TreeServer.prototype.matrixConnect = function(path, target, sources) { @@ -282,6 +305,12 @@ TreeServer.prototype.handleCommand = function(client, element, cmd) { TreeServer.prototype.handleGetDirectory = function(client, element) { if (client !== undefined) { + if ((element.isMatrix() || element.isParameter()) && + (!element.isStream())) { + // ember spec: parameter without streamIdentifier should + // report their value changes automatically. + this.subscribe(client, element); + } client.sendBERNode(element); } } @@ -398,6 +427,28 @@ const parseObj = function(parent, obj, isQualified) { } delete content.labels; } + + + if (content.connections) { + emberElement.connections = {}; + for (let c in content.connections) { + if (! content.connections.hasOwnProperty(c)) { + continue; + } + let t = content.connections[c].target !== undefined ? content.connections[c].target : 0; + let connection = new ember.MatrixConnection(t); + connection.setSources(content.connections[c].sources); + emberElement.connections[t] = connection; + } + delete content.connections; + } + else { + emberElement.connections = {}; + for (let t = 0; t < content.targetCount; t++) { + let connection = new ember.MatrixConnection(t); + emberElement.connections[t] = connection; + } + } } else { //console.log("new node"); From 9f39bf431af6fc053dcd1bfb1f4e34fa5032a623 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 15 Jan 2018 08:55:47 -0500 Subject: [PATCH 023/162] Added function replaceElement on server to update tree and update suscribers. Fixed getDirectory and setValue to send proper response. --- server.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/server.js b/server.js index baf57f3..88eda24 100755 --- a/server.js +++ b/server.js @@ -302,8 +302,20 @@ TreeServer.prototype.handleCommand = function(client, element, cmd) { } } +TreeServer.prototype.getResponse = function(element) { + let res = element; + if (element.isQualified()) { + res = new ember.Root(); + res.addChild(element); + } + else if (element._parent) { + res = element._parent.getTreeBranch(element); + } + return res; +} TreeServer.prototype.handleGetDirectory = function(client, element) { + if (client !== undefined) { if ((element.isMatrix() || element.isParameter()) && (!element.isStream())) { @@ -311,7 +323,8 @@ TreeServer.prototype.handleGetDirectory = function(client, element) { // report their value changes automatically. this.subscribe(client, element); } - client.sendBERNode(element); + let res = this.getResponse(element); + client.sendBERNode(res); } } @@ -340,22 +353,53 @@ TreeServer.prototype.unsubscribe = function(client, element) { this.subscribers[path].delete(client); } -TreeServer.prototype.setValue = function(element, value, origin) { +TreeServer.prototype.setValue = function(element, value, origin, key) { return new Promise((resolve, reject) => { // Change the element value if write access permitted. - if ((element.contents !== undefined) && - (element.contents.access !== undefined) && - (element.contents.access.value > 1)) { - element.contents.value = value; - this.emit("value-change", element); + if (element.contents !== undefined) { + if (element.isParameter()) { + if ((element.contents.access !== undefined) && + (element.contents.access.value > 1)) { + element.contents.value = value; + this.emit("value-change", element); + } + } + else if (element.isMatrix()) { + if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { + element.contents[key] = value; + this.emit("value-change", element); + } + } } - let res = this.handleGetDirectory(origin, element); + let res = this.getResponse(element); + if (origin) { + this.client.sendBERNode(res) + } // Update the subscribers this.updateSubscribers(element.getPath(), res, origin); }); } +TreeServer.prototype.replaceElement = function(element) { + let path = element.getPath(); + let parent = this.tree.getElementByPath(path); + if ((parent === undefined)||(parent._parent === undefined)) { + return; + } + parent = parent._parent; + let children = parent.getChildren(); + for(let i = 0; i <= children.length; i++) { + if (children[i].getPath() == path) { + children[i] = element; + let res = this.getResponse(element); + this.updateSubscribers(path,res); + break; + } + } +} + + TreeServer.prototype.updateSubscribers = function(path, response, origin) { if (this.subscribers[path] === undefined) { return; From f9deda38e975dd4d263db2897d30db18b00fa42a Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 15 Jan 2018 08:56:05 -0500 Subject: [PATCH 024/162] version 1.7.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb54a50..7ec34fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.1", + "version": "1.7.2", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 112b80288ccad3809ccc646661ae09838757693a Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 17 Jan 2018 06:42:27 -0500 Subject: [PATCH 025/162] Added a _debug option. Fixed setValue to always send the command even if value has not changed --- client.js | 20 +++++++++++++------- device.js | 20 ++++++++++++-------- ember.js | 52 +++++++++++++++++++++++++++++++++++++++------------- package.json | 2 +- server.js | 11 ++++++++++- 5 files changed, 75 insertions(+), 30 deletions(-) diff --git a/client.js b/client.js index 4c3b71b..5a6f85b 100755 --- a/client.js +++ b/client.js @@ -219,9 +219,13 @@ S101Socket.prototype.connect = function(timeout = 2) { } +S101Socket.prototype.isConnected = function() { + return ((this.socket !== null) && (this.socket !== undefined)); +} + S101Socket.prototype.disconnect = function() { var self = this; - if(self.socket !== null) { + if (self.isConnected()) { self.socket.destroy(); self.socket = null; self.status = "disconnected"; @@ -230,7 +234,7 @@ S101Socket.prototype.disconnect = function() { S101Socket.prototype.sendKeepaliveRequest = function() { var self = this; - if(self.socket !== null) { + if (self.isConnected()) { self.socket.write(self.codec.keepAliveRequest()); winston.debug('sent keepalive request'); } @@ -238,7 +242,7 @@ S101Socket.prototype.sendKeepaliveRequest = function() { S101Socket.prototype.sendKeepaliveResponse = function() { var self = this; - if(self.socket !== null) { + if (self.isConnected()) { self.socket.write(self.codec.keepAliveResponse()); winston.debug('sent keepalive response'); } @@ -246,15 +250,17 @@ S101Socket.prototype.sendKeepaliveResponse = function() { S101Socket.prototype.sendBER = function(data) { var self = this; - var frames = self.codec.encodeBER(data); - for(var i=0; i { self.handleRoot(root); + if (self._debug) {console.log("Received roo", root);} if (self.callback) { self.callback(undefined, root); } @@ -63,6 +65,10 @@ DeviceTree.prototype.saveTree = function(f) { f(writer.buffer); } +DeviceTree.prototype.isConnected = function() { + return ((this.client !== undefined) && (this.client.isConnected())); +} + DeviceTree.prototype.connect = function(timeout = 2) { return new Promise((resolve, reject) => { this.callback = (e) => { @@ -125,7 +131,7 @@ DeviceTree.prototype.getDirectory = function(qnode) { self.finishRequest(); }; - if (DEBUG) {console.log("Sending getDirectory", qnode);} + if (self._debug) {console.log("Sending getDirectory", qnode);} self.callback = cb; self.client.sendBERNode(qnode.getDirectory()); }); @@ -183,7 +189,7 @@ DeviceTree.prototype.timeoutRequest = function(id) { DeviceTree.prototype.handleRoot = function(root) { var self=this; - //console.log("handling root", JSON.stringify(root)); + if (self._debug) {console.log("handling root", JSON.stringify(root));} var callbacks = self.root.update(root); if(root.elements !== undefined) { for(var i=0; i { if(error) { self.finishRequest(); @@ -342,7 +346,7 @@ DeviceTree.prototype.setValue = function(node, value) { }; self.callback = cb; - //console.log('setValue sending ...', node.getPath(), value); + if (this._debug) { console.log('setValue sending ...', node.getPath(), value); } self.client.sendBERNode(node.setValue(value)); }); } diff --git a/ember.js b/ember.js index a2ec3b1..27d3f60 100755 --- a/ember.js +++ b/ember.js @@ -489,8 +489,14 @@ QualifiedNode.decode = function(ber) { QualifiedNode.prototype.update = function(other) { callbacks = QualifiedNode.super_.prototype.update.apply(this); - if ((other !== undefined) && (other.contents !== undefined)) { - this.contents = other.contents; + if((other === undefined) && (other.contents !== undefined)) { + //console.log("other: ", other.contents); + for(var key in other.contents) { + //console.log(key, other.contents.hasOwnProperty(key)); + if(other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } + } } return callbacks; } @@ -625,8 +631,14 @@ Node.prototype.encode = function(ber) { Node.prototype.update = function(other) { callbacks = Node.super_.prototype.update.apply(this); - if(other.contents !== undefined) { - this.contents = other.contents; + if ((other !== undefined) && (other.contents !== undefined)) { + //console.log("other: ", other.contents); + for(var key in other.contents) { + //console.log(key, other.contents.hasOwnProperty(key)); + if(other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } + } } return callbacks; } @@ -1215,7 +1227,12 @@ QualifiedMatrix.decode = function(ber) { function MatrixUpdate(matrix, newMatrix) { if (newMatrix !== undefined) { if (newMatrix.contents !== undefined) { - matrix.contents = newMatrix.contents; + for(var key in newMatrix.contents) { + //console.log(key, other.contents.hasOwnProperty(key)); + if (newMatrix.contents.hasOwnProperty(key)) { + matrix.contents[key] = newMatrix.contents[key]; + } + } } if (newMatrix.targets !== undefined) { matrix.targets = newMatrix.targets; @@ -1235,8 +1252,8 @@ function MatrixUpdate(matrix, newMatrix) { } } } - } + QualifiedMatrix.prototype.update = function(other) { callbacks = QualifiedMatrix.super_.prototype.update.apply(this); MatrixUpdate(this, other); @@ -1527,12 +1544,15 @@ QualifiedFunction.decode = function(ber) { QualifiedFunction.prototype.update = function(other) { callbacks = QualifiedFunction.super_.prototype.update.apply(this); - if (other !== undefined) { - if (other.contents !== undefined) { - this.contents = other.contents; + if ((other !== undefined) && (other.contents !== undefined)) { + //console.log("other: ", other.contents); + for(var key in other.contents) { + //console.log(key, other.contents.hasOwnProperty(key)); + if(other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } } } - return callbacks; } @@ -1787,8 +1807,14 @@ QualifiedParameter.prototype.encode = function(ber) { QualifiedParameter.prototype.update = function(other) { callbacks = QualifiedParameter.super_.prototype.update.apply(this); - if(other.contents !== undefined) { - this.contents = other.contents; + if ((other !== undefined) && (other.contents !== undefined)) { + //console.log("other: ", other.contents); + for(var key in other.contents) { + //console.log(key, other.contents.hasOwnProperty(key)); + if(other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } + } } return callbacks; } @@ -1917,7 +1943,7 @@ Parameter.prototype.update = function(other) { callbacks = Parameter.super_.prototype.update.apply(this); //console.log('update', this.getPath()); //console.log(callbacks); - if(other.contents !== undefined) { + if ((other !== undefined) && (other.contents !== undefined)) { //console.log("other: ", other.contents); for(var key in other.contents) { //console.log(key, other.contents.hasOwnProperty(key)); diff --git a/package.json b/package.json index 7ec34fc..2423618 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.2", + "version": "1.7.3", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 88eda24..c296dce 100755 --- a/server.js +++ b/server.js @@ -6,6 +6,7 @@ const ember = require('./ember.js'); function TreeServer(host, port, tree) { TreeServer.super_.call(this); var self = this; + self._debug = false; self.callback = undefined; self.timeoutValue = 2000; @@ -15,6 +16,7 @@ function TreeServer(host, port, tree) { self.subscribers = {}; self.server.on('listening', () => { + if (self._debug) { console.log("listening"); } self.emit('listening'); if (self.callback !== undefined) { self.callback(); @@ -23,8 +25,10 @@ function TreeServer(host, port, tree) { }); self.server.on('connection', (client) => { + if (self._debug) { console.log("new connection from", client.remoteAddress()); } self.clients.add(client); client.on("emberTree", (root) => { + if (self._debug) { console.log("new request from", client.remoteAddress(), root); } // Queue the action to make sure responses are sent in order. client.addRequest(() => { try { @@ -32,6 +36,7 @@ function TreeServer(host, port, tree) { self.emit("request", {client: client.remoteAddress(), root: root, path: path}); } catch(e) { + if (self._debug) { console.log(e.stack); } self.emit("error", e); } }); @@ -77,7 +82,10 @@ TreeServer.prototype.handleRoot = function(client, root) { return; } + const node = root.elements[0]; + if (this._debug) { console.log("new request", node); } + if (node.path !== undefined) { return this.handleQualifiedNode(client, node); } @@ -374,7 +382,8 @@ TreeServer.prototype.setValue = function(element, value, origin, key) { let res = this.getResponse(element); if (origin) { - this.client.sendBERNode(res) + if (this._debug) { console.log("Sending setvalue response", res); } + origin.sendBERNode(res) } // Update the subscribers this.updateSubscribers(element.getPath(), res, origin); From 32d3058b95f9e1bc148c16b078fb7ff11202c44e Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 18 Jan 2018 04:44:30 -0500 Subject: [PATCH 026/162] Added an emit value-change on DeviceTree when a new object is received --- device.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/device.js b/device.js index 73aab21..7be962b 100755 --- a/device.js +++ b/device.js @@ -44,7 +44,7 @@ function DeviceTree(host, port) { self.client.on('emberTree', (root) => { self.handleRoot(root); - if (self._debug) {console.log("Received roo", root);} + if (self._debug) {console.log("Received root", root);} if (self.callback) { self.callback(undefined, root); } @@ -216,6 +216,7 @@ DeviceTree.prototype.handleQualifiedNode = function(parent, node) { var element = parent.getElementByPath(node.path); if (element !== null) { //console.log("Found element", JSON.stringify(element)); + self.emit("value-change", node); callbacks = element.update(node); } else { @@ -262,6 +263,7 @@ DeviceTree.prototype.handleNode = function(parent, node) { parent.addChild(node); n = node; } else { + self.emit("value-change", node); callbacks = n.update(node); } From dfc9c9370029b0b4e8401afef6e2ada18c966c28 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 18 Jan 2018 04:44:43 -0500 Subject: [PATCH 027/162] Version 1.7.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2423618..be3eace 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.3", + "version": "1.7.4", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 6cb3830d997fd32677b8a844f8898d47db7d090d Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 25 Jan 2018 06:15:58 -0500 Subject: [PATCH 028/162] Fixed server parsing of node element setValue --- device.js | 2 +- ember.js | 3 +++ package.json | 2 +- server.js | 22 +++++++++++++++++----- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/device.js b/device.js index 7be962b..cb2a2ef 100755 --- a/device.js +++ b/device.js @@ -328,7 +328,7 @@ DeviceTree.prototype.setValue = function(node, value) { reject(new errors.EmberAccessError('not a property')); } else { - //if (this._debug) { console.log('setValue', node.getPath(), value); } + // if (this._debug) { console.log('setValue', node.getPath(), value); } self.addRequest((error) => { if(error) { self.finishRequest(); diff --git a/ember.js b/ember.js index 27d3f60..7729a0c 100755 --- a/ember.js +++ b/ember.js @@ -1241,6 +1241,9 @@ function MatrixUpdate(matrix, newMatrix) { matrix.sources = newMatrix.sources; } if (newMatrix.connections !== undefined) { + if (matrix.connections === undefined) { + matrix.connections = {}; + } for(let id in newMatrix.connections) { if (newMatrix.connections.hasOwnProperty(id)) { let connection = newMatrix.connections[id]; diff --git a/package.json b/package.json index be3eace..0419a2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.4", + "version": "1.7.5", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index c296dce..65b5c7a 100755 --- a/server.js +++ b/server.js @@ -84,7 +84,16 @@ TreeServer.prototype.handleRoot = function(client, root) { const node = root.elements[0]; - if (this._debug) { console.log("new request", node); } + if (this._debug) { + let n; + try { + n = JSON.stringify(node); + } + catch(e) { + n = node; + } + console.log("new request", n); + } if (node.path !== undefined) { return this.handleQualifiedNode(client, node); @@ -137,11 +146,12 @@ TreeServer.prototype.handleNode = function(client, node) { if (element instanceof ember.Command) { break; } + path.push(element.number); + let children = element.getChildren(); - if ((children === undefined) || (children.length === 0)) { + if ((! children) || (children.length === 0)) { break; } - path.push(element.number); element = element.children[0]; } let cmd = element; @@ -155,6 +165,7 @@ TreeServer.prototype.handleNode = function(client, node) { if ((element === null) || (element === undefined)) { this.emit("error", new Error(`unknown element at path ${path}`)); + if (this._debug) { console.log(`unknown element at path ${path}`); } return; } @@ -166,11 +177,12 @@ TreeServer.prototype.handleNode = function(client, node) { } else if ((cmd instanceof ember.Parameter) && (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { - // New value Received. + if (this._debug) { console.log(`setValue for element at path ${path} with value ${cmd.contents.value}`); } this.setValue(element, cmd.contents.value, client); } else { - this.emit("error", new Error(`invalid request format`)); + this.emit("error", new Error("invalid request format")); + if (this._debug) { console.log("invalid request format"); } } return path; } From a91d4673626523005b1aa94d77ceee282abfd45f Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 26 Jan 2018 06:34:19 -0500 Subject: [PATCH 029/162] Added some debugs to ember decoder/encoder. Added FieldFlag to Command --- device.js | 7 ++- ember.js | 117 +++++++++++++++++++++++++++++++++++++++++++++------ index.js | 3 +- package.json | 2 +- 4 files changed, 114 insertions(+), 15 deletions(-) diff --git a/device.js b/device.js index cb2a2ef..76b6873 100755 --- a/device.js +++ b/device.js @@ -77,6 +77,9 @@ DeviceTree.prototype.connect = function(timeout = 2) { } return reject(e); }; + if ((this.client !== undefined) && (this.client.isConnected())) { + this.client.disconnect(); + } this.client.connect(timeout); }); } @@ -139,7 +142,9 @@ DeviceTree.prototype.getDirectory = function(qnode) { } DeviceTree.prototype.disconnect = function() { - this.client.disconnect(); + if (this.client !== undefined) { + return this.client.disconnect(); + } } DeviceTree.prototype.makeRequest = function() { diff --git a/ember.js b/ember.js index 7729a0c..3ebf25b 100755 --- a/ember.js +++ b/ember.js @@ -10,7 +10,11 @@ module.exports.Subscribe = COMMAND_SUBSCRIBE; module.exports.Unsubscribe = COMMAND_UNSUBSCRIBE; module.exports.GetDirectory = COMMAND_GETDIRECTORY; +DEBUG = false; +module.exports.DEBUG = function(d) { + DEBUG = d; +}; /**************************************************************************** * Root @@ -18,6 +22,7 @@ module.exports.GetDirectory = COMMAND_GETDIRECTORY; function Root() { Root.super_.call(this); + //Object.defineProperty(this, '_parent', {value: null, enumerable: false}); }; @@ -29,16 +34,13 @@ Root.decode = function(ber) { let tag = undefined; while(ber.remain > 0) { - try { - ber = ber.getSequence(BER.APPLICATION(0)); - tag = ber.peek(); - } - catch (e) { - break; - } - + if (DEBUG) { console.log("Reading root"); } + ber = ber.getSequence(BER.APPLICATION(0)); + tag = ber.peek(); + if (DEBUG) { console.log("Application 0 start"); } if (tag == BER.APPLICATION(11)) { + if (DEBUG) { console.log("Application 11 start"); } var seq = ber.getSequence(BER.APPLICATION(11)); r.elements = []; while (seq.remain > 0) { @@ -413,19 +415,26 @@ function Element() {}; Element.decode = function(ber) { var tag = ber.peek(); if(tag == BER.APPLICATION(1)) { + if (DEBUG) { console.log("Parameter decode");} return Parameter.decode(ber); } else if(tag == BER.APPLICATION(3)) { + if (DEBUG) { console.log("Node decode");} return Node.decode(ber); } else if(tag == BER.APPLICATION(2)) { + if (DEBUG) { console.log("Command decode");} return Command.decode(ber); } else if(tag == BER.APPLICATION(9)) { + if (DEBUG) { console.log("QualifiedParameter decode");} return QualifiedParameter.decode(ber); } else if(tag == BER.APPLICATION(10)) { + if (DEBUG) { console.log("QualifiedNode decode");} return QualifiedNode.decode(ber); } else if(tag == BER.APPLICATION(13)) { + if (DEBUG) { console.log("MatrixNode decode");} return MatrixNode.decode(ber); } else if(tag == BER.APPLICATION(17)) { + if (DEBUG) { console.log("QualifiedMatrix decode");} return QualifiedMatrix.decode(ber); } else if(tag == BER.APPLICATION(19)) { @@ -484,6 +493,7 @@ QualifiedNode.decode = function(ber) { throw new errors.UnimplementedEmberTypeError(tag); } } + if (DEBUG) { console.log("QualifiedNode", qn); } return qn; } @@ -598,6 +608,7 @@ Node.decode = function(ber) { throw new errors.UnimplementedEmberTypeError(tag); } } + if (DEBUG) { console.log("Node", n); } return n; } @@ -700,6 +711,7 @@ MatrixNode.decode = function(ber) { throw new errors.UnimplementedEmberTypeError(tag); } } + if (DEBUG) { console.log("MatrixNode", m); } return m; }; @@ -1221,6 +1233,7 @@ QualifiedMatrix.decode = function(ber) { throw new errors.UnimplementedEmberTypeError(tag); } } + if (DEBUG) { console.log("QualifiedMatrix", qm); } return qm; } @@ -1701,7 +1714,19 @@ module.exports.NodeContents = NodeContents; function Command(number) { if(number !== undefined) this.number = number; -} + this.fieldFlags = FieldFlags.all; +} + +var FieldFlags = new Enum({ + sparse: -2, + all: -1, + default: 0, + identifier: 1, + description: 2, + tree: 3, + value: 4, + connections: 5 +}); Command.decode = function(ber) { var c = new Command(); @@ -1712,7 +1737,14 @@ Command.decode = function(ber) { var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { c.number = seq.readInt(); - } else { + } + else if(tag == BER.CONTEXT(1)) { + c.fieldFlags = FieldFlags.get(seq.readInt()); + } + else if(tag == BER.CONTEXT(2)) { + c.invocation = Invocation.decode(ber); + } + else { // TODO: options throw new errors.UnimplementedEmberTypeError(tag); } @@ -1728,6 +1760,17 @@ Command.prototype.encode = function(ber) { ber.writeInt(this.number); ber.endSequence(); // BER.CONTEXT(0) + if (this.fieldFlags) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeInt(this.fieldFlags.value); + ber.endSequence(); + } + + if (this.invocation) { + ber.startSequence(BER.CONTEXT(2)); + this.invocation.encode(ber); + ber.endSequence(); + } // TODO: options ber.endSequence(); // BER.APPLICATION(2) @@ -1735,7 +1778,56 @@ Command.prototype.encode = function(ber) { module.exports.Command = Command; +/**************************************************************************** + * Invocation + ***************************************************************************/ +function Invocation() { + Invocation.super_.call(this); +} +Invocation.decode = function(ber) { + let invocation = new Invocation(); + ber = ber.getSequence(BER.APPLICATION(22)); + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + invocation.invocationId = seq.readInt(); + } + if(tag == BER.CONTEXT(1)) { + invocation.arguments = []; + let seq = ber.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + tag = seq.peek(); + var dataSeq = seq.getSequence(BER.CONTEXT(0)); + if (tag === BER.CONTEXT(0)) { + invocation.arguments.push(dataSeq.readValue()); + } + } + } + else { + // TODO: options + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return invocation; +} + +Invocation.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(22)); + ber.startSequence(BER.EMBER_SEQUENCE); + + for(var i =0; i < this.arguments; i++) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeValue(this.arguments[i]); + ber.endSequence(); + } + + ber.endSequence(); + + ber.endSequence(); // BER.APPLICATION(22) +} /**************************************************************************** * QualifiedParameter ***************************************************************************/ @@ -1747,7 +1839,6 @@ function QualifiedParameter(path) { } util.inherits(QualifiedParameter, TreeNode); -module.exports.QualifiedParameter = QualifiedParameter; QualifiedParameter.decode = function(ber) { //console.log("Decoding QualifiedParameter"); @@ -1777,6 +1868,7 @@ QualifiedParameter.decode = function(ber) { //throw new errors.UnimplementedEmberTypeError(tag); } } + if (DEBUG) { console.log("QualifiedParameter", qp); } return qp; } @@ -1889,6 +1981,7 @@ Parameter.decode = function(ber) { var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { p.number = seq.readInt(); + } else if(tag == BER.CONTEXT(1)) { p.contents = ParameterContents.decode(seq); } else if(tag == BER.CONTEXT(2)) { @@ -1902,7 +1995,7 @@ Parameter.decode = function(ber) { throw new errors.UnimplementedEmberTypeError(tag); } } - + if (DEBUG) { console.log("Parameter", p); } return p; } diff --git a/index.js b/index.js index 2b6570d..8382e24 100755 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ const DeviceTree = require('./device.js').DeviceTree; const Decoder = require('./device.js').DecodeBuffer; const Ember = require("./ember.js"); +const S101 = require("./s101"); const TreeServer = require("./server"); -module.exports = {DeviceTree, Decoder, Ember, TreeServer}; +module.exports = {DeviceTree, Decoder, Ember, TreeServer, S101}; diff --git a/package.json b/package.json index 0419a2c..9292768 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.5", + "version": "1.7.6", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From af44c8db395b87746d31e7cbdda59cac2b92fc82 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 29 Jan 2018 03:43:01 -0500 Subject: [PATCH 030/162] Fixed QualifiedParameter not exported with all ember objects --- ember.js | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ember.js b/ember.js index 3ebf25b..9e0d420 100755 --- a/ember.js +++ b/ember.js @@ -1839,6 +1839,8 @@ function QualifiedParameter(path) { } util.inherits(QualifiedParameter, TreeNode); +module.exports.QualifiedParameter = QualifiedParameter; + QualifiedParameter.decode = function(ber) { //console.log("Decoding QualifiedParameter"); diff --git a/package.json b/package.json index 9292768..50019d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.6", + "version": "1.7.7", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 8d9e89e00e3b055e35d20d4cd6ce986894aa17e6 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 29 Jan 2018 09:49:45 -0500 Subject: [PATCH 031/162] Fixed getResponse for qualified element. --- device.js | 4 +++- package.json | 2 +- server.js | 14 ++++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/device.js b/device.js index 76b6873..2a741ba 100755 --- a/device.js +++ b/device.js @@ -268,7 +268,6 @@ DeviceTree.prototype.handleNode = function(parent, node) { parent.addChild(node); n = node; } else { - self.emit("value-change", node); callbacks = n.update(node); } @@ -278,6 +277,9 @@ DeviceTree.prototype.handleNode = function(parent, node) { callbacks = callbacks.concat(this.handleNode(n, children[i])); } } + else { + self.emit("value-change", node); + } //console.log('handleNode: ', callbacks); return callbacks; diff --git a/package.json b/package.json index 50019d2..e6f2cf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.7", + "version": "1.7.8", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 65b5c7a..a829fe9 100755 --- a/server.js +++ b/server.js @@ -206,7 +206,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti if (matrix.isQualified()) { root = new ember.Root(); res = new ember.QualifiedMatrix(matrix.path); - root.addChild(res); + root.elements = [res]; // do not use addchild or the element will get removed from the tree. } else { res = new ember.MatrixNode(matrix.number); @@ -277,7 +277,7 @@ const validateMatrixOperation = function(matrix, target, sources) { throw new Error(`invalid matrix at ${path} : no targetCount`); } if ((target < 0) || (target >= matrix.contents.targetCount)) { - throw new Error(`invalid target id at ${target}`); + throw new Error(`target id ${target} out of range 0 - ${matrix.contents.targetCount}`); } if (sources.length === undefined) { throw new Error("invalid sources format"); @@ -326,7 +326,7 @@ TreeServer.prototype.getResponse = function(element) { let res = element; if (element.isQualified()) { res = new ember.Root(); - res.addChild(element); + res.elements = [element]; } else if (element._parent) { res = element._parent.getTreeBranch(element); @@ -406,16 +406,18 @@ TreeServer.prototype.replaceElement = function(element) { let path = element.getPath(); let parent = this.tree.getElementByPath(path); if ((parent === undefined)||(parent._parent === undefined)) { - return; + throw new Error(`Could not find element at path ${path}`); } parent = parent._parent; let children = parent.getChildren(); + let newList = []; for(let i = 0; i <= children.length; i++) { - if (children[i].getPath() == path) { + if (children[i] && children[i].getPath() == path) { + element._parent = parent; // move it to new tree. children[i] = element; let res = this.getResponse(element); this.updateSubscribers(path,res); - break; + return; } } } From 4a88da0769d06e45cbfee746544a492beebb7faf Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 20 Apr 2018 17:41:08 +0200 Subject: [PATCH 032/162] Added toJSON function to convert the ember tree to json object --- ember.js | 1 + package.json | 2 +- server.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/ember.js b/ember.js index 9e0d420..bba9ed8 100755 --- a/ember.js +++ b/ember.js @@ -715,6 +715,7 @@ MatrixNode.decode = function(ber) { return m; }; + MatrixNode.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(13)); diff --git a/package.json b/package.json index e6f2cf9..b557853 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.8", + "version": "1.7.9", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index a829fe9..a099118 100755 --- a/server.js +++ b/server.js @@ -546,4 +546,67 @@ TreeServer.JSONtoTree = function(obj, isQualified = true) { return tree; } +const toJSON = function(node) { + let res = {}; + + if (node.number) { + res.number = node.number + } + if (node.path) { + res.path = node.path; + } + if (node.contents) { + for(let prop in node.contents) { + if (node.contents.hasOwnProperty(prop)) { + let type = typeof node.contents[prop]; + if ((type === "string") || (type === "number")) { + res[prop] = node.contents[prop]; + } + else if (node.contents[prop].value !== undefined) { + res[prop] = node.contents[prop].value; + } + else { + console.log(prop, node.contents[prop]); + res[prop] = node.contents[prop]; + } + } + } + } + if (node.isMatrix()) { + if (node.targets) { + res.targets = node.targets.slice(0); + } + if (node.sources) { + res.sources = node.sources.slice(0); + } + if (node.connections) { + res.connections = {}; + for (let target in connections) { + if (connections.hasOwnProperty(target)) { + res.connections[target] = {target: target, sources: []}; + if (connections[target].sources) { + res.connections[target].sources = connections[target].sources.slice(0); + } + } + } + + } + } + let children = node.getChildren(); + if (children) { + res.children = []; + for(let child of children) { + res.children.push(toJSON(child)); + } + } + return res; +}; + +TreeServer.prototype.toJSON = function() { + if ((!this.tree) || (!this.tree.elements) || (this.tree.elements.length == 0)) { + return []; + } + return [].push(toJSON(this.tree.elements[0])); +}; + module.exports = TreeServer; From 153eed853b31eeaf5f79b76bf603ba01e2c4e177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20St=C3=A6rk=C3=A6r?= Date: Thu, 3 May 2018 16:36:20 +0200 Subject: [PATCH 033/162] feat(ber encoding): Adds type to Ember.ParameterContents objects. Allows for explicitly setting Real ParameterContents types to enforce correct encoding. Solves a problem where 1.0 and 1 can't be distringuished by Number.parseInteger(). This results in Real properties wrongly being encoded to Integers, which breaks the protocol. --- ber.js | 10 +++++++++- ember.js | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ber.js b/ber.js index 47a0b74..8518c39 100755 --- a/ber.js +++ b/ber.js @@ -279,7 +279,15 @@ ExtendedWriter.prototype.writeReal = function(value, tag) { } ExtendedWriter.prototype.writeValue = function(value, tag) { - if(Number.isInteger(value)) { + // accepts Ember.ParameterContents for enforcing real types + if(typeof value === 'object' && value.type && value.type.key && value.type.key.length && typeof value.type.key === 'string') { + if(value.type.key === 'real') { + this.writeReal(value.value, tag); + return + } + } + + if(Number.isInteger(value)) { if (tag === undefined) { tag = EMBER_INTEGER; } diff --git a/ember.js b/ember.js index 9e0d420..d7f38f2 100755 --- a/ember.js +++ b/ember.js @@ -2074,10 +2074,15 @@ var ParameterType = new Enum({ module.exports.ParameterAccess = ParameterAccess; module.exports.ParameterType = ParameterType; -function ParameterContents(value) { +function ParameterContents(value, type) { if(value !== undefined) { this.value = value; } + if(type !== undefined) { + if((type = ParameterType.get(type)) !== undefined){ + this.type = type + } + } }; module.exports.ParameterContents = ParameterContents; From 16612512a242232f9595c02265d16e7efbe77d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20St=C3=A6rk=C3=A6r?= Date: Thu, 3 May 2018 19:54:02 +0200 Subject: [PATCH 034/162] fix(ber encoding): Fixed unecessary nesting of ParameterContents if using strong typed ParameterContents --- ember.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ember.js b/ember.js index d7f38f2..ee6c667 100755 --- a/ember.js +++ b/ember.js @@ -1957,7 +1957,7 @@ QualifiedParameter.prototype.setValue = function(value, callback) { let r = new Root(); let qp = new QualifiedParameter(this.path); r.addElement(qp); - qp.contents = new ParameterContents(value); + qp.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); return r; } @@ -2033,7 +2033,7 @@ Parameter.prototype.setValue = function(value, callback) { } return this.getTreeBranch(undefined, (m) => { - m.contents = new ParameterContents(value); + m.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); }); } From 8a99bb624c695f76bf9ae30b3a0d63e413bdcd52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20St=C3=A6rk=C3=A6r?= Date: Mon, 7 May 2018 19:24:57 +0200 Subject: [PATCH 035/162] fix(KeepAliveRequest): Fixes broken KeepAlieveRequest. Resolves https://github.com/evs-broadcast/node-emberplus/issues/8 --- s101.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/s101.js b/s101.js index fa467ce..4fff20e 100755 --- a/s101.js +++ b/s101.js @@ -245,6 +245,7 @@ S101Codec.prototype.keepAliveRequest = function() { packet.writeUInt8(SLOT); packet.writeUInt8(MSG_EMBER); packet.writeUInt8(CMD_KEEPALIVE_REQ); + packet.writeUInt8(VERSION); return finalizeBuffer(packet); } @@ -254,6 +255,7 @@ S101Codec.prototype.keepAliveResponse = function() { packet.writeUInt8(SLOT); packet.writeUInt8(MSG_EMBER); packet.writeUInt8(CMD_KEEPALIVE_RESP); + packet.writeUInt8(VERSION); return finalizeBuffer(packet); } From 9013dfea6b2392ca97f6e4423a3a98d5b6f087bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20St=C3=A6rk=C3=A6r?= Date: Thu, 3 May 2018 18:35:53 +0200 Subject: [PATCH 036/162] feat(Functions): Adds Invoke method for QualifiedFunctions with InvocationResult. Also started not-qualified functions --- device.js | 41 ++++++++++++- ember.js | 175 +++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 197 insertions(+), 19 deletions(-) diff --git a/device.js b/device.js index 2a741ba..63751de 100755 --- a/device.js +++ b/device.js @@ -7,7 +7,7 @@ const errors = require('./errors.js'); const DEBUG = false; -function DeviceTree(host, port) { +function DeviceTree(host, port = 9000) { DeviceTree.super_.call(this); var self = this; self._debug = false; @@ -43,8 +43,14 @@ function DeviceTree(host, port) { }); self.client.on('emberTree', (root) => { - self.handleRoot(root); - if (self._debug) {console.log("Received root", root);} + if(root instanceof ember.InvocationResult) { + self.emit('invocationResult', root) + if (self._debug) {console.log("Received InvocationResult", root);} + }else{ + self.handleRoot(root); + if (self._debug) {console.log("Received root", root);} + } + if (self.callback) { self.callback(undefined, root); } @@ -141,6 +147,35 @@ DeviceTree.prototype.getDirectory = function(qnode) { }); } +DeviceTree.prototype.invokeFunction = function(fnNode, params) { + var self = this + return new Promise((resolve, reject) => { + self.addRequest((error) => { + if (error) { + reject(error); + self.finishRequest(); + return; + } + + let cb = (error, result) => { + self.clearTimeout(); + if (error) { + reject(error); + } + else { + if (DEBUG) {console.log("InvocationResult", result);} + resolve(result); + } + self.finishRequest(); + }; + + if (self._debug) {console.log("Invocking function", fnNode);} + self.callback = cb; + self.client.sendBERNode(fnNode.invoke(params)); + }); + }) +} + DeviceTree.prototype.disconnect = function() { if (this.client !== undefined) { return this.client.disconnect(); diff --git a/ember.js b/ember.js index d7f38f2..7dd458a 100755 --- a/ember.js +++ b/ember.js @@ -6,9 +6,11 @@ const Enum = require('enum'); const COMMAND_SUBSCRIBE = 30; const COMMAND_UNSUBSCRIBE = 31; const COMMAND_GETDIRECTORY = 32; +const COMMAND_INVOKE = 33; module.exports.Subscribe = COMMAND_SUBSCRIBE; module.exports.Unsubscribe = COMMAND_UNSUBSCRIBE; module.exports.GetDirectory = COMMAND_GETDIRECTORY; +module.exports.GetDirectory = COMMAND_INVOKE; DEBUG = false; @@ -55,9 +57,10 @@ Root.decode = function(ber) { return r; } } + } else if (tag == BER.APPLICATION(23)) { // InvocationResult BER.APPLICATION(23) + return InvocationResult.decode(ber) } else { // StreamCollection BER.APPLICATION(6) - // InvocationResult BER.APPLICATION(23) throw new errors.UnimplementedEmberTypeError(tag); } } @@ -438,9 +441,10 @@ Element.decode = function(ber) { return QualifiedMatrix.decode(ber); } else if(tag == BER.APPLICATION(19)) { - // Function - throw new errors.UnimplementedEmberTypeError(tag); + if (DEBUG) { console.log("Function decode");} + return _Function.decode(ber); } else if (tag == BER.APPLICATION(20)) { + if (DEBUG) { console.log("QualifiedFunction decode");} return QualifiedFunction.decode(ber); } else if(tag == BER.APPLICATION(24)) { @@ -1492,7 +1496,7 @@ FunctionContent.prototype.encode = function(ber) { if(this.arguments !== undefined) { ber.startSequence(BER.CONTEXT(2)); ber.startSequence(BER.EMBER_SEQUENCE); - for(var i =0; i < this.arguments; i++) { + for(var i =0; i < this.arguments.length; i++) { ber.startSequence(BER.CONTEXT(0)); encodeTupleDescription(this.arguments[i], ber); ber.endSequence(); @@ -1584,6 +1588,17 @@ function QualifiedFunctionCommand(self, cmd, callback) { return r; } +QualifiedFunction.prototype.invoke = function(params, callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + var QualifiedFunctionNode = QualifiedFunctionCommand(this, COMMAND_INVOKE, callback); + var invocation = new Invocation() + invocation.arguments = params + QualifiedFunctionNode.elements[0].children[0].invocation = invocation + return QualifiedFunctionNode +} + QualifiedFunction.prototype.getDirectory = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); @@ -1635,6 +1650,88 @@ QualifiedFunction.prototype.encode = function(ber) { module.exports.QualifiedFunction = QualifiedFunction; +/**************************************************************************** + * Function + ***************************************************************************/ + + +function _Function(number) { + _Function.super_.call(this); + if(number !== undefined) + this.number = number; +}; + +util.inherits(_Function, TreeNode); + +_Function.decode = function(ber) { + var f = new _Function(); + ber = ber.getSequence(BER.APPLICATION(19)); + + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + f.number = seq.readInt(); + } else if(tag == BER.CONTEXT(1)) { + f.contents = FunctionContent.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + seq = seq.getSequence(BER.APPLICATION(4)); + f.children = []; + while(seq.remain > 0) { + var nodeSeq = seq.getSequence(BER.CONTEXT(0)); + f.addChild(Element.decode(nodeSeq)); + } + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + if (DEBUG) { console.log("_Function", f); } + return f; +} + +_Function.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(19)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.children !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.APPLICATION(4)); + for(var i=0; i { callback(error, node) }); + } + + return this.getTreeBranch(undefined, (m) => { + m.addChild(new Command(COMMAND_INVOKE)) + }); +} + + + /**************************************************************************** * NodeContents ***************************************************************************/ @@ -1714,7 +1811,9 @@ module.exports.NodeContents = NodeContents; function Command(number) { if(number !== undefined) this.number = number; - this.fieldFlags = FieldFlags.all; + if(number == COMMAND_GETDIRECTORY) { + this.fieldFlags = FieldFlags.all; + } } var FieldFlags = new Enum({ @@ -1760,13 +1859,13 @@ Command.prototype.encode = function(ber) { ber.writeInt(this.number); ber.endSequence(); // BER.CONTEXT(0) - if (this.fieldFlags) { + if (this.number === COMMAND_GETDIRECTORY && this.fieldFlags) { ber.startSequence(BER.CONTEXT(1)); ber.writeInt(this.fieldFlags.value); ber.endSequence(); } - if (this.invocation) { + if (this.number === COMMAND_INVOKE && this.invocation) { ber.startSequence(BER.CONTEXT(2)); this.invocation.encode(ber); ber.endSequence(); @@ -1782,10 +1881,9 @@ module.exports.Command = Command; * Invocation ***************************************************************************/ function Invocation() { - Invocation.super_.call(this); } -Invocation.decode = function(ber) { +Invocation.prototype.decode = function(ber) { let invocation = new Invocation(); ber = ber.getSequence(BER.APPLICATION(22)); while(ber.remain > 0) { @@ -1814,19 +1912,64 @@ Invocation.decode = function(ber) { return invocation; } +Invocation._id = 1 + Invocation.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(22)); - ber.startSequence(BER.EMBER_SEQUENCE); - - for(var i =0; i < this.arguments; i++) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeValue(this.arguments[i]); + // ber.startSequence(BER.EMBER_SEQUENCE); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(Invocation._id++) + ber.endSequence(); + + ber.startSequence(BER.CONTEXT(1)); + ber.startSequence(BER.EMBER_SEQUENCE) + for(var i =0; i < this.arguments.length; i++) { + ber.startSequence(BER.CONTEXT(0)) + ber.writeValue(this.arguments[i]) ber.endSequence(); } - ber.endSequence(); - + ber.endSequence(); + ber.endSequence(); // BER.APPLICATION(22) + +} +/**************************************************************************** + * InvocationResult + ***************************************************************************/ +function InvocationResult() { +} +module.exports.InvocationResult = InvocationResult; + +InvocationResult.decode = function(ber) { + let invocationResult = new InvocationResult(); + ber = ber.getSequence(BER.APPLICATION(23)); + while(ber.remain > 0) { + tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { // invocationId + invocationResult.invocationId = seq.readInt(); + } else if(tag == BER.CONTEXT(1)) { // success + invocationResult.success = seq.readBoolean() + }else if(tag == BER.CONTEXT(2)) { + invocationResult.result = []; + let res = seq.getSequence(BER.EMBER_SEQUENCE); + while(res.remain > 0) { + tag = res.peek(); + var resTag = res.getSequence(BER.CONTEXT(0)); + if (tag === BER.CONTEXT(0)) { + invocationResult.result.push(resTag.readValue()); + } + } + continue + } else { + // TODO: options + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return invocationResult; } /**************************************************************************** * QualifiedParameter From 33b8fe97ebba63cd85bb5a343476bbd92f2924a3 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Fri, 6 Jul 2018 11:29:35 +0200 Subject: [PATCH 037/162] expose a close function --- server.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server.js b/server.js index a099118..e3b21a1 100755 --- a/server.js +++ b/server.js @@ -76,6 +76,18 @@ TreeServer.prototype.listen = function() { }); }; +TreeServer.prototype.close = function () { + return new Promise((resolve, reject) => { + this.callback = (e) => { + if (e === undefined) { + return resolve(); + } + return reject(e); + }; + this.server.server.close(); + }); +}; + TreeServer.prototype.handleRoot = function(client, root) { if ((root === undefined) || (root.elements === undefined) || (root.elements < 1)) { this.emit("error", new Error("invalid request")); From 499bf7f1ef710136d3b552f7c5a09c652c96f8fd Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 30 Jul 2018 11:08:27 +0200 Subject: [PATCH 038/162] Fixed S101 flags parsing to handle properly last empty packet --- package.json | 2 +- s101.js | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index b557853..2f2552b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.9", + "version": "1.7.10", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/s101.js b/s101.js index 4fff20e..ef76f99 100755 --- a/s101.js +++ b/s101.js @@ -164,23 +164,18 @@ S101Codec.prototype.handleEmberFrame = function(frame) { var payload = frame.readBuffer(); payload = payload.slice(0, payload.length - 2); - if(flags === FLAG_SINGLE_PACKET) { - winston.debug('single ember packet'); - self.handleEmberPacket(SmartBuffer.fromBuffer(payload)); - self.emberbuf.clear(); - } else if(flags === FLAG_FIRST_MULTI_PACKET) { + if (flags & FLAG_FIRST_MULTI_PACKET) { winston.debug('multi ember packet start'); self.emberbuf.clear(); + } + if ((flags & FLAG_EMPTY_PACKET) === 0) { self.emberbuf.writeBuffer(payload); - } else if(flags === FLAG_LAST_MULTI_PACKET) { + } + if (flags & FLAG_LAST_MULTI_PACKET) { winston.debug('multi ember packet end'); - self.emberbuf.writeBuffer(payload); self.emberbuf.moveTo(0); self.handleEmberPacket(self.emberbuf); self.emberbuf.clear(); - } else { - winston.debug('multi ember packet'); - self.emberbuf.writeBuffer(payload); } } From d41ac58e0ca609d9f330918335f129edc9f5211d Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 2 Aug 2018 13:50:20 +0200 Subject: [PATCH 039/162] Fixed the server response format. Fixed the Matrix encoding/decoding. --- client.js | 8 +- device.js | 10 +- ember.js | 300 ++++++++++++++++++++++++++++++++++----------------- package.json | 2 +- s101.js | 79 +++++++------- server.js | 121 ++++++++++----------- 6 files changed, 309 insertions(+), 211 deletions(-) diff --git a/client.js b/client.js index 5a6f85b..a944b12 100755 --- a/client.js +++ b/client.js @@ -28,6 +28,7 @@ function S101Client(socket, server) { var self = this; S101Client.super_.call(this); + self.request = null; self.server = server; self.socket = socket; @@ -42,7 +43,6 @@ function S101Client(socket, server) { self.codec.on('emberPacket', (packet) => { self.emit('emberPacket', packet); - var ber = new BER.Reader(packet); try { var root = ember.Root.decode(ber); @@ -95,11 +95,11 @@ S101Server.prototype.listen = function() { if (self.status !== "disconnected") { return; } - + self.server = net.createServer((socket) => { self.addClient(socket); }); - + self.server.on("error", (e) => { self.emit("error", e); }); @@ -108,7 +108,7 @@ S101Server.prototype.listen = function() { self.emit("listening"); self.status = "listening"; }); - + self.server.listen(self.port, self.address); } diff --git a/device.js b/device.js index 2a741ba..cd3c238 100755 --- a/device.js +++ b/device.js @@ -5,8 +5,6 @@ const ember = require('./ember.js'); const BER = require('./ber.js'); const errors = require('./errors.js'); -const DEBUG = false; - function DeviceTree(host, port) { DeviceTree.super_.call(this); var self = this; @@ -90,12 +88,12 @@ DeviceTree.prototype.expand = function(node) return self.getDirectory(node).then((res) => { let children = node.getChildren(); if ((res === undefined) || (children === undefined) || (children === null)) { - if (DEBUG) {console.log("No more children for ", node);} + if (self._debug) {console.log("No more children for ", node);} return; } let p = []; for (let child of children) { - if (DEBUG) {console.log("Expanding child", child);} + if (self._debug) {console.log("Expanding child", child);} p.push( self.expand(child).catch((e) => { // We had an error on some expansion @@ -128,7 +126,7 @@ DeviceTree.prototype.getDirectory = function(qnode) { reject(error); } else { - if (DEBUG) {console.log("Received getDirectory response", node);} + if (self._debug) {console.log("Received getDirectory response", node);} resolve(node); // make sure the info is treated before going to next request. } self.finishRequest(); @@ -153,7 +151,7 @@ DeviceTree.prototype.makeRequest = function() { self.activeRequest = self.pendingRequests.shift(); const t = function(id) { - if (DEBUG) {console.log(`Making request ${id}`,Date.now());} + if (self._debug) {console.log(`Making request ${id}`,Date.now());} self.timeout = setTimeout(() => { self.timeoutRequest(id); }, self.timeoutValue); diff --git a/ember.js b/ember.js index 03fb5eb..d594527 100755 --- a/ember.js +++ b/ember.js @@ -35,30 +35,50 @@ Root.decode = function(ber) { while(ber.remain > 0) { if (DEBUG) { console.log("Reading root"); } - ber = ber.getSequence(BER.APPLICATION(0)); tag = ber.peek(); - if (DEBUG) { console.log("Application 0 start"); } + if (tag === BER.APPLICATION(0)) { + ber = ber.getSequence(BER.APPLICATION(0)); + tag = ber.peek(); + if (DEBUG) { + console.log("Application 0 start"); + } - if (tag == BER.APPLICATION(11)) { - if (DEBUG) { console.log("Application 11 start"); } - var seq = ber.getSequence(BER.APPLICATION(11)); - r.elements = []; - while (seq.remain > 0) { - try { - var rootReader = seq.getSequence(BER.CONTEXT(0)); - while (rootReader.remain > 0) { - r.addElement(RootElement.decode(rootReader)); - } + if (tag == BER.APPLICATION(11)) { + if (DEBUG) { + console.log("Application 11 start"); } - catch (e) { - console.log(e.stack); - return r; + var seq = ber.getSequence(BER.APPLICATION(11)); + r.elements = []; + while (seq.remain > 0) { + try { + var rootReader = seq.getSequence(BER.CONTEXT(0)); + while (rootReader.remain > 0) { + r.addElement(RootElement.decode(rootReader)); + } + } + catch (e) { + console.log(e.stack); + return r; + } } + } else { + // StreamCollection BER.APPLICATION(6) + // InvocationResult BER.APPLICATION(23) + throw new errors.UnimplementedEmberTypeError(tag); + } + } + else if (tag === BER.CONTEXT(0)) { + // continuation of previous message + try { + return RootElement.decode(rootReader) + } + catch (e) { + console.log(e.stack); + return r; } - } else { - // StreamCollection BER.APPLICATION(6) - // InvocationResult BER.APPLICATION(23) - throw new errors.UnimplementedEmberTypeError(tag); + } + else { + console.log("unexpected message tag", tag, ber.toString()); } } return r; @@ -166,6 +186,12 @@ TreeNode.prototype.cancelCallbacks = function() { } } +TreeNode.prototype.getDuplicate = function() { + let obj = this.getMinimal(); + obj.update(this); + return obj; +} + TreeNode.prototype.getMinimal = function() { if (this.isQualified()) { return new this.constructor(this.path); @@ -237,13 +263,10 @@ _getElementByPath = function(children, pathArray, path) { } var currPath = pathArray.join("."); var number = pathArray[pathArray.length - 1]; - //console.log(`looking for path ${currPath} or number ${number}`); for (var i = 0; i < children.length; i++) { - //console.log("looking at child", JSON.stringify(children[i])); - if ((children[i].path == currPath)|| - (children[i].number == number)){ + (children[i].number == number)) { if (path.length === 0) { return children[i]; } @@ -300,7 +323,7 @@ TreeNode.prototype.getElementByIdentifier = function(identifier) { var children = this.getChildren(); if(children === null) return null; for(var i=0; i 0) { (function(cb) { callbacks.push(() => { - //console.log(this.constructor.name, "dir cb", self.getPath()); cb(null, self) }); })(self._directoryCallbacks.shift()); } - //} for(var i=0; i { - //console.log(self.constructor.name, "cb", self.getPath()); cb(self) }); })(self._callbacks[i]); @@ -345,13 +364,13 @@ TreeNode.prototype.update = function(other) { TreeNode.prototype.getNodeByPath = function(client, path, callback) { var self=this; - + if(path.length == 0) { callback(null, self); return; } - + var child = self.getElement(path[0]); if(child !== null) { child.getNodeByPath(client, path.slice(1), callback); @@ -362,7 +381,6 @@ TreeNode.prototype.getNodeByPath = function(client, path, callback) { } child = node.getElement(path[0]); if(child === null) { - //console.log("inv:", path[0], self); callback('invalid path'); return; } else { @@ -390,7 +408,7 @@ TreeNode.prototype.getPath = function() { if(path.length > 0) { path = path + "."; } - return path + this.number; + return path + this.number; } } @@ -497,14 +515,38 @@ QualifiedNode.decode = function(ber) { return qn; } +function path2number(path) { + try { + let numbers = path.split("."); + if (numbers.length > 0) { + return Number(numbers[numbers.length - 1]); + } + } + catch(e) { + // ignore + } +} + +QualifiedNode.prototype.getMinimal = function(complete = false) { + let number = path2number(this.path); + let n = new Node(number); + if (complete && (this.contents != null)) { + n.contents = this.contents; + } + return n; +} + QualifiedNode.prototype.update = function(other) { callbacks = QualifiedNode.super_.prototype.update.apply(this); if((other === undefined) && (other.contents !== undefined)) { - //console.log("other: ", other.contents); - for(var key in other.contents) { - //console.log(key, other.contents.hasOwnProperty(key)); - if(other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; + if (this.contents == null) { + this.contents = other.contents; + } + else { + for(var key in other.contents) { + if(other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } } } } @@ -614,7 +656,7 @@ Node.decode = function(ber) { Node.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(3)); - + ber.startSequence(BER.CONTEXT(0)); ber.writeInt(this.number); ber.endSequence(); // BER.CONTEXT(0) @@ -640,14 +682,23 @@ Node.prototype.encode = function(ber) { ber.endSequence(); // BER.APPLICATION(3) } +Node.prototype.toQualified = function() { + let qn = new QualifiedNode(this.getPath()); + qn.update(this); + return qn; +} + Node.prototype.update = function(other) { callbacks = Node.super_.prototype.update.apply(this); if ((other !== undefined) && (other.contents !== undefined)) { - //console.log("other: ", other.contents); - for(var key in other.contents) { - //console.log(key, other.contents.hasOwnProperty(key)); - if(other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; + if (this.contents == null) { + this.contents = other.contents; + } + else { + for (var key in other.contents) { + if (other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } } } } @@ -697,15 +748,7 @@ MatrixNode.decode = function(ber) { } else if (tag == BER.CONTEXT(4)) { m.sources = decodeSources(seq); } else if (tag == BER.CONTEXT(5)) { - m.connections = {}; - seq = seq.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - var conSeq = seq.getSequence(BER.CONTEXT(0)); - var con = MatrixConnection.decode(conSeq); - if (con.target !== undefined) { - m.connections[con.target] = (con); - } - } + m.connections = decodeConnections(seq); } else { throw new errors.UnimplementedEmberTypeError(tag); @@ -780,14 +823,14 @@ MatrixNode.prototype.encode = function(ber) { if (this.connections !== undefined) { ber.startSequence(BER.CONTEXT(5)); + ber.startSequence(BER.EMBER_SEQUENCE); for(var id in this.connections) { if (this.connections.hasOwnProperty(id)) { - ber.startSequence(BER.CONTEXT(0)); this.connections[id].encode(ber); - ber.endSequence(); } } ber.endSequence(); + ber.endSequence(); } ber.endSequence(); // BER.APPLICATION(3) @@ -799,6 +842,12 @@ MatrixNode.prototype.update = function(other) { return callbacks; } +MatrixNode.prototype.toQualified = function() { + let qm = new QualifiedMatrix(this.getPath()); + qm.update(this); + return qm; +} + MatrixNode.prototype.connect = function(connections) { let r = this.getTreeBranch(); let m = r.getElementByPath(this.getPath()); @@ -818,13 +867,10 @@ function MatrixContents() { MatrixContents.decode = function(ber) { var mc = new MatrixContents(); - //console.log("\n\n Matrix Content\n\n", ber.buffer); ber = ber.getSequence(BER.EMBER_SET); - while(ber.remain > 0) { var tag = ber.peek(); - //console.log("Next tag", tag, ber.buffer); var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { @@ -849,13 +895,11 @@ MatrixContents.decode = function(ber) { mc.gainParameterNumber = seq.readInt(); } else if(tag == BER.CONTEXT(10)) { mc.labels = []; - //console.log("\n\nLABEL\n\n",seq.buffer); seq = seq.getSequence(BER.EMBER_SEQUENCE); while(seq.remain > 0) { var lSeq = seq.getSequence(BER.CONTEXT(0)); mc.labels.push(Label.decode(lSeq)); } - //console.log(mc); } else if(tag == BER.CONTEXT(11)) { mc.schemaIdentifiers = seq.readInt(); } else if(tag == BER.CONTEXT(12)) { @@ -865,7 +909,6 @@ MatrixContents.decode = function(ber) { throw new errors.UnimplementedEmberTypeError(tag); } } - //console.log("end of matrix contents"); return mc; }; @@ -976,6 +1019,19 @@ decodeSources = function(ber) { return sources; }; +decodeConnections = function(ber) { + let connections = {}; + + let seq = ber.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + var conSeq = seq.getSequence(BER.CONTEXT(0)); + var con = MatrixConnection.decode(conSeq); + if (con.target !== undefined) { + connections[con.target] = (con); + } + } + return connections; +} module.exports.MatrixContents = MatrixContents; @@ -1081,6 +1137,7 @@ MatrixConnection.decode = function(ber) { } MatrixConnection.prototype.encode = function(ber) { + ber.startSequence(BER.CONTEXT(0)); ber.startSequence(BER.APPLICATION(16)); ber.startSequence(BER.CONTEXT(0)); @@ -1102,6 +1159,8 @@ MatrixConnection.prototype.encode = function(ber) { ber.writeInt(this.disposition.value); ber.endSequence(); } + + ber.endSequence(); ber.endSequence(); } @@ -1196,6 +1255,26 @@ function QualifiedMatrix(path) { util.inherits(QualifiedMatrix, TreeNode); +QualifiedMatrix.prototype.getMinimal = function(complete = false) { + let number = path2number(this.path); + let m = new MatrixNode(number); + if (complete) { + if (this.contents != null) { + m.contents = this.contents; + } + if (this.targets != null) { + m.targets = this.targets; + } + if (this.sources != null) { + m.sources = this.sources; + } + if (this.connections != null) { + m.connections = this.connections; + } + } + return m; +} + QualifiedMatrix.decode = function(ber) { var qm = new QualifiedMatrix(); @@ -1241,10 +1320,14 @@ QualifiedMatrix.decode = function(ber) { function MatrixUpdate(matrix, newMatrix) { if (newMatrix !== undefined) { if (newMatrix.contents !== undefined) { - for(var key in newMatrix.contents) { - //console.log(key, other.contents.hasOwnProperty(key)); - if (newMatrix.contents.hasOwnProperty(key)) { - matrix.contents[key] = newMatrix.contents[key]; + if (matrix.contents == null) { + matrix.contents = newMatrix.contents; + } + else { + for (var key in newMatrix.contents) { + if (newMatrix.contents.hasOwnProperty(key)) { + matrix.contents[key] = newMatrix.contents[key]; + } } } } @@ -1263,6 +1346,9 @@ function MatrixUpdate(matrix, newMatrix) { let connection = newMatrix.connections[id]; if ((connection.target < matrix.contents.targetCount) && (connection.target >= 0)) { + if (matrix.connections[connection.target] == null) { + matrix.connections[connection.target] = new MatrixConnection(connection.target); + } matrix.connections[connection.target].setSources(connection.sources); } } @@ -1562,11 +1648,14 @@ QualifiedFunction.decode = function(ber) { QualifiedFunction.prototype.update = function(other) { callbacks = QualifiedFunction.super_.prototype.update.apply(this); if ((other !== undefined) && (other.contents !== undefined)) { - //console.log("other: ", other.contents); - for(var key in other.contents) { - //console.log(key, other.contents.hasOwnProperty(key)); - if(other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; + if (this.contents == null) { + this.contents = other.contents; + } + else { + for (var key in other.contents) { + if (other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } } } } @@ -1672,31 +1761,31 @@ NodeContents.decode = function(ber) { NodeContents.prototype.encode = function(ber) { ber.startSequence(BER.EMBER_SET); - + if(this.identifier !== undefined) { ber.startSequence(BER.CONTEXT(0)); ber.writeString(this.identifier, BER.EMBER_STRING); ber.endSequence(); // BER.CONTEXT(0) } - + if(this.description !== undefined) { ber.startSequence(BER.CONTEXT(1)); ber.writeString(this.description, BER.EMBER_STRING); ber.endSequence(); // BER.CONTEXT(1) } - + if(this.isRoot !== undefined) { ber.startSequence(BER.CONTEXT(2)); ber.writeBoolean(this.isRoot); ber.endSequence(); // BER.CONTEXT(2) } - + if(this.isOnline !== undefined) { ber.startSequence(BER.CONTEXT(3)); ber.writeBoolean(this.isOnline); ber.endSequence(); // BER.CONTEXT(3) } - + if(this.schemaIdentifiers !== undefined) { ber.startSequence(BER.CONTEXT(4)); ber.writeString(this.schemaIdentifiers, BER.EMBER_STRING); @@ -1756,7 +1845,7 @@ Command.decode = function(ber) { Command.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(2)); - + ber.startSequence(BER.CONTEXT(0)); ber.writeInt(this.number); ber.endSequence(); // BER.CONTEXT(0) @@ -1842,9 +1931,19 @@ function QualifiedParameter(path) { util.inherits(QualifiedParameter, TreeNode); module.exports.QualifiedParameter = QualifiedParameter; +QualifiedParameter.prototype.getMinimal = function(complete = false) { + let number = path2number(this.path); + let p = new Parameter(number); + if (complete) { + if (this.contents != null) { + p = this.contents; + } + } + return p; +} + QualifiedParameter.decode = function(ber) { - //console.log("Decoding QualifiedParameter"); var qp = new QualifiedParameter(); ber = ber.getSequence(BER.APPLICATION(9)); while(ber.remain > 0) { @@ -1852,15 +1951,11 @@ QualifiedParameter.decode = function(ber) { var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { qp.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - //console.log("Decoded path",qp.path); } else if(tag == BER.CONTEXT(1)) { - //console.log("Decoding content"); qp.contents = ParameterContents.decode(seq); - //console.log("Decoded content",qp.contents); } else if(tag == BER.CONTEXT(2)) { qp.children = []; - //console.log("Decoding children"); seq = seq.getSequence(BER.APPLICATION(4)); while(seq.remain > 0) { var nodeSeq = seq.getSequence(BER.CONTEXT(0)); @@ -1868,7 +1963,6 @@ QualifiedParameter.decode = function(ber) { } } else { return qp; - //throw new errors.UnimplementedEmberTypeError(tag); } } if (DEBUG) { console.log("QualifiedParameter", qp); } @@ -1906,11 +2000,14 @@ QualifiedParameter.prototype.encode = function(ber) { QualifiedParameter.prototype.update = function(other) { callbacks = QualifiedParameter.super_.prototype.update.apply(this); if ((other !== undefined) && (other.contents !== undefined)) { - //console.log("other: ", other.contents); - for(var key in other.contents) { - //console.log(key, other.contents.hasOwnProperty(key)); - if(other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; + if (this.contents == null) { + this.contents = other.contents; + } + else { + for (var key in other.contents) { + if (other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } } } } @@ -2032,22 +2129,29 @@ Parameter.prototype.setValue = function(value, callback) { if(callback !== undefined) { this._directoryCallbacks.push(callback); } - + return this.getTreeBranch(undefined, (m) => { m.contents = new ParameterContents(value); }); } +Parameter.prototype.toQualified = function() { + let qp = new QualifiedNode(this.getPath()); + qp.update(this); + return qp; +} + Parameter.prototype.update = function(other) { callbacks = Parameter.super_.prototype.update.apply(this); - //console.log('update', this.getPath()); - //console.log(callbacks); if ((other !== undefined) && (other.contents !== undefined)) { - //console.log("other: ", other.contents); - for(var key in other.contents) { - //console.log(key, other.contents.hasOwnProperty(key)); - if(other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; + if (this.contents == null) { + this.contents = other.contents; + } + else { + for (var key in other.contents) { + if (other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } } } } @@ -2143,7 +2247,7 @@ ParameterContents.decode = function(ber) { ParameterContents.prototype.encode = function(ber) { ber.startSequence(BER.EMBER_SET); - + ber.writeIfDefined(this.identifier, ber.writeString, 0, BER.EMBER_STRING); ber.writeIfDefined(this.description, ber.writeString, 1, BER.EMBER_STRING); ber.writeIfDefined(this.value, ber.writeValue, 2); @@ -2159,15 +2263,15 @@ ParameterContents.prototype.encode = function(ber) { ber.writeIfDefined(this.default, ber.writeValue, 12); ber.writeIfDefinedEnum(this.type, ParameterType, ber.writeInt, 13); ber.writeIfDefined(this.streamIdentifier, ber.writeInt, 14); - + if(this.emumMap !== undefined) { - ber.startSequence(BER.CONTEXT(15)); + ber.startSequence(BER.CONTEXT(15)); StringIntegerCollection.encode(ber, this.enumMap); ber.endSequence(); } if(this.streamDescriptor !== undefined) { - ber.startSequence(BER.CONTEXT(16)); + ber.startSequence(BER.CONTEXT(16)); this.streamDescriptor.encode(ber); ber.endSequence(); } diff --git a/package.json b/package.json index 2f2552b..b78ac68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.10", + "version": "1.7.11", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/s101.js b/s101.js index ef76f99..0537b8f 100755 --- a/s101.js +++ b/s101.js @@ -30,37 +30,37 @@ const DTD_VERSION_MAJOR = 0x02; const DTD_VERSION_MINOR = 0x1F; const CRC_TABLE = [ - 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, - 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, - 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, - 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, - 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, - 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, - 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, - 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, - 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, - 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, - 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, - 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, - 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, - 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, - 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, - 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, - 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, - 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, - 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, - 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, - 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, - 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, - 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, - 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, - 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, - 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, - 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, - 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, - 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, - 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, - 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 ]; @@ -98,9 +98,8 @@ S101Codec.prototype.dataIn = function(buf) { S101Codec.prototype.handleFrame = function(frame) { var self = this; - //console.log(frame.toBuffer()); if(!validateFrame(frame.toBuffer())) { - winston.error('dropping frame of length %d with invalid CRC', + winston.error('dropping frame of length %d with invalid CRC', frame.length); return; } @@ -164,14 +163,15 @@ S101Codec.prototype.handleEmberFrame = function(frame) { var payload = frame.readBuffer(); payload = payload.slice(0, payload.length - 2); - if (flags & FLAG_FIRST_MULTI_PACKET) { + if(flags & FLAG_FIRST_MULTI_PACKET) { winston.debug('multi ember packet start'); self.emberbuf.clear(); } if ((flags & FLAG_EMPTY_PACKET) === 0) { + // not empty, save the payload self.emberbuf.writeBuffer(payload); } - if (flags & FLAG_LAST_MULTI_PACKET) { + if(flags & FLAG_LAST_MULTI_PACKET) { winston.debug('multi ember packet end'); self.emberbuf.moveTo(0); self.handleEmberPacket(self.emberbuf); @@ -183,7 +183,6 @@ S101Codec.prototype.handleEmberPacket = function(packet) { var self = this; winston.debug('ember packet'); - //console.log(packet); self.emit('emberPacket', packet.toBuffer()); } @@ -214,7 +213,7 @@ S101Codec.prototype.encodeBER = function(data) { encbuf.writeUInt8(S101_CE); encbuf.writeUInt8(b ^ S101_XOR); } - + if(encbuf.length >= 1024 && i < data.length-1) { if(frames.length == 0) { frames.push(makeBERFrame(FLAG_FIRST_MULTI_PACKET, encbuf.toBuffer())); @@ -265,14 +264,14 @@ var finalizeBuffer = function(smartbuf) { smartbuf.writeUInt8(S101_CE); smartbuf.writeUInt8(crc_lo ^ S101_XOR); } - + if(crc_hi < S101_INV) { smartbuf.writeUInt8(crc_hi); } else { smartbuf.writeUInt8(S101_CE); smartbuf.writeUInt8(crc_hi ^ S101_XOR); } - + smartbuf.writeUInt8(S101_EOF); return smartbuf.toBuffer(); } @@ -299,7 +298,7 @@ var calculateCRCCE = function(buf) { } var validateFrame = function(buf) { - return calculateCRC(buf) == 0xF0B8; + return calculateCRC(buf) == 0xF0B8; } S101Codec.prototype.validateFrame = validateFrame; diff --git a/server.js b/server.js index a099118..8fbe618 100755 --- a/server.js +++ b/server.js @@ -25,10 +25,10 @@ function TreeServer(host, port, tree) { }); self.server.on('connection', (client) => { - if (self._debug) { console.log("new connection from", client.remoteAddress()); } + if (self._debug) { console.log("ember new connection from", client.remoteAddress()); } self.clients.add(client); client.on("emberTree", (root) => { - if (self._debug) { console.log("new request from", client.remoteAddress(), root); } + if (self._debug) { console.log("ember new request from", client.remoteAddress(), root); } // Queue the action to make sure responses are sent in order. client.addRequest(() => { try { @@ -84,16 +84,7 @@ TreeServer.prototype.handleRoot = function(client, root) { const node = root.elements[0]; - if (this._debug) { - let n; - try { - n = JSON.stringify(node); - } - catch(e) { - n = node; - } - console.log("new request", n); - } + client.request = node; if (node.path !== undefined) { return this.handleQualifiedNode(client, node); @@ -108,6 +99,14 @@ TreeServer.prototype.handleRoot = function(client, root) { } } +TreeServer.prototype.handleError = function(client, node) { + if (client !== undefined) { + let res = node == null ? this.tree._root.getMinimal() : node; + client.sendBERNode(res); + } +} + + TreeServer.prototype.handleQualifiedNode = function(client, node) { const path = node.path; // Find this element in our tree @@ -115,7 +114,7 @@ TreeServer.prototype.handleQualifiedNode = function(client, node) { if ((element === null) || (element === undefined)) { this.emit("error", new Error(`unknown element at path ${path}`)); - return; + return this.handleError(client); } if ((node.children !== undefined) && (node.children.length === 1) && @@ -158,15 +157,14 @@ TreeServer.prototype.handleNode = function(client, node) { if (cmd === undefined) { this.emit("error", "invalid request"); - return; + return this.handleError(client); } element = this.tree.getElementByPath(path.join(".")); - if ((element === null) || (element === undefined)) { + if (element == null) { this.emit("error", new Error(`unknown element at path ${path}`)); - if (this._debug) { console.log(`unknown element at path ${path}`); } - return; + return this.handleError(client); } if (cmd instanceof ember.Command) { @@ -179,10 +177,14 @@ TreeServer.prototype.handleNode = function(client, node) { (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { if (this._debug) { console.log(`setValue for element at path ${path} with value ${cmd.contents.value}`); } this.setValue(element, cmd.contents.value, client); + let res = this.getResponse(element); + client.sendBERNode(res) + this.updateSubscribers(element.getPath(), res, client); } else { this.emit("error", new Error("invalid request format")); if (this._debug) { console.log("invalid request format"); } + return this.handleError(client, element.getTreeBranch()); } return path; } @@ -196,6 +198,9 @@ TreeServer.prototype.handleQualifiedParameter = function(client, element, parame { if (parameter.contents.value !== undefined) { this.setValue(element, parameter.contents.value, client); + let res = this.getQualifiedResponse(element); + client.sendBERNode(res) + this.updateSubscribers(element.getPath(), res, client); } } @@ -323,14 +328,30 @@ TreeServer.prototype.handleCommand = function(client, element, cmd) { } TreeServer.prototype.getResponse = function(element) { - let res = element; - if (element.isQualified()) { - res = new ember.Root(); - res.elements = [element]; - } - else if (element._parent) { - res = element._parent.getTreeBranch(element); + return element.getTreeBranch(undefined, function(node) { + node.update(element); + let children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + node.addChild(children[i].getDuplicate()); + } + } + else if (this._debug) { + console.log("getResponse","no children"); + } + }); +} + +TreeServer.prototype.getQualifiedResponse = function(element) { + let res = new ember.Root(); + let dup = element.toQualified(); + let children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + dup.addChild(children[i].getDuplicate()); + } } + res.elements = [dup]; return res; } @@ -343,7 +364,13 @@ TreeServer.prototype.handleGetDirectory = function(client, element) { // report their value changes automatically. this.subscribe(client, element); } - let res = this.getResponse(element); + let res; + if (client.request.path == null) { + res = this.getResponse(element); + } + else { + res = this.getQualifiedResponse(element); + } client.sendBERNode(res); } } @@ -391,14 +418,6 @@ TreeServer.prototype.setValue = function(element, value, origin, key) { } } } - - let res = this.getResponse(element); - if (origin) { - if (this._debug) { console.log("Sending setvalue response", res); } - origin.sendBERNode(res) - } - // Update the subscribers - this.updateSubscribers(element.getPath(), res, origin); }); } @@ -442,23 +461,15 @@ TreeServer.prototype.updateSubscribers = function(path, response, origin) { } } -const parseObj = function(parent, obj, isQualified) { +const parseObj = function(parent, obj) { let path = parent.getPath(); for(let i = 0; i < obj.length; i++) { let emberElement; let content = obj[i]; let number = content.number !== undefined ? content.number : i; delete content.number; - //console.log(`parsing obj at number ${number}`, content); if (content.value !== undefined) { - //console.log("new parameter"); - // this is a parameter - if (isQualified) { - emberElement = new ember.QualifiedParameter(`${path}${path !== "" ? "." : ""}${number}`); - } - else { - emberElement = new ember.Parameter(number); - } + emberElement = new ember.Parameter(number); emberElement.contents = new ember.ParameterContents(content.value); if (content.type) { emberElement.contents.type = ember.ParameterType.get(content.type); @@ -476,13 +487,7 @@ const parseObj = function(parent, obj, isQualified) { } } else if (content.targetCount !== undefined) { - //console.log("new matrix"); - if (isQualified) { - emberElement = new ember.QualifiedMatrix(`${path}${path !== "" ? "." : ""}${number}`); - } - else { - emberElement = new ember.MatrixNode(number); - } + emberElement = new ember.MatrixNode(number); emberElement.contents = new ember.MatrixContents(); if (content.labels) { @@ -518,31 +523,24 @@ const parseObj = function(parent, obj, isQualified) { } } else { - //console.log("new node"); - if (isQualified) { - emberElement = new ember.QualifiedNode(`${path}${path !== "" ? "." : ""}${number}`); - } - else { - emberElement = new ember.Node(number); - } + emberElement = new ember.Node(number); emberElement.contents = new ember.NodeContents(); } for(let id in content) { if ((id !== "children") && (content.hasOwnProperty(id))) { - //console.log(`adding contents ${id}`); emberElement.contents[id] = content[id]; } else { - parseObj(emberElement, content.children, isQualified); + parseObj(emberElement, content.children); } } parent.addChild(emberElement); } } -TreeServer.JSONtoTree = function(obj, isQualified = true) { +TreeServer.JSONtoTree = function(obj) { let tree = new ember.Root(); - parseObj(tree, obj, isQualified); + parseObj(tree, obj); return tree; } @@ -566,7 +564,6 @@ const toJSON = function(node) { res[prop] = node.contents[prop].value; } else { - console.log(prop, node.contents[prop]); res[prop] = node.contents[prop]; } } From 033e0c054dfac814a0370a2080327b73090f273d Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 8 Aug 2018 10:34:04 +0200 Subject: [PATCH 040/162] Do not expand Parameter --- device.js | 24 +++++++++++++++++------- package.json | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/device.js b/device.js index cd3c238..ba87561 100755 --- a/device.js +++ b/device.js @@ -85,30 +85,40 @@ DeviceTree.prototype.connect = function(timeout = 2) { DeviceTree.prototype.expand = function(node) { let self = this; + if (node == null) { + return Promise.reject(new Error("Invalid null node")); + } + if (node.isParameter()) { + return self.getDirectory(node); + } return self.getDirectory(node).then((res) => { let children = node.getChildren(); if ((res === undefined) || (children === undefined) || (children === null)) { if (self._debug) {console.log("No more children for ", node);} return; } - let p = []; + let p = Promise.resolve(); for (let child of children) { + if (child.isParameter()) { + // Parameter can only have a single child of type Command. + continue; + } if (self._debug) {console.log("Expanding child", child);} - p.push( - self.expand(child).catch((e) => { + p = p.then(() => { + return self.expand(child).catch((e) => { // We had an error on some expansion // let's save it on the child itself child.error = e; - }) - ); + }); + }); } - return Promise.all(p); + return p; }); } DeviceTree.prototype.getDirectory = function(qnode) { var self = this; - if (qnode === undefined) { + if (qnode == null) { self.root.clear(); qnode = self.root; } diff --git a/package.json b/package.json index b78ac68..ddadc68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.11", + "version": "1.7.12", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 0e55a7ae18d984e51b6e04117af4a72c5dea9fc2 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 10 Aug 2018 10:55:47 +0200 Subject: [PATCH 041/162] Fix server getDirectory failure --- ember.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ember.js b/ember.js index c9f86b8..dec30ca 100755 --- a/ember.js +++ b/ember.js @@ -10,7 +10,7 @@ const COMMAND_INVOKE = 33; module.exports.Subscribe = COMMAND_SUBSCRIBE; module.exports.Unsubscribe = COMMAND_UNSUBSCRIBE; module.exports.GetDirectory = COMMAND_GETDIRECTORY; -module.exports.GetDirectory = COMMAND_INVOKE; +module.exports.Invoke = COMMAND_INVOKE; DEBUG = false; diff --git a/package.json b/package.json index ddadc68..9948623 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.12", + "version": "1.7.13", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 01b150ae15b84c878c83294b54360920bc62f3e5 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Thu, 23 Aug 2018 16:05:17 +0200 Subject: [PATCH 042/162] wait until closed --- client.js | 58 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/client.js b/client.js index a944b12..6be8686 100755 --- a/client.js +++ b/client.js @@ -49,7 +49,7 @@ function S101Client(socket, server) { if (root !== undefined) { self.emit('emberTree', root); } - } catch(e) { + } catch (e) { self.emit("error", e); } }); @@ -90,7 +90,7 @@ function S101Server(address, port) { util.inherits(S101Server, EventEmitter); -S101Server.prototype.listen = function() { +S101Server.prototype.listen = function () { var self = this; if (self.status !== "disconnected") { return; @@ -113,7 +113,7 @@ S101Server.prototype.listen = function() { } -S101Server.prototype.addClient = function(socket) { +S101Server.prototype.addClient = function (socket) { var client = new S101Client(socket, this); this.emit("connection", client); } @@ -123,29 +123,29 @@ S101Server.prototype.addClient = function(socket) { * Client *****************************************************/ -S101Client.prototype.remoteAddress = function() { +S101Client.prototype.remoteAddress = function () { if (this.socket === undefined) { return; } return `${this.socket.remoteAddress}:${this.socket.remotePort}` } -S101Client.prototype.queueMessage = function(node) { +S101Client.prototype.queueMessage = function (node) { const self = this; this.addRequest(() => { self.sendBERNode(node); }); } -S101Client.prototype.makeRequest = function() { - if(this.activeRequest === null && this.pendingRequests.length > 0) { +S101Client.prototype.makeRequest = function () { + if (this.activeRequest === null && this.pendingRequests.length > 0) { this.activeRequest = this.pendingRequests.shift(); this.activeRequest(); this.activeRequest = null; } }; -S101Client.prototype.addRequest = function(cb) { +S101Client.prototype.addRequest = function (cb) { this.pendingRequests.push(cb); this.makeRequest(); } @@ -155,7 +155,7 @@ S101Client.prototype.addRequest = function(cb) { * Socket *****************************************************/ -S101Socket.prototype.connect = function(timeout = 2) { +S101Socket.prototype.connect = function (timeout = 2) { var self = this; if (self.status !== "disconnected") { return; @@ -179,9 +179,9 @@ S101Socket.prototype.connect = function(timeout = 2) { } - setInterval(() => { + self.keepaliveIntervalTimer = setInterval(() => { self.sendKeepaliveRequest(); - }, 1000 * self.keepaliveInterval ); + }, 1000 * self.keepaliveInterval); self.codec.on('keepaliveReq', () => { self.sendKeepaliveResponse(); @@ -196,7 +196,7 @@ S101Socket.prototype.connect = function(timeout = 2) { if (root !== undefined) { self.emit('emberTree', root); } - } catch(e) { + } catch (e) { self.emit("error", e); } }); @@ -219,20 +219,29 @@ S101Socket.prototype.connect = function(timeout = 2) { } -S101Socket.prototype.isConnected = function() { +S101Socket.prototype.isConnected = function () { return ((this.socket !== null) && (this.socket !== undefined)); } -S101Socket.prototype.disconnect = function() { +S101Socket.prototype.disconnect = function () { var self = this; - if (self.isConnected()) { - self.socket.destroy(); - self.socket = null; - self.status = "disconnected"; + + if (!self.isConnected()) { + return Promise.resolve(); } -} -S101Socket.prototype.sendKeepaliveRequest = function() { + return new Promise((resolve, reject) => { + self.socket.once('close', resolve); + self.socket.once('error', reject); + clearInterval(self.keepaliveIntervalTimer); + self.socket.destroy(); + self.socket = null; + self.status = "disconnected"; + } + ); +}; + +S101Socket.prototype.sendKeepaliveRequest = function () { var self = this; if (self.isConnected()) { self.socket.write(self.codec.keepAliveRequest()); @@ -240,7 +249,7 @@ S101Socket.prototype.sendKeepaliveRequest = function() { } } -S101Socket.prototype.sendKeepaliveResponse = function() { +S101Socket.prototype.sendKeepaliveResponse = function () { var self = this; if (self.isConnected()) { self.socket.write(self.codec.keepAliveResponse()); @@ -248,7 +257,7 @@ S101Socket.prototype.sendKeepaliveResponse = function() { } } -S101Socket.prototype.sendBER = function(data) { +S101Socket.prototype.sendBER = function (data) { var self = this; if (self.isConnected()) { var frames = self.codec.encodeBER(data); @@ -258,8 +267,8 @@ S101Socket.prototype.sendBER = function(data) { } } -S101Socket.prototype.sendBERNode = function(node) { - var self=this; +S101Socket.prototype.sendBERNode = function (node) { + var self = this; if (!node) return; var writer = new BER.Writer(); node.encode(writer); @@ -267,6 +276,5 @@ S101Socket.prototype.sendBERNode = function(node) { } - module.exports = { S101Socket, S101Server, S101Client }; From cef7da083927926150661790a58c2479406edae7 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Thu, 23 Aug 2018 17:11:47 +0200 Subject: [PATCH 043/162] avoid memory leak on event listener --- client.js | 15 +++-- device.js | 162 ++++++++++++++++++++++++++++++------------------------ 2 files changed, 100 insertions(+), 77 deletions(-) diff --git a/client.js b/client.js index 6be8686..462480b 100755 --- a/client.js +++ b/client.js @@ -16,7 +16,7 @@ function S101Socket(address, port) { self.port = port; self.socket = null; self.keepaliveInterval = 10; - self.codec = new S101Codec(); + self.codec = null; self.status = "disconnected"; } @@ -171,6 +171,7 @@ S101Socket.prototype.connect = function (timeout = 2) { }, 1000 * timeout); } + self.codec = new S101Codec(); self.socket = net.createConnection(self.port, self.address, () => { winston.debug('socket connected'); @@ -178,7 +179,6 @@ S101Socket.prototype.connect = function (timeout = 2) { clearTimeout(self._timer); } - self.keepaliveIntervalTimer = setInterval(() => { self.sendKeepaliveRequest(); }, 1000 * self.keepaliveInterval); @@ -229,13 +229,16 @@ S101Socket.prototype.disconnect = function () { if (!self.isConnected()) { return Promise.resolve(); } - return new Promise((resolve, reject) => { - self.socket.once('close', resolve); + self.socket.once('close', () => { + self.codec = null; + self.socket = null; + resolve(); + }); self.socket.once('error', reject); clearInterval(self.keepaliveIntervalTimer); - self.socket.destroy(); - self.socket = null; + clearTimeout(self._timeout); + self.socket.end(); self.status = "disconnected"; } ); diff --git a/device.js b/device.js index a4cbf3d..35b4b91 100755 --- a/device.js +++ b/device.js @@ -42,12 +42,16 @@ function DeviceTree(host, port = 9000) { }); self.client.on('emberTree', (root) => { - if(root instanceof ember.InvocationResult) { + if (root instanceof ember.InvocationResult) { self.emit('invocationResult', root) - if (self._debug) {console.log("Received InvocationResult", root);} - }else{ + if (self._debug) { + console.log("Received InvocationResult", root); + } + } else { self.handleRoot(root); - if (self._debug) {console.log("Received root", root);} + if (self._debug) { + console.log("Received root", root); + } } if (self.callback) { @@ -59,22 +63,22 @@ function DeviceTree(host, port = 9000) { util.inherits(DeviceTree, EventEmitter); -DecodeBuffer = function(packet) { - var ber = new BER.Reader(packet); - return ember.Root.decode(ber); +var DecodeBuffer = function (packet) { + var ber = new BER.Reader(packet); + return ember.Root.decode(ber); } -DeviceTree.prototype.saveTree = function(f) { +DeviceTree.prototype.saveTree = function (f) { var writer = new BER.Writer(); this.root.encode(writer); f(writer.buffer); } -DeviceTree.prototype.isConnected = function() { +DeviceTree.prototype.isConnected = function () { return ((this.client !== undefined) && (this.client.isConnected())); } -DeviceTree.prototype.connect = function(timeout = 2) { +DeviceTree.prototype.connect = function (timeout = 2) { return new Promise((resolve, reject) => { this.callback = (e) => { if (e === undefined) { @@ -89,8 +93,7 @@ DeviceTree.prototype.connect = function(timeout = 2) { }); } -DeviceTree.prototype.expand = function(node) -{ +DeviceTree.prototype.expand = function (node) { let self = this; if (node == null) { return Promise.reject(new Error("Invalid null node")); @@ -101,7 +104,9 @@ DeviceTree.prototype.expand = function(node) return self.getDirectory(node).then((res) => { let children = node.getChildren(); if ((res === undefined) || (children === undefined) || (children === null)) { - if (self._debug) {console.log("No more children for ", node);} + if (self._debug) { + console.log("No more children for ", node); + } return; } let p = Promise.resolve(); @@ -110,7 +115,9 @@ DeviceTree.prototype.expand = function(node) // Parameter can only have a single child of type Command. continue; } - if (self._debug) {console.log("Expanding child", child);} + if (self._debug) { + console.log("Expanding child", child); + } p = p.then(() => { return self.expand(child).catch((e) => { // We had an error on some expansion @@ -123,7 +130,7 @@ DeviceTree.prototype.expand = function(node) }); } -DeviceTree.prototype.getDirectory = function(qnode) { +DeviceTree.prototype.getDirectory = function (qnode) { var self = this; if (qnode == null) { self.root.clear(); @@ -143,20 +150,24 @@ DeviceTree.prototype.getDirectory = function(qnode) { reject(error); } else { - if (self._debug) {console.log("Received getDirectory response", node);} + if (self._debug) { + console.log("Received getDirectory response", node); + } resolve(node); // make sure the info is treated before going to next request. } self.finishRequest(); }; - if (self._debug) {console.log("Sending getDirectory", qnode);} + if (self._debug) { + console.log("Sending getDirectory", qnode); + } self.callback = cb; self.client.sendBERNode(qnode.getDirectory()); }); }); } -DeviceTree.prototype.invokeFunction = function(fnNode, params) { +DeviceTree.prototype.invokeFunction = function (fnNode, params) { var self = this return new Promise((resolve, reject) => { self.addRequest((error) => { @@ -167,37 +178,43 @@ DeviceTree.prototype.invokeFunction = function(fnNode, params) { } let cb = (error, result) => { - self.clearTimeout(); + self.clearTimeout(); if (error) { reject(error); } else { - if (DEBUG) {console.log("InvocationResult", result);} - resolve(result); + if (DEBUG) { + console.log("InvocationResult", result); + } + resolve(result); } self.finishRequest(); }; - if (self._debug) {console.log("Invocking function", fnNode);} + if (self._debug) { + console.log("Invocking function", fnNode); + } self.callback = cb; self.client.sendBERNode(fnNode.invoke(params)); }); }) } -DeviceTree.prototype.disconnect = function() { +DeviceTree.prototype.disconnect = function () { if (this.client !== undefined) { return this.client.disconnect(); } } -DeviceTree.prototype.makeRequest = function() { - var self=this; - if(self.activeRequest === null && self.pendingRequests.length > 0) { +DeviceTree.prototype.makeRequest = function () { + var self = this; + if (self.activeRequest === null && self.pendingRequests.length > 0) { self.activeRequest = self.pendingRequests.shift(); - const t = function(id) { - if (self._debug) {console.log(`Making request ${id}`,Date.now());} + const t = function (id) { + if (self._debug) { + console.log(`Making request ${id}`, Date.now()); + } self.timeout = setTimeout(() => { self.timeoutRequest(id); }, self.timeoutValue); @@ -208,40 +225,42 @@ DeviceTree.prototype.makeRequest = function() { } }; -DeviceTree.prototype.addRequest = function(cb) { - var self=this; +DeviceTree.prototype.addRequest = function (cb) { + var self = this; self.pendingRequests.push(cb); self.makeRequest(); } -DeviceTree.prototype.clearTimeout = function() { - if(this.timeout != null) { +DeviceTree.prototype.clearTimeout = function () { + if (this.timeout != null) { clearTimeout(this.timeout); this.timeout = null; } } -DeviceTree.prototype.finishRequest = function() { - var self=this; +DeviceTree.prototype.finishRequest = function () { + var self = this; self.callback = undefined; self.clearTimeout(); self.activeRequest = null; self.makeRequest(); } -DeviceTree.prototype.timeoutRequest = function(id) { +DeviceTree.prototype.timeoutRequest = function (id) { var self = this; self.root.cancelCallbacks(); self.activeRequest(new errors.EmberTimeoutError(`Request ${id !== undefined ? id : ""} timed out`)); } -DeviceTree.prototype.handleRoot = function(root) { - var self=this; +DeviceTree.prototype.handleRoot = function (root) { + var self = this; - if (self._debug) {console.log("handling root", JSON.stringify(root));} + if (self._debug) { + console.log("handling root", JSON.stringify(root)); + } var callbacks = self.root.update(root); - if(root.elements !== undefined) { - for(var i=0; i { self.addRequest((error) => { - if(error) { + if (error) { reject(error); self.finishRequest(); return; } self.root.getNodeByPath(self.client, path, (error, node) => { - if(error) { + if (error) { reject(error); } else { resolve(node); @@ -354,34 +373,33 @@ DeviceTree.prototype.getNodeByPath = function(path) { }); } -DeviceTree.prototype.subscribe = function(node, callback) { - if(node instanceof ember.Parameter && node.isStream()) { +DeviceTree.prototype.subscribe = function (node, callback) { + if (node instanceof ember.Parameter && node.isStream()) { // TODO: implement } else { node.addCallback(callback); } } -DeviceTree.prototype.unsubscribe = function(node, callback) { - if(node instanceof ember.Parameter && node.isStream()) { +DeviceTree.prototype.unsubscribe = function (node, callback) { + if (node instanceof ember.Parameter && node.isStream()) { // TODO: implement } else { node.addCallback(callback); } } -DeviceTree.prototype.setValue = function(node, value) { - var self=this; +DeviceTree.prototype.setValue = function (node, value) { + var self = this; return new Promise((resolve, reject) => { - if((!(node instanceof ember.Parameter)) && - (!(node instanceof ember.QualifiedParameter))) - { + if ((!(node instanceof ember.Parameter)) && + (!(node instanceof ember.QualifiedParameter))) { reject(new errors.EmberAccessError('not a property')); } else { // if (this._debug) { console.log('setValue', node.getPath(), value); } self.addRequest((error) => { - if(error) { + if (error) { self.finishRequest(); reject(error); return; @@ -399,7 +417,9 @@ DeviceTree.prototype.setValue = function(node, value) { }; self.callback = cb; - if (this._debug) { console.log('setValue sending ...', node.getPath(), value); } + if (this._debug) { + console.log('setValue sending ...', node.getPath(), value); + } self.client.sendBERNode(node.setValue(value)); }); } @@ -410,9 +430,9 @@ function TreePath(path) { this.identifiers = []; this.numbers = []; - if(path !== undefined) { - for(var i=0; i Date: Tue, 28 Aug 2018 16:19:58 +0200 Subject: [PATCH 044/162] fix client socket server --- client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client.js b/client.js index 462480b..4e8bd88 100755 --- a/client.js +++ b/client.js @@ -37,6 +37,7 @@ function S101Client(socket, server) { self.status = "connected"; + self.codec = new S101Codec(); self.codec.on('keepaliveReq', () => { self.sendKeepaliveResponse(); }); From 6d9e18e068d5608a240ad2543cb28c18825470aa Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Tue, 28 Aug 2018 16:26:04 +0200 Subject: [PATCH 045/162] sample test server & client --- sample.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 sample.js diff --git a/sample.js b/sample.js new file mode 100644 index 0000000..4c80897 --- /dev/null +++ b/sample.js @@ -0,0 +1,41 @@ +const Decoder = require('.').Decoder; +const DeviceTree = require(".").DeviceTree; +const fs = require("fs"); + +const LOCALHOST = "127.0.0.1"; +const PORT = 9008; + +fs.readFile("./embrionix.ember", (e, data) => { + let root = Decoder(data); + + const TreeServer = require("./").TreeServer; + const server = new TreeServer(LOCALHOST, PORT, root); + server.listen() + .then(() => { + console.log("listening"); + }) + .catch((e) => { + console.log(e.stack); + }) + .then(() => { + let tree = new DeviceTree(LOCALHOST, PORT); + return Promise.resolve() + .then(() => tree.connect()) + .then(() => { + return tree.getDirectory(); + }) + .then(() => tree.disconnect()) + .then(() => tree.connect()) + .then(() => { + return tree.getDirectory(); + }) + .catch((e) => { + console.log(e.stack); + }) + .then(() => tree.disconnect()) + }) + .then(() => { + console.log("close"); + server.close(); + }); +}); From 0603d0b5f6896cf37cccd57a689752503092b59c Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Tue, 28 Aug 2018 16:28:46 +0200 Subject: [PATCH 046/162] move logging elsewhere --- sample.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sample.js b/sample.js index 4c80897..5cc0e5d 100644 --- a/sample.js +++ b/sample.js @@ -14,9 +14,6 @@ fs.readFile("./embrionix.ember", (e, data) => { .then(() => { console.log("listening"); }) - .catch((e) => { - console.log(e.stack); - }) .then(() => { let tree = new DeviceTree(LOCALHOST, PORT); return Promise.resolve() @@ -34,6 +31,9 @@ fs.readFile("./embrionix.ember", (e, data) => { }) .then(() => tree.disconnect()) }) + .catch((e) => { + console.log(e.stack); + }) .then(() => { console.log("close"); server.close(); From 75c80ec685c676a412325c86393c0f4e31fdc92e Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Fri, 31 Aug 2018 11:42:38 +0200 Subject: [PATCH 047/162] add missing dependency winston used in: - client.js - s101.js --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b557853..2d136de 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,14 @@ "repository": { "type": "git", "url": "https://github.com/bmayton/node-emberplus" - }, + }, "license": "MIT", "dependencies": { "asn1": "evs-broadcast/node-asn1", "enum": "^2.4.0", "long": "^3.2.0", "smart-buffer": "^3.0.3", + "winston": "^2.1.1", "winston-color": "^1.0.0" } } From 95ccd30adde160f7dd70dffabe636b60921ea288 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Tue, 4 Sep 2018 17:08:04 +0200 Subject: [PATCH 048/162] do not resolve when the incomming data does not match the queried path --- device.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/device.js b/device.js index 35b4b91..65dc3fc 100755 --- a/device.js +++ b/device.js @@ -137,6 +137,7 @@ DeviceTree.prototype.getDirectory = function (qnode) { qnode = self.root; } return new Promise((resolve, reject) => { + const qpath = qnode.path != null ? qnode.path : qnode.elements['0'].path; self.addRequest((error) => { if (error) { reject(error); @@ -148,19 +149,26 @@ DeviceTree.prototype.getDirectory = function (qnode) { self.clearTimeout(); // clear the timeout now. The resolve below may take a while. if (error) { reject(error); + self.finishRequest(); } else { - if (self._debug) { - console.log("Received getDirectory response", node); + const nodeElems = node.elements; + if (qpath != null && nodeElems != null && nodeElems.every(el => el.path.startsWith(qpath))) { + if (self._debug) { + console.log('Received getDirectory response', node); + } + resolve(node); // make sure the info is treated before going to next request. + self.finishRequest(); } - resolve(node); // make sure the info is treated before going to next request. } - self.finishRequest(); }; if (self._debug) { console.log("Sending getDirectory", qnode); } + if (self.callback != null) { + console.log('erasing callback'); + } self.callback = cb; self.client.sendBERNode(qnode.getDirectory()); }); From 3ac4881d07402c6a649878e04c01b01075222cd7 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 10:01:36 +0200 Subject: [PATCH 049/162] fix a bit more --- device.js | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/device.js b/device.js index 65dc3fc..51e24ca 100755 --- a/device.js +++ b/device.js @@ -81,6 +81,7 @@ DeviceTree.prototype.isConnected = function () { DeviceTree.prototype.connect = function (timeout = 2) { return new Promise((resolve, reject) => { this.callback = (e) => { + this.callback = undefined; if (e === undefined) { return resolve(); } @@ -130,6 +131,10 @@ DeviceTree.prototype.expand = function (node) { }); } +function isDirectSubPathOf(path, parent) { + return path.lastIndexOf('.') === parent.length && path.startsWith(parent) +} + DeviceTree.prototype.getDirectory = function (qnode) { var self = this; if (qnode == null) { @@ -137,28 +142,30 @@ DeviceTree.prototype.getDirectory = function (qnode) { qnode = self.root; } return new Promise((resolve, reject) => { - const qpath = qnode.path != null ? qnode.path : qnode.elements['0'].path; self.addRequest((error) => { if (error) { - reject(error); self.finishRequest(); + reject(error); return; } let cb = (error, node) => { - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. + const qpath = qnode.path != null ? qnode.path : qnode.elements["0"].path; if (error) { + self.clearTimeout(); // clear the timeout now. The resolve below may take a while. + self.finishRequest(cb); reject(error); - self.finishRequest(); } else { - const nodeElems = node.elements; - if (qpath != null && nodeElems != null && nodeElems.every(el => el.path.startsWith(qpath))) { + const nodeElements = node == null ? null : node.elements; + if (nodeElements != null + && nodeElements.every(el => el.path === qpath || isDirectSubPathOf(el.path, qpath))) { if (self._debug) { - console.log('Received getDirectory response', node); + console.log("Received getDirectory response", node); } + self.clearTimeout(); // clear the timeout now. The resolve below may take a while. + self.finishRequest(cb); resolve(node); // make sure the info is treated before going to next request. - self.finishRequest(); } } }; @@ -166,14 +173,14 @@ DeviceTree.prototype.getDirectory = function (qnode) { if (self._debug) { console.log("Sending getDirectory", qnode); } - if (self.callback != null) { - console.log('erasing callback'); + if (self._debug && self.callback != null) { + console.log("erasing callback"); } self.callback = cb; self.client.sendBERNode(qnode.getDirectory()); }); }); -} +}; DeviceTree.prototype.invokeFunction = function (fnNode, params) { var self = this @@ -246,8 +253,11 @@ DeviceTree.prototype.clearTimeout = function () { } } -DeviceTree.prototype.finishRequest = function () { +DeviceTree.prototype.finishRequest = function (cb) { var self = this; + if (self._debug && cb && cb !== self.callback()) { + console.error("BUG!", self.callback); + } self.callback = undefined; self.clearTimeout(); self.activeRequest = null; From bc582d9fc1da173ebae02063978464d675207c1c Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 10:48:46 +0200 Subject: [PATCH 050/162] improve code --- device.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/device.js b/device.js index 51e24ca..98ac12b 100755 --- a/device.js +++ b/device.js @@ -150,23 +150,27 @@ DeviceTree.prototype.getDirectory = function (qnode) { } let cb = (error, node) => { - const qpath = qnode.path != null ? qnode.path : qnode.elements["0"].path; + const requestedPath = qnode.path != null ? qnode.path : qnode.elements["0"].path; if (error) { + if (self._debug) { + console.log("Received getDirectory error", error); + } + self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(cb); + self.finishRequest(); reject(error); + return; } - else { - const nodeElements = node == null ? null : node.elements; - if (nodeElements != null - && nodeElements.every(el => el.path === qpath || isDirectSubPathOf(el.path, qpath))) { - if (self._debug) { - console.log("Received getDirectory response", node); - } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(cb); - resolve(node); // make sure the info is treated before going to next request. + const nodeElements = node == null ? null : node.elements; + if (nodeElements != null + && nodeElements.every(el => el.path === requestedPath || isDirectSubPathOf(el.path, requestedPath))) { + if (self._debug) { + console.log("Received getDirectory response", node); } + + self.clearTimeout(); // clear the timeout now. The resolve below may take a while. + self.finishRequest(); + resolve(node); // make sure the info is treated before going to next request. } }; @@ -253,11 +257,8 @@ DeviceTree.prototype.clearTimeout = function () { } } -DeviceTree.prototype.finishRequest = function (cb) { +DeviceTree.prototype.finishRequest = function () { var self = this; - if (self._debug && cb && cb !== self.callback()) { - console.error("BUG!", self.callback); - } self.callback = undefined; self.clearTimeout(); self.activeRequest = null; From b1737cdb66f8de2edb38d31a2e5434c3f489bc2d Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 11:51:37 +0200 Subject: [PATCH 051/162] ignore .idea directory --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d6166ec..c882358 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ +# intellij, pycharm, webstorm... +/.idea/* + node_modules .*.swp From 94ea25a878ce743707177df548ce17eb802835d6 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 14:38:00 +0200 Subject: [PATCH 052/162] target a tag for node-asn1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9948623..b737351 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,10 @@ "repository": { "type": "git", "url": "https://github.com/bmayton/node-emberplus" - }, + }, "license": "MIT", "dependencies": { - "asn1": "evs-broadcast/node-asn1", + "asn1": "evs-broadcast/node-asn1#date_2018_01_02", "enum": "^2.4.0", "long": "^3.2.0", "smart-buffer": "^3.0.3", From 168d5782acb6b8ff871ba03db4d3b7b224b5f4a1 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 15:05:22 +0200 Subject: [PATCH 053/162] add skeleton for eslint and jest ! --- package.json | 7 ++++++- test/device.test.js | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 test/device.test.js diff --git a/package.json b/package.json index b737351..850bf0d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest test", + "eslint": "eslint ./" }, "author": "Brian Mayton (http://bdm.cc)", "contributors": [ @@ -24,5 +25,9 @@ "long": "^3.2.0", "smart-buffer": "^3.0.3", "winston-color": "^1.0.0" + }, + "devDependencies": { + "eslint": "^5.5.0", + "jest": "^23.5.0" } } diff --git a/test/device.test.js b/test/device.test.js new file mode 100644 index 0000000..8eb42ec --- /dev/null +++ b/test/device.test.js @@ -0,0 +1,5 @@ +describe("DeviceTree", () => { + it("expect true", () => { + expect(true).toBe(true); + }) +}); From 7bf7ab5583bd4b6819ebf988f2a7f260e1a3b731 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 15:09:41 +0200 Subject: [PATCH 054/162] add missing semi-colon in the modified file --- device.js | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/device.js b/device.js index 98ac12b..2f4f88f 100755 --- a/device.js +++ b/device.js @@ -43,7 +43,7 @@ function DeviceTree(host, port = 9000) { self.client.on('emberTree', (root) => { if (root instanceof ember.InvocationResult) { - self.emit('invocationResult', root) + self.emit('invocationResult', root); if (self._debug) { console.log("Received InvocationResult", root); } @@ -66,17 +66,17 @@ util.inherits(DeviceTree, EventEmitter); var DecodeBuffer = function (packet) { var ber = new BER.Reader(packet); return ember.Root.decode(ber); -} +}; DeviceTree.prototype.saveTree = function (f) { var writer = new BER.Writer(); this.root.encode(writer); f(writer.buffer); -} +}; DeviceTree.prototype.isConnected = function () { return ((this.client !== undefined) && (this.client.isConnected())); -} +}; DeviceTree.prototype.connect = function (timeout = 2) { return new Promise((resolve, reject) => { @@ -92,7 +92,7 @@ DeviceTree.prototype.connect = function (timeout = 2) { } this.client.connect(timeout); }); -} +}; DeviceTree.prototype.expand = function (node) { let self = this; @@ -129,7 +129,7 @@ DeviceTree.prototype.expand = function (node) { } return p; }); -} +}; function isDirectSubPathOf(path, parent) { return path.lastIndexOf('.') === parent.length && path.startsWith(parent) @@ -187,7 +187,7 @@ DeviceTree.prototype.getDirectory = function (qnode) { }; DeviceTree.prototype.invokeFunction = function (fnNode, params) { - var self = this + var self = this; return new Promise((resolve, reject) => { self.addRequest((error) => { if (error) { @@ -202,7 +202,7 @@ DeviceTree.prototype.invokeFunction = function (fnNode, params) { reject(error); } else { - if (DEBUG) { + if (self._debug) { console.log("InvocationResult", result); } resolve(result); @@ -217,13 +217,13 @@ DeviceTree.prototype.invokeFunction = function (fnNode, params) { self.client.sendBERNode(fnNode.invoke(params)); }); }) -} +}; DeviceTree.prototype.disconnect = function () { if (this.client !== undefined) { return this.client.disconnect(); } -} +}; DeviceTree.prototype.makeRequest = function () { var self = this; @@ -248,14 +248,14 @@ DeviceTree.prototype.addRequest = function (cb) { var self = this; self.pendingRequests.push(cb); self.makeRequest(); -} +}; DeviceTree.prototype.clearTimeout = function () { if (this.timeout != null) { clearTimeout(this.timeout); this.timeout = null; } -} +}; DeviceTree.prototype.finishRequest = function () { var self = this; @@ -263,13 +263,13 @@ DeviceTree.prototype.finishRequest = function () { self.clearTimeout(); self.activeRequest = null; self.makeRequest(); -} +}; DeviceTree.prototype.timeoutRequest = function (id) { var self = this; self.root.cancelCallbacks(); self.activeRequest(new errors.EmberTimeoutError(`Request ${id !== undefined ? id : ""} timed out`)); -} +}; DeviceTree.prototype.handleRoot = function (root) { var self = this; @@ -294,7 +294,7 @@ DeviceTree.prototype.handleRoot = function (root) { callbacks[j](); } } -} +}; DeviceTree.prototype.handleQualifiedNode = function (parent, node) { var self = this; @@ -339,7 +339,7 @@ DeviceTree.prototype.handleQualifiedNode = function (parent, node) { //callbacks = parent.update(); return callbacks; -} +}; DeviceTree.prototype.handleNode = function (parent, node) { var self = this; @@ -365,7 +365,7 @@ DeviceTree.prototype.handleNode = function (parent, node) { //console.log('handleNode: ', callbacks); return callbacks; -} +}; DeviceTree.prototype.getNodeByPath = function (path) { var self = this; @@ -390,7 +390,7 @@ DeviceTree.prototype.getNodeByPath = function (path) { }); }); }); -} +}; DeviceTree.prototype.subscribe = function (node, callback) { if (node instanceof ember.Parameter && node.isStream()) { @@ -398,7 +398,7 @@ DeviceTree.prototype.subscribe = function (node, callback) { } else { node.addCallback(callback); } -} +}; DeviceTree.prototype.unsubscribe = function (node, callback) { if (node instanceof ember.Parameter && node.isStream()) { @@ -406,7 +406,7 @@ DeviceTree.prototype.unsubscribe = function (node, callback) { } else { node.addCallback(callback); } -} +}; DeviceTree.prototype.setValue = function (node, value) { var self = this; @@ -443,7 +443,7 @@ DeviceTree.prototype.setValue = function (node, value) { }); } }); -} +}; function TreePath(path) { this.identifiers = []; From a0819ed0229bca82e99cccd1d2fabefcfdd385b1 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 15:10:57 +0200 Subject: [PATCH 055/162] removing useless logging --- device.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/device.js b/device.js index 2f4f88f..2fcd381 100755 --- a/device.js +++ b/device.js @@ -177,9 +177,6 @@ DeviceTree.prototype.getDirectory = function (qnode) { if (self._debug) { console.log("Sending getDirectory", qnode); } - if (self._debug && self.callback != null) { - console.log("erasing callback"); - } self.callback = cb; self.client.sendBERNode(qnode.getDirectory()); }); From c557895333df98d5b9349b54baa148c6e44f791e Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 15:12:24 +0200 Subject: [PATCH 056/162] inline useless cb variable --- device.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/device.js b/device.js index 2fcd381..fdc3af9 100755 --- a/device.js +++ b/device.js @@ -149,7 +149,7 @@ DeviceTree.prototype.getDirectory = function (qnode) { return; } - let cb = (error, node) => { + self.callback = (error, node) => { const requestedPath = qnode.path != null ? qnode.path : qnode.elements["0"].path; if (error) { if (self._debug) { @@ -177,7 +177,6 @@ DeviceTree.prototype.getDirectory = function (qnode) { if (self._debug) { console.log("Sending getDirectory", qnode); } - self.callback = cb; self.client.sendBERNode(qnode.getDirectory()); }); }); From e10a64d126bb3a0800eb83e2f4a3b01a6d8097a7 Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 15:39:03 +0200 Subject: [PATCH 057/162] add eslint config --- .eslintrc.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..fe05738 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + "env": { + "es6": true, + "node": true, + "jest": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2015 + }, + "rules": { + "no-unused-vars": "warn", + "no-undef": "warn", + "no-redeclare": "warn", + "no-extra-semi": "warn", + "no-console": "warn", + "semi-style": + [ + "error", + "last" + ] + } +} +; From 055179553d6aed9e97761257a6a3136bd702f71a Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Wed, 5 Sep 2018 15:39:19 +0200 Subject: [PATCH 058/162] add test for previously developed functionality - it should gracefully connect and disconnect --- test/DeviceTree.test.js | 43 +++++++++++++++++++++++++++++++++++++++++ test/device.test.js | 5 ----- 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 test/DeviceTree.test.js delete mode 100644 test/device.test.js diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js new file mode 100644 index 0000000..21c55a9 --- /dev/null +++ b/test/DeviceTree.test.js @@ -0,0 +1,43 @@ +const fs = require("fs"); +const Decoder = require('../').Decoder; +const DeviceTree = require("../").DeviceTree; +const TreeServer = require("../").TreeServer; + +const LOCALHOST = "127.0.0.1"; +const PORT = 9008; + +describe("DeviceTree", () => { + let server; + + beforeAll(() => { + return Promise.resolve() + .then(() => new Promise((resolve, reject) => { + fs.readFile("./embrionix.ember", (e, data) => { + if (e) { + reject(e); + } + resolve(Decoder(data)); + }); + })) + .then(root => { + server = new TreeServer(LOCALHOST, PORT, root); + return server.listen(); + }); + }); + + afterAll(() => server.close()); + + it("should gracefully connect and disconnect", () => { + return Promise.resolve() + .then(() => { + let tree = new DeviceTree(LOCALHOST, PORT); + return Promise.resolve() + .then(() => tree.connect()) + .then(() => tree.getDirectory()) + .then(() => tree.disconnect()) + .then(() => tree.connect()) + .then(() => tree.getDirectory()) + .then(() => tree.disconnect()) + }) + }); +}); diff --git a/test/device.test.js b/test/device.test.js deleted file mode 100644 index 8eb42ec..0000000 --- a/test/device.test.js +++ /dev/null @@ -1,5 +0,0 @@ -describe("DeviceTree", () => { - it("expect true", () => { - expect(true).toBe(true); - }) -}); From 4204ec829eff09767e501ff05bcb02142ee2fa83 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 11 Sep 2018 15:19:56 +0200 Subject: [PATCH 059/162] Fixing connect timeout doulbe error --- client.js | 66 +++++++++++++++++++++++++------------------------------ 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/client.js b/client.js index 4e8bd88..f603fed 100755 --- a/client.js +++ b/client.js @@ -164,46 +164,40 @@ S101Socket.prototype.connect = function (timeout = 2) { self.emit('connecting'); - if (timeout > 0) { - self._timeout = timeout; - self._timer = setTimeout(() => { - self.socket = undefined; - self.emit("error", new Error("connection timeout")); - }, 1000 * timeout); - } - self.codec = new S101Codec(); - self.socket = net.createConnection(self.port, self.address, () => { - winston.debug('socket connected'); - - if (self._timer) { - clearTimeout(self._timer); - } - - self.keepaliveIntervalTimer = setInterval(() => { - self.sendKeepaliveRequest(); - }, 1000 * self.keepaliveInterval); - - self.codec.on('keepaliveReq', () => { - self.sendKeepaliveResponse(); - }); - - self.codec.on('emberPacket', (packet) => { - self.emit('emberPacket', packet); + self.socket = net.createConnection({ + port: self.port, + host: self.address, + timeout: timeout + }, + () => { + winston.debug('socket connected'); + + self.keepaliveIntervalTimer = setInterval(() => { + self.sendKeepaliveRequest(); + }, 1000 * self.keepaliveInterval); + + self.codec.on('keepaliveReq', () => { + self.sendKeepaliveResponse(); + }); - var ber = new BER.Reader(packet); - try { - var root = ember.Root.decode(ber); - if (root !== undefined) { - self.emit('emberTree', root); + self.codec.on('emberPacket', (packet) => { + self.emit('emberPacket', packet); + + var ber = new BER.Reader(packet); + try { + var root = ember.Root.decode(ber); + if (root !== undefined) { + self.emit('emberTree', root); + } + } catch (e) { + self.emit("error", e); } - } catch (e) { - self.emit("error", e); - } - }); + }); - self.emit('connected'); - }).on('error', (e) => { + self.emit('connected'); + } + ).on('error', (e) => { self.emit("error", e); }); From 41d0ed642ba502f74581ee9f3ca509448d3f3f13 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 11 Sep 2018 15:20:19 +0200 Subject: [PATCH 060/162] Version 1.7.14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 850bf0d..42af429 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.13", + "version": "1.7.14", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From da670a3db0e44033a7b1f052e8f74fad69658c7b Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 13 Sep 2018 10:01:01 +0200 Subject: [PATCH 061/162] Fixed Matrix parametersLocation decoding/encoding --- ember.js | 23 +++++++++++++++++++---- index.js | 3 ++- package.json | 1 + test/Ember.test.js | 21 +++++++++++++++++++++ 4 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 test/Ember.test.js diff --git a/ember.js b/ember.js index dec30ca..8cace70 100755 --- a/ember.js +++ b/ember.js @@ -59,8 +59,11 @@ Root.decode = function(ber) { } } catch (e) { - console.log(e.stack); - return r; + if (DEBUG) { + console.log("Decode ERROR", e.stack); + return r; + } + throw e; } } } else { @@ -894,7 +897,13 @@ MatrixContents.decode = function(ber) { } else if(tag == BER.CONTEXT(7)) { mc.maximumConnectsPerTarget = seq.readInt(); } else if(tag == BER.CONTEXT(8)) { - mc.parametersLocation = seq.readInt(); + tag = seq.peek(); + if (tag === BER.EMBER_RELATIVE_OID) { + mc.parametersLocation = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else { + mc.parametersLocation = seq.readInt(); + } } else if(tag == BER.CONTEXT(9)) { mc.gainParameterNumber = seq.readInt(); } else if(tag == BER.CONTEXT(10)) { @@ -960,7 +969,13 @@ MatrixContents.prototype.encode = function(ber) { } if (this.parametersLocation !== undefined) { ber.startSequence(BER.CONTEXT(8)); - ber.writeInt(this.parametersLocation); + let param = Number(this.parametersLocation) + if (isNaN(param)) { + ber.writeRelativeOID(this.parametersLocation, BER.EMBER_RELATIVE_OID); + } + else { + ber.writeInt(param); + } ber.endSequence(); } if (this.gainParameterNumber !== undefined) { diff --git a/index.js b/index.js index 8382e24..924f192 100755 --- a/index.js +++ b/index.js @@ -3,4 +3,5 @@ const Decoder = require('./device.js').DecodeBuffer; const Ember = require("./ember.js"); const S101 = require("./s101"); const TreeServer = require("./server"); -module.exports = {DeviceTree, Decoder, Ember, TreeServer, S101}; +const {S101Client} = require("./client"); +module.exports = {DeviceTree, Decoder, Ember, TreeServer, S101, S101Client}; diff --git a/package.json b/package.json index 42af429..643ccb3 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "winston-color": "^1.0.0" }, "devDependencies": { + "expect": "^23.6.0", "eslint": "^5.5.0", "jest": "^23.5.0" } diff --git a/test/Ember.test.js b/test/Ember.test.js new file mode 100644 index 0000000..358c000 --- /dev/null +++ b/test/Ember.test.js @@ -0,0 +1,21 @@ +const {S101Client} = require("../index"); +const expect = require("expect"); +const s101Buffer = new Buffer("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); + +describe("Ember", () => { + let client; + + beforeAll(() => { + client = new S101Client(); + }); + + it("should parse S101 message without error", (done) => { + client.on("emberPacket", () => { done(); }); + client.on("error", e => { + console.log(e); + expect(e).toBeUndefined(); + done(); + }); + client.codec.dataIn(s101Buffer); + }); +}); From 3d98628511c97d71c28f78e1d5be31cf61f9df5a Mon Sep 17 00:00:00 2001 From: "COUNASSE, Emmanuel" Date: Fri, 21 Sep 2018 15:17:29 +0200 Subject: [PATCH 062/162] fix crash if getDirectory respond with an error --- README.md | 2 +- device.js | 2 +- package.json | 3 +-- test/Ember.test.js | 7 ++++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e989b14..2464cc9 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ tree.connect() // Simple packet decoder -const Decoder = require('emberplus').DecodeBuffer; +const Decoder = require('emberplus').Decoder; const fs = require("fs"); fs.readFile("tree.ember", (e,data) => { diff --git a/device.js b/device.js index fdc3af9..f954c4f 100755 --- a/device.js +++ b/device.js @@ -150,7 +150,6 @@ DeviceTree.prototype.getDirectory = function (qnode) { } self.callback = (error, node) => { - const requestedPath = qnode.path != null ? qnode.path : qnode.elements["0"].path; if (error) { if (self._debug) { console.log("Received getDirectory error", error); @@ -161,6 +160,7 @@ DeviceTree.prototype.getDirectory = function (qnode) { reject(error); return; } + const requestedPath = qnode.path != null ? qnode.path : qnode.elements["0"].path; const nodeElements = node == null ? null : node.elements; if (nodeElements != null && nodeElements.every(el => el.path === requestedPath || isDirectSubPathOf(el.path, requestedPath))) { diff --git a/package.json b/package.json index ccf5129..6c2eb63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.14", + "version": "1.7.15", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { @@ -28,7 +28,6 @@ "winston-color": "^1.0.0" }, "devDependencies": { - "expect": "^23.6.0", "eslint": "^5.5.0", "jest": "^23.5.0" } diff --git a/test/Ember.test.js b/test/Ember.test.js index 358c000..b9b27d3 100644 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -1,5 +1,4 @@ -const {S101Client} = require("../index"); -const expect = require("expect"); +const { S101Client } = require("../index"); const s101Buffer = new Buffer("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); describe("Ember", () => { @@ -10,7 +9,9 @@ describe("Ember", () => { }); it("should parse S101 message without error", (done) => { - client.on("emberPacket", () => { done(); }); + client.on("emberPacket", () => { + done(); + }); client.on("error", e => { console.log(e); expect(e).toBeUndefined(); From 16f9ff57fb844b0e2d9356a084fa4af8378b31c3 Mon Sep 17 00:00:00 2001 From: manuc66 Date: Fri, 26 Oct 2018 12:07:14 +0200 Subject: [PATCH 063/162] send clientError event when receiving an error from a client connection this handle an unhandled exception --- sample.js | 7 +++++-- server.js | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/sample.js b/sample.js index 5cc0e5d..84d3741 100644 --- a/sample.js +++ b/sample.js @@ -2,14 +2,17 @@ const Decoder = require('.').Decoder; const DeviceTree = require(".").DeviceTree; const fs = require("fs"); -const LOCALHOST = "127.0.0.1"; -const PORT = 9008; +const LOCALHOST = "0.0.0.0"; +const PORT = 3336; fs.readFile("./embrionix.ember", (e, data) => { let root = Decoder(data); const TreeServer = require("./").TreeServer; const server = new TreeServer(LOCALHOST, PORT, root); + server.on("clientError", (e) => { + console.log(e); + }); server.listen() .then(() => { console.log("listening"); diff --git a/server.js b/server.js index cc2fbf9..304dc53 100755 --- a/server.js +++ b/server.js @@ -45,6 +45,9 @@ function TreeServer(host, port, tree) { self.clients.delete(client); self.emit('disconnect', client.remoteAddress()); }); + client.on("error", error => { + self.emit('clientError', { remoteAddress: client.remoteAddress(), error }); + }); self.emit('connection', client.remoteAddress()); }); From 25bbea5bcb6c360f5e9db7e2e25c11dde82257b4 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 30 Nov 2018 14:42:15 +0100 Subject: [PATCH 064/162] Fix crash when parsing getDirectory response. --- client.js | 8 +++----- device.js | 16 ++++++++++++---- package.json | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/client.js b/client.js index f603fed..38b03f8 100755 --- a/client.js +++ b/client.js @@ -257,11 +257,9 @@ S101Socket.prototype.sendKeepaliveResponse = function () { S101Socket.prototype.sendBER = function (data) { var self = this; - if (self.isConnected()) { - var frames = self.codec.encodeBER(data); - for (var i = 0; i < frames.length; i++) { - self.socket.write(frames[i]); - } + var frames = self.codec.encodeBER(data); + for (var i = 0; i < frames.length; i++) { + self.socket.write(frames[i]); } } diff --git a/device.js b/device.js index f954c4f..37e81cf 100755 --- a/device.js +++ b/device.js @@ -150,24 +150,32 @@ DeviceTree.prototype.getDirectory = function (qnode) { } self.callback = (error, node) => { + if (node == null) { return; } if (error) { if (self._debug) { console.log("Received getDirectory error", error); } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. self.finishRequest(); reject(error); return; } - const requestedPath = qnode.path != null ? qnode.path : qnode.elements["0"].path; + let requestedPath = qnode.getPath(); + if (requestedPath === "") { + if (qnode.elements == null || qnode.elements.length === 0) { + if (self._debug) { + console.log("getDirectory response", node); + } + return self.callback(new Error("Invalid qnode for getDirectory")); + } + requestedPath = qnode.elements["0"].getPath(); + } const nodeElements = node == null ? null : node.elements; if (nodeElements != null - && nodeElements.every(el => el.path === requestedPath || isDirectSubPathOf(el.path, requestedPath))) { + && nodeElements.every(el => el.getPath() === requestedPath || isDirectSubPathOf(el.getPath(), requestedPath))) { if (self._debug) { console.log("Received getDirectory response", node); } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. self.finishRequest(); resolve(node); // make sure the info is treated before going to next request. diff --git a/package.json b/package.json index 6c2eb63..e0a7beb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.15", + "version": "1.7.16", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From a7e72ab36cef90b0f8e08549d03d387d020fe920 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 4 Dec 2018 10:55:25 +0100 Subject: [PATCH 065/162] Fixing matrix connections encoding --- ember.js | 13 +++++++++---- package.json | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ember.js b/ember.js index 8cace70..feb52cb 100755 --- a/ember.js +++ b/ember.js @@ -823,7 +823,6 @@ MatrixNode.prototype.encode = function(ber) { ber.endSequence(); ber.endSequence(); } - ber.endSequence(); ber.endSequence(); } @@ -831,9 +830,12 @@ MatrixNode.prototype.encode = function(ber) { if (this.connections !== undefined) { ber.startSequence(BER.CONTEXT(5)); ber.startSequence(BER.EMBER_SEQUENCE); + for(var id in this.connections) { if (this.connections.hasOwnProperty(id)) { + ber.startSequence(BER.CONTEXT(0)); this.connections[id].encode(ber); + ber.endSequence(); } } ber.endSequence(); @@ -1156,7 +1158,6 @@ MatrixConnection.decode = function(ber) { } MatrixConnection.prototype.encode = function(ber) { - ber.startSequence(BER.CONTEXT(0)); ber.startSequence(BER.APPLICATION(16)); ber.startSequence(BER.CONTEXT(0)); @@ -1178,8 +1179,6 @@ MatrixConnection.prototype.encode = function(ber) { ber.writeInt(this.disposition.value); ber.endSequence(); } - - ber.endSequence(); ber.endSequence(); } @@ -1454,6 +1453,7 @@ QualifiedMatrix.prototype.encode = function(ber) { if (this.targets !== undefined) { ber.startSequence(BER.CONTEXT(3)); + ber.startSequence(BER.EMBER_SEQUENCE); for(var i=0; i Date: Mon, 10 Dec 2018 11:25:38 +0100 Subject: [PATCH 066/162] Fix matrix connect/disconnect sources handling --- ember.js | 6 ++---- package.json | 2 +- server.js | 5 ++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ember.js b/ember.js index feb52cb..9758a70 100755 --- a/ember.js +++ b/ember.js @@ -1105,7 +1105,7 @@ MatrixConnection.prototype.setSources = function(sources) { delete this.sources; return; } - let s = new Set(sources); + let s = new Set(sources.map(i => Number(i))); this.sources = [...s].sort(); // sources should be an array } @@ -1141,9 +1141,7 @@ MatrixConnection.decode = function(ber) { c.target = seq.readInt(); } else if (tag == BER.CONTEXT(1)) { - //sources - var sources = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); - c.sources = sources.split("."); + c.sources = seq.readRelativeOID(BER.EMBER_RELATIVE_OID).split(".").map(i => Number(i)); } else if (tag == BER.CONTEXT(2)) { c.operation = MatrixOperation.get(seq.readInt()); diff --git a/package.json b/package.json index 5f9335a..5ff7a9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.17", + "version": "1.7.18", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 304dc53..5ca85e3 100755 --- a/server.js +++ b/server.js @@ -281,9 +281,8 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti if (client !== undefined) { client.sendBERNode(root); } - else { - this.updateSubscribers(matrix.getPath(), root, client); - } + if (this._debug) { console.log("Updating subscribers for matrix change"); } + this.updateSubscribers(matrix.getPath(), root, client); } const validateMatrixOperation = function(matrix, target, sources) { From 936e3cba00bf9247661e9c193613be68a37d767d Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 17 Dec 2018 12:26:50 +0100 Subject: [PATCH 067/162] Default matrix type should be oneToN. Allow override when building tree from JSON --- ember.js | 2 +- package.json | 2 +- server.js | 52 ++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/ember.js b/ember.js index 9758a70..b54fcf9 100755 --- a/ember.js +++ b/ember.js @@ -869,7 +869,7 @@ util.inherits(MatrixNode, TreeNode); module.exports.MatrixNode = MatrixNode; function MatrixContents() { - this.type = MatrixType.oneToOne; + this.type = MatrixType.oneToN; this.mode = MatrixMode.linear; } diff --git a/package.json b/package.json index 5ff7a9a..dd1cea6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.18", + "version": "1.7.19", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 5ca85e3..f191273 100755 --- a/server.js +++ b/server.js @@ -475,6 +475,45 @@ TreeServer.prototype.updateSubscribers = function(path, response, origin) { } } +const parseMatrixContent = function(matrixContent, content) { + if (content.labels) { + matrixContent.labels = []; + for(let l = 0; l < content.labels.length; l++) { + matrixContent.labels.push( + new ember.Label(content.labels[l]) + ); + } + delete content.labels; + } + if (content.type != null) { + if (content.type == "oneToN") { + matrixContent.type = ember.MatrixType.oneToN; + } + else if (content.type == "oneToOne") { + matrixContent.type = ember.MatrixType.oneToOne; + } + else if (content.type == "nToN") { + matrixContent.type = ember.MatrixType.nToN; + } + else { + throw new Error(`Invalid matrix type ${content.type}`); + } + delete content.type; + } + if (content.mode != null) { + if (content.mode == "linear") { + matrixContent.mode = ember.MatrixMode.linear; + } + else if (content.mode == "nonLinear") { + matrixContent.mode = ember.MatrixMode.nonLinear; + } + else { + throw new Error(`Invalid matrix mode ${content.mode}`); + } + delete content.mode; + } +} + const parseObj = function(parent, obj) { let path = parent.getPath(); for(let i = 0; i < obj.length; i++) { @@ -503,18 +542,7 @@ const parseObj = function(parent, obj) { else if (content.targetCount !== undefined) { emberElement = new ember.MatrixNode(number); emberElement.contents = new ember.MatrixContents(); - - if (content.labels) { - emberElement.contents.labels = []; - for(let l = 0; l < content.labels.length; l++) { - emberElement.contents.labels.push( - new ember.Label(content.labels[l]) - ); - } - delete content.labels; - } - - + parseMatrixContent(emberElement.contents, content); if (content.connections) { emberElement.connections = {}; for (let c in content.connections) { From 275ed87e0ece301cd3f3185be67036eea8d66cbd Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Tue, 8 Jan 2019 18:01:43 +0100 Subject: [PATCH 068/162] Fix unhandled errors on disconnect --- client.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/client.js b/client.js index 38b03f8..b037dad 100755 --- a/client.js +++ b/client.js @@ -174,7 +174,12 @@ S101Socket.prototype.connect = function (timeout = 2) { winston.debug('socket connected'); self.keepaliveIntervalTimer = setInterval(() => { - self.sendKeepaliveRequest(); + try { + self.sendKeepaliveRequest(); + } + catch(e) { + self.emit("error", e); + } }, 1000 * self.keepaliveInterval); self.codec.on('keepaliveReq', () => { @@ -202,7 +207,9 @@ S101Socket.prototype.connect = function (timeout = 2) { }); self.socket.on('data', (data) => { - self.codec.dataIn(data); + if (self.isConnected()) { + self.codec.dataIn(data); + } }); self.socket.on('close', () => { @@ -257,9 +264,11 @@ S101Socket.prototype.sendKeepaliveResponse = function () { S101Socket.prototype.sendBER = function (data) { var self = this; - var frames = self.codec.encodeBER(data); - for (var i = 0; i < frames.length; i++) { - self.socket.write(frames[i]); + if (self.isConnected()) { + var frames = self.codec.encodeBER(data); + for (var i = 0; i < frames.length; i++) { + self.socket.write(frames[i]); + } } } From 637f2f00bacc7b02e9f8fa5ed1778f6a3f2480bc Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Wed, 9 Jan 2019 10:54:56 +0100 Subject: [PATCH 069/162] call clearInterval on close remove clearTimeout because never initialized --- client.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client.js b/client.js index b037dad..b4dbfb6 100755 --- a/client.js +++ b/client.js @@ -169,7 +169,7 @@ S101Socket.prototype.connect = function (timeout = 2) { port: self.port, host: self.address, timeout: timeout - }, + }, () => { winston.debug('socket connected'); @@ -213,12 +213,11 @@ S101Socket.prototype.connect = function (timeout = 2) { }); self.socket.on('close', () => { + clearInterval(self.keepaliveIntervalTimer); self.emit('disconnected'); self.status = "disconnected"; self.socket = null; }); - - } S101Socket.prototype.isConnected = function () { @@ -239,7 +238,6 @@ S101Socket.prototype.disconnect = function () { }); self.socket.once('error', reject); clearInterval(self.keepaliveIntervalTimer); - clearTimeout(self._timeout); self.socket.end(); self.status = "disconnected"; } From ae881e9716e9ce85fa3ac6385acda0cd7a284950 Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Wed, 9 Jan 2019 11:37:31 +0100 Subject: [PATCH 070/162] Remove "new Buffer" because it's deprecated Add test to reproduce endless loop without fix. Throw error instead of logging and continuing --- ember.js | 10 +++++----- test/Ember.test.js | 12 +++++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ember.js b/ember.js index b54fcf9..f65ba78 100755 --- a/ember.js +++ b/ember.js @@ -84,7 +84,7 @@ Root.decode = function(ber) { } else if (tag == BER.APPLICATION(23)) { // InvocationResult BER.APPLICATION(23) return InvocationResult.decode(ber) } else { - console.log("unexpected message tag", tag, ber.toString()); + throw new errors.UnimplementedEmberTypeError(tag); } } return r; @@ -1799,7 +1799,7 @@ _Function.decode = function(ber) { _Function.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(19)); - + ber.startSequence(BER.CONTEXT(0)); ber.writeInt(this.number); ber.endSequence(); // BER.CONTEXT(0) @@ -2025,11 +2025,11 @@ Invocation._id = 1 Invocation.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(22)); // ber.startSequence(BER.EMBER_SEQUENCE); - + ber.startSequence(BER.CONTEXT(0)); ber.writeInt(Invocation._id++) ber.endSequence(); - + ber.startSequence(BER.CONTEXT(1)); ber.startSequence(BER.EMBER_SEQUENCE) for(var i =0; i < this.arguments.length; i++) { @@ -2039,7 +2039,7 @@ Invocation.prototype.encode = function(ber) { } ber.endSequence(); ber.endSequence(); - + ber.endSequence(); // BER.APPLICATION(22) } diff --git a/test/Ember.test.js b/test/Ember.test.js index b9b27d3..e1c6e23 100644 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -1,5 +1,10 @@ +const expect = require("expect"); const { S101Client } = require("../index"); -const s101Buffer = new Buffer("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); +const s101Buffer = Buffer.from("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); +const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); +const ember = require("../ember"); +const BER = require('../ber.js'); +const errors = require('../errors.js'); describe("Ember", () => { let client; @@ -19,4 +24,9 @@ describe("Ember", () => { }); client.codec.dataIn(s101Buffer); }); + + it("should handle errors in message", () => { + var ber = new BER.Reader(errorBuffer); + expect(() => ember.Root.decode(ber)).toThrow(errors.UnimplementedEmberTypeError); + }) }); From 1f3cf0f8011e45dc89380098db3339b8e717b2cd Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 9 Jan 2019 16:37:37 +0100 Subject: [PATCH 071/162] Fixing server getDirectory replies --- ember.js | 28 +++++++-- package.json | 2 +- server.js | 21 ++++--- test/Server.test.js | 148 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 17 deletions(-) create mode 100755 test/Server.test.js diff --git a/ember.js b/ember.js index f65ba78..ed0a98e 100755 --- a/ember.js +++ b/ember.js @@ -106,17 +106,15 @@ Root.prototype.addChild = function(child) { Root.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(0)); - if(this.elements !== undefined) { ber.startSequence(BER.APPLICATION(11)); - ber.startSequence(BER.CONTEXT(0)); for(var i=0; i { + console.log("server listening"); + }); + }); + after(function() { + client.disconnect(); + server.close(); + }) + it("should receive and decode the full tree", function () { + client = new DeviceTree(LOCALHOST, PORT); + //client._debug = true; + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + console.log("client connected"); + return client.getDirectory() + }) + .then(() => { + expect(client.root).toBeDefined(); + expect(client.root.elements).toBeDefined(); + expect(client.root.elements.length).toBe(1); + expect(client.root.elements[0].contents.identifier).toBe("scoreMaster"); + return client.getDirectory(client.root.elements[0]); + }) + .then(() => { + expect(client.root.elements[0].children.length).toBe(2); + return client.getDirectory(client.root.elements[0].children[0]); + }) + .then(() => { + expect(client.root.elements[0].children[0].children.length).toBe(4); + expect(client.root.elements[0].children[0].children[3].contents.identifier).toBe("author"); + }); + }); + }); +}); From fecc148e87e5b6e3845da5d110bd9b08da6eb9f1 Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Wed, 16 Jan 2019 14:57:07 +0100 Subject: [PATCH 072/162] Timeout is not taken into account when a endpoint does not respond. Handle timeout event Add test --- client.js | 13 ++++--- test/DeviceTree.test.js | 76 ++++++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/client.js b/client.js index b4dbfb6..cba85d5 100755 --- a/client.js +++ b/client.js @@ -168,7 +168,7 @@ S101Socket.prototype.connect = function (timeout = 2) { self.socket = net.createConnection({ port: self.port, host: self.address, - timeout: timeout + timeout: 1000 * timeout }, () => { winston.debug('socket connected'); @@ -204,15 +204,14 @@ S101Socket.prototype.connect = function (timeout = 2) { } ).on('error', (e) => { self.emit("error", e); - }); - - self.socket.on('data', (data) => { + }).on("timeout", () => { + self.socket.destroy(); + self.emit("error", new Error(`Could not connect to ${self.address}:${self.port} after a timeout of ${timeout} seconds`)); + }).on('data', (data) => { if (self.isConnected()) { self.codec.dataIn(data); } - }); - - self.socket.on('close', () => { + }).on('close', () => { clearInterval(self.keepaliveIntervalTimer); self.emit('disconnected'); self.status = "disconnected"; diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index 21c55a9..c0a7c73 100644 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -4,40 +4,60 @@ const DeviceTree = require("../").DeviceTree; const TreeServer = require("../").TreeServer; const LOCALHOST = "127.0.0.1"; +const UNKNOWN_HOST = "192.168.99.99"; const PORT = 9008; describe("DeviceTree", () => { - let server; - - beforeAll(() => { - return Promise.resolve() - .then(() => new Promise((resolve, reject) => { - fs.readFile("./embrionix.ember", (e, data) => { - if (e) { - reject(e); - } - resolve(Decoder(data)); + describe("With server", () => { + let server; + beforeAll(() => { + return Promise.resolve() + .then(() => new Promise((resolve, reject) => { + fs.readFile("./embrionix.ember", (e, data) => { + if (e) { + reject(e); + } + resolve(Decoder(data)); + }); + })) + .then(root => { + server = new TreeServer(LOCALHOST, PORT, root); + return server.listen(); }); - })) - .then(root => { - server = new TreeServer(LOCALHOST, PORT, root); - return server.listen(); - }); - }); + }); - afterAll(() => server.close()); + afterAll(() => server.close()); - it("should gracefully connect and disconnect", () => { - return Promise.resolve() + it("should gracefully connect and disconnect", () => { + return Promise.resolve() + .then(() => { + let tree = new DeviceTree(LOCALHOST, PORT); + return Promise.resolve() + .then(() => tree.connect()) + .then(() => tree.getDirectory()) + .then(() => tree.disconnect()) + .then(() => tree.connect()) + .then(() => tree.getDirectory()) + .then(() => tree.disconnect()) + }) + }); + }); + + it("timeout should be taken into account when connecting to unknown host", () => { + let tree = new DeviceTree(UNKNOWN_HOST, PORT); + tree.on("error", () => { + }); + const expectedTimeoutInSec = 2; + const initialTime = performance.now(); + return tree.connect(expectedTimeoutInSec) .then(() => { - let tree = new DeviceTree(LOCALHOST, PORT); - return Promise.resolve() - .then(() => tree.connect()) - .then(() => tree.getDirectory()) - .then(() => tree.disconnect()) - .then(() => tree.connect()) - .then(() => tree.getDirectory()) - .then(() => tree.disconnect()) - }) + throw new Error("Should have thrown"); + }, + error => { + const durationMs = performance.now() - initialTime; + const deltaMs = Math.abs(expectedTimeoutInSec * 1000 - durationMs); + expect(deltaMs).toBeLessThan(10); + expect(error.message).toBe(`Could not connect to ${UNKNOWN_HOST}:${PORT} after a timeout of ${expectedTimeoutInSec} seconds`) + }); }); }); From 963026c87cef3641deeada5243f14cedb422feb5 Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Thu, 17 Jan 2019 21:09:37 +0100 Subject: [PATCH 073/162] The socket reported erroneously timeouts once the connection has been established. This was simply due to inactivity on that socket. I disabled the timeout once the connection is established so that the keepalive mechanism of ember can take place. --- client.js | 16 ++++++++++++---- test/DeviceTree.test.js | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/client.js b/client.js index cba85d5..6dc0d8a 100755 --- a/client.js +++ b/client.js @@ -165,6 +165,12 @@ S101Socket.prototype.connect = function (timeout = 2) { self.emit('connecting'); self.codec = new S101Codec(); + + const connectTimeoutListener = () => { + self.socket.destroy(); + self.emit("error", new Error(`Could not connect to ${self.address}:${self.port} after a timeout of ${timeout} seconds`)); + }; + self.socket = net.createConnection({ port: self.port, host: self.address, @@ -173,6 +179,10 @@ S101Socket.prototype.connect = function (timeout = 2) { () => { winston.debug('socket connected'); + // Disable connect timeout to hand-over to keepalive mechanism + self.socket.removeListener("timeout", connectTimeoutListener); + self.socket.setTimeout(0); + self.keepaliveIntervalTimer = setInterval(() => { try { self.sendKeepaliveRequest(); @@ -204,10 +214,8 @@ S101Socket.prototype.connect = function (timeout = 2) { } ).on('error', (e) => { self.emit("error", e); - }).on("timeout", () => { - self.socket.destroy(); - self.emit("error", new Error(`Could not connect to ${self.address}:${self.port} after a timeout of ${timeout} seconds`)); - }).on('data', (data) => { + }).once("timeout", connectTimeoutListener + ).on('data', (data) => { if (self.isConnected()) { self.codec.dataIn(data); } diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index c0a7c73..0c82675 100644 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -41,6 +41,22 @@ describe("DeviceTree", () => { .then(() => tree.disconnect()) }) }); + + it("should not disconnect after 5 seconds of inactivity", () => { + return Promise.resolve() + .then(() => { + let tree = new DeviceTree(LOCALHOST, PORT); + + tree.on("error", error => { + throw error; + }); + + return Promise.resolve() + .then(() => tree.connect()) + .then(() => new Promise(resolve => setTimeout(resolve, 5000))) + .then(() => tree.disconnect()) + }) + }, 7000); }); it("timeout should be taken into account when connecting to unknown host", () => { From 5f6a8e755114d0b9e4278b9d5fa0c848266d71aa Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Thu, 17 Jan 2019 21:17:43 +0100 Subject: [PATCH 074/162] The socket reported erroneously timeouts once the connection has been established. This was simply due to inactivity on that socket. I disabled the timeout once the connection is established so that the keepalive mechanism of ember can take place. --- client.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/client.js b/client.js index 6dc0d8a..a60916a 100755 --- a/client.js +++ b/client.js @@ -186,8 +186,7 @@ S101Socket.prototype.connect = function (timeout = 2) { self.keepaliveIntervalTimer = setInterval(() => { try { self.sendKeepaliveRequest(); - } - catch(e) { + } catch (e) { self.emit("error", e); } }, 1000 * self.keepaliveInterval); @@ -211,20 +210,22 @@ S101Socket.prototype.connect = function (timeout = 2) { }); self.emit('connected'); - } - ).on('error', (e) => { - self.emit("error", e); - }).once("timeout", connectTimeoutListener - ).on('data', (data) => { - if (self.isConnected()) { - self.codec.dataIn(data); - } - }).on('close', () => { - clearInterval(self.keepaliveIntervalTimer); - self.emit('disconnected'); - self.status = "disconnected"; - self.socket = null; - }); + }) + .on('error', (e) => { + self.emit("error", e); + }) + .once("timeout", connectTimeoutListener) + .on('data', (data) => { + if (self.isConnected()) { + self.codec.dataIn(data); + } + }) + .on('close', () => { + clearInterval(self.keepaliveIntervalTimer); + self.emit('disconnected'); + self.status = "disconnected"; + self.socket = null; + }); } S101Socket.prototype.isConnected = function () { From 2b2fa7b0661407d8086bc0c8515b7acd3e82c250 Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Fri, 18 Jan 2019 20:07:04 +0100 Subject: [PATCH 075/162] version 1.7.22 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd1cea6..67899de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.19", + "version": "1.7.22", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 396875de146db2b3fbdf7b0be753771a87c0faff Mon Sep 17 00:00:00 2001 From: Conny Lichtenberg Date: Thu, 28 Feb 2019 16:50:47 +0100 Subject: [PATCH 076/162] Fix parameter toQualified function --- ember.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ember.js b/ember.js index f65ba78..f0dce17 100755 --- a/ember.js +++ b/ember.js @@ -2297,7 +2297,7 @@ Parameter.prototype.setValue = function(value, callback) { } Parameter.prototype.toQualified = function() { - let qp = new QualifiedNode(this.getPath()); + let qp = new QualifiedParameter(this.getPath()); qp.update(this); return qp; } From 1f8269d46af5d9bcb7ef8835795afc58fc66f5a5 Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Wed, 13 Mar 2019 14:09:26 +0100 Subject: [PATCH 077/162] Handle ember node with two roots --- device.js | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/device.js b/device.js index 37e81cf..37711f3 100755 --- a/device.js +++ b/device.js @@ -150,7 +150,7 @@ DeviceTree.prototype.getDirectory = function (qnode) { } self.callback = (error, node) => { - if (node == null) { return; } + if (node == null) { return; } if (error) { if (self._debug) { console.log("Received getDirectory error", error); @@ -160,25 +160,37 @@ DeviceTree.prototype.getDirectory = function (qnode) { reject(error); return; } - let requestedPath = qnode.getPath(); - if (requestedPath === "") { + if (qnode instanceof ember.Root) { if (qnode.elements == null || qnode.elements.length === 0) { if (self._debug) { - console.log("getDirectory response", node); + console.log("getDirectory response", node); } return self.callback(new Error("Invalid qnode for getDirectory")); } - requestedPath = qnode.elements["0"].getPath(); - } - const nodeElements = node == null ? null : node.elements; - if (nodeElements != null - && nodeElements.every(el => el.getPath() === requestedPath || isDirectSubPathOf(el.getPath(), requestedPath))) { - if (self._debug) { - console.log("Received getDirectory response", node); + + const nodeElements = node == null ? null : node.elements; + + if (nodeElements != null + && nodeElements.every(el => el._parent instanceof ember.Root)) { + if (self._debug) { + console.log("Received getDirectory response", node); + } + self.clearTimeout(); // clear the timeout now. The resolve below may take a while. + self.finishRequest(); + resolve(node); // make sure the info is treated before going to next request. + } + } else { + let requestedPath = qnode.getPath(); + const nodeElements = node == null ? null : node.elements; + if (nodeElements != null + && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))) { + if (self._debug) { + console.log("Received getDirectory response", node); + } + self.clearTimeout(); // clear the timeout now. The resolve below may take a while. + self.finishRequest(); + resolve(node); // make sure the info is treated before going to next request. } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(); - resolve(node); // make sure the info is treated before going to next request. } }; From 66578f008ab3f7c127bdcfbb1dc2247deb675a77 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 27 May 2019 14:37:49 +0200 Subject: [PATCH 078/162] Fix: getDirectory on node should auto subscribe to child modification --- ember.js | 4 ++++ package.json | 2 +- server.js | 13 +++++++++++++ test/Server.test.js | 9 +++++---- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ember.js b/ember.js index ed0a98e..e8c4ab3 100755 --- a/ember.js +++ b/ember.js @@ -149,6 +149,10 @@ TreeNode.prototype.addChild = function(child) { this.children.push(child); } +TreeNode.prototype.isNode = function() { + return ((this instanceof Node) || (this instanceof QualifiedNode)); +} + TreeNode.prototype.isMatrix = function() { return ((this instanceof MatrixNode) || (this instanceof QualifiedMatrix)); } diff --git a/package.json b/package.json index 67899de..9909f27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.22", + "version": "1.7.23", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index ad4dff3..2a9c6c3 100755 --- a/server.js +++ b/server.js @@ -382,6 +382,19 @@ TreeServer.prototype.handleGetDirectory = function(client, element) { // report their value changes automatically. this.subscribe(client, element); } + else if (element.isNode()) { + const children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if ((child.isMatrix() || child.isParameter()) && + (!child.isStream())) { + this.subscribe(client, child); + } + } + } + } + let res = this.getQualifiedResponse(element); if (this._debug) { console.log("getDirectory response", res); diff --git a/test/Server.test.js b/test/Server.test.js index 86bc530..de9cc2d 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -4,7 +4,7 @@ const DeviceTree = require("../").DeviceTree; const LOCALHOST = "127.0.0.1"; -const PORT = 9008; +const PORT = 9009; const init = function(_src,_tgt) { @@ -90,7 +90,7 @@ describe("server", function() { describe("JSONtoTree", function() { let jsonTree; - before(function() { + beforeAll(function() { jsonTree = init(); }); it("should generate an ember tree from json", function() { @@ -106,7 +106,7 @@ describe("server", function() { describe("Server - Client communication", function() { let server,client; - before(function() { + beforeAll(function() { jsonTree = init(); const root = TreeServer.JSONtoTree(jsonTree); server = new TreeServer(LOCALHOST, PORT, root); @@ -115,7 +115,7 @@ describe("server", function() { console.log("server listening"); }); }); - after(function() { + afterAll(function() { client.disconnect(); server.close(); }) @@ -142,6 +142,7 @@ describe("server", function() { .then(() => { expect(client.root.elements[0].children[0].children.length).toBe(4); expect(client.root.elements[0].children[0].children[3].contents.identifier).toBe("author"); + expect(server.subscribers["0.0.0"]).toBeDefined(); }); }); }); From 53f6d55b7753d8a8e1ee77d2c47ce712b00521f9 Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Tue, 6 Aug 2019 14:17:53 +0200 Subject: [PATCH 079/162] Fix unhandled exception when socket is disconnected while sending data and the previous command is in timeout --- device.js | 6 +++++- package.json | 3 ++- test/DeviceTree.test.js | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/device.js b/device.js index 37711f3..e0e0180 100755 --- a/device.js +++ b/device.js @@ -278,7 +278,11 @@ DeviceTree.prototype.finishRequest = function () { self.callback = undefined; self.clearTimeout(); self.activeRequest = null; - self.makeRequest(); + try { + self.makeRequest(); + } catch(e) { + console.log("warn:" + e.message) + } }; DeviceTree.prototype.timeoutRequest = function (id) { diff --git a/package.json b/package.json index 67899de..187e18f 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ }, "devDependencies": { "eslint": "^5.5.0", - "jest": "^23.5.0" + "jest": "^23.5.0", + "sinon": "latest" } } diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index 0c82675..7b1920e 100644 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -1,7 +1,9 @@ const fs = require("fs"); +const sinon = require("sinon"); const Decoder = require('../').Decoder; const DeviceTree = require("../").DeviceTree; const TreeServer = require("../").TreeServer; +const S101Client = require("../").S101Client; const LOCALHOST = "127.0.0.1"; const UNKNOWN_HOST = "192.168.99.99"; @@ -9,6 +11,7 @@ const PORT = 9008; describe("DeviceTree", () => { describe("With server", () => { + /** @type {TreeServer} */ let server; beforeAll(() => { return Promise.resolve() @@ -42,6 +45,26 @@ describe("DeviceTree", () => { }) }); + it("should gracefully connect and getDirectory", () => { + let tree = new DeviceTree(LOCALHOST, PORT); + let stub = sinon.stub(tree.client, "sendBER"); + stub.onFirstCall().returns(); + stub.onSecondCall().throws(new Error("blah")); + stub.callThrough(); + return Promise.resolve() + .then(() => tree.connect()) + .then(() => {tree.getDirectory().catch(() => {})}) + .then(() => tree.getDirectory()) + .then(() => { + stub.restore(); + tree.disconnect(); + }, error => { + stub.restore(); + tree.disconnect(); + throw error; + }) + + }, 10000); it("should not disconnect after 5 seconds of inactivity", () => { return Promise.resolve() .then(() => { From 72841a5829aab057326ba894f5d38f2ba2998462 Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Tue, 6 Aug 2019 14:18:31 +0200 Subject: [PATCH 080/162] Fix sinon version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 187e18f..b57c1f4 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,6 @@ "devDependencies": { "eslint": "^5.5.0", "jest": "^23.5.0", - "sinon": "latest" + "sinon": "^7.4.1" } } From 7f99389d047f30af58766b41e648092a781a0040 Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Tue, 6 Aug 2019 14:27:29 +0200 Subject: [PATCH 081/162] remove unused required --- test/DeviceTree.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index 7b1920e..64d83af 100644 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -3,7 +3,6 @@ const sinon = require("sinon"); const Decoder = require('../').Decoder; const DeviceTree = require("../").DeviceTree; const TreeServer = require("../").TreeServer; -const S101Client = require("../").S101Client; const LOCALHOST = "127.0.0.1"; const UNKNOWN_HOST = "192.168.99.99"; From 5f6c99e1bbde65cdfc432bcadba08f3bdded3c5c Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Tue, 13 Aug 2019 10:14:41 +0200 Subject: [PATCH 082/162] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b57c1f4..499a390 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.22", + "version": "1.7.23", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 22c34b8ed3b1e4803b0b411afefe24156887aca6 Mon Sep 17 00:00:00 2001 From: "BIALOBOS, Christophe" Date: Tue, 13 Aug 2019 11:17:54 +0200 Subject: [PATCH 083/162] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 499a390..322bfc7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.23", + "version": "1.7.24", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 78eaf2f802ec6f68012cf6c823ad41b7e1dd56ff Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 1 Oct 2019 08:20:47 +0200 Subject: [PATCH 084/162] Fixing the getNodeByPath function --- .eslintrc.js | 0 .gitignore | 0 LICENSE | 0 README.md | 0 device.js | 1 + ember.js | 12 ++++++++++++ embrionix.ember | Bin errors.js | 0 package.json | 5 +++-- sample.js | 0 test/DeviceTree.test.js | 0 test/Ember.test.js | 0 test/Server.test.js | 36 ++++++++++++++++++++++++++++++++++-- 13 files changed, 50 insertions(+), 4 deletions(-) mode change 100644 => 100755 .eslintrc.js mode change 100644 => 100755 .gitignore mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md mode change 100644 => 100755 embrionix.ember mode change 100644 => 100755 errors.js mode change 100644 => 100755 package.json mode change 100644 => 100755 sample.js mode change 100644 => 100755 test/DeviceTree.test.js mode change 100644 => 100755 test/Ember.test.js diff --git a/.eslintrc.js b/.eslintrc.js old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/device.js b/device.js index 37711f3..3410844 100755 --- a/device.js +++ b/device.js @@ -336,6 +336,7 @@ DeviceTree.prototype.handleQualifiedNode = function (parent, node) { return callbacks; } parent.addChild(node); + callbacks = parent.update(parent); } element = node; } diff --git a/ember.js b/ember.js index e8c4ab3..d33df87 100755 --- a/ember.js +++ b/ember.js @@ -593,6 +593,9 @@ function QualifiedNodeCommand(self, cmd, callback) { } QualifiedNode.prototype.getDirectory = function(callback) { + if(callback !== undefined) { + this._directoryCallbacks.push((error, node) => { callback(error, node) }); + } if (this.path === undefined) { throw new Error("Invalid path"); } @@ -1412,6 +1415,9 @@ function QualifiedMatrixCommand(self, cmd, callback) { } QualifiedMatrix.prototype.getDirectory = function(callback) { + if(callback !== undefined) { + this._directoryCallbacks.push((error, node) => { callback(error, node) }); + } if (this.path === undefined) { throw new Error("Invalid path"); } @@ -1728,6 +1734,9 @@ QualifiedFunction.prototype.invoke = function(params, callback) { } QualifiedFunction.prototype.getDirectory = function(callback) { + if(callback !== undefined) { + this._directoryCallbacks.push((error, node) => { callback(error, node) }); + } if (this.path === undefined) { throw new Error("Invalid path"); } @@ -2208,6 +2217,9 @@ function QualifiedParameterCommand(self, cmd, callback) { } QualifiedParameter.prototype.getDirectory = function(callback) { + if(callback !== undefined) { + this._directoryCallbacks.push((error, node) => { callback(error, node) }); + } if (this.path === undefined) { throw new Error("Invalid path"); } diff --git a/embrionix.ember b/embrionix.ember old mode 100644 new mode 100755 diff --git a/errors.js b/errors.js old mode 100644 new mode 100755 diff --git a/package.json b/package.json old mode 100644 new mode 100755 index 9909f27..0dfc06e --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.23", + "version": "1.7.25", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { @@ -29,6 +29,7 @@ }, "devDependencies": { "eslint": "^5.5.0", - "jest": "^23.5.0" + "jest": "^23.5.0", + "jest-cli": "^24.9.0" } } diff --git a/sample.js b/sample.js old mode 100644 new mode 100755 diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js old mode 100644 new mode 100755 diff --git a/test/Ember.test.js b/test/Ember.test.js old mode 100644 new mode 100755 diff --git a/test/Server.test.js b/test/Server.test.js index de9cc2d..b2e124d 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -6,7 +6,11 @@ const DeviceTree = require("../").DeviceTree; const LOCALHOST = "127.0.0.1"; const PORT = 9009; - +const wait = function(t) { + return new Promise(resolve => { + setTimeout(resolve, t); + }); +} const init = function(_src,_tgt) { const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; @@ -126,7 +130,7 @@ describe("server", function() { .then(() => client.connect()) .then(() => { console.log("client connected"); - return client.getDirectory() + return client.getDirectory(); }) .then(() => { expect(client.root).toBeDefined(); @@ -142,8 +146,36 @@ describe("server", function() { .then(() => { expect(client.root.elements[0].children[0].children.length).toBe(4); expect(client.root.elements[0].children[0].children[3].contents.identifier).toBe("author"); + // Issue #33 TreeServer.handleGetDirectory does not subscribe to child parameters expect(server.subscribers["0.0.0"]).toBeDefined(); + // Keepalive + server.clie }); }); + it("should be able to get child with getNodeByPath", function() { + //server._debug = true; + client = new DeviceTree(LOCALHOST, PORT); + //client._debug = true; + //client._debug = true; + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + console.log("client connected"); + return client.getDirectory(); + }) + .then(() => { + return new Promise((resolve, reject) => { + client.root.getNodeByPath(client.client, ["scoreMaster", "identity", "product"], (err, child) => { + if (err) { reject(err) } + else { + resolve(child); + } + }); + }); + }) + .then(child => { + console.log(child); + }); + }); }); }); From 4ee1b9c02a91f6097a7f2b9f72f00057c0dc0826 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 1 Oct 2019 11:42:17 +0200 Subject: [PATCH 085/162] Fixing package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9419086..152025c 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.24", + "version": "1.7.25", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { @@ -30,7 +30,7 @@ "devDependencies": { "eslint": "^5.5.0", "jest": "^23.5.0", - "jest-cli": "^24.9.0" + "jest-cli": "^24.9.0", "sinon": "^7.4.1" } } From 0b5d3e34a9bdb9790d3dfe47ed078f57d38cd74b Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 1 Oct 2019 11:46:19 +0200 Subject: [PATCH 086/162] Fixing Server test case to exit properly --- test/Server.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Server.test.js b/test/Server.test.js index b2e124d..e4359a4 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -149,7 +149,7 @@ describe("server", function() { // Issue #33 TreeServer.handleGetDirectory does not subscribe to child parameters expect(server.subscribers["0.0.0"]).toBeDefined(); // Keepalive - server.clie + client.disconnect(); }); }); it("should be able to get child with getNodeByPath", function() { From 1c3d45ab15c8fc1789d6ff6ea2447ea7952e8589 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 2 Oct 2019 02:37:20 +0200 Subject: [PATCH 087/162] More tests for getNodeByPath --- ember.js | 2 +- test/Server.test.js | 62 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/ember.js b/ember.js index d33df87..1662095 100755 --- a/ember.js +++ b/ember.js @@ -407,7 +407,7 @@ TreeNode.prototype.getNodeByPath = function(client, path, callback) { } child = node.getElement(path[0]); if(child === null) { - callback('invalid path'); + callback('invalid path: "' + path[0] + '"'); return; } else { child.getNodeByPath(client, path.slice(1), callback); diff --git a/test/Server.test.js b/test/Server.test.js index e4359a4..bf5791f 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -80,7 +80,11 @@ const init = function(_src,_tgt) { // Must be 2 number: 2, children: labels(sources) - } + }, + { + identifier: "group 1", + children: [ {identifier: "sdp A", value: "A"}, {identifier: "sdp B", value: "B"}] + } ] } ] @@ -175,7 +179,61 @@ describe("server", function() { }) .then(child => { console.log(child); + }) + .then(() => { + return new Promise((resolve, reject) => { + client.root.getNodeByPath(client.client, ["scoreMaster", "router", "labels"], (err, child) => { + if (err) { reject(err) } + else { + resolve(child); + } + }); + }); + }) + .then(child => { + console.log(child); + client.disconnect(); + }); + }); + it("should be able to get child with tree.getNodeByPath", function() { + //server._debug = true; + client = new DeviceTree(LOCALHOST, PORT); + //client._debug = true; + //client._debug = true; + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + console.log("client connected"); + return client.getDirectory(); + }) + .then(() => client.getNodeByPath("scoreMaster/identity/product")) + .then(child => { + console.log(child); + return client.getNodeByPath("scoreMaster/router/labels/group 1"); + }) + .then(child => { + console.log("router/labels", child); + client.disconnect(); + }); + }); + it("should throw an erro if getNodeByPath for unknown path", function() { + //server._debug = true; + client = new DeviceTree(LOCALHOST, PORT); + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + console.log("client connected"); + return client.getDirectory(); + }) + .then(() => client.getNodeByPath("scoreMaster/router/labels/group 1")) + .then(child => { + console.log("router/labels", child); + throw new Error("Should not succeed"); + }) + .catch(e => { + client.disconnect(); + expect(e).toMatch(/invalid path/); }); - }); + }); }); }); From e79589a1ecd80d67d15cc06096c96fe6eb1acfe9 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 2 Oct 2019 03:08:25 +0200 Subject: [PATCH 088/162] Fix for gerNodeByPath --- device.js | 19 +++++++++++++++---- ember.js | 5 ++++- test/Server.test.js | 3 ++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/device.js b/device.js index e020b21..277d461 100755 --- a/device.js +++ b/device.js @@ -388,12 +388,12 @@ DeviceTree.prototype.handleNode = function (parent, node) { return callbacks; }; -DeviceTree.prototype.getNodeByPath = function (path) { +DeviceTree.prototype.getNodeByPath = function (path, timeout = 2) { var self = this; if (typeof path === 'string') { path = path.split('/'); } - + var timeoutError = new Error("Request timeout"); return new Promise((resolve, reject) => { self.addRequest((error) => { if (error) { @@ -401,14 +401,25 @@ DeviceTree.prototype.getNodeByPath = function (path) { self.finishRequest(); return; } - self.root.getNodeByPath(self.client, path, (error, node) => { + var timedOut = false; + var cb = (error, node) => { + if (timer) { + clearTimeout(timer); + } + if (timedOut) { return; } if (error) { reject(error); } else { resolve(node); } self.finishRequest(); - }); + }; + var cbTimeout = () => { + timedOut = true; + reject(timeoutError); + } + var timer = timeout === 0 ? null : setTimeout(cbTimeout, timeout * 1000); + self.root.getNodeByPath(self.client, path, cb); }); }); }; diff --git a/ember.js b/ember.js index 1662095..d255b83 100755 --- a/ember.js +++ b/ember.js @@ -407,7 +407,10 @@ TreeNode.prototype.getNodeByPath = function(client, path, callback) { } child = node.getElement(path[0]); if(child === null) { - callback('invalid path: "' + path[0] + '"'); + //let e = new Error('invalid path: "' + path[0] + '"'); + // console.log(e.message ,"Self:", self, "Node:", node); + //callback(e); + //DO NOT REJECT !!!! We could still be updating the tree. return; } else { child.getNodeByPath(client, path.slice(1), callback); diff --git a/test/Server.test.js b/test/Server.test.js index bf5791f..e8f8c4d 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -232,7 +232,8 @@ describe("server", function() { }) .catch(e => { client.disconnect(); - expect(e).toMatch(/invalid path/); + console.log(e); + expect(e.message).toMatch(/timeout/); }); }); }); From f7c8c8da645ab6f7477f74c63f48703eda1e277f Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 5 Dec 2019 12:12:30 +0100 Subject: [PATCH 089/162] adding a new test to show the setValue function --- server.js | 2 +- test/DeviceTree.test.js | 2 +- test/Server.test.js | 109 +++++++++------------------------------- test/utils.js | 88 ++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 88 deletions(-) create mode 100755 test/utils.js diff --git a/server.js b/server.js index 2a9c6c3..c3deb54 100755 --- a/server.js +++ b/server.js @@ -535,7 +535,7 @@ const parseObj = function(parent, obj) { let content = obj[i]; let number = content.number !== undefined ? content.number : i; delete content.number; - if (content.value !== undefined) { + if (content.value !== undefined) { emberElement = new ember.Parameter(number); emberElement.contents = new ember.ParameterContents(content.value); if (content.type) { diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index 64d83af..85a0037 100755 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -29,7 +29,7 @@ describe("DeviceTree", () => { }); afterAll(() => server.close()); - + }); it("should gracefully connect and disconnect", () => { return Promise.resolve() .then(() => { diff --git a/test/Server.test.js b/test/Server.test.js index e8f8c4d..0ee5d87 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -1,7 +1,7 @@ const expect = require("expect"); const TreeServer = require("../server"); const DeviceTree = require("../").DeviceTree; - +const {jsonRoot} = require("./utils"); const LOCALHOST = "127.0.0.1"; const PORT = 9009; @@ -11,95 +11,13 @@ const wait = function(t) { setTimeout(resolve, t); }); } -const init = function(_src,_tgt) { - const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; - const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; - const labels = function(endpoints) { - let labels = []; - for (let i = 0; i < endpoints.length; i++) { - let endpoint = endpoints[i]; - let l = { identifier: `Label-${i}` }; - if (endpoint) { - l.value = endpoint; - } - labels.push(l); - } - return labels; - }; - - const buildConnections = function(s, t) { - let connections = []; - for (let i = 0; i < t.length; i++) { - connections.push({target: `${i}`}); - } - return connections; - }; - return [ - { - // path "0" - identifier: "scoreMaster", - children: [ - { - // path "0.0" - identifier: "identity", - children: [ - {identifier: "product", value: "S-CORE Master"}, - {identifier: "company", value: "EVS"}, - {identifier: "version", value: "1.2.0"}, - {identifier: "author", value: "g.dufour@evs.com"} - ] - }, - { - // path "0.1" - identifier: "router", - children: [ - { - // path 0.1.0 - identifier: "matrix", - type: "oneToN", - mode: "linear", - targetCount: targets.length, - sourceCount: sources.length, - connections: buildConnections(sources, targets), - labels: ["0.1.1000"] - }, - { - identifier: "labels", - // path "0.1.1000" - number: 1000, - children: [ - { - identifier: "targets", - // Must be 1 - number: 1, - children: labels(targets) - }, - { - identifier: "sources", - // Must be 2 - number: 2, - children: labels(sources) - }, - { - identifier: "group 1", - children: [ {identifier: "sdp A", value: "A"}, {identifier: "sdp B", value: "B"}] - } - ] - } - ] - } - ] - } - ]; - -} describe("server", function() { describe("JSONtoTree", function() { let jsonTree; beforeAll(function() { - jsonTree = init(); + jsonTree = jsonRoot(); }); it("should generate an ember tree from json", function() { const root = TreeServer.JSONtoTree(jsonTree); @@ -115,7 +33,7 @@ describe("server", function() { describe("Server - Client communication", function() { let server,client; beforeAll(function() { - jsonTree = init(); + jsonTree = jsonRoot(); const root = TreeServer.JSONtoTree(jsonTree); server = new TreeServer(LOCALHOST, PORT, root); //server._debug = true; @@ -153,9 +71,28 @@ describe("server", function() { // Issue #33 TreeServer.handleGetDirectory does not subscribe to child parameters expect(server.subscribers["0.0.0"]).toBeDefined(); // Keepalive - client.disconnect(); + return client.disconnect(); + }); + }); + it("should be able to modify a parameter", () => { + client = new DeviceTree(LOCALHOST, PORT); + //client._debug = true; + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + return client.getDirectory(); + }) + .then(() => client.expand(client.root.elements[0])) + .then(() => { + expect(server.tree.elements[0].children[0].children[1].contents.value).not.toBe("gdnet"); + return client.setValue(client.root.elements[0].children[0].children[1], "gdnet"); + }) + .then(() => { + expect(server.tree.elements[0].children[0].children[1].contents.value).toBe("gdnet"); + return client.disconnect(); }); }); + it("should be able to get child with getNodeByPath", function() { //server._debug = true; client = new DeviceTree(LOCALHOST, PORT); diff --git a/test/utils.js b/test/utils.js new file mode 100755 index 0000000..61063e9 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,88 @@ +const {ParameterAccess} = require("../ember"); + +const init = function(_src,_tgt) { + const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; + const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; + const labels = function(endpoints) { + let labels = []; + for (let i = 0; i < endpoints.length; i++) { + let endpoint = endpoints[i]; + let l = { identifier: `Label-${i}` }; + if (endpoint) { + l.value = endpoint; + } + labels.push(l); + } + return labels; + }; + + const buildConnections = function(s, t) { + let connections = []; + for (let i = 0; i < t.length; i++) { + connections.push({target: `${i}`}); + } + return connections; + }; + + return [ + { + // path "0" + identifier: "scoreMaster", + children: [ + { + // path "0.0" + identifier: "identity", + children: [ + {identifier: "product", value: "S-CORE Master"}, + {identifier: "company", value: "EVS", access: "readWrite"}, + {identifier: "version", value: "1.2.0"}, + {identifier: "author", value: "g.dufour@evs.com"}, + ] + }, + { + // path "0.1" + identifier: "router", + children: [ + { + // path 0.1.0 + identifier: "matrix", + type: "oneToN", + mode: "linear", + targetCount: targets.length, + sourceCount: sources.length, + connections: buildConnections(sources, targets), + labels: ["0.1.1000"] + }, + { + identifier: "labels", + // path "0.1.1000" + number: 1000, + children: [ + { + identifier: "targets", + // Must be 1 + number: 1, + children: labels(targets) + }, + { + identifier: "sources", + // Must be 2 + number: 2, + children: labels(sources) + }, + { + identifier: "group 1", + children: [ {identifier: "sdp A", value: "A"}, {identifier: "sdp B", value: "B"}] + } + ] + } + ] + } + ] + } + ]; +} + +module.exports = { + jsonRoot: init +} \ No newline at end of file From 685c782e33a5c92898ef479bb76c82dcc69e1407 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 6 Dec 2019 12:41:51 +0100 Subject: [PATCH 090/162] Fixed client timeout on matrix expand. Fixed matrix error on connect. Add test on matrix --- device.js | 100 ++++++++++++++++++++++++++------------------ ember.js | 58 ++++++++++++++----------- package.json | 2 +- server.js | 8 ++-- test/Server.test.js | 34 +++++++++++++++ test/utils.js | 4 +- 6 files changed, 134 insertions(+), 72 deletions(-) diff --git a/device.js b/device.js index 277d461..56d7f82 100755 --- a/device.js +++ b/device.js @@ -42,20 +42,29 @@ function DeviceTree(host, port = 9000) { }); self.client.on('emberTree', (root) => { - if (root instanceof ember.InvocationResult) { - self.emit('invocationResult', root); - if (self._debug) { - console.log("Received InvocationResult", root); + try { + if (root instanceof ember.InvocationResult) { + self.emit('invocationResult', root); + if (self._debug) { + console.log("Received InvocationResult", root); + } + } else { + self.handleRoot(root); + if (self._debug) { + console.log("Received root", root); + } } - } else { - self.handleRoot(root); - if (self._debug) { - console.log("Received root", root); + if (self.callback) { + self.callback(undefined, root); } } - - if (self.callback) { - self.callback(undefined, root); + catch(e) { + if (self._debug) { + console.log(e, root); + } + if (self.callback) { + self.callback(e); + } } }); } @@ -99,9 +108,9 @@ DeviceTree.prototype.expand = function (node) { if (node == null) { return Promise.reject(new Error("Invalid null node")); } - if (node.isParameter()) { + if (node.isParameter() || node.isMatrix()) { return self.getDirectory(node); - } + } return self.getDirectory(node).then((res) => { let children = node.getChildren(); if ((res === undefined) || (children === undefined) || (children === null)) { @@ -142,7 +151,7 @@ DeviceTree.prototype.getDirectory = function (qnode) { qnode = self.root; } return new Promise((resolve, reject) => { - self.addRequest((error) => { + self.addRequest({node: qnode, func: (error) => { if (error) { self.finishRequest(); reject(error); @@ -150,7 +159,13 @@ DeviceTree.prototype.getDirectory = function (qnode) { } self.callback = (error, node) => { - if (node == null) { return; } + const requestedPath = qnode.getPath(); + if (node == null) { + if (self._debug) { + console.log(`received null response for ${requestedPath}`); + } + return; + } if (error) { if (self._debug) { console.log("Received getDirectory error", error); @@ -179,11 +194,14 @@ DeviceTree.prototype.getDirectory = function (qnode) { self.finishRequest(); resolve(node); // make sure the info is treated before going to next request. } - } else { - let requestedPath = qnode.getPath(); + else { + return self.callback(new Error(`Invalid response for getDirectory ${requestedPath}`)); + } + } else { const nodeElements = node == null ? null : node.elements; - if (nodeElements != null - && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))) { + if (nodeElements != null && + ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || + (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { if (self._debug) { console.log("Received getDirectory response", node); } @@ -191,6 +209,10 @@ DeviceTree.prototype.getDirectory = function (qnode) { self.finishRequest(); resolve(node); // make sure the info is treated before going to next request. } + else if (self._debug) { + console.log(node); + console.log(new Error(requestedPath)); + } } }; @@ -198,14 +220,14 @@ DeviceTree.prototype.getDirectory = function (qnode) { console.log("Sending getDirectory", qnode); } self.client.sendBERNode(qnode.getDirectory()); - }); + }}); }); }; DeviceTree.prototype.invokeFunction = function (fnNode, params) { var self = this; return new Promise((resolve, reject) => { - self.addRequest((error) => { + self.addRequest({node: fnNode, func: (error) => { if (error) { reject(error); self.finishRequest(); @@ -231,7 +253,7 @@ DeviceTree.prototype.invokeFunction = function (fnNode, params) { } self.callback = cb; self.client.sendBERNode(fnNode.invoke(params)); - }); + }}); }) }; @@ -247,22 +269,26 @@ DeviceTree.prototype.makeRequest = function () { self.activeRequest = self.pendingRequests.shift(); const t = function (id) { - if (self._debug) { - console.log(`Making request ${id}`, Date.now()); + var path = self.activeRequest.path == null ? + self.activeRequest.node.getPath() : + self.activeRequest.path; + var req = `${id} - ${path}`; + if (self._debug) { + console.log(`Making request ${req}`, Date.now()); } self.timeout = setTimeout(() => { - self.timeoutRequest(id); + self.timeoutRequest(req); }, self.timeoutValue); }; t(self.requestID++); - self.activeRequest(); + self.activeRequest.func(); } }; -DeviceTree.prototype.addRequest = function (cb) { +DeviceTree.prototype.addRequest = function (req) { var self = this; - self.pendingRequests.push(cb); + self.pendingRequests.push(req); self.makeRequest(); }; @@ -288,7 +314,7 @@ DeviceTree.prototype.finishRequest = function () { DeviceTree.prototype.timeoutRequest = function (id) { var self = this; self.root.cancelCallbacks(); - self.activeRequest(new errors.EmberTimeoutError(`Request ${id !== undefined ? id : ""} timed out`)); + self.activeRequest.func(new errors.EmberTimeoutError(`Request ${id !== undefined ? id : ""} timed out`)); }; DeviceTree.prototype.handleRoot = function (root) { @@ -319,15 +345,12 @@ DeviceTree.prototype.handleRoot = function (root) { DeviceTree.prototype.handleQualifiedNode = function (parent, node) { var self = this; var callbacks = []; - //console.log(`handling element with a path ${node.path}`); var element = parent.getElementByPath(node.path); if (element !== null) { - //console.log("Found element", JSON.stringify(element)); self.emit("value-change", node); callbacks = element.update(node); } else { - //console.log("new element", JSON.stringify(node)); var path = node.path.split("."); if (path.length === 1) { this.root.addChild(node); @@ -357,8 +380,6 @@ DeviceTree.prototype.handleQualifiedNode = function (parent, node) { } } - //callbacks = parent.update(); - return callbacks; }; @@ -366,7 +387,7 @@ DeviceTree.prototype.handleNode = function (parent, node) { var self = this; var callbacks = []; - var n = parent.getElementByNumber(node.number); + var n = parent.getElementByNumber(node.getNumber()); if (n === null) { parent.addChild(node); n = node; @@ -384,7 +405,6 @@ DeviceTree.prototype.handleNode = function (parent, node) { self.emit("value-change", node); } - //console.log('handleNode: ', callbacks); return callbacks; }; @@ -395,7 +415,7 @@ DeviceTree.prototype.getNodeByPath = function (path, timeout = 2) { } var timeoutError = new Error("Request timeout"); return new Promise((resolve, reject) => { - self.addRequest((error) => { + self.addRequest({path: path, func: (error) => { if (error) { reject(error); self.finishRequest(); @@ -420,7 +440,7 @@ DeviceTree.prototype.getNodeByPath = function (path, timeout = 2) { } var timer = timeout === 0 ? null : setTimeout(cbTimeout, timeout * 1000); self.root.getNodeByPath(self.client, path, cb); - }); + }}); }); }; @@ -449,7 +469,7 @@ DeviceTree.prototype.setValue = function (node, value) { } else { // if (this._debug) { console.log('setValue', node.getPath(), value); } - self.addRequest((error) => { + self.addRequest({node: node, func: (error) => { if (error) { self.finishRequest(); reject(error); @@ -472,7 +492,7 @@ DeviceTree.prototype.setValue = function (node, value) { console.log('setValue sending ...', node.getPath(), value); } self.client.sendBERNode(node.setValue(value)); - }); + }}); } }); }; diff --git a/ember.js b/ember.js index d255b83..6d6cb73 100755 --- a/ember.js +++ b/ember.js @@ -282,6 +282,26 @@ TreeNode.prototype.getChildren = function() { return null; } +function path2number(path) { + try { + let numbers = path.split("."); + if (numbers.length > 0) { + return Number(numbers[numbers.length - 1]); + } + } + catch(e) { + // ignore + } +} + +TreeNode.prototype.getNumber = function() { + if (this.isQualified()) { + return path2number(this.getPath()); + } + else { + return this.number; + } +} _getElementByPath = function(children, pathArray, path) { if ((children === null)||(children === undefined)||(pathArray.length < 1)) { @@ -338,7 +358,7 @@ TreeNode.prototype.getElementByNumber = function(index) { var children = this.getChildren(); if(children === null) return null; for(var i=0; i 0) { - return Number(numbers[numbers.length - 1]); - } - } - catch(e) { - // ignore - } -} - QualifiedNode.prototype.getMinimal = function(complete = false) { - let number = path2number(this.path); - let n = new Node(number); + const number = this.getNumber(); + const n = new Node(number); if (complete && (this.contents != null)) { n.contents = this.contents; } @@ -1298,8 +1306,8 @@ function QualifiedMatrix(path) { util.inherits(QualifiedMatrix, TreeNode); QualifiedMatrix.prototype.getMinimal = function(complete = false) { - let number = path2number(this.path); - let m = new MatrixNode(number); + const number = this.getNumber(); + const m = new MatrixNode(number); if (complete) { if (this.contents != null) { m.contents = this.contents; @@ -1360,8 +1368,8 @@ QualifiedMatrix.decode = function(ber) { } function MatrixUpdate(matrix, newMatrix) { - if (newMatrix !== undefined) { - if (newMatrix.contents !== undefined) { + if (newMatrix != null) { + if (newMatrix.contents != null) { if (matrix.contents == null) { matrix.contents = newMatrix.contents; } @@ -1373,14 +1381,14 @@ function MatrixUpdate(matrix, newMatrix) { } } } - if (newMatrix.targets !== undefined) { + if (newMatrix.targets != null) { matrix.targets = newMatrix.targets; } - if (newMatrix.sources !== undefined) { + if (newMatrix.sources != null) { matrix.sources = newMatrix.sources; } - if (newMatrix.connections !== undefined) { - if (matrix.connections === undefined) { + if (newMatrix.connections != null) { + if (matrix.connections == null) { matrix.connections = {}; } for(let id in newMatrix.connections) { @@ -2125,8 +2133,8 @@ util.inherits(QualifiedParameter, TreeNode); module.exports.QualifiedParameter = QualifiedParameter; QualifiedParameter.prototype.getMinimal = function(complete = false) { - let number = path2number(this.path); - let p = new Parameter(number); + const number = this.getNumber(); + const p = new Parameter(number); if (complete) { if (this.contents != null) { p = this.contents; diff --git a/package.json b/package.json index 152025c..a7097a1 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.25", + "version": "1.7.26", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index c3deb54..ccdf71b 100755 --- a/server.js +++ b/server.js @@ -225,15 +225,15 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti var root; // ember message root if (matrix.isQualified()) { root = new ember.Root(); - res = new ember.QualifiedMatrix(matrix.path); + res = new ember.QualifiedMatrix(matrix.getPath()); root.elements = [res]; // do not use addchild or the element will get removed from the tree. } else { res = new ember.MatrixNode(matrix.number); root = matrix._parent.getTreeBranch(res); - } + } res.connections = {}; - for(let id in connections) { + for(let id in connections) { if (!connections.hasOwnProperty(id)) { continue; } @@ -241,7 +241,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti let conResult = new ember.MatrixConnection(connection.target); let emitType; res.connections[connection.target] = conResult; - + // Apply changes diff --git a/test/Server.test.js b/test/Server.test.js index 0ee5d87..ed44416 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -1,6 +1,7 @@ const expect = require("expect"); const TreeServer = require("../server"); const DeviceTree = require("../").DeviceTree; +const ember = require("../ember"); const {jsonRoot} = require("./utils"); const LOCALHOST = "127.0.0.1"; @@ -92,6 +93,39 @@ describe("server", function() { return client.disconnect(); }); }); + it("should be able to make a matrix connection", () => { + client = new DeviceTree(LOCALHOST, PORT); + //client._debug = true; + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + return client.getDirectory(); + }) + .then(() => client.expand(client.root.elements[0])) + .then(() => { + console.log(client.root.elements[0].children[1].children[0]); + const matrix = client.root.elements[0].children[1].children[0]; + const connections = {} + const target0Connection = new ember.MatrixConnection(0); + target0Connection.operation = ember.MatrixOperation.connect; + target0Connection.setSources([1]); // connect with src 1 + connections[0] = target0Connection; + const p = new Promise(resolve => { + client.on("value-change", node => { + resolve(node); + }); + }); + client.client.sendBERNode(matrix.connect(connections)); + return p; + }) + .then(node => { + console.log(client.root.elements[0].children[1].children[0]); + expect(client.root.elements[0].children[1].children[0].connections['0'].sources).toBeDefined(); + expect(client.root.elements[0].children[1].children[0].connections['0'].sources.length).toBe(1); + expect(client.root.elements[0].children[1].children[0].connections['0'].sources[0]).toBe(1); + return client.disconnect(); + }); + }); it("should be able to get child with getNodeByPath", function() { //server._debug = true; diff --git a/test/utils.js b/test/utils.js index 61063e9..74b6eb9 100755 --- a/test/utils.js +++ b/test/utils.js @@ -71,9 +71,9 @@ const init = function(_src,_tgt) { children: labels(sources) }, { - identifier: "group 1", + identifier: "group 1", children: [ {identifier: "sdp A", value: "A"}, {identifier: "sdp B", value: "B"}] - } + } ] } ] From 8656805f0afd0d8fddbce0f5d1926f41829d5b5c Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 8 Dec 2019 12:15:33 +0100 Subject: [PATCH 091/162] Fixing function invocation --- device.js | 2 +- ember.js | 233 ++++++++++++++++++++++++++++++++++++------------- package.json | 2 +- server.js | 56 +----------- test/client.js | 21 +++++ 5 files changed, 198 insertions(+), 116 deletions(-) create mode 100755 test/client.js diff --git a/device.js b/device.js index 56d7f82..a238066 100755 --- a/device.js +++ b/device.js @@ -108,7 +108,7 @@ DeviceTree.prototype.expand = function (node) { if (node == null) { return Promise.reject(new Error("Invalid null node")); } - if (node.isParameter() || node.isMatrix()) { + if (node.isParameter() || node.isMatrix() || node.isFunction()) { return self.getDirectory(node); } return self.getDirectory(node).then((res) => { diff --git a/ember.js b/ember.js index 6d6cb73..3e594bb 100755 --- a/ember.js +++ b/ember.js @@ -45,7 +45,7 @@ Root.decode = function(ber) { console.log("Application 0 start"); } - if (tag == BER.APPLICATION(11)) { + if (tag === BER.APPLICATION(11)) { if (DEBUG) { console.log("Application 11 start"); } @@ -66,7 +66,11 @@ Root.decode = function(ber) { throw e; } } - } else { + } + else if (tag === BER.APPLICATION(23)) { // InvocationResult BER.APPLICATION(23) + return InvocationResult.decode(ber) + } + else { // StreamCollection BER.APPLICATION(6) // InvocationResult BER.APPLICATION(23) throw new errors.UnimplementedEmberTypeError(tag); @@ -81,9 +85,8 @@ Root.decode = function(ber) { console.log(e.stack); return r; } - } else if (tag == BER.APPLICATION(23)) { // InvocationResult BER.APPLICATION(23) - return InvocationResult.decode(ber) - } else { + } + else { throw new errors.UnimplementedEmberTypeError(tag); } } @@ -324,6 +327,61 @@ _getElementByPath = function(children, pathArray, path) { return null; } +TreeNode.prototype.toJSON = function() { + let res = {}; + const node = this; + if (node.number) { + res.number = node.number + } + if (node.path) { + res.path = node.path; + } + if (node.contents) { + for(let prop in node.contents) { + if (node.contents.hasOwnProperty(prop)) { + let type = typeof node.contents[prop]; + if ((type === "string") || (type === "number")) { + res[prop] = node.contents[prop]; + } + else if (node.contents[prop].value !== undefined) { + res[prop] = node.contents[prop].value; + } + else { + res[prop] = node.contents[prop]; + } + } + } + } + if (node.isMatrix()) { + if (node.targets) { + res.targets = node.targets.slice(0); + } + if (node.sources) { + res.sources = node.sources.slice(0); + } + if (node.connections) { + res.connections = {}; + for (let target in node.connections) { + if (node.connections.hasOwnProperty(target)) { + res.connections[target] = {target: target, sources: []}; + if (node.connections[target].sources) { + res.connections[target].sources = node.connections[target].sources.slice(0); + } + } + } + + } + } + let children = node.getChildren(); + if (children) { + res.children = []; + for(let child of children) { + res.children.push(child.toJSON()); + } + } + return res; +}; + TreeNode.prototype.getElementByPath = function(path) { var children = this.getChildren(); if ((children === null)||(children === undefined)) { @@ -506,7 +564,7 @@ Element.decode = function(ber) { } else if(tag == BER.APPLICATION(19)) { if (DEBUG) { console.log("Function decode");} - return _Function.decode(ber); + return Function.decode(ber); } else if (tag == BER.APPLICATION(20)) { if (DEBUG) { console.log("QualifiedFunction decode");} return QualifiedFunction.decode(ber); @@ -1543,44 +1601,76 @@ QualifiedMatrix.prototype.encode = function(ber) { module.exports.QualifiedMatrix = QualifiedMatrix; -/**************************************************************************** - * FunctionContent - ***************************************************************************/ -function FunctionContent() { +/********************************* + * FunctionArgument + ********************************/ +/* +TupleDescription ::= + SEQUENCE OF [0] TupleItemDescription +TupleItemDescription ::= + [APPLICATION 21] IMPLICIT + SEQUENCE { + type [0] ParameterType, + name [1] EmberString OPTIONAL + } +Invocation ::= + [APPLICATION 22] IMPLICIT + SEQUENCE { + invocationId [0] Integer32 OPTIONAL, + arguments [1] Tuple OPTIONAL + } +Tuple ::= + SEQUENCE OF [0] Value +*/ + +function FunctionArgument(type = null, value = null) { + /** @type {ParameterType} */ + this.type = type; + this.value = value; +} + +FunctionArgument.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(21)); + if (this.type != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.type.value); + ber.endSequence(); + } + if (this.name != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.name); + ber.endSequence(); + } + ber.endSequence(); } -decodeTupleDescription = function(ber) { - var tuple = {}; +FunctionArgument.decode = function(ber) { + var tuple = new FunctionArgument(); ber = ber.getSequence(BER.APPLICATION(21)); while(ber.remain > 0) { tag = ber.peek(); var seq = ber.getSequence(tag); if (tag === BER.CONTEXT(0)) { - tuple.type = seq.readInt(); + tuple.type = ParameterType.get(seq.readInt()); } else if (tag === BER.CONTEXT(1)) { tuple.name = seq.readString(BER.EMBER_STRING); } } return tuple; -}; +} -encodeTupleDescription = function(tuple, ber) { - ber.startSequence(BER.APPLICATION(21)); - if (tuple.type !== undefined) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(tuple.type); - ber.endSequence(); - } - if (tuple.name !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(tuple.name); - ber.endSequence(); - } - ber.endSequence(); +module.exports.FunctionArgument = FunctionArgument; + +/**************************************************************************** + * FunctionContent + ***************************************************************************/ + +function FunctionContent() { } + FunctionContent.decode = function(ber) { var fc = new FunctionContent(); ber = ber.getSequence(BER.EMBER_SET); @@ -1598,7 +1688,7 @@ FunctionContent.decode = function(ber) { tag = seq.peek(); var dataSeq = seq.getSequence(BER.CONTEXT(0)); if (tag === BER.CONTEXT(0)) { - fc.arguments.push(decodeTupleDescription(dataSeq)); + fc.arguments.push(FunctionArgument.decode(dataSeq)); } } } else if(tag == BER.CONTEXT(3)) { @@ -1607,7 +1697,7 @@ FunctionContent.decode = function(ber) { tag = seq.peek(); var dataSeq = seq.getSequence(tag); if (tag === BER.CONTEXT(0)) { - fc.result.push(decodeTupleDescription(dataSeq)); + fc.result.push(FunctionArgument.decode(dataSeq)); } } } else if(tag == BER.CONTEXT(4)) { @@ -1623,36 +1713,34 @@ FunctionContent.decode = function(ber) { FunctionContent.prototype.encode = function(ber) { ber.startSequence(BER.EMBER_SET); - if(this.identifier !== undefined) { + if(this.identifier != null) { ber.startSequence(BER.CONTEXT(0)); ber.writeString(this.identifier, BER.EMBER_STRING); ber.endSequence(); // BER.CONTEXT(0) } - if(this.description !== undefined) { + if(this.description != null) { ber.startSequence(BER.CONTEXT(1)); ber.writeString(this.description, BER.EMBER_STRING); ber.endSequence(); // BER.CONTEXT(1) } - if(this.arguments !== undefined) { + if(this.arguments != null) { ber.startSequence(BER.CONTEXT(2)); ber.startSequence(BER.EMBER_SEQUENCE); for(var i =0; i < this.arguments.length; i++) { - ber.startSequence(BER.CONTEXT(0)); - encodeTupleDescription(this.arguments[i], ber); - ber.endSequence(); + this.arguments[i].encode(ber); } ber.endSequence(); ber.endSequence(); // BER.CONTEXT(2) } - if(this.result !== undefined) { + if(this.result != null) { ber.startSequence(BER.CONTEXT(3)); ber.startSequence(BER.EMBER_SEQUENCE); for(var i =0; i < this.result; i++) { ber.startSequence(BER.CONTEXT(0)); - encodeTupleDescription(this.result[i], ber); + this.result[i].encode(ber); ber.endSequence(); } ber.endSequence(); @@ -1803,16 +1891,16 @@ module.exports.QualifiedFunction = QualifiedFunction; ***************************************************************************/ -function _Function(number) { - _Function.super_.call(this); +function Function(number) { + Function.super_.call(this); if(number !== undefined) this.number = number; }; -util.inherits(_Function, TreeNode); +util.inherits(Function, TreeNode); -_Function.decode = function(ber) { - var f = new _Function(); +Function.decode = function(ber) { + var f = new Function(); ber = ber.getSequence(BER.APPLICATION(19)); while(ber.remain > 0) { @@ -1833,11 +1921,11 @@ _Function.decode = function(ber) { throw new errors.UnimplementedEmberTypeError(tag); } } - if (DEBUG) { console.log("_Function", f); } + if (DEBUG) { console.log("Function", f); } return f; } -_Function.prototype.encode = function(ber) { +Function.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(19)); ber.startSequence(BER.CONTEXT(0)); @@ -1865,10 +1953,10 @@ _Function.prototype.encode = function(ber) { ber.endSequence(); // BER.APPLICATION(19) } -module.exports._Function = _Function; +module.exports.Function = Function; -_Function.prototype.invoke = function(callback) { +Function.prototype.invoke = function(callback) { if(callback !== undefined) { this._directoryCallbacks.push((error, node) => { callback(error, node) }); } @@ -2028,9 +2116,13 @@ module.exports.Command = Command; /**************************************************************************** * Invocation ***************************************************************************/ -function Invocation() { +function Invocation(id = null) { + this.id = id == null ? Invocation._id++ : id; + this.arguments = []; } +Invocation._id = 1 + Invocation.prototype.decode = function(ber) { let invocation = new Invocation(); ber = ber.getSequence(BER.APPLICATION(22)); @@ -2042,13 +2134,9 @@ Invocation.prototype.decode = function(ber) { } if(tag == BER.CONTEXT(1)) { invocation.arguments = []; - let seq = ber.getSequence(BER.EMBER_SEQUENCE); + const seq = ber.getSequence(BER.EMBER_SEQUENCE); while(seq.remain > 0) { - tag = seq.peek(); - var dataSeq = seq.getSequence(BER.CONTEXT(0)); - if (tag === BER.CONTEXT(0)) { - invocation.arguments.push(dataSeq.readValue()); - } + invocation.arguments.push(FunctionArgument.decode(seq)); } } else { @@ -2060,21 +2148,23 @@ Invocation.prototype.decode = function(ber) { return invocation; } -Invocation._id = 1 Invocation.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(22)); // ber.startSequence(BER.EMBER_SEQUENCE); ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(Invocation._id++) + ber.writeInt(this.id) ber.endSequence(); ber.startSequence(BER.CONTEXT(1)); - ber.startSequence(BER.EMBER_SEQUENCE) - for(var i =0; i < this.arguments.length; i++) { - ber.startSequence(BER.CONTEXT(0)) - ber.writeValue(this.arguments[i]) + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i = 0; i < this.arguments.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeValue( + this.arguments[i].value, + ParameterTypetoBERTAG(this.arguments[i].type + )); ber.endSequence(); } ber.endSequence(); @@ -2370,6 +2460,19 @@ var ParameterAccess = new Enum({ readWrite: 3 }); + +/* +BER VAlue +Value ::= + CHOICE { + integer Integer64, + real REAL, + string EmberString, + boolean BOOLEAN, + octets OCTET STRING, + null NULL + }*/ + var ParameterType = new Enum({ integer: 1, real: 2, @@ -2380,6 +2483,18 @@ var ParameterType = new Enum({ octets: 7 }); +function ParameterTypetoBERTAG(type) { + switch (type.value) { + case 1: return BER.EMBER_INTEGER; + case 2: return BER.EMBER_REAL; + case 3: return BER.EMBER_STRING; + case 4: return BER.EMBER_BOOLEAN; + case 7: return BER.EMBER_OCTETSTRING; + default: + throw new Error(`Unhandled ParameterType ${type}`); + } +} + module.exports.ParameterAccess = ParameterAccess; module.exports.ParameterType = ParameterType; diff --git a/package.json b/package.json index a7097a1..270514d 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.7.26", + "version": "1.8.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index ccdf71b..8bdfee5 100755 --- a/server.js +++ b/server.js @@ -600,66 +600,12 @@ TreeServer.JSONtoTree = function(obj) { return tree; } -const toJSON = function(node) { - let res = {}; - - if (node.number) { - res.number = node.number - } - if (node.path) { - res.path = node.path; - } - if (node.contents) { - for(let prop in node.contents) { - if (node.contents.hasOwnProperty(prop)) { - let type = typeof node.contents[prop]; - if ((type === "string") || (type === "number")) { - res[prop] = node.contents[prop]; - } - else if (node.contents[prop].value !== undefined) { - res[prop] = node.contents[prop].value; - } - else { - res[prop] = node.contents[prop]; - } - } - } - } - if (node.isMatrix()) { - if (node.targets) { - res.targets = node.targets.slice(0); - } - if (node.sources) { - res.sources = node.sources.slice(0); - } - if (node.connections) { - res.connections = {}; - for (let target in connections) { - if (connections.hasOwnProperty(target)) { - res.connections[target] = {target: target, sources: []}; - if (connections[target].sources) { - res.connections[target].sources = connections[target].sources.slice(0); - } - } - } - - } - } - let children = node.getChildren(); - if (children) { - res.children = []; - for(let child of children) { - res.children.push(toJSON(child)); - } - } - return res; -}; TreeServer.prototype.toJSON = function() { if ((!this.tree) || (!this.tree.elements) || (this.tree.elements.length == 0)) { return []; } - return [].push(toJSON(this.tree.elements[0])); + return [].push(this.tree.elements[0].toJSON()); }; module.exports = TreeServer; diff --git a/test/client.js b/test/client.js new file mode 100755 index 0000000..880e0cc --- /dev/null +++ b/test/client.js @@ -0,0 +1,21 @@ +const DeviceTree = require("../").DeviceTree; +const ember = require("../ember"); + +const HOST = "192.168.4.4"; +const PORT = 9092; + +client = new DeviceTree(HOST, PORT); +client.connect() +.then(() => client.getDirectory()) +.then(() => client.expand(client.root.elements[0])) +.then(() => { + //console.log(client.root.elements[0].children[4].children[2].toJSON()); + console.log(JSON.stringify(client.root.elements[0].children[4].toJSON(), null, 4)); + return client.invokeFunction(client.root.elements[0].children[4].children[0], [ + new ember.FunctionArgument(ember.ParameterType.integer, 1), + new ember.FunctionArgument(ember.ParameterType.integer, 7) + ]); +}) +.then(result => { + console.log(result); +}); From 9f8e9c8d731c6f4a7464b7dc1f68aa08bd66385f Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 9 Dec 2019 11:41:36 +0100 Subject: [PATCH 092/162] Added supported for Function on server side. Fixed invokationresult decoding --- ember.js | 169 ++++++++++++++++++++++++++++++++++++-------- package.json | 7 +- server.js | 90 +++++++++++++++++------ test/Server.test.js | 29 +++++++- test/client.js | 21 ------ test/utils.js | 24 ++++++- 6 files changed, 261 insertions(+), 79 deletions(-) delete mode 100755 test/client.js diff --git a/ember.js b/ember.js index 3e594bb..d82292d 100755 --- a/ember.js +++ b/ember.js @@ -101,7 +101,9 @@ Root.prototype.addElement = function(ele) { this.elements.push(ele); } - +Root.prototype.addResult = function(result) { + this.result = result; +} Root.prototype.addChild = function(child) { this.addElement(child); @@ -109,7 +111,7 @@ Root.prototype.addChild = function(child) { Root.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(0)); - if(this.elements !== undefined) { + if(this.elements != null) { ber.startSequence(BER.APPLICATION(11)); for(var i=0; i 0) { - tag = seq.peek(); - var dataSeq = seq.getSequence(BER.CONTEXT(0)); - if (tag === BER.CONTEXT(0)) { - fc.arguments.push(FunctionArgument.decode(dataSeq)); - } + var dataSeq = seq.getSequence(BER.EMBER_SEQUENCE); + while(dataSeq.remain > 0) { + seq = dataSeq.getSequence(BER.CONTEXT(0)); + fc.arguments.push(FunctionArgument.decode(seq)); } } else if(tag == BER.CONTEXT(3)) { fc.result = []; @@ -1728,8 +1733,10 @@ FunctionContent.prototype.encode = function(ber) { if(this.arguments != null) { ber.startSequence(BER.CONTEXT(2)); ber.startSequence(BER.EMBER_SEQUENCE); - for(var i =0; i < this.arguments.length; i++) { + for(var i = 0; i < this.arguments.length; i++) { + ber.startSequence(BER.CONTEXT(0)); this.arguments[i].encode(ber); + ber.endSequence(); } ber.endSequence(); ber.endSequence(); // BER.CONTEXT(2) @@ -1756,11 +1763,12 @@ module.exports.FunctionContent = FunctionContent; * QualifiedFunction ***************************************************************************/ -function QualifiedFunction(path) { +function QualifiedFunction(path, func) { QualifiedFunction.super_.call(this); if (path != undefined) { this.path = path; } + this.func = func; } util.inherits(QualifiedFunction, TreeNode); @@ -1891,10 +1899,10 @@ module.exports.QualifiedFunction = QualifiedFunction; ***************************************************************************/ -function Function(number) { +function Function(number, func) { Function.super_.call(this); - if(number !== undefined) - this.number = number; + this.number = number; + this.func = func; }; util.inherits(Function, TreeNode); @@ -1953,8 +1961,11 @@ Function.prototype.encode = function(ber) { ber.endSequence(); // BER.APPLICATION(19) } -module.exports.Function = Function; - +Function.prototype.toQualified = function() { + const qf = new QualifiedFunction(this.getPath()); + qf.update(this); + return qf; +} Function.prototype.invoke = function(callback) { if(callback !== undefined) { @@ -1966,6 +1977,24 @@ Function.prototype.invoke = function(callback) { }); } +Function.prototype.update = function(other) { + callbacks = Function.super_.prototype.update.apply(this); + if ((other !== undefined) && (other.contents !== undefined)) { + if (this.contents == null) { + this.contents = other.contents; + } + else { + for (var key in other.contents) { + if (other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } + } + } + } + return callbacks; +} + +module.exports.Function = Function; /**************************************************************************** @@ -2077,7 +2106,7 @@ Command.decode = function(ber) { c.fieldFlags = FieldFlags.get(seq.readInt()); } else if(tag == BER.CONTEXT(2)) { - c.invocation = Invocation.decode(ber); + c.invocation = Invocation.decode(seq); } else { // TODO: options @@ -2123,20 +2152,29 @@ function Invocation(id = null) { Invocation._id = 1 -Invocation.prototype.decode = function(ber) { - let invocation = new Invocation(); +Invocation.decode = function(ber) { + let invocation = null; ber = ber.getSequence(BER.APPLICATION(22)); while(ber.remain > 0) { var tag = ber.peek(); var seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - invocation.invocationId = seq.readInt(); + const invocationId = seq.readInt(); + invocation = new Invocation(invocationId); } - if(tag == BER.CONTEXT(1)) { + else if(tag == BER.CONTEXT(1)) { + if (invocation == null) { + throw new Error("Missing invocationID"); + } invocation.arguments = []; - const seq = ber.getSequence(BER.EMBER_SEQUENCE); + seq = seq.getSequence(BER.EMBER_SEQUENCE); while(seq.remain > 0) { - invocation.arguments.push(FunctionArgument.decode(seq)); + const dataSeq = seq.getSequence(BER.CONTEXT(0)); + tag = dataSeq.peek(); + const val = dataSeq.readValue(); + invocation.arguments.push( + new FunctionArgument(ParameterTypefromBERTAG(tag), val) + ); } } else { @@ -2171,15 +2209,66 @@ Invocation.prototype.encode = function(ber) { ber.endSequence(); ber.endSequence(); // BER.APPLICATION(22) - } /**************************************************************************** * InvocationResult ***************************************************************************/ -function InvocationResult() { + + function InvocationResult(invocationId = null) { + this.invocationId = invocationId; } + +util.inherits(InvocationResult, TreeNode); module.exports.InvocationResult = InvocationResult; +InvocationResult.prototype.setFailure = function() { + this.success = false; +} + +InvocationResult.prototype.setSuccess = function() { + this.success = true; +} + +/** + * @param{} + */ +InvocationResult.prototype.setResult = function(result) { + if (!Array.isArray(result)) { + throw new Error("Invalid inovation result. Should be array"); + } + this.result = result; +} + +InvocationResult.prototype.encode = function(ber) { + ber.startSequence(BER.APPLICATION(23)); + if (this.invocationId != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.invocationId); + ber.endSequence(); + } + if (this.success != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeBoolean(this.success); + ber.endSequence(); + } + if (this.result != null && this.result.length) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.EMBER_SEQUENCE); + for (let i = 0; i < this.result.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeValue(this.result[i].value, ParameterTypetoBERTAG(this.result[i].type)); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + } + ber.endSequence(); // BER.APPLICATION(23)} +} + +InvocationResult.prototype.toQualified = function() { + return this; +} + InvocationResult.decode = function(ber) { let invocationResult = new InvocationResult(); ber = ber.getSequence(BER.APPLICATION(23)); @@ -2195,9 +2284,14 @@ InvocationResult.decode = function(ber) { let res = seq.getSequence(BER.EMBER_SEQUENCE); while(res.remain > 0) { tag = res.peek(); - var resTag = res.getSequence(BER.CONTEXT(0)); if (tag === BER.CONTEXT(0)) { - invocationResult.result.push(resTag.readValue()); + var resTag = res.getSequence(BER.CONTEXT(0)); + tag = resTag.peek(); + invocationResult.result.push( + new FunctionArgument( + ParameterTypefromBERTAG(tag), + resTag.readValue() + )); } } continue @@ -2209,6 +2303,9 @@ InvocationResult.decode = function(ber) { return invocationResult; } + + + /**************************************************************************** * QualifiedParameter ***************************************************************************/ @@ -2495,6 +2592,18 @@ function ParameterTypetoBERTAG(type) { } } +function ParameterTypefromBERTAG(tag) { + switch (tag) { + case BER.EMBER_INTEGER: return ParameterType.integer; + case BER.EMBER_REAL: return ParameterType.real; + case BER.EMBER_STRING: return ParameterType.string; + case BER.EMBER_BOOLEAN: return ParameterType.boolean; + case BER.EMBER_OCTETSTRING: return ParameterType.octets; + default: + throw new Error(`Unhandled BER TAB ${tag}`); + } +} + module.exports.ParameterAccess = ParameterAccess; module.exports.ParameterType = ParameterType; diff --git a/package.json b/package.json index 270514d..8de1d96 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "emberplus", - "version": "1.8.0", + "version": "1.9.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { @@ -11,12 +11,13 @@ "contributors": [ { "name": "Gilles Dufour", - "email": "dufour.gilles@gmail.com" + "email": "dufour.gilles@gmail.com", + "web": "www.gdnet.be" } ], "repository": { "type": "git", - "url": "https://github.com/bmayton/node-emberplus" + "url": "https://github.com/dufourgilles/node-emberplus" }, "license": "MIT", "dependencies": { diff --git a/server.js b/server.js index 8bdfee5..d99daf5 100755 --- a/server.js +++ b/server.js @@ -106,7 +106,7 @@ TreeServer.prototype.handleRoot = function(client, root) { } else if (node instanceof ember.Command) { // Command on root element - this.handleCommand(client, this.tree, node.number); + this.handleCommand(client, this.tree, node); return "root"; } else { @@ -127,14 +127,14 @@ TreeServer.prototype.handleQualifiedNode = function(client, node) { // Find this element in our tree const element = this.tree.getElementByPath(path); - if ((element === null) || (element === undefined)) { + if (element == null) { this.emit("error", new Error(`unknown element at path ${path}`)); return this.handleError(client); } - if ((node.children !== undefined) && (node.children.length === 1) && + if ((node.children != null) && (node.children.length === 1) && (node.children[0] instanceof ember.Command)) { - this.handleCommand(client, element, node.children[0].number); + this.handleCommand(client, element, node.children[0]); } else { if (node instanceof ember.QualifiedMatrix) { @@ -142,7 +142,7 @@ TreeServer.prototype.handleQualifiedNode = function(client, node) { } else if (node instanceof ember.QualifiedParameter) { this.handleQualifiedParameter(client, element, node); - } + } } return path; } @@ -326,18 +326,28 @@ TreeServer.prototype.matrixSet = function(path, target, sources) { doMatrixOperation(this, path, target, sources, ember.MatrixOperation.absolute); } +TreeServer.prototype.handleQualifiedFunction = function(client, element, node) { + +} + + TreeServer.prototype.handleCommand = function(client, element, cmd) { - if (cmd === ember.GetDirectory) { - this.handleGetDirectory(client, element); - } - else if (cmd === ember.Subscribe) { - this.handleSubscribe(client, element); - } - else if (cmd === ember.Unsubscribe) { - this.handleUnSubscribe(client, element); - } - else { - this.emit("error", new Error(`invalid command ${cmd}`)); + switch(cmd.number) { + case ember.GetDirectory: + this.handleGetDirectory(client, element); + break; + case ember.Subscribe: + this.handleSubscribe(client, element); + break; + case ember.Unsubscribe: + this.handleUnSubscribe(client, element); + break; + case ember.Invoke: + this.handleInvoke(client, cmd.invocation, element); + break; + default: + this.emit("error", new Error(`invalid command ${cmd}`)); + break; } } @@ -357,7 +367,7 @@ TreeServer.prototype.getResponse = function(element) { } TreeServer.prototype.getQualifiedResponse = function(element) { - let res = new ember.Root(); + const res = new ember.Root(); let dup; if (element.isRoot() === false) { dup = element.toQualified(); @@ -374,6 +384,26 @@ TreeServer.prototype.getQualifiedResponse = function(element) { return res; } +TreeServer.prototype.handleInvoke = function(client, invocation, element) { + const result = new ember.InvocationResult(); + result.invocationId = invocation.id; + if (element == null || !element.isFunction()) { + result.setFailure(); + } + else { + try { + result.setResult(element.func(invocation.arguments)); + } + catch(e){ + this.emit("error", e); + result.setFailure(); + } + } + const res = new ember.Root(); + res.addResult(result); + client.sendBERNode(res); +} + TreeServer.prototype.handleGetDirectory = function(client, element) { if (client !== undefined) { if ((element.isMatrix() || element.isParameter()) && @@ -395,7 +425,7 @@ TreeServer.prototype.handleGetDirectory = function(client, element) { } } - let res = this.getQualifiedResponse(element); + const res = this.getQualifiedResponse(element); if (this._debug) { console.log("getDirectory response", res); } @@ -529,13 +559,12 @@ const parseMatrixContent = function(matrixContent, content) { } const parseObj = function(parent, obj) { - let path = parent.getPath(); for(let i = 0; i < obj.length; i++) { let emberElement; let content = obj[i]; let number = content.number !== undefined ? content.number : i; delete content.number; - if (content.value !== undefined) { + if (content.value != null) { emberElement = new ember.Parameter(number); emberElement.contents = new ember.ParameterContents(content.value); if (content.type) { @@ -553,7 +582,20 @@ const parseObj = function(parent, obj) { emberElement.contents.access = ember.ParameterAccess.read; } } - else if (content.targetCount !== undefined) { + else if (content.func != null) { + emberElement = new ember.Function(number, content.func); + emberElement.contents = new ember.FunctionContent(); + if (content.arguments != null) { + for(let argument of content.arguments) { + emberElement.contents.arguments.push(new ember.FunctionArgument( + argument.type, + argument.value, + argument.name + )); + } + } + } + else if (content.targetCount != null) { emberElement = new ember.MatrixNode(number); emberElement.contents = new ember.MatrixContents(); parseMatrixContent(emberElement.contents, content); @@ -583,7 +625,11 @@ const parseObj = function(parent, obj) { emberElement.contents = new ember.NodeContents(); } for(let id in content) { - if ((id !== "children") && (content.hasOwnProperty(id))) { + if (emberElement.isFunction() && id === "arguments") { + // we did it already. + continue; + } + if ((id !== "children") && (content.hasOwnProperty(id))) { emberElement.contents[id] = content[id]; } else { diff --git a/test/Server.test.js b/test/Server.test.js index ed44416..c163b32 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -27,7 +27,7 @@ describe("server", function() { expect(root.elements.length).toBe(1); console.log("root", root.elements[0].contents); expect(root.elements[0].contents.identifier).toBe("scoreMaster"); - expect(root.elements[0].children.length).toBe(2); + expect(root.elements[0].children.length).toBe(jsonTree[0].children.length); }); }); @@ -63,7 +63,7 @@ describe("server", function() { return client.getDirectory(client.root.elements[0]); }) .then(() => { - expect(client.root.elements[0].children.length).toBe(2); + expect(client.root.elements[0].children.length).toBe(jsonTree[0].children.length); return client.getDirectory(client.root.elements[0].children[0]); }) .then(() => { @@ -126,6 +126,31 @@ describe("server", function() { return client.disconnect(); }); }); + it("should be able to call a function with parameters", () => { + client = new DeviceTree(LOCALHOST, PORT); + //client._debug = true; + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + return client.getDirectory(); + }) + .then(() => client.expand(client.root.elements[0])) + .then(() => { + const func = client.root.elements[0].children[2]; + return client.invokeFunction(func, [ + new ember.FunctionArgument(ember.ParameterType.integer, 1), + new ember.FunctionArgument(ember.ParameterType.integer, 7) + ]); + }) + .then(result => { + console.log(result); + expect(result).toBeDefined(); + expect(result.result).toBeDefined(); + expect(result.result.length).toBe(1); + expect(result.result[0].value).toBe(8); + return client.disconnect(); + }); + }); it("should be able to get child with getNodeByPath", function() { //server._debug = true; diff --git a/test/client.js b/test/client.js deleted file mode 100755 index 880e0cc..0000000 --- a/test/client.js +++ /dev/null @@ -1,21 +0,0 @@ -const DeviceTree = require("../").DeviceTree; -const ember = require("../ember"); - -const HOST = "192.168.4.4"; -const PORT = 9092; - -client = new DeviceTree(HOST, PORT); -client.connect() -.then(() => client.getDirectory()) -.then(() => client.expand(client.root.elements[0])) -.then(() => { - //console.log(client.root.elements[0].children[4].children[2].toJSON()); - console.log(JSON.stringify(client.root.elements[0].children[4].toJSON(), null, 4)); - return client.invokeFunction(client.root.elements[0].children[4].children[0], [ - new ember.FunctionArgument(ember.ParameterType.integer, 1), - new ember.FunctionArgument(ember.ParameterType.integer, 7) - ]); -}) -.then(result => { - console.log(result); -}); diff --git a/test/utils.js b/test/utils.js index 74b6eb9..103b542 100755 --- a/test/utils.js +++ b/test/utils.js @@ -1,4 +1,4 @@ -const {ParameterAccess} = require("../ember"); +const {ParameterType, FunctionArgument} = require("../ember"); const init = function(_src,_tgt) { const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; @@ -77,6 +77,28 @@ const init = function(_src,_tgt) { ] } ] + }, + { + // path "0.2" + identifier: "addFunction", + func: args => { + const res = new FunctionArgument(); + res.type = ParameterType.integer; + res.value = args[0].value + args[1].value; + return [res]; + }, + arguments: [ + { + type: ParameterType.integer, + value: null, + name: "arg1" + }, + { + type: ParameterType.integer, + value: null, + name: "arg2" + } + ] } ] } From ed2ff32c7d8f870a7b59bca7d347d48b12c36f73 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 9 Dec 2019 11:53:54 +0100 Subject: [PATCH 093/162] new documentation --- README.md | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/README.md b/README.md index 2464cc9..8f7f3aa 100755 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Server has been added in version 1.6.0. ## Example usage +### Client ```javascript const DeviceTree = require('emberplus').DeviceTree; var root; @@ -37,8 +38,33 @@ tree.connect() .catch((e) => { console.log(e.stack); }); +``` +### Function +```javascript +const client = new DeviceTree(HOST, PORT); +client.connect()) +.then(() => client.getDirectory()) +.then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) +.then(() => client.expand(client.root.elements[0])) +.then(() => { + console.log(JSON.stringify(client.root.elements[0].toJSON(), null, 4)); + let func; + if (TINYEMBER) { + func = client.root.elements[0].children[4].children[0]; + } + else { + func = client.root.elements[0].children[2]; + } + return client.invokeFunction(func, [ + new ember.FunctionArgument(ember.ParameterType.integer, 1), + new ember.FunctionArgument(ember.ParameterType.integer, 7) + ]); +}) +``` +### Packet decoder +```javascript // Simple packet decoder const Decoder = require('emberplus').Decoder; const fs = require("fs"); @@ -46,8 +72,125 @@ const fs = require("fs"); fs.readFile("tree.ember", (e,data) => { var root = Decoder(data); }); +``` +### Server +```javascript // Server const TreeServer = require("emberplus").TreeServer; const server = new TreeServer("127.0.0.1", 9000, root); +server.on("error", e => { + console.log("Server Error", e); +}); +server.on("clientError", info => { + console.log("clientError", info); +}); server.listen().then(() => { console.log("listening"); }).catch((e) => { console.log(e.stack); }); +``` + +### Construct Tree +```javascript +const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; +const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; +const labels = function(endpoints) { + let labels = []; + for (let i = 0; i < endpoints.length; i++) { + let endpoint = endpoints[i]; + let l = { identifier: `Label-${i}` }; + if (endpoint) { + l.value = endpoint; + } + labels.push(l); + } + return labels; +}; + +const buildConnections = function(s, t) { + let connections = []; + for (let i = 0; i < t.length; i++) { + connections.push({target: `${i}`}); + } + return connections; +}; +const jsonTree = [ + { + // path "0" + identifier: "GDNet Tree", + children: [ + { + // path "0.0" + identifier: "identity", + children: [ + {identifier: "product", value: "gdnet core"}, + {identifier: "company", value: "GDNet", access: "readWrite"}, + {identifier: "version", value: "1.2.0"}, + {identifier: "author", value: "dufour.gilles@gmail.com"}, + ] + }, + { + // path "0.1" + identifier: "router", + children: [ + { + // path 0.1.0 + identifier: "matrix", + type: "oneToN", + mode: "linear", + targetCount: targets.length, + sourceCount: sources.length, + connections: buildConnections(sources, targets), + labels: ["0.1.1000"] + }, + { + identifier: "labels", + // path "0.1.1000" + number: 1000, + children: [ + { + identifier: "targets", + // Must be 1 + number: 1, + children: labels(targets) + }, + { + identifier: "sources", + // Must be 2 + number: 2, + children: labels(sources) + }, + { + identifier: "group 1", + children: [ {identifier: "sdp A", value: "A"}, {identifier: "sdp B", value: "B"}] + } + ] + } + ] + }, + { + // path "0.2" + identifier: "addFunction", + func: args => { + const res = new FunctionArgument(); + res.type = ParameterType.integer; + res.value = args[0].value + args[1].value; + return [res]; + }, + arguments: [ + { + type: ParameterType.integer, + value: null, + name: "arg1" + }, + { + type: ParameterType.integer, + value: null, + name: "arg2" + } + ] + } + ] + } +]; +const root = TreeServer.JSONtoTree(jsonTree); +``` + From 26380558f811aaedf38e8af120950ab7106ad97c Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 9 Dec 2019 11:58:52 +0100 Subject: [PATCH 094/162] new documentation --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8f7f3aa..dfcf56f 100755 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ tree.connect() ### Function ```javascript +const DeviceTree = require('emberplus').DeviceTree; +const ember = require("emberplus").Ember; + const client = new DeviceTree(HOST, PORT); client.connect()) .then(() => client.getDirectory()) @@ -90,6 +93,9 @@ server.listen().then(() => { console.log("listening"); }).catch((e) => { console ### Construct Tree ```javascript +const TreeServer = require("emberplus").TreeServer; +const {ParameterType, FunctionArgument} = require("emberplus").Ember; + const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; const labels = function(endpoints) { From 46297db422aa98d934151694d9242ac78d9836d5 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 9 Dec 2019 17:11:22 +0100 Subject: [PATCH 095/162] Added all matrixConnect and Disconnect functions. Fixed all tests. Modified the Doc. --- README.md | 100 ++++++++++++++++------- device.js | 177 +++++++++++++++++++++++++++++++++------- ember.js | 63 +++++++++++++- package.json | 30 ++++--- server.js | 9 +- test/DeviceTree.test.js | 88 ++++++++++---------- test/Server.test.js | 170 +++++++++++++++++++------------------- 7 files changed, 435 insertions(+), 202 deletions(-) diff --git a/README.md b/README.md index dfcf56f..7a583c3 100755 --- a/README.md +++ b/README.md @@ -20,50 +20,88 @@ Server has been added in version 1.6.0. ## Example usage ### Client +Get Full tree: ```javascript const DeviceTree = require('emberplus').DeviceTree; var root; var tree = new DeviceTree("10.9.8.7", 9000); -tree.connect() -.then(() => { - return tree.getDirectory(); -}) -.then((r) => { - root = r ; - return tree.expand(r.elements[0]); -}) -.then(() => { - console.log("done"); -}) -.catch((e) => { - console.log(e.stack); +tree.on("error", e => { + console.log(e); }); +tree.connect() + .then(() => tree.getDirectory()) + .then((r) => { + root = r ; + return tree.expand(r.elements[0]); + }) + .then(() => { + console.log("done"); + }) + .catch((e) => { + console.log(e.stack); + }); +``` + +Get Specific Branch: +```javascript +const DeviceTree = require('emberplus').DeviceTree; +const ember = require("emberplus").Ember; + +const client = new DeviceTree(HOST, PORT); +client.connect()) + .then(() => client.getDirectory()) + .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) + .then(() => client.getNodeByPath("scoreMaster/router/labels/group 1")) + .then(() => client.getNodeByPathnum("0.2")) + .then(() => { + console.log(JSON.stringify(client.root.elements[0].toJSON(), null, 4)); + }); ``` -### Function +### Invoking Function ```javascript const DeviceTree = require('emberplus').DeviceTree; const ember = require("emberplus").Ember; const client = new DeviceTree(HOST, PORT); client.connect()) -.then(() => client.getDirectory()) -.then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) -.then(() => client.expand(client.root.elements[0])) -.then(() => { - console.log(JSON.stringify(client.root.elements[0].toJSON(), null, 4)); - let func; - if (TINYEMBER) { - func = client.root.elements[0].children[4].children[0]; - } - else { - func = client.root.elements[0].children[2]; - } - return client.invokeFunction(func, [ - new ember.FunctionArgument(ember.ParameterType.integer, 1), - new ember.FunctionArgument(ember.ParameterType.integer, 7) - ]); -}) + .then(() => client.getDirectory()) + .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) + .then(() => client.expand(client.root.elements[0])) + .then(() => { + console.log(JSON.stringify(client.root.elements[0].toJSON(), null, 4)); + let func; + if (TINYEMBER) { + func = client.root.elements[0].children[4].children[0]; + } + else { + func = client.root.elements[0].children[2]; + } + return client.invokeFunction(func, [ + new ember.FunctionArgument(ember.ParameterType.integer, 1), + new ember.FunctionArgument(ember.ParameterType.integer, 7) + ]); + }); +``` + +### Matrix Connection +```javascript +const DeviceTree = require('emberplus').DeviceTree; +const ember = require("emberplus").Ember; + +const client = new DeviceTree(HOST, PORT); +client.connect() + .then(() => client.getDirectory()) + .then(() => client.getNodeByPathnum("0.1.0")) + .then(matrix => { + console.log("Connecting source 1 to target 0); + return client.matrixConnect(matrix, 0, [1]); + }) + .then(() => client.matrixDisconnect(matrix, 0, [1])) + .then(() => client.matrixSetConnection(matrix, 0, [0,1])) + .then(matrix => client.getNodeByPathnum(matrix.getPath())) + .then(() => client.disconnect()); + ``` ### Packet decoder diff --git a/device.js b/device.js index a238066..c5e9272 100755 --- a/device.js +++ b/device.js @@ -224,6 +224,80 @@ DeviceTree.prototype.getDirectory = function (qnode) { }); }; +DeviceTree.prototype.matrixOPeration = function(matrixNode, targetID, sources, operation = ember.MatrixOperation.connect) { + return new Promise((resolve, reject) => { + if (!Array.isArray(sources)) { + return reject(new Error("Sources should be an array")); + } + try { + matrixNode.validateConnection(targetID, sources); + } + catch(e) { + return reject(e); + } + const connections = {} + const targetConnection = new ember.MatrixConnection(targetID); + targetConnection.operation = operation; + targetConnection.setSources(sources); + connections[targetID] = targetConnection; + + this.addRequest({node: matrixNode, func: (error) => { + if (error) { + this.finishRequest(); + reject(error); + return; + } + + this.callback = (error, node) => { + const requestedPath = matrixNode.getPath(); + if (node == null) { + if (this._debug) { + console.log(`received null response for ${requestedPath}`); + } + return; + } + if (error) { + if (this._debug) { + console.log("Received getDirectory error", error); + } + this.clearTimeout(); // clear the timeout now. The resolve below may take a while. + this.finishRequest(); + reject(error); + return; + } + let matrix = null; + if (node != null) { + matrix = node.elements[0]; + } + if (matrix != null && matrix.isMatrix() && matrix.getPath() === requestedPath) { + this.clearTimeout(); // clear the timeout now. The resolve below may take a while. + this.finishRequest(); + resolve(matrix); + } + else { + if (this._debug) { + console.log(`unexpected node response during matrix connect ${requestedPath}`, + JSON.stringify(matrix.toJSON(), null, 4)); + } + } + } + this.client.sendBERNode(matrixNode.connect(connections)); + }}); + }); +} + +DeviceTree.prototype.matrixConnect = function(matrixNode, targetID, sources) { + return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.connect) +} + +DeviceTree.prototype.matrixDisconnect = function(matrixNode, targetID, sources) { + return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.disconnect) +} + +DeviceTree.prototype.matrixSetConnection = function(matrixNode, targetID, sources) { + return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.absolute) +} + DeviceTree.prototype.invokeFunction = function (fnNode, params) { var self = this; return new Promise((resolve, reject) => { @@ -245,6 +319,7 @@ DeviceTree.prototype.invokeFunction = function (fnNode, params) { } resolve(result); } + // cleaning callback and making next request. self.finishRequest(); }; @@ -307,7 +382,11 @@ DeviceTree.prototype.finishRequest = function () { try { self.makeRequest(); } catch(e) { - console.log("warn:" + e.message) + if (self._debug) {console.log(e);} + if (self.callback != null) { + self.callback(e); + } + self.emit("error", e); } }; @@ -408,40 +487,80 @@ DeviceTree.prototype.handleNode = function (parent, node) { return callbacks; }; -DeviceTree.prototype.getNodeByPath = function (path, timeout = 2) { +DeviceTree.prototype.getNodeByPathnum = function (path) { var self = this; if (typeof path === 'string') { - path = path.split('/'); + path = path.split('.'); } - var timeoutError = new Error("Request timeout"); - return new Promise((resolve, reject) => { - self.addRequest({path: path, func: (error) => { - if (error) { - reject(error); - self.finishRequest(); - return; + var pos = 0; + var lastMissingPos = -1; + var currentNode = this.root; + const getNext = () => { + return Promise.resolve() + .then(() => { + const children = currentNode.getChildren(); + const number = Number(path[pos]); + if (children != null) { + for (let i = 0; i < children.length; i++) { + var node = children[i]; + if (node.getNumber() === number) { + // We have this part already. + pos++; + if (pos >= path.length) { + return node; + } + currentNode = node; + return getNext(); + } + } } - var timedOut = false; - var cb = (error, node) => { - if (timer) { - clearTimeout(timer); - } - if (timedOut) { return; } - if (error) { - reject(error); - } else { - resolve(node); + // We do not have that node yet. + if (lastMissingPos === pos) { + throw new Error(`Failed path discovery at ${path.slice(0, pos).join("/")}`); + } + lastMissingPos = pos; + return this.getDirectory(currentNode).then(() => getNext()); + }); + } + return getNext(); +}; + +DeviceTree.prototype.getNodeByPath = function (path) { + var self = this; + if (typeof path === 'string') { + path = path.split('/'); + } + var pos = 0; + var lastMissingPos = -1; + var currentNode = this.root; + const getNext = () => { + return Promise.resolve() + .then(() => { + const children = currentNode.getChildren(); + const identifier = path[pos]; + if (children != null) { + for (let i = 0; i < children.length; i++) { + var node = children[i]; + if (node.contents != null && node.contents.identifier === identifier) { + // We have this part already. + pos++; + if (pos >= path.length) { + return node; + } + currentNode = node; + return getNext(); + } } - self.finishRequest(); - }; - var cbTimeout = () => { - timedOut = true; - reject(timeoutError); } - var timer = timeout === 0 ? null : setTimeout(cbTimeout, timeout * 1000); - self.root.getNodeByPath(self.client, path, cb); - }}); - }); + // We do not have that node yet. + if (lastMissingPos === pos) { + throw new Error(`Failed path discovery at ${path.slice(0, pos + 1).join("/")}`); + } + lastMissingPos = pos; + return this.getDirectory(currentNode).then(() => getNext()); + }); + } + return getNext(); }; DeviceTree.prototype.subscribe = function (node, callback) { diff --git a/ember.js b/ember.js index d82292d..9ed41a4 100755 --- a/ember.js +++ b/ember.js @@ -828,6 +828,55 @@ function MatrixNode(number) { } +function validateConnection(matrixNode, targetID, sources) { + if (targetID < 0) { + throw new Error(`Invalid negative target index ${targetID}`); + } + for(let i = 0; i < sources.length; i++) { + if (sources[i] < 0) { + throw new Error(`Invalid negative source at index ${i}`); + } + } + if (matrixNode.contents.mode === MatrixMode.linear) { + if (targetID >= matrixNode.contents.targetCount) { + throw new Error(`targetID ${targetID} higher than max value ${matrixNode.contents.targetCount}`); + } + for(let i = 0; i < sources.length; i++) { + if (sources[i] >= matrixNode.contents.sourceCount) { + throw new Error(`Invalid source at index ${i}`); + } + } + } + else if ((matrixNode.targets == null) || (matrixNode.sources == null)) { + throw new Error("Non-Linear matrix should have targets and sources"); + } + else { + let found = false; + for(let i = 0; i < matrixNode.targets; i++) { + if (matrixNode.targets[i] === targetID) { + found = true; + break; + } + } + if (!found) { + throw new Error(`Unknown targetid ${targetID}`); + } + found = false; + for(let i = 0; i < sources.length; i++) { + for(let j = 0; i < matrixNode.sources; j++) { + if (matrixNode.sources[j] === sources[i]) { + found = true; + break; + } + } + if (!found) { + throw new Error(`Unknown source at index ${i}`); + } + } + } +} + + MatrixNode.decode = function(ber) { var m = new MatrixNode(); ber = ber.getSequence(BER.APPLICATION(13)); @@ -862,6 +911,9 @@ MatrixNode.decode = function(ber) { return m; }; +MatrixNode.prototype.validateConnection = function(targetID, sources) { + validateConnection(this, targetID, sources); +} MatrixNode.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(13)); @@ -1238,7 +1290,13 @@ MatrixConnection.decode = function(ber) { c.target = seq.readInt(); } else if (tag == BER.CONTEXT(1)) { - c.sources = seq.readRelativeOID(BER.EMBER_RELATIVE_OID).split(".").map(i => Number(i)); + const sources = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); + if (sources === "") { + c .sources = []; + } + else { + c.sources = sources.split(".").map(i => Number(i)); + } } else if (tag == BER.CONTEXT(2)) { c.operation = MatrixOperation.get(seq.readInt()); @@ -1388,6 +1446,9 @@ QualifiedMatrix.prototype.getMinimal = function(complete = false) { return m; } +QualifiedMatrix.prototype.validateConnection = function(targetID, sources) { + validateConnection(this, targetID, sources); +} QualifiedMatrix.decode = function(ber) { var qm = new QualifiedMatrix(); diff --git a/package.json b/package.json index 8de1d96..c0d839d 100755 --- a/package.json +++ b/package.json @@ -1,27 +1,24 @@ { - "name": "emberplus", - "version": "1.9.0", + "name": "node-emberplus", + "version": "1.10.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { "test": "jest test", - "eslint": "eslint ./" + "eslint": "eslint ./", + "start": "node server.js" }, "author": "Brian Mayton (http://bdm.cc)", "contributors": [ - { - "name": "Gilles Dufour", - "email": "dufour.gilles@gmail.com", - "web": "www.gdnet.be" - } + "Gilles Dufour (www.gdnet.be)" ], "repository": { "type": "git", - "url": "https://github.com/dufourgilles/node-emberplus" + "url": "git+https://github.com/dufourgilles/node-emberplus.git" }, "license": "MIT", "dependencies": { - "asn1": "evs-broadcast/node-asn1#date_2018_01_02", + "asn1": "github:evs-broadcast/node-asn1#date_2018_01_02", "enum": "^2.4.0", "long": "^3.2.0", "smart-buffer": "^3.0.3", @@ -33,5 +30,16 @@ "jest": "^23.5.0", "jest-cli": "^24.9.0", "sinon": "^7.4.1" - } + }, + "bugs": { + "url": "https://github.com/dufourgilles/node-emberplus/issues" + }, + "homepage": "https://github.com/dufourgilles/node-emberplus#readme", + "directories": { + "test": "test" + }, + "keywords": [ + "emberplus", + "lawo" + ] } diff --git a/server.js b/server.js index d99daf5..ed0e506 100755 --- a/server.js +++ b/server.js @@ -223,7 +223,10 @@ TreeServer.prototype.handleQualifiedParameter = function(client, element, parame TreeServer.prototype.handleMatrixConnections = function(client, matrix, connections, response = true) { var res; var root; // ember message root - if (matrix.isQualified()) { + if (this._debug) { + console.log("Handling Matrix Connection"); + } + if (client.request.isQualified()) { root = new ember.Root(); res = new ember.QualifiedMatrix(matrix.getPath()); root.elements = [res]; // do not use addchild or the element will get removed from the tree. @@ -281,7 +284,9 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti if (client !== undefined) { client.sendBERNode(root); } - if (this._debug) { console.log("Updating subscribers for matrix change"); } + if (this._debug) { + console.log("Updating subscribers for matrix change"); + } this.updateSubscribers(matrix.getPath(), root, client); } diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index 85a0037..20c3995 100755 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -27,9 +27,7 @@ describe("DeviceTree", () => { return server.listen(); }); }); - afterAll(() => server.close()); - }); it("should gracefully connect and disconnect", () => { return Promise.resolve() .then(() => { @@ -41,12 +39,52 @@ describe("DeviceTree", () => { .then(() => tree.connect()) .then(() => tree.getDirectory()) .then(() => tree.disconnect()) + }); + }); + + it("should not disconnect after 5 seconds of inactivity", () => { + return Promise.resolve() + .then(() => { + let tree = new DeviceTree(LOCALHOST, PORT); + + tree.on("error", error => { + throw error; + }); + + return Promise.resolve() + .then(() => tree.connect()) + .then(() => new Promise(resolve => setTimeout(resolve, 5000))) + .then(() => tree.disconnect()) }) - }); - + }, 7000); + + it("timeout should be taken into account when connecting to unknown host", () => { + let tree = new DeviceTree(UNKNOWN_HOST, PORT); + tree.on("error", () => { + }); + const expectedTimeoutInSec = 2; + const initialTime = performance.now(); + return tree.connect(expectedTimeoutInSec) + .then(() => { + throw new Error("Should have thrown"); + }, + error => { + const durationMs = performance.now() - initialTime; + const deltaMs = Math.abs(expectedTimeoutInSec * 1000 - durationMs); + expect(deltaMs).toBeLessThan(10); + expect(error.message).toBe(`Could not connect to ${UNKNOWN_HOST}:${PORT} after a timeout of ${expectedTimeoutInSec} seconds`) + }); + }); + + it("should gracefully connect and getDirectory", () => { let tree = new DeviceTree(LOCALHOST, PORT); + tree.on("error", e => { + console.log(e); + }) let stub = sinon.stub(tree.client, "sendBER"); + tree._debug = true; + server._debug = true; stub.onFirstCall().returns(); stub.onSecondCall().throws(new Error("blah")); stub.callThrough(); @@ -59,43 +97,9 @@ describe("DeviceTree", () => { tree.disconnect(); }, error => { stub.restore(); - tree.disconnect(); - throw error; - }) - - }, 10000); - it("should not disconnect after 5 seconds of inactivity", () => { - return Promise.resolve() - .then(() => { - let tree = new DeviceTree(LOCALHOST, PORT); - - tree.on("error", error => { - throw error; - }); - - return Promise.resolve() - .then(() => tree.connect()) - .then(() => new Promise(resolve => setTimeout(resolve, 5000))) - .then(() => tree.disconnect()) - }) - }, 7000); - }); - - it("timeout should be taken into account when connecting to unknown host", () => { - let tree = new DeviceTree(UNKNOWN_HOST, PORT); - tree.on("error", () => { - }); - const expectedTimeoutInSec = 2; - const initialTime = performance.now(); - return tree.connect(expectedTimeoutInSec) - .then(() => { - throw new Error("Should have thrown"); - }, - error => { - const durationMs = performance.now() - initialTime; - const deltaMs = Math.abs(expectedTimeoutInSec * 1000 - durationMs); - expect(deltaMs).toBeLessThan(10); - expect(error.message).toBe(`Could not connect to ${UNKNOWN_HOST}:${PORT} after a timeout of ${expectedTimeoutInSec} seconds`) + tree.disconnect(); + console.log(error); }); - }); + }, 10000); + }); }); diff --git a/test/Server.test.js b/test/Server.test.js index c163b32..4a63399 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -37,6 +37,12 @@ describe("server", function() { jsonTree = jsonRoot(); const root = TreeServer.JSONtoTree(jsonTree); server = new TreeServer(LOCALHOST, PORT, root); + server.on("error", e => { + console.log(e); + }); + server.on("clientError", e => { + console.log(e); + }); //server._debug = true; return server.listen().then(() => { console.log("server listening"); @@ -93,39 +99,7 @@ describe("server", function() { return client.disconnect(); }); }); - it("should be able to make a matrix connection", () => { - client = new DeviceTree(LOCALHOST, PORT); - //client._debug = true; - return Promise.resolve() - .then(() => client.connect()) - .then(() => { - return client.getDirectory(); - }) - .then(() => client.expand(client.root.elements[0])) - .then(() => { - console.log(client.root.elements[0].children[1].children[0]); - const matrix = client.root.elements[0].children[1].children[0]; - const connections = {} - const target0Connection = new ember.MatrixConnection(0); - target0Connection.operation = ember.MatrixOperation.connect; - target0Connection.setSources([1]); // connect with src 1 - connections[0] = target0Connection; - const p = new Promise(resolve => { - client.on("value-change", node => { - resolve(node); - }); - }); - client.client.sendBERNode(matrix.connect(connections)); - return p; - }) - .then(node => { - console.log(client.root.elements[0].children[1].children[0]); - expect(client.root.elements[0].children[1].children[0].connections['0'].sources).toBeDefined(); - expect(client.root.elements[0].children[1].children[0].connections['0'].sources.length).toBe(1); - expect(client.root.elements[0].children[1].children[0].connections['0'].sources[0]).toBe(1); - return client.disconnect(); - }); - }); + it("should be able to call a function with parameters", () => { client = new DeviceTree(LOCALHOST, PORT); //client._debug = true; @@ -152,49 +126,49 @@ describe("server", function() { }); }); - it("should be able to get child with getNodeByPath", function() { - //server._debug = true; - client = new DeviceTree(LOCALHOST, PORT); - //client._debug = true; + it("should be able to get child with getNodeByPath", function() { + //server._debug = true; + client = new DeviceTree(LOCALHOST, PORT); + //client._debug = true; //client._debug = true; return Promise.resolve() - .then(() => client.connect()) + .then(() => client.connect()) .then(() => { console.log("client connected"); - return client.getDirectory(); - }) - .then(() => { - return new Promise((resolve, reject) => { - client.root.getNodeByPath(client.client, ["scoreMaster", "identity", "product"], (err, child) => { - if (err) { reject(err) } - else { - resolve(child); - } - }); - }); + return client.getDirectory(); }) - .then(child => { - console.log(child); - }) - .then(() => { + .then(() => { return new Promise((resolve, reject) => { - client.root.getNodeByPath(client.client, ["scoreMaster", "router", "labels"], (err, child) => { - if (err) { reject(err) } - else { - resolve(child); - } - }); + client.root.getNodeByPath(client.client, ["scoreMaster", "identity", "product"], (err, child) => { + if (err) { reject(err) } + else { + resolve(child); + } + }); }); }) - .then(child => { - console.log(child); - client.disconnect(); + .then(child => { + console.log(child); + }) + .then(() => { + return new Promise((resolve, reject) => { + client.root.getNodeByPath(client.client, ["scoreMaster", "router", "labels"], (err, child) => { + if (err) { reject(err) } + else { + resolve(child); + } + }); }); - }); - it("should be able to get child with tree.getNodeByPath", function() { - //server._debug = true; - client = new DeviceTree(LOCALHOST, PORT); - //client._debug = true; + }) + .then(child => { + console.log(child); + client.disconnect(); + }); + }); + it("should be able to get child with tree.getNodeByPath", function() { + //server._debug = true; + client = new DeviceTree(LOCALHOST, PORT); + //client._debug = true; //client._debug = true; return Promise.resolve() .then(() => client.connect()) @@ -204,33 +178,57 @@ describe("server", function() { }) .then(() => client.getNodeByPath("scoreMaster/identity/product")) .then(child => { - console.log(child); - return client.getNodeByPath("scoreMaster/router/labels/group 1"); - }) + console.log(child); + return client.getNodeByPath("scoreMaster/router/labels/group 1"); + }) .then(child => { - console.log("router/labels", child); - client.disconnect(); + console.log("router/labels", child); + client.disconnect(); }); - }); - it("should throw an erro if getNodeByPath for unknown path", function() { - //server._debug = true; - client = new DeviceTree(LOCALHOST, PORT); - return Promise.resolve() + }); + it("should throw an error if getNodeByPath for unknown path", function() { + //server._debug = true; + client = new DeviceTree(LOCALHOST, PORT); + return Promise.resolve() .then(() => client.connect()) .then(() => { console.log("client connected"); return client.getDirectory(); }) - .then(() => client.getNodeByPath("scoreMaster/router/labels/group 1")) + .then(() => client.getNodeByPath("scoreMaster/router/labels/group")) .then(child => { - console.log("router/labels", child); - throw new Error("Should not succeed"); + console.log("router/labels", child); + throw new Error("Should not succeed"); + }) + .catch(e => { + client.disconnect(); + console.log(e); + expect(e.message).toMatch(/Failed path discovery/); + }); + }); + it("should be able to make a matrix connection", () => { + client = new DeviceTree(LOCALHOST, PORT); + //client._debug = true; + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + return client.getDirectory(); + }) + .then(() => client.getNodeByPathnum("0.1.0")) + .then(matrix => { + console.log(matrix); + client._debug = true; + server._debug = true; + return client.matrixConnect(matrix, 0, [1]); }) - .catch(e => { - client.disconnect(); - console.log(e); - expect(e.message).toMatch(/timeout/); - }); - }); + .then(matrix => client.getNodeByPathnum(matrix.getPath())) + .then(matrix => { + console.log(matrix); + expect(matrix.connections['0'].sources).toBeDefined(); + expect(matrix.connections['0'].sources.length).toBe(1); + expect(matrix.connections['0'].sources[0]).toBe(1); + return client.disconnect(); + }); + }); }); }); From 3b0808197d559a3556795e151de1a6f1b7b037aa Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 13 Dec 2019 11:32:18 +0100 Subject: [PATCH 096/162] Add description to label when converting json to tree --- README.md | 2 +- ember.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- server.js | 17 ++++++++++--- test/utils.js | 2 +- 5 files changed, 82 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7a583c3..9620414 100755 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ const jsonTree = [ targetCount: targets.length, sourceCount: sources.length, connections: buildConnections(sources, targets), - labels: ["0.1.1000"] + labels: [{basePath: "0.1.1000", description: "primary"}] }, { identifier: "labels", diff --git a/ember.js b/ember.js index 9ed41a4..1a99f45 100755 --- a/ember.js +++ b/ember.js @@ -827,6 +827,56 @@ function MatrixNode(number) { this.number = number; } +function canConnect(matrixNode, targetID, sources, operation) { + const type = matrixNode.contents.type == null ? MatrixType.oneToN : matrixNode.contents.type; + const mode = matrixNode.contents.mode == null ? MatrixConnection.linear : matrixNode.contents.mode; + const connection = matrixNode.connections[targetID];; + const oldSources = connection == null || connection.sources == null ? [] : connection.sources.slice(); + const newSources = operation === MatrixOperation.absolute ? sources : oldSources.concat(sources); + const sMap = new Set(newSources.map(i => Number(i))); + if (type === MatrixType.oneToN) { + return sMap.size < 2; + } + else if (type === MatrixType.oneToOne) { + if (sMap.size > 1) { + return false; + } + if (mode === MatrixMode.linear) { + for(let s = 0; s < sources.length; s++) { + for(let i = 0; i < matrixNode.targetCount; i++) { + const connection = matrixNode.connections[i]; + if (connection == null || connection.sources == null) { + continue; + } + for(let j = 0; j < connection.sources.length; j++) { + if (connection.sources[j] === sources[s] && targetID !== i) { + // This source is already connected to another target + return false; + } + } + } + } + } + else { + for(let s = 0; s < sources.length; s++) { + for(let t = 0; t < matrixNode.targets.length; t++) { + const target = matrixNode.targets[t]; + const connection = matrixNode.connections[target]; + if (connection == null || connection.sources == null) { + continue; + } + for(let j = 0; j < connection.sources.length; j++) { + if (connection.sources[j] === sources[s] && targetID !== target) { + // This source is already connected to another target + return false; + } + } + } + } + } + } + return true; +} function validateConnection(matrixNode, targetID, sources) { if (targetID < 0) { @@ -873,7 +923,7 @@ function validateConnection(matrixNode, targetID, sources) { throw new Error(`Unknown source at index ${i}`); } } - } + } } @@ -915,6 +965,10 @@ MatrixNode.prototype.validateConnection = function(targetID, sources) { validateConnection(this, targetID, sources); } +MatrixNode.prototype.canConnect = function(targetID, sources, operation) { + canConnect(this, targetID, sources, operation); +} + MatrixNode.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(13)); @@ -1337,10 +1391,13 @@ MatrixConnection.prototype.encode = function(ber) { module.exports.MatrixConnection = MatrixConnection; -function Label(path) { +function Label(path, description) { if (path) { this.basePath = path; } + if (description) { + this.description = description; + } } Label.decode = function(ber) { @@ -1365,12 +1422,12 @@ Label.decode = function(ber) { Label.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(18)); - if (this.basePath !== undefined) { + if (this.basePath != null) { ber.startSequence(BER.CONTEXT(0)); ber.writeRelativeOID(this.basePath, BER.EMBER_RELATIVE_OID); ber.endSequence(); } - if (this.description !== undefined) { + if (this.description != null) { ber.startSequence(BER.CONTEXT(1)); ber.writeString(this.description, BER.EMBER_STRING); ber.endSequence(); @@ -1450,6 +1507,10 @@ QualifiedMatrix.prototype.validateConnection = function(targetID, sources) { validateConnection(this, targetID, sources); } +QualifiedMatrix.prototype.canConnect = function(targetID, sources, operation) { + canConnect(this, targetID, sources, operation); +} + QualifiedMatrix.decode = function(ber) { var qm = new QualifiedMatrix(); ber = ber.getSequence(BER.APPLICATION(17)); diff --git a/package.json b/package.json index c0d839d..7812d6c 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.10.0", + "version": "1.10.1", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index ed0e506..322e530 100755 --- a/server.js +++ b/server.js @@ -528,9 +528,20 @@ const parseMatrixContent = function(matrixContent, content) { if (content.labels) { matrixContent.labels = []; for(let l = 0; l < content.labels.length; l++) { - matrixContent.labels.push( - new ember.Label(content.labels[l]) - ); + if (typeof (content.labels[l]) === "object") { + matrixContent.labels.push( + new ember.Label( + content.labels[l].basePath, + content.labels[l].description + ) + ); + } + else { + // for backward compatibility... Remove in the future + matrixContent.labels.push( + new ember.Label(content.labels[l]) + ); + } } delete content.labels; } diff --git a/test/utils.js b/test/utils.js index 103b542..84c3dca 100755 --- a/test/utils.js +++ b/test/utils.js @@ -51,7 +51,7 @@ const init = function(_src,_tgt) { targetCount: targets.length, sourceCount: sources.length, connections: buildConnections(sources, targets), - labels: ["0.1.1000"] + labels: [{basePath: "0.1.1000", description: "primary"}] }, { identifier: "labels", From dfbd5d14273832bca7a3e6410250bd399d384549 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 13 Dec 2019 16:09:46 +0100 Subject: [PATCH 097/162] Adding validation of connection request on server --- ember.js | 6 ++--- package.json | 2 +- server.js | 56 +++++++++++++++++++++++++++------------------ test/Server.test.js | 44 +++++++++++++++++++++++++++++++++-- 4 files changed, 80 insertions(+), 28 deletions(-) diff --git a/ember.js b/ember.js index 1a99f45..bf512c9 100755 --- a/ember.js +++ b/ember.js @@ -843,7 +843,7 @@ function canConnect(matrixNode, targetID, sources, operation) { } if (mode === MatrixMode.linear) { for(let s = 0; s < sources.length; s++) { - for(let i = 0; i < matrixNode.targetCount; i++) { + for(let i = 0; i < matrixNode.contents.targetCount; i++) { const connection = matrixNode.connections[i]; if (connection == null || connection.sources == null) { continue; @@ -966,7 +966,7 @@ MatrixNode.prototype.validateConnection = function(targetID, sources) { } MatrixNode.prototype.canConnect = function(targetID, sources, operation) { - canConnect(this, targetID, sources, operation); + return canConnect(this, targetID, sources, operation); } MatrixNode.prototype.encode = function(ber) { @@ -1508,7 +1508,7 @@ QualifiedMatrix.prototype.validateConnection = function(targetID, sources) { } QualifiedMatrix.prototype.canConnect = function(targetID, sources, operation) { - canConnect(this, targetID, sources, operation); + return canConnect(this, targetID, sources, operation); } QualifiedMatrix.decode = function(ber) { diff --git a/package.json b/package.json index 7812d6c..19a9981 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.10.1", + "version": "1.10.2", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 322e530..3ca056c 100755 --- a/server.js +++ b/server.js @@ -226,7 +226,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti if (this._debug) { console.log("Handling Matrix Connection"); } - if (client.request.isQualified()) { + if (client != null && client.request.isQualified()) { root = new ember.Root(); res = new ember.QualifiedMatrix(matrix.getPath()); root.elements = [res]; // do not use addchild or the element will get removed from the tree. @@ -246,27 +246,37 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti res.connections[connection.target] = conResult; - // Apply changes - - if ((connection.operation === undefined) || - (connection.operation.value == ember.MatrixOperation.absolute)) { - matrix.connections[connection.target].setSources(connection.sources); - emitType = "matrix-change"; - } - else if (connection.operation == ember.MatrixOperation.connect) { - matrix.connections[connection.target].connectSources(connection.sources); - emitType = "matrix-connect"; - } - else { // Disconnect + if (((connection.operation === undefined) || + (connection.operation === ember.MatrixOperation.connect)) && + (matrix.canConnect(connection.target,connection.sources,connection.operation))) { + // Apply changes + if ((connection.operation === undefined) || + (connection.operation.value == ember.MatrixOperation.absolute)) { + matrix.connections[connection.target].setSources(connection.sources); + emitType = "matrix-change"; + } + else if (connection.operation == ember.MatrixOperation.connect) { + matrix.connections[connection.target].connectSources(connection.sources); + emitType = "matrix-connect"; + } + conResult.disposition = ember.MatrixDisposition.modified; + } + else if (connection.operarion === ember.MatrixOperation.disconnect) { // Disconnect matrix.connections[connection.target].disconnectSources(connection.sources); + conResult.disposition = ember.MatrixDisposition.modified; emitType = "matrix-disconnect"; } + else { + if (this._debug) { + console.log(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); + } + conResult.disposition = ember.MatrixDisposition.tally; + return; + } // Send response or update subscribers. - - if (response) { - conResult.sources = matrix.connections[connection.target].sources; - conResult.disposition = ember.MatrixDisposition.modified; + conResult.sources = matrix.connections[connection.target].sources; + if (response) { // We got a request so emit something. this.emit(emitType, { target: connection.target, @@ -277,17 +287,19 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti else { // the action has been applied. So we should either send the current state (absolute) // or send the action itself (connection.sources) - conResult.sources = matrix.connections[connection.target].sources; conResult.operation = ember.MatrixOperation.absolute; } } if (client !== undefined) { client.sendBERNode(root); } - if (this._debug) { - console.log("Updating subscribers for matrix change"); - } - this.updateSubscribers(matrix.getPath(), root, client); + + if (conResult.disposition !== ember.MatrixDisposition.tally) { + if (this._debug) { + console.log("Updating subscribers for matrix change"); + } + this.updateSubscribers(matrix.getPath(), root, client); + } } const validateMatrixOperation = function(matrix, target, sources) { diff --git a/test/Server.test.js b/test/Server.test.js index 4a63399..49570d1 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -14,7 +14,6 @@ const wait = function(t) { } describe("server", function() { - describe("JSONtoTree", function() { let jsonTree; beforeAll(function() { @@ -51,7 +50,7 @@ describe("server", function() { afterAll(function() { client.disconnect(); server.close(); - }) + }); it("should receive and decode the full tree", function () { client = new DeviceTree(LOCALHOST, PORT); //client._debug = true; @@ -231,4 +230,45 @@ describe("server", function() { }); }); }); + describe("Matrix Connect", function() { + let jsonTree; + let server; + beforeEach(function() { + jsonTree = jsonRoot(); + const root = TreeServer.JSONtoTree(jsonTree); + server = new TreeServer(LOCALHOST, PORT, root); + }); + it("should verify if connection allowed in 1-to-N", function() { + const matrix = server.tree.elements[0].children[1].children[0]; + const connection = new ember.MatrixConnection(0); + connection.setSources([1]); + connection.operation = ember.MatrixOperation.connect; + let res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + matrix.connections[0].sources = [0]; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + connection.operation = ember.MatrixOperation.absolute; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + }); + it("should verify if connection allowed in 1-to-1", function() { + const matrix = server.tree.elements[0].children[1].children[0]; + matrix.contents.type = ember.MatrixType.oneToOne; + const connection = new ember.MatrixConnection(0); + connection.setSources([1]); + connection.operation = ember.MatrixOperation.connect; + let res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + matrix.connections[0].sources = [0]; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + connection.operation = ember.MatrixOperation.absolute; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + matrix.connections[1].sources = [1]; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + }); + }); }); From 22fa47cc82f902e6af1dc1770f4d9f2ddc613d6d Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 13 Dec 2019 16:18:02 +0100 Subject: [PATCH 098/162] fix contributors --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 19a9981..d911c42 100755 --- a/package.json +++ b/package.json @@ -8,9 +8,10 @@ "eslint": "eslint ./", "start": "node server.js" }, - "author": "Brian Mayton (http://bdm.cc)", + "author": "Gilles Dufour (www.gdnet.be)", "contributors": [ - "Gilles Dufour (www.gdnet.be)" + "Gilles Dufour (www.gdnet.be)", + "Brian Mayton (http://bdm.cc)", ], "repository": { "type": "git", From 5a830dee324e113a0b4a83258bdaac1cb816784d Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 13 Dec 2019 16:18:38 +0100 Subject: [PATCH 099/162] fix contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d911c42..519fc1d 100755 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "author": "Gilles Dufour (www.gdnet.be)", "contributors": [ "Gilles Dufour (www.gdnet.be)", - "Brian Mayton (http://bdm.cc)", + "Brian Mayton (http://bdm.cc)" ], "repository": { "type": "git", From 876ed096d8697104b4060c918939bccf832d0658 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 14 Dec 2019 12:08:34 +0100 Subject: [PATCH 100/162] Fixing getDirectory when root has more than 1 elements --- device.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/device.js b/device.js index c5e9272..7265ae6 100755 --- a/device.js +++ b/device.js @@ -141,7 +141,7 @@ DeviceTree.prototype.expand = function (node) { }; function isDirectSubPathOf(path, parent) { - return path.lastIndexOf('.') === parent.length && path.startsWith(parent) + return path === parent || (path.lastIndexOf('.') === parent.length && path.startsWith(parent)); } DeviceTree.prototype.getDirectory = function (qnode) { diff --git a/package.json b/package.json index 519fc1d..4cfe896 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.10.2", + "version": "1.10.3", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 42d351c52e8eabe75a0b4e2a1d7c367f3e297239 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 14 Dec 2019 16:18:36 +0100 Subject: [PATCH 101/162] Added support for subscribe and unsubscribe of streams --- device.js | 42 +++++++++++++---- ember.js | 38 ++++++++++++++- package.json | 2 +- server.js | 12 +++-- test/Server.test.js | 111 ++++++++++++++++++++++++++++++++++++++++---- test/utils.js | 2 +- 6 files changed, 183 insertions(+), 24 deletions(-) diff --git a/device.js b/device.js index 7265ae6..2d02c72 100755 --- a/device.js +++ b/device.js @@ -563,19 +563,45 @@ DeviceTree.prototype.getNodeByPath = function (path) { return getNext(); }; -DeviceTree.prototype.subscribe = function (node, callback) { - if (node instanceof ember.Parameter && node.isStream()) { - // TODO: implement +DeviceTree.prototype.subscribe = function (qnode, callback) { + if (qnode.isParameter() && qnode.isStream()) { + var self = this; + if (qnode == null) { + self.root.clear(); + qnode = self.root; + } + return new Promise((resolve, reject) => { + self.addRequest({node: qnode, func: (error) => { + if (self._debug) { + console.log("Sending subscribe", qnode); + } + self.client.sendBERNode(qnode.subscribe(callback)); + self.finishRequest(); + resolve(); + }}); + }); } else { node.addCallback(callback); } }; -DeviceTree.prototype.unsubscribe = function (node, callback) { - if (node instanceof ember.Parameter && node.isStream()) { - // TODO: implement - } else { - node.addCallback(callback); +DeviceTree.prototype.unsubscribe = function (qnode, callback) { + if (qnode.isParameter() && qnode.isStream()) { + var self = this; + if (qnode == null) { + self.root.clear(); + qnode = self.root; + } + return new Promise((resolve, reject) => { + self.addRequest({node: qnode, func: (error) => { + if (self._debug) { + console.log("Sending subscribe", qnode); + } + self.client.sendBERNode(qnode.unsubscribe(callback)); + self.finishRequest(); + resolve(); + }}); + }); } }; diff --git a/ember.js b/ember.js index bf512c9..a134f3c 100755 --- a/ember.js +++ b/ember.js @@ -185,8 +185,8 @@ TreeNode.prototype.isQualified = function() { } TreeNode.prototype.isStream = function() { - return this.contents !== undefined && - this.contents.streamDescriptor !== undefined; + return this.contents != null && + this.contents.streamIdentifier != null; } TreeNode.prototype.addCallback = function(callback) { @@ -273,6 +273,9 @@ TreeNode.prototype.subscribe = function(callback) { if(callback !== undefined) { this._directoryCallbacks.push((error, node) => { callback(error, node) }); } + if (this.isParameter() && this.isStream()) { + this.contents.subscribers.add(callback); + } return this.getTreeBranch(new Command(COMMAND_SUBSCRIBE)); } @@ -280,6 +283,9 @@ TreeNode.prototype.unsubscribe = function(callback) { if(callback !== undefined) { this._directoryCallbacks.push((error, node) => { callback(error, node) }); } + if (this.isParameter() && this.isStream()) { + this.contents.subscribers.delete(callback); + } return this.getTreeBranch(new Command(COMMAND_UNSUBSCRIBE)); } @@ -680,6 +686,9 @@ QualifiedNode.prototype.subscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } + if (this.isStream()) { + this.contents.subscribers.add(callback); + } return QualifiedNodeCommand(this, COMMAND_SUBSCRIBE, callback) } @@ -687,6 +696,9 @@ QualifiedNode.prototype.unsubscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } + if (this.isStream()) { + this.contents.subscribers.delete(callback); + } return QualifiedNodeCommand(this, COMMAND_UNSUBSCRIBE, callback) } @@ -813,6 +825,9 @@ Node.prototype.subscribe = function(callback) { if(this._callbacks.indexOf(callback) < 0) { this._callbacks.push(callback); } + if (this.isStream()) { + this.contents.subscribers.add(callback); + } } module.exports.Node = Node; @@ -1624,6 +1639,9 @@ QualifiedMatrix.prototype.subscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } + if (this.isStream()) { + this.contents.subscribers.add(callback); + } return QualifiedMatrixCommand(this, COMMAND_SUBSCRIBE, callback); } @@ -1631,6 +1649,9 @@ QualifiedMatrix.prototype.unsubscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } + if (this.isStream()) { + this.contents.subscribers.delete(callback); + } return QualifiedMatrixCommand(this, COMMAND_UNSUBSCRIBE, callback); } @@ -2519,6 +2540,9 @@ QualifiedParameter.prototype.update = function(other) { this.contents[key] = other.contents[key]; } } + for(let cb of this.contents.subscribers) { + cb(); + } } } return callbacks; @@ -2550,6 +2574,9 @@ QualifiedParameter.prototype.subscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } + if (this.isStream()) { + this.contents.subscribers.add(callback); + } return QualifiedParameterCommand(this, COMMAND_SUBSCRIBE, callback); } @@ -2557,6 +2584,9 @@ QualifiedParameter.prototype.unsubscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } + if (this.isStream()) { + this.contents.subscribers.delete(callback); + } return QualifiedParameterCommand(this, COMMAND_UNSUBSCRIBE, callback); } @@ -2665,6 +2695,9 @@ Parameter.prototype.update = function(other) { if (other.contents.hasOwnProperty(key)) { this.contents[key] = other.contents[key]; } + } + for(let cb of this.contents.subscribers) { + cb(); } } } @@ -2730,6 +2763,7 @@ module.exports.ParameterAccess = ParameterAccess; module.exports.ParameterType = ParameterType; function ParameterContents(value, type) { + this.subscribers = new Set(); if(value !== undefined) { this.value = value; } diff --git a/package.json b/package.json index 4cfe896..11ead78 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.10.3", + "version": "1.10.4", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 3ca056c..056d830 100755 --- a/server.js +++ b/server.js @@ -221,7 +221,7 @@ TreeServer.prototype.handleQualifiedParameter = function(client, element, parame TreeServer.prototype.handleMatrixConnections = function(client, matrix, connections, response = true) { - var res; + var res,conResult; var root; // ember message root if (this._debug) { console.log("Handling Matrix Connection"); @@ -241,7 +241,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti continue; } let connection = connections[id]; - let conResult = new ember.MatrixConnection(connection.target); + conResult = new ember.MatrixConnection(connection.target); let emitType; res.connections[connection.target] = conResult; @@ -294,7 +294,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti client.sendBERNode(root); } - if (conResult.disposition !== ember.MatrixDisposition.tally) { + if (conResult != null && conResult.disposition !== ember.MatrixDisposition.tally) { if (this._debug) { console.log("Updating subscribers for matrix change"); } @@ -451,10 +451,16 @@ TreeServer.prototype.handleGetDirectory = function(client, element) { } TreeServer.prototype.handleSubscribe = function(client, element) { + if (this._debug) { + console.log("subscribe"); + } this.subscribe(client, element); } TreeServer.prototype.handleUnSubscribe = function(client, element) { + if (this._debug) { + console.log("unsubscribe"); + } this.unsubscribe(client, element); } diff --git a/test/Server.test.js b/test/Server.test.js index 49570d1..2c0a0e5 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -48,8 +48,7 @@ describe("server", function() { }); }); afterAll(function() { - client.disconnect(); - server.close(); + return server.close(); }); it("should receive and decode the full tree", function () { client = new DeviceTree(LOCALHOST, PORT); @@ -154,14 +153,14 @@ describe("server", function() { client.root.getNodeByPath(client.client, ["scoreMaster", "router", "labels"], (err, child) => { if (err) { reject(err) } else { - resolve(child); + resolve(child); } }); }); }) .then(child => { console.log(child); - client.disconnect(); + return client.disconnect(); }); }); it("should be able to get child with tree.getNodeByPath", function() { @@ -182,7 +181,7 @@ describe("server", function() { }) .then(child => { console.log("router/labels", child); - client.disconnect(); + return client.disconnect(); }); }); it("should throw an error if getNodeByPath for unknown path", function() { @@ -200,9 +199,9 @@ describe("server", function() { throw new Error("Should not succeed"); }) .catch(e => { - client.disconnect(); console.log(e); expect(e.message).toMatch(/Failed path discovery/); + return client.disconnect(); }); }); it("should be able to make a matrix connection", () => { @@ -229,7 +228,7 @@ describe("server", function() { return client.disconnect(); }); }); - }); + }); describe("Matrix Connect", function() { let jsonTree; let server; @@ -240,7 +239,7 @@ describe("server", function() { }); it("should verify if connection allowed in 1-to-N", function() { const matrix = server.tree.elements[0].children[1].children[0]; - const connection = new ember.MatrixConnection(0); + let connection = new ember.MatrixConnection(0); connection.setSources([1]); connection.operation = ember.MatrixOperation.connect; let res = matrix.canConnect(connection.target,connection.sources,connection.operation); @@ -251,6 +250,11 @@ describe("server", function() { connection.operation = ember.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); + connection = new ember.MatrixConnection(1); + connection.operation = ember.MatrixOperation.absolute; + connection.setSources([1]); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); }); it("should verify if connection allowed in 1-to-1", function() { const matrix = server.tree.elements[0].children[1].children[0]; @@ -266,9 +270,98 @@ describe("server", function() { connection.operation = ember.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); + matrix.connections[0].sources = []; matrix.connections[1].sources = [1]; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); }); - }); + }); + describe("Parameters subscribe/unsubscribe", function( ){ + let jsonTree; + let server; + beforeAll(function() { + jsonTree = jsonRoot(); + const root = TreeServer.JSONtoTree(jsonTree); + server = new TreeServer(LOCALHOST, PORT, root); + server.on("error", e => { + console.log(e); + }); + server.on("clientError", e => { + console.log(e); + }); + return server.listen(); + }); + afterAll(function() { + return server.close(); + }); + it("should not auto subscribe stream parameter", function() { + const parameter = server.tree.elements[0].children[0].children[2]; + console.log(parameter); + expect(parameter.isStream()).toBeTruthy(); + expect(server.subscribers["0.0.2"]).not.toBeDefined(); + }); + it("should be able subscribe to parameter changes", function() { + const client = new DeviceTree(LOCALHOST, PORT); + const cb = () => { + return "updated"; + } + //client._debug = true; + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + return client.getDirectory(); + }) + .then(() => client.getNodeByPathnum("0.0.2")) + .then(parameter => { + console.log(parameter); + expect(server.subscribers["0.0.2"]).not.toBeDefined(); + expect(parameter.contents.subscribers).toBeDefined(); + expect(parameter.contents.subscribers.size).toBe(0); + server._subscribe = server.subscribe; + let _resolve; + const p = new Promise((resolve, reject) => { + _resolve = resolve; + }); + server.subscribe = (c,e) => { + server._subscribe(c,e); + _resolve(); + }; + return client.subscribe(parameter, cb).then(() => (p)) + }) + .then(() => { + expect(server.subscribers["0.0.2"]).toBeDefined(); + expect(server.subscribers["0.0.2"].size).toBe(1); + return client.getNodeByPathnum("0.0.2"); + }) + .then(parameter => { + expect(parameter.contents.subscribers).toBeDefined(); + expect(parameter.contents.subscribers.size).toBe(1); + server._unsubscribe = server.unsubscribe; + let _resolve; + const p = new Promise((resolve, reject) => { + _resolve = resolve; + }); + server.unsubscribe = (c,e) => { + console.log("unsubscribe"); + server._unsubscribe(c,e); + _resolve(); + }; + console.log(parameter); + return client.unsubscribe(parameter, cb).then(() => (p)) + }) + .then(() => { + console.log(server.subscribers); + expect(server.subscribers["0.0.2"]).toBeDefined(); + return client.getNodeByPathnum("0.0.2"); + }) + .then(parameter => { + console.log(parameter); + expect(server.subscribers["0.0.2"]).toBeDefined(); + expect(server.subscribers["0.0.2"].size).toBe(0); + expect(parameter.contents.subscribers).toBeDefined(); + expect(parameter.contents.subscribers.size).toBe(0); + }) + .then(() => client.disconnect()); + }); + }); }); diff --git a/test/utils.js b/test/utils.js index 84c3dca..ad7a396 100755 --- a/test/utils.js +++ b/test/utils.js @@ -35,7 +35,7 @@ const init = function(_src,_tgt) { children: [ {identifier: "product", value: "S-CORE Master"}, {identifier: "company", value: "EVS", access: "readWrite"}, - {identifier: "version", value: "1.2.0"}, + {identifier: "version", value: "1.2.0", streamIdentifier: 1234567}, {identifier: "author", value: "g.dufour@evs.com"}, ] }, From 58bc2d856956800032301f9eefea76f36ddd8a86 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 14 Dec 2019 16:42:42 +0100 Subject: [PATCH 102/162] fixing matrix absolute connect --- package.json | 2 +- server.js | 1 + test/Server.test.js | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 11ead78..c128d03 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.10.4", + "version": "1.10.5", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 056d830..58ee1c8 100755 --- a/server.js +++ b/server.js @@ -247,6 +247,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti if (((connection.operation === undefined) || + (connection.operation === ember.MatrixOperation.absolute) || (connection.operation === ember.MatrixOperation.connect)) && (matrix.canConnect(connection.target,connection.sources,connection.operation))) { // Apply changes diff --git a/test/Server.test.js b/test/Server.test.js index 2c0a0e5..5a5f1b8 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -275,6 +275,40 @@ describe("server", function() { res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); }); + it("should return modified answer on absolute connect", function() { + let client; + server.on("error", e => { + console.log(e); + }); + server.on("clientError", e => { + console.log(e); + }); + //server._debug = true; + return server.listen() + .then(() => { + client = new DeviceTree(LOCALHOST, PORT); + return Promise.resolve() + }) + .then(() => client.connect()) + .then(() => client.getDirectory()) + .then(() => client.getNodeByPathnum("0.1.0")) + .then(matrix => { + console.log(matrix); + return client.matrixSetConnection(matrix, 0, [1]); + }) + .then(result => { + console.log(result); + expect(result).toBeDefined(); + expect(result.connections).toBeDefined(); + expect(result.connections[0]).toBeDefined(); + expect(result.connections[0].disposition).toBe(ember.MatrixDisposition.modified); + return client.disconnect(); + }) + .then(() => { + console.log("closing server"); + server.close(); + }); + }); }); describe("Parameters subscribe/unsubscribe", function( ){ let jsonTree; From fd31d8d0ebf5edd2895fdec7a515f46002bbeca1 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 16 Dec 2019 10:45:18 +0100 Subject: [PATCH 103/162] Better error when doing getNodeByPath --- README.md | 8 ++++---- device.js | 6 ++++-- package.json | 2 +- test/utils.js | 9 +++++---- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9620414..ca6ec73 100755 --- a/README.md +++ b/README.md @@ -136,11 +136,11 @@ const {ParameterType, FunctionArgument} = require("emberplus").Ember; const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; -const labels = function(endpoints) { +const labels = function(endpoints, type) { let labels = []; for (let i = 0; i < endpoints.length; i++) { let endpoint = endpoints[i]; - let l = { identifier: `Label-${i}` }; + let l = { identifier: `${type}-${i}` }; if (endpoint) { l.value = endpoint; } @@ -194,13 +194,13 @@ const jsonTree = [ identifier: "targets", // Must be 1 number: 1, - children: labels(targets) + children: labels(targets, "t") }, { identifier: "sources", // Must be 2 number: 2, - children: labels(sources) + children: labels(sources, "s") }, { identifier: "group 1", diff --git a/device.js b/device.js index 2d02c72..5233eee 100755 --- a/device.js +++ b/device.js @@ -492,6 +492,7 @@ DeviceTree.prototype.getNodeByPathnum = function (path) { if (typeof path === 'string') { path = path.split('.'); } + var pathnumError = new Error(`Failed path discovery at ${path.slice(0, pos).join("/")}`); var pos = 0; var lastMissingPos = -1; var currentNode = this.root; @@ -516,7 +517,7 @@ DeviceTree.prototype.getNodeByPathnum = function (path) { } // We do not have that node yet. if (lastMissingPos === pos) { - throw new Error(`Failed path discovery at ${path.slice(0, pos).join("/")}`); + throw pathnumError; } lastMissingPos = pos; return this.getDirectory(currentNode).then(() => getNext()); @@ -530,6 +531,7 @@ DeviceTree.prototype.getNodeByPath = function (path) { if (typeof path === 'string') { path = path.split('/'); } + var pathError = new Error(`Failed path discovery at ${path.slice(0, pos + 1).join("/")}`); var pos = 0; var lastMissingPos = -1; var currentNode = this.root; @@ -554,7 +556,7 @@ DeviceTree.prototype.getNodeByPath = function (path) { } // We do not have that node yet. if (lastMissingPos === pos) { - throw new Error(`Failed path discovery at ${path.slice(0, pos + 1).join("/")}`); + throw pathError; } lastMissingPos = pos; return this.getDirectory(currentNode).then(() => getNext()); diff --git a/package.json b/package.json index c128d03..ebf91b8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.10.5", + "version": "1.10.6", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/test/utils.js b/test/utils.js index ad7a396..b0ae7c0 100755 --- a/test/utils.js +++ b/test/utils.js @@ -1,13 +1,14 @@ const {ParameterType, FunctionArgument} = require("../ember"); + const init = function(_src,_tgt) { const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; - const labels = function(endpoints) { + const labels = function(endpoints, type) { let labels = []; for (let i = 0; i < endpoints.length; i++) { let endpoint = endpoints[i]; - let l = { identifier: `Label-${i}` }; + let l = { identifier: `${type}-${i}` }; if (endpoint) { l.value = endpoint; } @@ -62,13 +63,13 @@ const init = function(_src,_tgt) { identifier: "targets", // Must be 1 number: 1, - children: labels(targets) + children: labels(targets, "t") }, { identifier: "sources", // Must be 2 number: 2, - children: labels(sources) + children: labels(sources, "s") }, { identifier: "group 1", From 88594780ecb22818b0a6b47de4ee97b1d1156e40 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 18 Dec 2019 17:46:23 +0100 Subject: [PATCH 104/162] handle connection for 1-to-1 matrix --- ember.js | 135 ++++++++++++++++++++++++++++++++++---------- package.json | 2 +- server.js | 67 +++++++++++++--------- test/Server.test.js | 61 +++++++++++++++++++- 4 files changed, 203 insertions(+), 62 deletions(-) diff --git a/ember.js b/ember.js index a134f3c..510d598 100755 --- a/ember.js +++ b/ember.js @@ -838,8 +838,11 @@ module.exports.Node = Node; function MatrixNode(number) { MatrixNode.super_.call(this); - if(number !== undefined) + if(number !== undefined) { this.number = number; + } + this._connectedSources = {}; + this._numConnections = 0; } function canConnect(matrixNode, targetID, sources, operation) { @@ -856,39 +859,26 @@ function canConnect(matrixNode, targetID, sources, operation) { if (sMap.size > 1) { return false; } - if (mode === MatrixMode.linear) { - for(let s = 0; s < sources.length; s++) { - for(let i = 0; i < matrixNode.contents.targetCount; i++) { - const connection = matrixNode.connections[i]; - if (connection == null || connection.sources == null) { - continue; - } - for(let j = 0; j < connection.sources.length; j++) { - if (connection.sources[j] === sources[s] && targetID !== i) { - // This source is already connected to another target - return false; - } - } - } - } + const sourceConnections = matrixNode._connectedSources[sources[0]]; + return sourceConnections == null || sourceConnections.size === 0 || sourceConnections.has(targetID); + } + else { + // N to N + if (matrixNode.contents.maximumConnectsPerTarget != null && + newSources.length > matrixNode.contents.maximumConnectsPerTarget) { + return false; } - else { - for(let s = 0; s < sources.length; s++) { - for(let t = 0; t < matrixNode.targets.length; t++) { - const target = matrixNode.targets[t]; - const connection = matrixNode.connections[target]; - if (connection == null || connection.sources == null) { - continue; - } - for(let j = 0; j < connection.sources.length; j++) { - if (connection.sources[j] === sources[s] && targetID !== target) { - // This source is already connected to another target - return false; - } - } - } + if (matrixNode.contents.maximumTotalConnects != null) { + let count = matrixNode._numConnections; + if (oldSources) { + count -= oldSources.length; } + if (newSources) { + count += newSources.length; + } + return count <= matrixNode.contents.maximumTotalConnects; } + } return true; } @@ -1063,6 +1053,74 @@ MatrixNode.prototype.encode = function(ber) { ber.endSequence(); // BER.APPLICATION(3) } +MatrixNode.prototype.setSources = function(targetID, sources) { + return MatrixNode.setSources(this, targetID, sources); +} +MatrixNode.prototype.connectSources = function(targetID, sources) { + return MatrixNode.connectSources(this, targetID, sources); +} +MatrixNode.prototype.disconnectSources = function(targetID, sources) { + return MatrixNode.disconnectSources(this, targetID, sources); +} +MatrixNode.prototype.getSourceConnections = function(source) { + return MatrixNode.getSourceConnections(this, source); +} + +MatrixNode.getSourceConnections = function(matrix, source) { + const targets = matrix._connectedSources[source]; + if (targets) { + return [...targets]; + } + return []; +} + +MatrixNode.setSources = function(matrix, targetID, sources) { + const currentSource = matrix.connections[targetID] == null || matrix.connections[targetID].sources == null ? + [] : matrix.connections[targetID].sources; + if (currentSource.length > 0) { + MatrixNode.disconnectSources(matrix, targetID, currentSource) + } + MatrixNode.connectSources(matrix, targetID, sources); +} + +MatrixNode.connectSources = function(matrix, targetID, sources) { + const target = Number(targetID); + if (matrix.connections[target] == null) { + matrix.connections[target] = new MatrixConnection(target); + } + matrix.connections[target].connectSources(sources); + if (sources != null) { + for(let source of sources) { + if (matrix._connectedSources[source] == null) { + matrix._connectedSources[source] = new Set(); + } + if (!matrix._connectedSources[source].has(target)) { + matrix._connectedSources[source].add(target); + matrix._numConnections++; + } + } + } +} + +MatrixNode.disconnectSources = function(matrix, targetID, sources) { + const target = Number(targetID); + if (matrix.connections[target] == null) { + matrix.connections[target] = new MatrixConnection(target); + } + matrix.connections[target].disconnectSources(sources); + if (sources != null) { + for(let source of sources) { + if (matrix._connectedSources[source] == null) { + continue; + } + if (matrix._connectedSources[source].has(target)) { + matrix._connectedSources[source].delete(target); + matrix._numConnections--; + } + } + } +} + MatrixNode.prototype.update = function(other) { callbacks = MatrixNode.super_.prototype.update.apply(this); MatrixUpdate(this, other); @@ -1494,6 +1552,8 @@ function QualifiedMatrix(path) { if (path != undefined) { this.path = path; } + this._connectedSources = {}; + this._numConnections = 0; } util.inherits(QualifiedMatrix, TreeNode); @@ -1526,6 +1586,19 @@ QualifiedMatrix.prototype.canConnect = function(targetID, sources, operation) { return canConnect(this, targetID, sources, operation); } +QualifiedMatrix.prototype.setSources = function(targetID, sources) { + return MatrixNode.setSources(this, targetID, sources); +} +QualifiedMatrix.prototype.connectSources = function(targetID, sources) { + return MatrixNode.connectSources(this, targetID, sources); +} +QualifiedMatrix.prototype.disconnectSources = function(targetID, sources) { + return MatrixNode.disconnectSources(this, targetID, sources); +} +QualifiedMatrix.prototype.getSourceConnections = function(source) { + return MatrixNode.getSourceConnections(this, source); +} + QualifiedMatrix.decode = function(ber) { var qm = new QualifiedMatrix(); ber = ber.getSequence(BER.APPLICATION(17)); diff --git a/package.json b/package.json index ebf91b8..f234479 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.10.6", + "version": "1.11.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 58ee1c8..e36390d 100755 --- a/server.js +++ b/server.js @@ -70,7 +70,7 @@ util.inherits(TreeServer, EventEmitter); TreeServer.prototype.listen = function() { return new Promise((resolve, reject) => { this.callback = (e) => { - if (e === undefined) { + if (e == null) { return resolve(); } return reject(e); @@ -82,7 +82,7 @@ TreeServer.prototype.listen = function() { TreeServer.prototype.close = function () { return new Promise((resolve, reject) => { this.callback = (e) => { - if (e === undefined) { + if (e == null) { return resolve(); } return reject(e); @@ -92,7 +92,7 @@ TreeServer.prototype.close = function () { }; TreeServer.prototype.handleRoot = function(client, root) { - if ((root === undefined) || (root.elements === undefined) || (root.elements < 1)) { + if ((root == null) || (root.elements == null) || (root.elements < 1)) { this.emit("error", new Error("invalid request")); return; } @@ -153,7 +153,7 @@ TreeServer.prototype.handleNode = function(client, node) { let element = node; let path = []; while(element !== undefined) { - if (element.number === undefined) { + if (element.number == null) { this.emit("error", "invalid request"); return; } @@ -170,7 +170,7 @@ TreeServer.prototype.handleNode = function(client, node) { } let cmd = element; - if (cmd === undefined) { + if (cmd == null) { this.emit("error", "invalid request"); return this.handleError(client); } @@ -245,25 +245,40 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti let emitType; res.connections[connection.target] = conResult; - - if (((connection.operation === undefined) || - (connection.operation === ember.MatrixOperation.absolute) || - (connection.operation === ember.MatrixOperation.connect)) && - (matrix.canConnect(connection.target,connection.sources,connection.operation))) { + if (matrix.contents.type === ember.MatrixType.oneToOne && + connection.operation !== ember.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 1) { + // if the source is being used already, disconnect it. + const targets = matrix.getSourceConnections(connection.sources[0]); + if (targets.length === 1) { + const disconnect = new ember.MatrixConnection(targets[0]); + disconnect.setSources([]); + disconnect.disposition = ember.MatrixDisposition.modified; + res.connections[targets[0]] = disconnect; + matrix.setSources(targets[0], []); + } + // if the target is connected already, disconnect it + if (matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length === 1) { + matrix.setSources(connection.target, []); + } + } + if (connection.operation !== ember.MatrixOperation.disconnect && + matrix.canConnect(connection.target,connection.sources,connection.operation)) { // Apply changes - if ((connection.operation === undefined) || + if ((connection.operation == null) || (connection.operation.value == ember.MatrixOperation.absolute)) { - matrix.connections[connection.target].setSources(connection.sources); + matrix.setSources(connection.target, connection.sources); emitType = "matrix-change"; } else if (connection.operation == ember.MatrixOperation.connect) { - matrix.connections[connection.target].connectSources(connection.sources); + matrix.connectSources(connection.target, connection.sources); emitType = "matrix-connect"; } conResult.disposition = ember.MatrixDisposition.modified; } else if (connection.operarion === ember.MatrixOperation.disconnect) { // Disconnect - matrix.connections[connection.target].disconnectSources(connection.sources); + matrix.disconnectSources(connection.target, connection.sources); conResult.disposition = ember.MatrixDisposition.modified; emitType = "matrix-disconnect"; } @@ -291,7 +306,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti conResult.operation = ember.MatrixOperation.absolute; } } - if (client !== undefined) { + if (client != null) { client.sendBERNode(root); } @@ -304,19 +319,19 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti } const validateMatrixOperation = function(matrix, target, sources) { - if (matrix === undefined) { + if (matrix == null) { throw new Error(`matrix not found with path ${path}`); } - if (matrix.contents === undefined) { + if (matrix.contents == null) { throw new Error(`invalid matrix at ${path} : no contents`); } - if (matrix.contents.targetCount === undefined) { + if (matrix.contents.targetCount == null) { throw new Error(`invalid matrix at ${path} : no targetCount`); } if ((target < 0) || (target >= matrix.contents.targetCount)) { throw new Error(`target id ${target} out of range 0 - ${matrix.contents.targetCount}`); } - if (sources.length === undefined) { + if (sources.length == null) { throw new Error("invalid sources format"); } } @@ -468,7 +483,7 @@ TreeServer.prototype.handleUnSubscribe = function(client, element) { TreeServer.prototype.subscribe = function(client, element) { const path = element.getPath(); - if (this.subscribers[path] === undefined) { + if (this.subscribers[path] == null) { this.subscribers[path] = new Set(); } this.subscribers[path].add(client); @@ -476,7 +491,7 @@ TreeServer.prototype.subscribe = function(client, element) { TreeServer.prototype.unsubscribe = function(client, element) { const path = element.getPath(); - if (this.subscribers[path] === undefined) { + if (this.subscribers[path] == null) { return; } this.subscribers[path].delete(client); @@ -506,7 +521,7 @@ TreeServer.prototype.setValue = function(element, value, origin, key) { TreeServer.prototype.replaceElement = function(element) { let path = element.getPath(); let parent = this.tree.getElementByPath(path); - if ((parent === undefined)||(parent._parent === undefined)) { + if ((parent == null)||(parent._parent == null)) { throw new Error(`Could not find element at path ${path}`); } parent = parent._parent; @@ -525,7 +540,7 @@ TreeServer.prototype.replaceElement = function(element) { TreeServer.prototype.updateSubscribers = function(path, response, origin) { - if (this.subscribers[path] === undefined) { + if (this.subscribers[path] == null) { return; } @@ -640,10 +655,8 @@ const parseObj = function(parent, obj) { if (! content.connections.hasOwnProperty(c)) { continue; } - let t = content.connections[c].target !== undefined ? content.connections[c].target : 0; - let connection = new ember.MatrixConnection(t); - connection.setSources(content.connections[c].sources); - emberElement.connections[t] = connection; + const t = content.connections[c].target != null ? content.connections[c].target : 0; + emberElement.setSources(t, content.connections[c].sources); } delete content.connections; } diff --git a/test/Server.test.js b/test/Server.test.js index 5a5f1b8..61c45d8 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -264,16 +264,71 @@ describe("server", function() { connection.operation = ember.MatrixOperation.connect; let res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); - matrix.connections[0].sources = [0]; + matrix.setSources(0, [0]); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + // We can't connect but in 1-on-1 server should disconnect existing source and connect new one. + server.handleMatrixConnections(null, matrix, {0: connection}, false); + expect(matrix.connections[0].sources[0]).toBe(1); + + connection.operation = ember.MatrixOperation.absolute; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + matrix.setSources(0, []); + matrix.setSources(1, [1]); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + }); + it("should verify if connection allowed in N-to-N", function() { + const matrix = server.tree.elements[0].children[1].children[0]; + matrix.contents.type = ember.MatrixType.nToN; + matrix.contents.maximumTotalConnects = 2; + matrix.setSources(0, [0,1]); + + const connection = new ember.MatrixConnection(0); + connection.setSources([2]); + connection.operation = ember.MatrixOperation.connect; + let res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + + matrix.setSources(2, [2]); + matrix.setSources(1, [1]); + matrix.setSources(0, []); res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); + + matrix.setSources(1, []); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + + matrix.setSources(0, [1,2]); + matrix.setSources(1, []); connection.operation = ember.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); - matrix.connections[0].sources = []; - matrix.connections[1].sources = [1]; + + + matrix.contents.maximumTotalConnects = 20; + matrix.contents.maximumConnectsPerTarget = 1; + + matrix.setSources(2, [2]); + matrix.setSources(1, [1]); + matrix.setSources(0, [0]); + connection.setSources([2]); + connection.operation = ember.MatrixOperation.connect; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); + + matrix.setSources(0, []); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + + matrix.setSources(0, [0]); + connection.operation = ember.MatrixOperation.absolute; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + }); it("should return modified answer on absolute connect", function() { let client; From 4df675c69932f46fb322a728c03b9f31cc755e9a Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 18 Dec 2019 17:57:38 +0100 Subject: [PATCH 105/162] add maximumTotalConnects --- server.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index e36390d..81204f5 100755 --- a/server.js +++ b/server.js @@ -587,7 +587,11 @@ const parseMatrixContent = function(matrixContent, content) { matrixContent.type = ember.MatrixType.oneToOne; } else if (content.type == "nToN") { - matrixContent.type = ember.MatrixType.nToN; + matrixContent.type = ember.MatrixType.nToN; + matrixContent.maximumTotalConnects = content.maximumTotalConnects == null ? + Number(content.targetCount) * Number(content.sourceCount) : Number(content.maximumTotalConnects); + matrixContent.maximumConnectsPerTarget = content.maximumConnectsPerTarget == null ? + Number(content.sourceCount) : Number(content.maximumConnectsPerTarget); } else { throw new Error(`Invalid matrix type ${content.type}`); From 2011b647634c8b33e469eb6d8eb8b086fd80e6be Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 18 Dec 2019 22:33:07 +0100 Subject: [PATCH 106/162] fix 1 to N matrix connect --- ember.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ember.js b/ember.js index 510d598..b63e1a8 100755 --- a/ember.js +++ b/ember.js @@ -852,9 +852,14 @@ function canConnect(matrixNode, targetID, sources, operation) { const oldSources = connection == null || connection.sources == null ? [] : connection.sources.slice(); const newSources = operation === MatrixOperation.absolute ? sources : oldSources.concat(sources); const sMap = new Set(newSources.map(i => Number(i))); - if (type === MatrixType.oneToN) { + if (type === MatrixType.oneToN && + matrixNode.contents.maximumConnectsPerTarget == null && + matrixNode.contents.maximumConnectsPerTarget == null) { return sMap.size < 2; } + else if (type === MatrixType.oneToN && sMap.size >= 2) { + return false; + } else if (type === MatrixType.oneToOne) { if (sMap.size > 1) { return false; From 6d03d10d93e51ed0d38d13253fb60ea00e8bacb7 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 19 Dec 2019 09:18:28 +0100 Subject: [PATCH 107/162] Fix 1toN. Disconnect old source and connect to new one --- package.json | 2 +- server.js | 20 +++++++++++--------- test/Server.test.js | 6 +++++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index f234479..1555442 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.11.0", + "version": "1.11.1", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 81204f5..072aceb 100755 --- a/server.js +++ b/server.js @@ -245,17 +245,19 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti let emitType; res.connections[connection.target] = conResult; - if (matrix.contents.type === ember.MatrixType.oneToOne && + if (matrix.contents.type !== ember.MatrixType.nToN && connection.operation !== ember.MatrixOperation.disconnect && connection.sources != null && connection.sources.length === 1) { - // if the source is being used already, disconnect it. - const targets = matrix.getSourceConnections(connection.sources[0]); - if (targets.length === 1) { - const disconnect = new ember.MatrixConnection(targets[0]); - disconnect.setSources([]); - disconnect.disposition = ember.MatrixDisposition.modified; - res.connections[targets[0]] = disconnect; - matrix.setSources(targets[0], []); + if (matrix.contents.type === ember.MatrixType.oneToOne) { + // if the source is being used already, disconnect it. + const targets = matrix.getSourceConnections(connection.sources[0]); + if (targets.length === 1) { + const disconnect = new ember.MatrixConnection(targets[0]); + disconnect.setSources([]); + disconnect.disposition = ember.MatrixDisposition.modified; + res.connections[targets[0]] = disconnect; + matrix.setSources(targets[0], []); + } } // if the target is connected already, disconnect it if (matrix.connections[connection.target].sources != null && diff --git a/test/Server.test.js b/test/Server.test.js index 61c45d8..e6da2a6 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -244,12 +244,16 @@ describe("server", function() { connection.operation = ember.MatrixOperation.connect; let res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); - matrix.connections[0].sources = [0]; + matrix.setSources(0, [0]); res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); connection.operation = ember.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); + // We can't connect. But server will disconnect existing source and connect new one. + server.handleMatrixConnections(null, matrix, {0: connection}, false); + expect(matrix.connections[0].sources[0]).toBe(1); + matrix.setSources(0, [0]); connection = new ember.MatrixConnection(1); connection.operation = ember.MatrixOperation.absolute; connection.setSources([1]); From 0283ce38126a06815bd44326b51f9aee21ed617a Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 19 Dec 2019 09:31:29 +0100 Subject: [PATCH 108/162] Server should emit disconnect when connecting target with new source --- package.json | 2 +- server.js | 16 ++++++++++++++-- test/Server.test.js | 20 +++++++++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1555442..d20eda5 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.11.1", + "version": "1.11.2", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 072aceb..3459f3b 100755 --- a/server.js +++ b/server.js @@ -251,18 +251,30 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti if (matrix.contents.type === ember.MatrixType.oneToOne) { // if the source is being used already, disconnect it. const targets = matrix.getSourceConnections(connection.sources[0]); - if (targets.length === 1) { + if (targets.length === 1 && targets[0] !== connection.target) { const disconnect = new ember.MatrixConnection(targets[0]); disconnect.setSources([]); disconnect.disposition = ember.MatrixDisposition.modified; res.connections[targets[0]] = disconnect; matrix.setSources(targets[0], []); + this.emit("matrix-disconnect", { + target: targets[0], + sources: connection.sources, + client: client == null ? null : client.remoteAddress() + }); } } // if the target is connected already, disconnect it if (matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length === 1) { + matrix.connections[connection.target].sources.length === 1 && + matrix.connections[connection.target].sources[0] !== connection.sources[0]) { + const source = matrix.connections[connection.target].sources[0]; matrix.setSources(connection.target, []); + this.emit("matrix-disconnect", { + target: connection.target, + sources: [source], + client: client == null ? null : client.remoteAddress() + }); } } if (connection.operation !== ember.MatrixOperation.disconnect && diff --git a/test/Server.test.js b/test/Server.test.js index e6da2a6..aaa6346 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -238,6 +238,11 @@ describe("server", function() { server = new TreeServer(LOCALHOST, PORT, root); }); it("should verify if connection allowed in 1-to-N", function() { + let disconnectCount = 0; + const handleDisconnect = info => { + disconnectCount++; + } + server.on("matrix-disconnect", handleDisconnect.bind(this)); const matrix = server.tree.elements[0].children[1].children[0]; let connection = new ember.MatrixConnection(0); connection.setSources([1]); @@ -253,6 +258,10 @@ describe("server", function() { // We can't connect. But server will disconnect existing source and connect new one. server.handleMatrixConnections(null, matrix, {0: connection}, false); expect(matrix.connections[0].sources[0]).toBe(1); + expect(disconnectCount).toBe(1); + // But if connecting same source and dest. Don't disconnect and reconnect. + server.handleMatrixConnections(null, matrix, {0: connection}, false); + expect(disconnectCount).toBe(1); matrix.setSources(0, [0]); connection = new ember.MatrixConnection(1); connection.operation = ember.MatrixOperation.absolute; @@ -262,6 +271,11 @@ describe("server", function() { }); it("should verify if connection allowed in 1-to-1", function() { const matrix = server.tree.elements[0].children[1].children[0]; + let disconnectCount = 0; + const handleDisconnect = info => { + disconnectCount++; + } + server.on("matrix-disconnect", handleDisconnect.bind(this)); matrix.contents.type = ember.MatrixType.oneToOne; const connection = new ember.MatrixConnection(0); connection.setSources([1]); @@ -274,7 +288,10 @@ describe("server", function() { // We can't connect but in 1-on-1 server should disconnect existing source and connect new one. server.handleMatrixConnections(null, matrix, {0: connection}, false); expect(matrix.connections[0].sources[0]).toBe(1); - + expect(disconnectCount).toBe(1); + // But if connecting same source and dest. Don't disconnect and reconnect. + server.handleMatrixConnections(null, matrix, {0: connection}, false); + expect(disconnectCount).toBe(1); connection.operation = ember.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); @@ -282,6 +299,7 @@ describe("server", function() { matrix.setSources(1, [1]); res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); + server.off("matrix-disconnect", handleDisconnect); }); it("should verify if connection allowed in N-to-N", function() { const matrix = server.tree.elements[0].children[1].children[0]; From a402f26f821d4aed6ba5c095cc1724b9be8da1bb Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 19 Dec 2019 09:38:15 +0100 Subject: [PATCH 109/162] Update doc --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index ca6ec73..696e011 100755 --- a/README.md +++ b/README.md @@ -126,6 +126,15 @@ server.on("error", e => { server.on("clientError", info => { console.log("clientError", info); }); +server.on("matrix-disconnect", info => { + console.log(`Client ${info.client} disconnected ${info.target} and ${info.sources}`); +} +server.on("matrix-connect", info => { + console.log(`Client ${info.client} connected ${info.target} and ${info.sources}`); +} +server.on("matrix-change", info => { + console.log(`Client ${info.client} changed ${info.target} and ${info.sources}`); +} server.listen().then(() => { console.log("listening"); }).catch((e) => { console.log(e.stack); }); ``` From b46b9176b775b9707e9481fb06e937548dc6a547 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 19 Dec 2019 12:11:08 +0100 Subject: [PATCH 110/162] Do a disconnect if attempting to connect same src and target --- package.json | 2 +- server.js | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index d20eda5..11f586e 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.11.2", + "version": "1.11.3", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 3459f3b..9e48e92 100755 --- a/server.js +++ b/server.js @@ -266,15 +266,21 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti } // if the target is connected already, disconnect it if (matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length === 1 && - matrix.connections[connection.target].sources[0] !== connection.sources[0]) { - const source = matrix.connections[connection.target].sources[0]; - matrix.setSources(connection.target, []); - this.emit("matrix-disconnect", { - target: connection.target, - sources: [source], - client: client == null ? null : client.remoteAddress() - }); + matrix.connections[connection.target].sources.length === 1) { + if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { + const source = matrix.connections[connection.target].sources[0]; + matrix.setSources(connection.target, []); + this.emit("matrix-disconnect", { + target: connection.target, + sources: [source], + client: client == null ? null : client.remoteAddress() + }); + } + else { + // let's change the request into a disconnect + connection.operation = ember.MatrixOperation.disconnect; + } + } } if (connection.operation !== ember.MatrixOperation.disconnect && From 61d9e01ed70445982258ce6c962223974d576874 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 19 Dec 2019 13:33:20 +0100 Subject: [PATCH 111/162] fix disconnect --- package.json | 2 +- server.js | 37 ++++++++++++++++++------------------- test/Server.test.js | 32 ++++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 11f586e..de77dbe 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.11.3", + "version": "1.11.4", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 9e48e92..57ee655 100755 --- a/server.js +++ b/server.js @@ -257,11 +257,13 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti disconnect.disposition = ember.MatrixDisposition.modified; res.connections[targets[0]] = disconnect; matrix.setSources(targets[0], []); - this.emit("matrix-disconnect", { - target: targets[0], - sources: connection.sources, - client: client == null ? null : client.remoteAddress() - }); + if (response) { + this.emit("matrix-disconnect", { + target: targets[0], + sources: connection.sources, + client: client == null ? null : client.remoteAddress() + }); + } } } // if the target is connected already, disconnect it @@ -270,11 +272,13 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { const source = matrix.connections[connection.target].sources[0]; matrix.setSources(connection.target, []); - this.emit("matrix-disconnect", { - target: connection.target, - sources: [source], - client: client == null ? null : client.remoteAddress() - }); + if (response) { + this.emit("matrix-disconnect", { + target: connection.target, + sources: [source], + client: client == null ? null : client.remoteAddress() + }); + } } else { // let's change the request into a disconnect @@ -297,7 +301,8 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti } conResult.disposition = ember.MatrixDisposition.modified; } - else if (connection.operarion === ember.MatrixOperation.disconnect) { // Disconnect + else if (connection.operation === ember.MatrixOperation.disconnect) { + // Disconnect matrix.disconnectSources(connection.target, connection.sources); conResult.disposition = ember.MatrixDisposition.modified; emitType = "matrix-disconnect"; @@ -307,7 +312,6 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti console.log(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); } conResult.disposition = ember.MatrixDisposition.tally; - return; } // Send response or update subscribers. @@ -317,14 +321,9 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti this.emit(emitType, { target: connection.target, sources: connection.sources, - client: client.remoteAddress() + client: client == null ? null : client.remoteAddress() }); - } - else { - // the action has been applied. So we should either send the current state (absolute) - // or send the action itself (connection.sources) - conResult.operation = ember.MatrixOperation.absolute; - } + } } if (client != null) { client.sendBERNode(root); diff --git a/test/Server.test.js b/test/Server.test.js index aaa6346..94c8873 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -256,12 +256,12 @@ describe("server", function() { res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); // We can't connect. But server will disconnect existing source and connect new one. - server.handleMatrixConnections(null, matrix, {0: connection}, false); + server.handleMatrixConnections(null, matrix, {0: connection}); expect(matrix.connections[0].sources[0]).toBe(1); expect(disconnectCount).toBe(1); - // But if connecting same source and dest. Don't disconnect and reconnect. - server.handleMatrixConnections(null, matrix, {0: connection}, false); - expect(disconnectCount).toBe(1); + // But if connecting same source and dest. just disconnect and don't reconnect. + server.handleMatrixConnections(null, matrix, {0: connection}); + expect(disconnectCount).toBe(2); matrix.setSources(0, [0]); connection = new ember.MatrixConnection(1); connection.operation = ember.MatrixOperation.absolute; @@ -286,12 +286,12 @@ describe("server", function() { res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); // We can't connect but in 1-on-1 server should disconnect existing source and connect new one. - server.handleMatrixConnections(null, matrix, {0: connection}, false); + server.handleMatrixConnections(null, matrix, {0: connection}); expect(matrix.connections[0].sources[0]).toBe(1); expect(disconnectCount).toBe(1); - // But if connecting same source and dest. Don't disconnect and reconnect. - server.handleMatrixConnections(null, matrix, {0: connection}, false); - expect(disconnectCount).toBe(1); + // But if connecting same source and dest. just disconnect and do not reconnect. + server.handleMatrixConnections(null, matrix, {0: connection}); + expect(disconnectCount).toBe(2); connection.operation = ember.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); @@ -301,6 +301,22 @@ describe("server", function() { expect(res).toBeFalsy(); server.off("matrix-disconnect", handleDisconnect); }); + it("should disconnect if trying to connect same source and target in 1-to-1", function() { + const matrix = server.tree.elements[0].children[1].children[0]; + let disconnectCount = 0; + const handleDisconnect = info => { + disconnectCount++; + } + server.on("matrix-disconnect", handleDisconnect.bind(this)); + matrix.contents.type = ember.MatrixType.oneToOne; + matrix.setSources(0, [1]); + const connection = new ember.MatrixConnection(0); + connection.setSources([1]); + connection.operation = ember.MatrixOperation.connect; + server.handleMatrixConnections(null, matrix, {0: connection}); + expect(matrix.connections[0].sources.length).toBe(0); + expect(disconnectCount).toBe(1); + }); it("should verify if connection allowed in N-to-N", function() { const matrix = server.tree.elements[0].children[1].children[0]; matrix.contents.type = ember.MatrixType.nToN; From c7126760dbf7fe963e0379f4cb08d0da8d4961ea Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 20 Dec 2019 10:21:36 +0100 Subject: [PATCH 112/162] handle connect with empty sources --- README.md | 2 ++ server.js | 27 ++++++++++++++++++++++++--- test/Server.test.js | 7 +++++-- test/utils.js | 4 +++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 696e011..9f40cbd 100755 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ const {ParameterType, FunctionArgument} = require("emberplus").Ember; const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; +const defaultSources = [0, 0, 0]; const labels = function(endpoints, type) { let labels = []; for (let i = 0; i < endpoints.length; i++) { @@ -191,6 +192,7 @@ const jsonTree = [ mode: "linear", targetCount: targets.length, sourceCount: sources.length, + defaultSources: defaultSources, connections: buildConnections(sources, targets), labels: [{basePath: "0.1.1000", description: "primary"}] }, diff --git a/server.js b/server.js index 57ee655..7370ff1 100755 --- a/server.js +++ b/server.js @@ -280,14 +280,19 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti }); } } - else { + else if (matrix.contents.type === ember.MatrixType.oneToOne) { // let's change the request into a disconnect connection.operation = ember.MatrixOperation.disconnect; } - + else if (matrix.contents.type === ember.MatrixType.oneToN && + (matrix.defaultSources != null)) { + connection.sources = [matrix.defaultSources[connection.target]] + } } } + if (connection.operation !== ember.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length > 0 && matrix.canConnect(connection.target,connection.sources,connection.operation)) { // Apply changes if ((connection.operation == null) || @@ -300,7 +305,22 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti emitType = "matrix-connect"; } conResult.disposition = ember.MatrixDisposition.modified; - } + } + else if (connection.operation !== ember.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 0 && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // let's disconnect + if (response) { + this.emit("matrix-disconnect", { + target: connection.target, + sources: matrix.connections[connection.target].sources, + client: client == null ? null : client.remoteAddress() + }); + } + matrix.setSources(connection.target, []); + conResult.disposition = ember.MatrixDisposition.modified; + } else if (connection.operation === ember.MatrixOperation.disconnect) { // Disconnect matrix.disconnectSources(connection.target, connection.sources); @@ -671,6 +691,7 @@ const parseObj = function(parent, obj) { else if (content.targetCount != null) { emberElement = new ember.MatrixNode(number); emberElement.contents = new ember.MatrixContents(); + emberElement.defaultSources = content.defaultSources; parseMatrixContent(emberElement.contents, content); if (content.connections) { emberElement.connections = {}; diff --git a/test/Server.test.js b/test/Server.test.js index 94c8873..5aa12c0 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -259,9 +259,12 @@ describe("server", function() { server.handleMatrixConnections(null, matrix, {0: connection}); expect(matrix.connections[0].sources[0]).toBe(1); expect(disconnectCount).toBe(1); - // But if connecting same source and dest. just disconnect and don't reconnect. + // But if connecting same source and dest this is a disconnect. But not possible in 1toN. + // instead connect with defaultSource or do nothing + matrix.defaultSources[0] = 222; server.handleMatrixConnections(null, matrix, {0: connection}); - expect(disconnectCount).toBe(2); + expect(disconnectCount).toBe(1); + expect(matrix.connections[0].sources[0]).toBe(222); matrix.setSources(0, [0]); connection = new ember.MatrixConnection(1); connection.operation = ember.MatrixOperation.absolute; diff --git a/test/utils.js b/test/utils.js index b0ae7c0..d1e0727 100755 --- a/test/utils.js +++ b/test/utils.js @@ -4,6 +4,7 @@ const {ParameterType, FunctionArgument} = require("../ember"); const init = function(_src,_tgt) { const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; + const defaultSources = [0, 0, 0]; const labels = function(endpoints, type) { let labels = []; for (let i = 0; i < endpoints.length; i++) { @@ -52,7 +53,8 @@ const init = function(_src,_tgt) { targetCount: targets.length, sourceCount: sources.length, connections: buildConnections(sources, targets), - labels: [{basePath: "0.1.1000", description: "primary"}] + labels: [{basePath: "0.1.1000", description: "primary"}], + defaultSources: defaultSources }, { identifier: "labels", From 84d325cf0075609488a51cb2f1aa17bacd6e7716 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 21 Dec 2019 14:20:58 +0100 Subject: [PATCH 113/162] added lock/unlock of connection --- ember.js | 19 ++++++++++++++++++ server.js | 49 ++++++++++++++++++++++++++++++++++++--------- test/Server.test.js | 23 +++++++++++++++++++-- test/utils.js | 10 ++++++--- 4 files changed, 87 insertions(+), 14 deletions(-) diff --git a/ember.js b/ember.js index b63e1a8..c6adce6 100755 --- a/ember.js +++ b/ember.js @@ -393,6 +393,10 @@ TreeNode.prototype.toJSON = function() { return res; }; +TreeNode.prototype.getParent = function() { + return this._parent; +} + TreeNode.prototype.getElementByPath = function(path) { var children = this.getChildren(); if ((children === null)||(children === undefined)) { @@ -852,6 +856,10 @@ function canConnect(matrixNode, targetID, sources, operation) { const oldSources = connection == null || connection.sources == null ? [] : connection.sources.slice(); const newSources = operation === MatrixOperation.absolute ? sources : oldSources.concat(sources); const sMap = new Set(newSources.map(i => Number(i))); + + if (matrixNode.connections[targetID].isLocked()) { + return false; + } if (type === MatrixType.oneToN && matrixNode.contents.maximumConnectsPerTarget == null && matrixNode.contents.maximumConnectsPerTarget == null) { @@ -1348,6 +1356,7 @@ function MatrixConnection(target) { else { this.target = 0; } + this._locked = false; } // ConnectionOperation ::= @@ -1401,6 +1410,16 @@ MatrixConnection.prototype.connectSources = function(sources) { this.sources = [...s].sort(); } +MatrixConnection.prototype.lock = function() { + this._locked = true; +} +MatrixConnection.prototype.unlock = function() { + this._locked = false; +} +MatrixConnection.prototype.isLocked = function() { + return this._locked; +} + MatrixConnection.prototype.disconnectSources = function(sources) { if (sources === undefined) { return; diff --git a/server.js b/server.js index 7370ff1..66cf2e1 100755 --- a/server.js +++ b/server.js @@ -245,7 +245,10 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti let emitType; res.connections[connection.target] = conResult; - if (matrix.contents.type !== ember.MatrixType.nToN && + if (matrix.connections[connection.target].isLocked()) { + conResult.disposition = ember.MatrixDisposition.locked; + } + else if (matrix.contents.type !== ember.MatrixType.nToN && connection.operation !== ember.MatrixOperation.disconnect && connection.sources != null && connection.sources.length === 1) { if (matrix.contents.type === ember.MatrixType.oneToOne) { @@ -269,6 +272,19 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti // if the target is connected already, disconnect it if (matrix.connections[connection.target].sources != null && matrix.connections[connection.target].sources.length === 1) { + if (matrix.contents.type === ember.MatrixType.oneToN) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + if (disconnectSource != null && + disconnectSource != connection.sources[0]) { + connection.sources = [disconnectSource]; + } + else { + // do nothing + connection.operarion = ember.MatrixOperation.tally; + } + } + } if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { const source = matrix.connections[connection.target].sources[0]; matrix.setSources(connection.target, []); @@ -284,10 +300,6 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti // let's change the request into a disconnect connection.operation = ember.MatrixOperation.disconnect; } - else if (matrix.contents.type === ember.MatrixType.oneToN && - (matrix.defaultSources != null)) { - connection.sources = [matrix.defaultSources[connection.target]] - } } } @@ -327,7 +339,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti conResult.disposition = ember.MatrixDisposition.modified; emitType = "matrix-disconnect"; } - else { + else if (conResult.disposition !== ember.MatrixDisposition.locked){ if (this._debug) { console.log(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); } @@ -577,6 +589,26 @@ TreeServer.prototype.replaceElement = function(element) { } } +TreeServer.prototype.getDisconnectSource = function(matrix, targetID) { + if (matrix.defaultSources) { + return matrix.defaultSources[targetID].contents.value; + } + if (matrix.contents.labels == null || matrix.contents.labels.length == 0) { + return null; + } + const basePath = matrix.contents.labels[0].basePath; + const labels = this.tree.getElementByPath(basePath); + const number = labels.getNumber() + 1; + const parent = labels.getParent(); + const children = parent.getChildren(); + for(let child of children) { + if (child.getNumber() === number) { + matrix.defaultSources = child.getChildren(); + return matrix.defaultSources[targetID].contents.value; + } + } + return null; +} TreeServer.prototype.updateSubscribers = function(path, response, origin) { if (this.subscribers[path] == null) { @@ -690,9 +722,8 @@ const parseObj = function(parent, obj) { } else if (content.targetCount != null) { emberElement = new ember.MatrixNode(number); - emberElement.contents = new ember.MatrixContents(); - emberElement.defaultSources = content.defaultSources; - parseMatrixContent(emberElement.contents, content); + emberElement.contents = new ember.MatrixContents(); + parseMatrixContent(emberElement.contents, content); if (content.connections) { emberElement.connections = {}; for (let c in content.connections) { diff --git a/test/Server.test.js b/test/Server.test.js index 5aa12c0..69050ac 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -261,9 +261,10 @@ describe("server", function() { expect(disconnectCount).toBe(1); // But if connecting same source and dest this is a disconnect. But not possible in 1toN. // instead connect with defaultSource or do nothing - matrix.defaultSources[0] = 222; + server.getDisconnectSource(matrix, 0); + matrix.defaultSources[0].contents.value = 222; server.handleMatrixConnections(null, matrix, {0: connection}); - expect(disconnectCount).toBe(1); + expect(disconnectCount).toBe(2); expect(matrix.connections[0].sources[0]).toBe(222); matrix.setSources(0, [0]); connection = new ember.MatrixConnection(1); @@ -320,6 +321,24 @@ describe("server", function() { expect(matrix.connections[0].sources.length).toBe(0); expect(disconnectCount).toBe(1); }); + it("should be able to lock a connection", function() { + const matrix = server.tree.elements[0].children[1].children[0]; + let disconnectCount = 0; + const handleDisconnect = info => { + disconnectCount++; + } + server.on("matrix-disconnect", handleDisconnect.bind(this)); + matrix.contents.type = ember.MatrixType.oneToOne; + matrix.setSources(0, [1]); + matrix.connections[0].lock(); + const connection = new ember.MatrixConnection(0); + connection.setSources([0]); + connection.operation = ember.MatrixOperation.connect; + server.handleMatrixConnections(null, matrix, {0: connection}); + expect(matrix.connections[0].sources.length).toBe(1); + expect(matrix.connections[0].sources[0]).toBe(1); + expect(disconnectCount).toBe(0); + }); it("should verify if connection allowed in N-to-N", function() { const matrix = server.tree.elements[0].children[1].children[0]; matrix.contents.type = ember.MatrixType.nToN; diff --git a/test/utils.js b/test/utils.js index d1e0727..1092e18 100755 --- a/test/utils.js +++ b/test/utils.js @@ -4,7 +4,7 @@ const {ParameterType, FunctionArgument} = require("../ember"); const init = function(_src,_tgt) { const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; - const defaultSources = [0, 0, 0]; + const defaultSources = [{identifier: "t-0", value: 0}, {identifier: "t-1", value: 0}, {identifier: "t-2", value: 0}]; const labels = function(endpoints, type) { let labels = []; for (let i = 0; i < endpoints.length; i++) { @@ -53,8 +53,7 @@ const init = function(_src,_tgt) { targetCount: targets.length, sourceCount: sources.length, connections: buildConnections(sources, targets), - labels: [{basePath: "0.1.1000", description: "primary"}], - defaultSources: defaultSources + labels: [{basePath: "0.1.1000", description: "primary"}], }, { identifier: "labels", @@ -78,6 +77,11 @@ const init = function(_src,_tgt) { children: [ {identifier: "sdp A", value: "A"}, {identifier: "sdp B", value: "B"}] } ] + }, + { + identifier: "disconnect sources", + number: 1001, + children: defaultSources } ] }, From b00b1b31991344b1df22bef1b15d79f9108e7edc Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 21 Dec 2019 14:22:25 +0100 Subject: [PATCH 114/162] added lock/unlock of connection --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f40cbd..c30838f 100755 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ const {ParameterType, FunctionArgument} = require("emberplus").Ember; const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; -const defaultSources = [0, 0, 0]; +const defaultSources = [{identifier: "t-0", value: 0}, {identifier: "t-1", value: 0}, {identifier: "t-2", value: 0}]; const labels = function(endpoints, type) { let labels = []; for (let i = 0; i < endpoints.length; i++) { @@ -192,7 +192,6 @@ const jsonTree = [ mode: "linear", targetCount: targets.length, sourceCount: sources.length, - defaultSources: defaultSources, connections: buildConnections(sources, targets), labels: [{basePath: "0.1.1000", description: "primary"}] }, @@ -218,6 +217,12 @@ const jsonTree = [ children: [ {identifier: "sdp A", value: "A"}, {identifier: "sdp B", value: "B"}] } ] + }, + { + identifier: "disconnect sources", + // must be labels + 1 + number: 1001, + children: defaultSources } ] }, From 8b2109f2f79ed9769aafabb2a0e48d235e66d7f5 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 21 Dec 2019 19:12:32 +0100 Subject: [PATCH 115/162] Fix disconnect for 1toN --- server.js | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/server.js b/server.js index 66cf2e1..fac5511 100755 --- a/server.js +++ b/server.js @@ -241,6 +241,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti continue; } let connection = connections[id]; + console.log(connection); conResult = new ember.MatrixConnection(connection.target); let emitType; res.connections[connection.target] = conResult; @@ -335,9 +336,32 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti } else if (connection.operation === ember.MatrixOperation.disconnect) { // Disconnect - matrix.disconnectSources(connection.target, connection.sources); - conResult.disposition = ember.MatrixDisposition.modified; - emitType = "matrix-disconnect"; + if (matrix.contents.type === ember.MatrixType.oneToN) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + if (disconnectSource != null && + disconnectSource != connection.sources[0]) { + if (response) { + this.emit("matrix-disconnect", { + target: connection.target, + sources: matrix.connections[connection.target].sources, + client: client == null ? null : client.remoteAddress() + }); + } + matrix.setSources(connection.target, [disconnectSource]); + connection.operarion = ember.MatrixOperation.modified; + } + else { + // do nothing + connection.operarion = ember.MatrixOperation.tally; + } + } + } + else { + matrix.disconnectSources(connection.target, connection.sources); + conResult.disposition = ember.MatrixDisposition.modified; + emitType = "matrix-disconnect"; + } } else if (conResult.disposition !== ember.MatrixDisposition.locked){ if (this._debug) { From 734c0ea5498d6d3fe28f6cf2e28cb1d4be1b58c8 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 22 Dec 2019 10:19:37 +0100 Subject: [PATCH 116/162] Fix function result missing --- README.md | 7 +++++++ ember.js | 2 +- package.json | 2 +- server.js | 11 ++++++++++- test/utils.js | 7 +++++++ 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c30838f..095f679 100755 --- a/README.md +++ b/README.md @@ -246,6 +246,13 @@ const jsonTree = [ value: null, name: "arg2" } + ], + result: [ + { + type: ParameterType.integer, + value: null, + name: "changeCount" + } ] } ] diff --git a/ember.js b/ember.js index c6adce6..35e7e7b 100755 --- a/ember.js +++ b/ember.js @@ -1985,7 +1985,7 @@ FunctionContent.prototype.encode = function(ber) { if(this.result != null) { ber.startSequence(BER.CONTEXT(3)); ber.startSequence(BER.EMBER_SEQUENCE); - for(var i =0; i < this.result; i++) { + for(var i = 0; i < this.result.length; i++) { ber.startSequence(BER.CONTEXT(0)); this.result[i].encode(ber); ber.endSequence(); diff --git a/package.json b/package.json index de77dbe..1586315 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.11.4", + "version": "1.11.5", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index fac5511..f1b935c 100755 --- a/server.js +++ b/server.js @@ -241,7 +241,6 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti continue; } let connection = connections[id]; - console.log(connection); conResult = new ember.MatrixConnection(connection.target); let emitType; res.connections[connection.target] = conResult; @@ -743,6 +742,16 @@ const parseObj = function(parent, obj) { )); } } + if (content.result != null) { + for(let argument of content.result) { + emberElement.contents.result.push(new ember.FunctionArgument( + argument.type, + argument.value, + argument.name + )); + } + } + delete content.result; } else if (content.targetCount != null) { emberElement = new ember.MatrixNode(number); diff --git a/test/utils.js b/test/utils.js index 1092e18..b4cff7e 100755 --- a/test/utils.js +++ b/test/utils.js @@ -105,6 +105,13 @@ const init = function(_src,_tgt) { value: null, name: "arg2" } + ], + result: [ + { + type: ParameterType.integer, + value: null, + name: "changeCount" + } ] } ] From 341e88784eea0c199384d80a135f286bf3820b88 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 22 Dec 2019 11:08:35 +0100 Subject: [PATCH 117/162] Added -1 for defaultSources to disable disconnect --- README.md | 10 +++++++--- server.js | 2 +- test/utils.js | 6 +++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 095f679..1d2052f 100755 --- a/README.md +++ b/README.md @@ -143,9 +143,13 @@ server.listen().then(() => { console.log("listening"); }).catch((e) => { console const TreeServer = require("emberplus").TreeServer; const {ParameterType, FunctionArgument} = require("emberplus").Ember; -const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; -const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; -const defaultSources = [{identifier: "t-0", value: 0}, {identifier: "t-1", value: 0}, {identifier: "t-2", value: 0}]; +const targets = [ "tgt1", "tgt2", "tgt3" ]; +const sources = [ "src1", "src2", "src3" ]; +const defaultSources = [ + {identifier: "t-0", value: -1, access: "readWrite" }, + {identifier: "t-1", value: 0, access: "readWrite"}, + {identifier: "t-2", value: 0, access: "readWrite"} +]; const labels = function(endpoints, type) { let labels = []; for (let i = 0; i < endpoints.length; i++) { diff --git a/server.js b/server.js index f1b935c..0c8de4c 100755 --- a/server.js +++ b/server.js @@ -275,7 +275,7 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti if (matrix.contents.type === ember.MatrixType.oneToN) { const disconnectSource = this.getDisconnectSource(matrix, connection.target); if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { - if (disconnectSource != null && + if (disconnectSource != null && disconnectSource != -1 && disconnectSource != connection.sources[0]) { connection.sources = [disconnectSource]; } diff --git a/test/utils.js b/test/utils.js index b4cff7e..2324391 100755 --- a/test/utils.js +++ b/test/utils.js @@ -4,7 +4,11 @@ const {ParameterType, FunctionArgument} = require("../ember"); const init = function(_src,_tgt) { const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; - const defaultSources = [{identifier: "t-0", value: 0}, {identifier: "t-1", value: 0}, {identifier: "t-2", value: 0}]; + const defaultSources = [ + {identifier: "t-0", value: -1, access: "readWrite" }, + {identifier: "t-1", value: 0, access: "readWrite"}, + {identifier: "t-2", value: 0, access: "readWrite"} + ]; const labels = function(endpoints, type) { let labels = []; for (let i = 0; i < endpoints.length; i++) { From b11242e1a95b7bf096e694ca10359a2fc8265d24 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 22 Dec 2019 11:52:28 +0100 Subject: [PATCH 118/162] fix disconnect when mismatch client server --- package.json | 2 +- server.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1586315..03442dc 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.11.5", + "version": "1.11.6", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 0c8de4c..1b3c03b 100755 --- a/server.js +++ b/server.js @@ -333,12 +333,14 @@ TreeServer.prototype.handleMatrixConnections = function(client, matrix, connecti matrix.setSources(connection.target, []); conResult.disposition = ember.MatrixDisposition.modified; } - else if (connection.operation === ember.MatrixOperation.disconnect) { - // Disconnect + else if (connection.operation === ember.MatrixOperation.disconnect && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // Disconnect if (matrix.contents.type === ember.MatrixType.oneToN) { const disconnectSource = this.getDisconnectSource(matrix, connection.target); if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { - if (disconnectSource != null && + if (disconnectSource != null && disconnectSource != -1 && disconnectSource != connection.sources[0]) { if (response) { this.emit("matrix-disconnect", { From 3fdf5fe07e58a5d9170d8a312e4ad2e3004420c5 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 22 Dec 2019 17:58:36 +0100 Subject: [PATCH 119/162] Fix bug in command handling --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 1b3c03b..ff94e35 100755 --- a/server.js +++ b/server.js @@ -183,7 +183,7 @@ TreeServer.prototype.handleNode = function(client, node) { } if (cmd instanceof ember.Command) { - this.handleCommand(client, element, cmd.number); + this.handleCommand(client, element, cmd); } else if ((cmd instanceof ember.MatrixNode) && (cmd.connections !== undefined)) { this.handleMatrixConnections(client, element, cmd.connections); @@ -455,7 +455,7 @@ TreeServer.prototype.handleCommand = function(client, element, cmd) { this.handleInvoke(client, cmd.invocation, element); break; default: - this.emit("error", new Error(`invalid command ${cmd}`)); + this.emit("error", new Error(`invalid command ${cmd.number}`)); break; } } From 9efb3e3cf2f68f6125e7c4a6f92d42d9b1f3f3c5 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 23 Dec 2019 09:50:45 +0100 Subject: [PATCH 120/162] fix continuation message --- ember.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ember.js b/ember.js index 35e7e7b..87cab26 100755 --- a/ember.js +++ b/ember.js @@ -79,6 +79,7 @@ Root.decode = function(ber) { else if (tag === BER.CONTEXT(0)) { // continuation of previous message try { + var rootReader = ber.getSequence(BER.CONTEXT(0)); return RootElement.decode(rootReader) } catch (e) { From 60eab03f3e9e17d73f1d3427c4b5300c4e03c4c4 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 26 Dec 2019 10:24:35 +0100 Subject: [PATCH 121/162] Ignore empty request on server --- ember.js | 10 +++++++++- package.json | 2 +- server.js | 3 +-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ember.js b/ember.js index 87cab26..9f19b15 100755 --- a/ember.js +++ b/ember.js @@ -343,7 +343,7 @@ TreeNode.prototype.toJSON = function() { let res = {}; const node = this; if (node.number) { - res.number = node.number + res.number = node.number; } if (node.path) { res.path = node.path; @@ -2358,6 +2358,14 @@ Command.decode = function(ber) { return c; } +Command.prototype.toJSON = function() { + return { + number: this.number, + fieldFlags: this.fieldFlags, + invocation: this.invocation == null ? null : this.invocation.toJSON() + }; +} + Command.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(2)); diff --git a/package.json b/package.json index 03442dc..5be2d70 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.11.6", + "version": "1.11.7", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index ff94e35..43e86db 100755 --- a/server.js +++ b/server.js @@ -93,11 +93,10 @@ TreeServer.prototype.close = function () { TreeServer.prototype.handleRoot = function(client, root) { if ((root == null) || (root.elements == null) || (root.elements < 1)) { - this.emit("error", new Error("invalid request")); + // ignore empty requests. return; } - const node = root.elements[0]; client.request = node; From cf182c4563e6bfc4115f0f0f0b1804cbdef4233b Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 26 Dec 2019 11:24:20 +0100 Subject: [PATCH 122/162] catch write error and disconnect --- client.js | 45 +++++++++++++++++++++++++++++++-------------- package.json | 2 +- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/client.js b/client.js index a60916a..7d41308 100755 --- a/client.js +++ b/client.js @@ -177,8 +177,7 @@ S101Socket.prototype.connect = function (timeout = 2) { timeout: 1000 * timeout }, () => { - winston.debug('socket connected'); - + winston.debug('socket connected'); // Disable connect timeout to hand-over to keepalive mechanism self.socket.removeListener("timeout", connectTimeoutListener); self.socket.setTimeout(0); @@ -220,14 +219,17 @@ S101Socket.prototype.connect = function (timeout = 2) { self.codec.dataIn(data); } }) - .on('close', () => { - clearInterval(self.keepaliveIntervalTimer); - self.emit('disconnected'); - self.status = "disconnected"; - self.socket = null; - }); + .on('close', self.handleClose) + .on("end", self.handleClose); } +S101Socket.prototype.handleClose = function() { + this.socket = null; + clearInterval(this.keepaliveIntervalTimer); + this.status = "disconnected"; + this.emit('disconnected'); +}; + S101Socket.prototype.isConnected = function () { return ((this.socket !== null) && (this.socket !== undefined)); } @@ -255,15 +257,25 @@ S101Socket.prototype.disconnect = function () { S101Socket.prototype.sendKeepaliveRequest = function () { var self = this; if (self.isConnected()) { - self.socket.write(self.codec.keepAliveRequest()); - winston.debug('sent keepalive request'); + try { + self.socket.write(self.codec.keepAliveRequest()); + winston.debug('sent keepalive request'); + } + catch(e){ + self.handleClose(); + } } } S101Socket.prototype.sendKeepaliveResponse = function () { var self = this; if (self.isConnected()) { - self.socket.write(self.codec.keepAliveResponse()); + try { + self.socket.write(self.codec.keepAliveResponse()); + } + catch(e){ + self.handleClose(); + } winston.debug('sent keepalive response'); } } @@ -271,9 +283,14 @@ S101Socket.prototype.sendKeepaliveResponse = function () { S101Socket.prototype.sendBER = function (data) { var self = this; if (self.isConnected()) { - var frames = self.codec.encodeBER(data); - for (var i = 0; i < frames.length; i++) { - self.socket.write(frames[i]); + try { + var frames = self.codec.encodeBER(data); + for (var i = 0; i < frames.length; i++) { + self.socket.write(frames[i]); + } + } + catch(e){ + self.handleClose(); } } } diff --git a/package.json b/package.json index 5be2d70..60508df 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.11.7", + "version": "1.11.8", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 2bb46c2bd0bbbd414ba9fddfb30f2324da1662f6 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 29 Dec 2019 16:57:14 +0100 Subject: [PATCH 123/162] Removed all ref to callabcks...using different approach...Added support for subscribe --- README.md | 35 ++++++- client.js | 2 +- device.js | 86 +++++++--------- ember.js | 238 +++++++++++++++----------------------------- package.json | 2 +- server.js | 30 +++--- test/Server.test.js | 51 ++-------- test/utils.js | 2 +- 8 files changed, 176 insertions(+), 270 deletions(-) diff --git a/README.md b/README.md index 1d2052f..5b3d203 100755 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ tree.connect() }); ``` -Get Specific Branch: +Get Specific Node: ```javascript const DeviceTree = require('emberplus').DeviceTree; const ember = require("emberplus").Ember; @@ -53,11 +53,40 @@ client.connect()) .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) .then(() => client.getNodeByPath("scoreMaster/router/labels/group 1")) .then(() => client.getNodeByPathnum("0.2")) - .then(() => { - console.log(JSON.stringify(client.root.elements[0].toJSON(), null, 4)); + .then(node => { + console.log(JSON.stringify(node.toJSON(), null, 4)); }); ``` +Subsribe to changes +```javascript +const DeviceTree = require('emberplus').DeviceTree; +const ember = require("emberplus").Ember; + +const client = new DeviceTree(HOST, PORT); +client.connect()) + .then(() => client.getDirectory()) + .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) + .then(() => client.getNodeByPath("scoreMaster/router/labels/group 1")) + .then(node => { + // For streams, use subscribe + return client.subscribe(node, update => { + console.log(udpate); + }); + }) + .then(() => client.getNodeByPathnum("0.2")) + .then(node => { + // For non-streams a getDirectory will automatically subscribe for update + return client.getDirectory(node, update => { + console.log(udpate); + }); + }) + // You can also provide a callback to the getNodeNyPath + // Be carefull that subscription will be done for all elements in the path + .then(() => client.getNodeByPathnum("0.3", update => {console.log(update);})) + ; +``` + ### Invoking Function ```javascript const DeviceTree = require('emberplus').DeviceTree; diff --git a/client.js b/client.js index 7d41308..20bf7ad 100755 --- a/client.js +++ b/client.js @@ -287,7 +287,7 @@ S101Socket.prototype.sendBER = function (data) { var frames = self.codec.encodeBER(data); for (var i = 0; i < frames.length; i++) { self.socket.write(frames[i]); - } + } } catch(e){ self.handleClose(); diff --git a/device.js b/device.js index 5233eee..ba32402 100755 --- a/device.js +++ b/device.js @@ -103,7 +103,7 @@ DeviceTree.prototype.connect = function (timeout = 2) { }); }; -DeviceTree.prototype.expand = function (node) { +DeviceTree.prototype.expand = function (node, callback = null) { let self = this; if (node == null) { return Promise.reject(new Error("Invalid null node")); @@ -111,7 +111,7 @@ DeviceTree.prototype.expand = function (node) { if (node.isParameter() || node.isMatrix() || node.isFunction()) { return self.getDirectory(node); } - return self.getDirectory(node).then((res) => { + return self.getDirectory(node, callback).then((res) => { let children = node.getChildren(); if ((res === undefined) || (children === undefined) || (children === null)) { if (self._debug) { @@ -144,7 +144,7 @@ function isDirectSubPathOf(path, parent) { return path === parent || (path.lastIndexOf('.') === parent.length && path.startsWith(parent)); } -DeviceTree.prototype.getDirectory = function (qnode) { +DeviceTree.prototype.getDirectory = function (qnode, callback = null) { var self = this; if (qnode == null) { self.root.clear(); @@ -219,7 +219,7 @@ DeviceTree.prototype.getDirectory = function (qnode) { if (self._debug) { console.log("Sending getDirectory", qnode); } - self.client.sendBERNode(qnode.getDirectory()); + self.client.sendBERNode(qnode.getDirectory(callback)); }}); }); }; @@ -339,24 +339,18 @@ DeviceTree.prototype.disconnect = function () { }; DeviceTree.prototype.makeRequest = function () { - var self = this; + const self = this; if (self.activeRequest === null && self.pendingRequests.length > 0) { self.activeRequest = self.pendingRequests.shift(); - - const t = function (id) { - var path = self.activeRequest.path == null ? - self.activeRequest.node.getPath() : - self.activeRequest.path; - var req = `${id} - ${path}`; - if (self._debug) { - console.log(`Making request ${req}`, Date.now()); - } - self.timeout = setTimeout(() => { - self.timeoutRequest(req); - }, self.timeoutValue); - }; - - t(self.requestID++); + const req = `${ self.requestID++} - ${self.activeRequest.node.getPath()}`; + self.activeRequest.timeoutError = new errors.EmberTimeoutError(`Request ${req} timed out`) + + if (self._debug) { + console.log(`Making request ${req}`, Date.now()); + } + self.timeout = setTimeout(() => { + self.timeoutRequest(); + }, self.timeoutValue); self.activeRequest.func(); } }; @@ -376,7 +370,6 @@ DeviceTree.prototype.clearTimeout = function () { DeviceTree.prototype.finishRequest = function () { var self = this; - self.callback = undefined; self.clearTimeout(); self.activeRequest = null; try { @@ -391,9 +384,7 @@ DeviceTree.prototype.finishRequest = function () { }; DeviceTree.prototype.timeoutRequest = function (id) { - var self = this; - self.root.cancelCallbacks(); - self.activeRequest.func(new errors.EmberTimeoutError(`Request ${id !== undefined ? id : ""} timed out`)); + this.activeRequest.func(this.activeRequest.timeoutError); }; DeviceTree.prototype.handleRoot = function (root) { @@ -402,32 +393,28 @@ DeviceTree.prototype.handleRoot = function (root) { if (self._debug) { console.log("handling root", JSON.stringify(root)); } - var callbacks = self.root.update(root); + self.root.update(root); if (root.elements !== undefined) { for (var i = 0; i < root.elements.length; i++) { if (root.elements[i].isQualified()) { - callbacks = callbacks.concat(this.handleQualifiedNode(this.root, root.elements[i])); + this.handleQualifiedNode(this.root, root.elements[i]); } else { - callbacks = callbacks.concat(this.handleNode(this.root, root.elements[i])); + this.handleNode(this.root, root.elements[i]); } } - - // Fire callbacks once entire tree has been updated - for (var j = 0; j < callbacks.length; j++) { - //console.log('hr cb'); - callbacks[j](); - } + } + if (self.callback) { + self.callback(null, root); } }; DeviceTree.prototype.handleQualifiedNode = function (parent, node) { var self = this; - var callbacks = []; var element = parent.getElementByPath(node.path); if (element !== null) { self.emit("value-change", node); - callbacks = element.update(node); + element.update(node); } else { var path = node.path.split("."); @@ -439,10 +426,10 @@ DeviceTree.prototype.handleQualifiedNode = function (parent, node) { path.pop(); parent = this.root.getElementByPath(path.join(".")); if (parent === null) { - return callbacks; + return; } parent.addChild(node); - callbacks = parent.update(parent); + parent.update(parent); } element = node; } @@ -451,43 +438,40 @@ DeviceTree.prototype.handleQualifiedNode = function (parent, node) { if (children !== null) { for (var i = 0; i < children.length; i++) { if (children[i].isQualified()) { - callbacks = callbacks.concat(this.handleQualifiedNode(element, children[i])); + this.handleQualifiedNode(element, children[i]); } else { - callbacks = callbacks.concat(this.handleNode(element, children[i])); + this.handleNode(element, children[i]); } } } - return callbacks; + return; }; DeviceTree.prototype.handleNode = function (parent, node) { var self = this; - var callbacks = []; - var n = parent.getElementByNumber(node.getNumber()); if (n === null) { parent.addChild(node); n = node; } else { - callbacks = n.update(node); + n.update(node); } var children = node.getChildren(); if (children !== null) { for (var i = 0; i < children.length; i++) { - callbacks = callbacks.concat(this.handleNode(n, children[i])); + this.handleNode(n, children[i]); } } else { self.emit("value-change", node); } - - return callbacks; + return; }; -DeviceTree.prototype.getNodeByPathnum = function (path) { +DeviceTree.prototype.getNodeByPathnum = function (path, callback = null) { var self = this; if (typeof path === 'string') { path = path.split('.'); @@ -520,13 +504,13 @@ DeviceTree.prototype.getNodeByPathnum = function (path) { throw pathnumError; } lastMissingPos = pos; - return this.getDirectory(currentNode).then(() => getNext()); + return this.getDirectory(currentNode, callback).then(() => getNext()); }); } return getNext(); }; -DeviceTree.prototype.getNodeByPath = function (path) { +DeviceTree.prototype.getNodeByPath = function (path, callback = null) { var self = this; if (typeof path === 'string') { path = path.split('/'); @@ -559,14 +543,14 @@ DeviceTree.prototype.getNodeByPath = function (path) { throw pathError; } lastMissingPos = pos; - return this.getDirectory(currentNode).then(() => getNext()); + return this.getDirectory(currentNode, callback).then(() => getNext()); }); } return getNext(); }; DeviceTree.prototype.subscribe = function (qnode, callback) { - if (qnode.isParameter() && qnode.isStream()) { + if ((qnode.isParameter() || qnode.isMatrix()) && qnode.isStream()) { var self = this; if (qnode == null) { self.root.clear(); diff --git a/ember.js b/ember.js index 9f19b15..b1580a0 100755 --- a/ember.js +++ b/ember.js @@ -145,9 +145,7 @@ module.exports.Root = Root; ***************************************************************************/ function TreeNode() { - Object.defineProperty(this, '_parent', {value: null, enumerable: false, writable: true}); - Object.defineProperty(this, '_directoryCallbacks', {value: [], enumerable: false, writable: true}); - Object.defineProperty(this, '_callbacks', {value: [], enumerable: false, writable: true}); + Object.defineProperty(this, '_parent', {value: null, enumerable: false, writable: true}); } TreeNode.prototype.addChild = function(child) { @@ -190,23 +188,6 @@ TreeNode.prototype.isStream = function() { this.contents.streamIdentifier != null; } -TreeNode.prototype.addCallback = function(callback) { - if(this._callbacks.indexOf(callback) < 0) { - this._callbacks.push(callback); - } -} - -TreeNode.prototype.cancelCallbacks = function() { - var self=this; - self._directoryCallbacks = []; - var children = self.getChildren(); - if(children !== null) { - for(var i=0; i { callback(error, node) }); + if (callback != null && this.contents != null && !this.isStream()) { + this.contents._subscribers.add(callback); } return this.getTreeBranch(new Command(COMMAND_GETDIRECTORY)); } TreeNode.prototype.subscribe = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - if (this.isParameter() && this.isStream()) { - this.contents.subscribers.add(callback); + if (callback != null && this.isParameter() && this.isStream()) { + this.contents._subscribers.add(callback); } return this.getTreeBranch(new Command(COMMAND_SUBSCRIBE)); } TreeNode.prototype.unsubscribe = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - if (this.isParameter() && this.isStream()) { - this.contents.subscribers.delete(callback); + if (callback != null && this.isParameter() && this.isStream()) { + this.contents._subscribers.delete(callback); } return this.getTreeBranch(new Command(COMMAND_UNSUBSCRIBE)); } @@ -342,12 +317,8 @@ _getElementByPath = function(children, pathArray, path) { TreeNode.prototype.toJSON = function() { let res = {}; const node = this; - if (node.number) { - res.number = node.number; - } - if (node.path) { - res.path = node.path; - } + res.number = node.getNumber(); + res.path = node.getPath(); if (node.contents) { for(let prop in node.contents) { if (node.contents.hasOwnProperty(prop)) { @@ -460,26 +431,7 @@ TreeNode.prototype.getElement = function(id) { } TreeNode.prototype.update = function(other) { - var self=this; - var callbacks = []; - - while(self._directoryCallbacks.length > 0) { - (function(cb) { - callbacks.push(() => { - cb(null, self) - }); - })(self._directoryCallbacks.shift()); - } - - for(var i=0; i { - cb(self) - }); - })(self._callbacks[i]); - } - - return callbacks; + return; } TreeNode.prototype.getNodeByPath = function(client, path, callback) { @@ -490,7 +442,6 @@ TreeNode.prototype.getNodeByPath = function(client, path, callback) { return; } - var child = self.getElement(path[0]); if(child !== null) { child.getNodeByPath(client, path.slice(1), callback); @@ -649,7 +600,7 @@ QualifiedNode.prototype.getMinimal = function(complete = false) { } QualifiedNode.prototype.update = function(other) { - callbacks = QualifiedNode.super_.prototype.update.apply(this); + QualifiedNode.super_.prototype.update.apply(this); if((other !== undefined) && (other.contents !== undefined)) { if (this.contents == null) { this.contents = other.contents; @@ -662,49 +613,46 @@ QualifiedNode.prototype.update = function(other) { } } } - return callbacks; + return; } -function QualifiedNodeCommand(self, cmd, callback) { +function QualifiedNodeCommand(self, cmd) { var r = new Root(); var qn = new QualifiedNode(); qn.path = self.path; r.addElement(qn); qn.addChild(new Command(cmd)); - if(callback !== undefined) { - self._directoryCallbacks.push((error, node) => { callback(error, node) }); - } return r; } QualifiedNode.prototype.getDirectory = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } if (this.path === undefined) { throw new Error("Invalid path"); } - return QualifiedNodeCommand(this, COMMAND_GETDIRECTORY, callback) + if (callback != null && this.contents != null && !this.isStream()) { + this.contents._subscribers.add(callback); + } + return QualifiedNodeCommand(this, COMMAND_GETDIRECTORY) } QualifiedNode.prototype.subscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } - if (this.isStream()) { - this.contents.subscribers.add(callback); + if (callback != null && this.isStream()) { + this.contents._subscribers.add(callback); } - return QualifiedNodeCommand(this, COMMAND_SUBSCRIBE, callback) + return QualifiedNodeCommand(this, COMMAND_SUBSCRIBE) } QualifiedNode.prototype.unsubscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } - if (this.isStream()) { - this.contents.subscribers.delete(callback); + if (callback != null && this.isStream()) { + this.contents._subscribers.delete(callback); } - return QualifiedNodeCommand(this, COMMAND_UNSUBSCRIBE, callback) + return QualifiedNodeCommand(this, COMMAND_UNSUBSCRIBE) } QualifiedNode.prototype.encode = function(ber) { @@ -810,7 +758,7 @@ Node.prototype.toQualified = function() { } Node.prototype.update = function(other) { - callbacks = Node.super_.prototype.update.apply(this); + Node.super_.prototype.update.apply(this); if ((other !== undefined) && (other.contents !== undefined)) { if (this.contents == null) { this.contents = other.contents; @@ -823,15 +771,12 @@ Node.prototype.update = function(other) { } } } - return callbacks; + return; } Node.prototype.subscribe = function(callback) { - if(this._callbacks.indexOf(callback) < 0) { - this._callbacks.push(callback); - } - if (this.isStream()) { - this.contents.subscribers.add(callback); + if (callback != null && this.isStream()) { + this.contents._subscribers.add(callback); } } @@ -1136,9 +1081,9 @@ MatrixNode.disconnectSources = function(matrix, targetID, sources) { } MatrixNode.prototype.update = function(other) { - callbacks = MatrixNode.super_.prototype.update.apply(this); + MatrixNode.super_.prototype.update.apply(this); MatrixUpdate(this, other); - return callbacks; + return; } MatrixNode.prototype.toQualified = function() { @@ -1706,51 +1651,48 @@ function MatrixUpdate(matrix, newMatrix) { } QualifiedMatrix.prototype.update = function(other) { - callbacks = QualifiedMatrix.super_.prototype.update.apply(this); + QualifiedMatrix.super_.prototype.update.apply(this); MatrixUpdate(this, other); - return callbacks; + return; } -function QualifiedMatrixCommand(self, cmd, callback) { +function QualifiedMatrixCommand(self, cmd) { var r = new Root(); var qn = new QualifiedMatrix(); qn.path = self.path; r.addElement(qn); qn.addChild(new Command(cmd)); - if(callback !== undefined) { - self._directoryCallbacks.push((error, node) => { callback(error, node) }); - } return r; } QualifiedMatrix.prototype.getDirectory = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } if (this.path === undefined) { throw new Error("Invalid path"); } - return QualifiedMatrixCommand(this, COMMAND_GETDIRECTORY, callback); + if (callback != null && !this.isStream()) { + this.contents._subscribers.add(callback); + } + return QualifiedMatrixCommand(this, COMMAND_GETDIRECTORY); } QualifiedMatrix.prototype.subscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } - if (this.isStream()) { - this.contents.subscribers.add(callback); + if (callback != null && this.isStream()) { + this.contents._subscribers.add(callback); } - return QualifiedMatrixCommand(this, COMMAND_SUBSCRIBE, callback); + return QualifiedMatrixCommand(this, COMMAND_SUBSCRIBE); } QualifiedMatrix.prototype.unsubscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } - if (this.isStream()) { - this.contents.subscribers.delete(callback); + if (callback != null && this.isStream()) { + this.contents._subscribers.delete(callback); } - return QualifiedMatrixCommand(this, COMMAND_UNSUBSCRIBE, callback); + return QualifiedMatrixCommand(this, COMMAND_UNSUBSCRIBE); } QualifiedMatrix.prototype.connect = function(connections) { @@ -2042,7 +1984,7 @@ QualifiedFunction.decode = function(ber) { } QualifiedFunction.prototype.update = function(other) { - callbacks = QualifiedFunction.super_.prototype.update.apply(this); + QualifiedFunction.super_.prototype.update.apply(this); if ((other !== undefined) && (other.contents !== undefined)) { if (this.contents == null) { this.contents = other.contents; @@ -2055,54 +1997,48 @@ QualifiedFunction.prototype.update = function(other) { } } } - return callbacks; + return; } -function QualifiedFunctionCommand(self, cmd, callback) { +function QualifiedFunctionCommand(self, cmd) { var r = new Root(); var qf = new QualifiedFunction(); qf.path = self.path; r.addElement(qf); qf.addChild(new Command(cmd)); - if(callback !== undefined) { - self._directoryCallbacks.push((error, node) => { callback(error, node) }); - } return r; } -QualifiedFunction.prototype.invoke = function(params, callback) { +QualifiedFunction.prototype.invoke = function(params) { if (this.path === undefined) { throw new Error("Invalid path"); } - var QualifiedFunctionNode = QualifiedFunctionCommand(this, COMMAND_INVOKE, callback); + var QualifiedFunctionNode = QualifiedFunctionCommand(this, COMMAND_INVOKE); var invocation = new Invocation() invocation.arguments = params QualifiedFunctionNode.elements[0].children[0].invocation = invocation return QualifiedFunctionNode } -QualifiedFunction.prototype.getDirectory = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } +QualifiedFunction.prototype.getDirectory = function() { if (this.path === undefined) { throw new Error("Invalid path"); - } - return QualifiedFunctionCommand(this, COMMAND_GETDIRECTORY, callback); + } + return QualifiedFunctionCommand(this, COMMAND_GETDIRECTORY); } QualifiedFunction.prototype.subscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } - return QualifiedFunctionCommand(this, COMMAND_SUBSCRIBE, callback); + return QualifiedFunctionCommand(this, COMMAND_SUBSCRIBE); } QualifiedFunction.prototype.unsubscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } - return QualifiedFunctionCommand(this, COMMAND_UNSUBSCRIBE, callback); + return QualifiedFunctionCommand(this, COMMAND_UNSUBSCRIBE); } QualifiedFunction.prototype.encode = function(ber) { @@ -2208,18 +2144,14 @@ Function.prototype.toQualified = function() { return qf; } -Function.prototype.invoke = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - +Function.prototype.invoke = function() { return this.getTreeBranch(undefined, (m) => { m.addChild(new Command(COMMAND_INVOKE)) }); } Function.prototype.update = function(other) { - callbacks = Function.super_.prototype.update.apply(this); + Function.super_.prototype.update.apply(this); if ((other !== undefined) && (other.contents !== undefined)) { if (this.contents == null) { this.contents = other.contents; @@ -2232,7 +2164,7 @@ Function.prototype.update = function(other) { } } } - return callbacks; + return; } module.exports.Function = Function; @@ -2244,6 +2176,7 @@ module.exports.Function = Function; function NodeContents() { this.isOnline = true; + this._subscribers = new Set(); }; @@ -2635,72 +2568,68 @@ QualifiedParameter.prototype.encode = function(ber) { } QualifiedParameter.prototype.update = function(other) { - callbacks = QualifiedParameter.super_.prototype.update.apply(this); + QualifiedParameter.super_.prototype.update.apply(this); if ((other !== undefined) && (other.contents !== undefined)) { if (this.contents == null) { this.contents = other.contents; } else { for (var key in other.contents) { + if (key[0] === "_") { + continue; + } if (other.contents.hasOwnProperty(key)) { this.contents[key] = other.contents[key]; } } - for(let cb of this.contents.subscribers) { - cb(); + for(let cb of this.contents._subscribers) { + cb(this); } } } - return callbacks; + return; } -function QualifiedParameterCommand(self, cmd, callback) { +function QualifiedParameterCommand(self, cmd) { let r = new Root(); let qp = new QualifiedParameter(); qp.path = self.path; r.addElement(qp); qp.addChild(new Command(cmd)); - if(callback !== undefined) { - self._directoryCallbacks.push((error, node) => { callback(error, node) }); - } return r; } QualifiedParameter.prototype.getDirectory = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } if (this.path === undefined) { throw new Error("Invalid path"); } - return QualifiedParameterCommand(this, COMMAND_GETDIRECTORY, callback); + if (callback != null && !this.isStream()) { + this.contents._subscribers.add(callback); + } + return QualifiedParameterCommand(this, COMMAND_GETDIRECTORY); } QualifiedParameter.prototype.subscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } - if (this.isStream()) { - this.contents.subscribers.add(callback); + if (callback != null && this.isStream()) { + this.contents._subscribers.add(callback); } - return QualifiedParameterCommand(this, COMMAND_SUBSCRIBE, callback); + return QualifiedParameterCommand(this, COMMAND_SUBSCRIBE); } QualifiedParameter.prototype.unsubscribe = function(callback) { if (this.path === undefined) { throw new Error("Invalid path"); } - if (this.isStream()) { - this.contents.subscribers.delete(callback); + if (callback != null && this.isStream()) { + this.contents._subscribers.delete(callback); } - return QualifiedParameterCommand(this, COMMAND_UNSUBSCRIBE, callback); + return QualifiedParameterCommand(this, COMMAND_UNSUBSCRIBE); } -QualifiedParameter.prototype.setValue = function(value, callback) { - if(callback !== undefined) { - this._directoryCallbacks.push(callback); - } - +QualifiedParameter.prototype.setValue = function(value) { let r = new Root(); let qp = new QualifiedParameter(this.path); r.addElement(qp); @@ -2774,11 +2703,7 @@ Parameter.prototype.encode = function(ber) { ber.endSequence(); } -Parameter.prototype.setValue = function(value, callback) { - if(callback !== undefined) { - this._directoryCallbacks.push(callback); - } - +Parameter.prototype.setValue = function(value) { return this.getTreeBranch(undefined, (m) => { m.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); }); @@ -2791,23 +2716,24 @@ Parameter.prototype.toQualified = function() { } Parameter.prototype.update = function(other) { - callbacks = Parameter.super_.prototype.update.apply(this); + Parameter.super_.prototype.update.apply(this); if ((other !== undefined) && (other.contents !== undefined)) { if (this.contents == null) { this.contents = other.contents; } else { for (var key in other.contents) { + if (key[0] === "_") { continue; } if (other.contents.hasOwnProperty(key)) { this.contents[key] = other.contents[key]; } } - for(let cb of this.contents.subscribers) { - cb(); + for(let cb of this.contents._subscribers) { + cb(this); } } } - return callbacks; + return; } @@ -2869,7 +2795,7 @@ module.exports.ParameterAccess = ParameterAccess; module.exports.ParameterType = ParameterType; function ParameterContents(value, type) { - this.subscribers = new Set(); + this._subscribers = new Set(); if(value !== undefined) { this.value = value; } diff --git a/package.json b/package.json index 60508df..b2025bb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.11.8", + "version": "1.12.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/server.js b/server.js index 43e86db..4d26789 100755 --- a/server.js +++ b/server.js @@ -575,21 +575,27 @@ TreeServer.prototype.unsubscribe = function(client, element) { TreeServer.prototype.setValue = function(element, value, origin, key) { return new Promise((resolve, reject) => { // Change the element value if write access permitted. - if (element.contents !== undefined) { - if (element.isParameter()) { - if ((element.contents.access !== undefined) && - (element.contents.access.value > 1)) { - element.contents.value = value; - this.emit("value-change", element); - } + if (element.contents == null) { + return resolve(); + } + if (element.isParameter()) { + if ((element.contents.access !== undefined) && + (element.contents.access.value > 1)) { + element.contents.value = value; + const res = this.getResponse(element); + this.updateSubscribers(element.getPath(),res, origin); + this.emit("value-change", element); } - else if (element.isMatrix()) { - if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { - element.contents[key] = value; - this.emit("value-change", element); - } + } + else if (element.isMatrix()) { + if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { + element.contents[key] = value; + const res = this.getResponse(element); + this.updateSubscribers(element.getPath(),res, origin); + this.emit("value-change", element); } } + return resolve(); }); } diff --git a/test/Server.test.js b/test/Server.test.js index 69050ac..898ef55 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -124,45 +124,6 @@ describe("server", function() { }); }); - it("should be able to get child with getNodeByPath", function() { - //server._debug = true; - client = new DeviceTree(LOCALHOST, PORT); - //client._debug = true; - //client._debug = true; - return Promise.resolve() - .then(() => client.connect()) - .then(() => { - console.log("client connected"); - return client.getDirectory(); - }) - .then(() => { - return new Promise((resolve, reject) => { - client.root.getNodeByPath(client.client, ["scoreMaster", "identity", "product"], (err, child) => { - if (err) { reject(err) } - else { - resolve(child); - } - }); - }); - }) - .then(child => { - console.log(child); - }) - .then(() => { - return new Promise((resolve, reject) => { - client.root.getNodeByPath(client.client, ["scoreMaster", "router", "labels"], (err, child) => { - if (err) { reject(err) } - else { - resolve(child); - } - }); - }); - }) - .then(child => { - console.log(child); - return client.disconnect(); - }); - }); it("should be able to get child with tree.getNodeByPath", function() { //server._debug = true; client = new DeviceTree(LOCALHOST, PORT); @@ -464,8 +425,8 @@ describe("server", function() { .then(parameter => { console.log(parameter); expect(server.subscribers["0.0.2"]).not.toBeDefined(); - expect(parameter.contents.subscribers).toBeDefined(); - expect(parameter.contents.subscribers.size).toBe(0); + expect(parameter.contents._subscribers).toBeDefined(); + expect(parameter.contents._subscribers.size).toBe(0); server._subscribe = server.subscribe; let _resolve; const p = new Promise((resolve, reject) => { @@ -483,8 +444,8 @@ describe("server", function() { return client.getNodeByPathnum("0.0.2"); }) .then(parameter => { - expect(parameter.contents.subscribers).toBeDefined(); - expect(parameter.contents.subscribers.size).toBe(1); + expect(parameter.contents._subscribers).toBeDefined(); + expect(parameter.contents._subscribers.size).toBe(1); server._unsubscribe = server.unsubscribe; let _resolve; const p = new Promise((resolve, reject) => { @@ -507,8 +468,8 @@ describe("server", function() { console.log(parameter); expect(server.subscribers["0.0.2"]).toBeDefined(); expect(server.subscribers["0.0.2"].size).toBe(0); - expect(parameter.contents.subscribers).toBeDefined(); - expect(parameter.contents.subscribers.size).toBe(0); + expect(parameter.contents._subscribers).toBeDefined(); + expect(parameter.contents._subscribers.size).toBe(0); }) .then(() => client.disconnect()); }); diff --git a/test/utils.js b/test/utils.js index 2324391..5393953 100755 --- a/test/utils.js +++ b/test/utils.js @@ -41,7 +41,7 @@ const init = function(_src,_tgt) { children: [ {identifier: "product", value: "S-CORE Master"}, {identifier: "company", value: "EVS", access: "readWrite"}, - {identifier: "version", value: "1.2.0", streamIdentifier: 1234567}, + {identifier: "version", value: "1.2.0", access: "readWrite", streamIdentifier: 1234567}, {identifier: "author", value: "g.dufour@evs.com"}, ] }, From f68f8347f05c500a8fd69365ece1cbfe403f2c18 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 29 Dec 2019 18:04:00 +0100 Subject: [PATCH 124/162] Fix toJSON for root --- ember.js | 6 ++++-- package.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ember.js b/ember.js index b1580a0..2201ae8 100755 --- a/ember.js +++ b/ember.js @@ -317,8 +317,10 @@ _getElementByPath = function(children, pathArray, path) { TreeNode.prototype.toJSON = function() { let res = {}; const node = this; - res.number = node.getNumber(); - res.path = node.getPath(); + if (!this.isRoot()) { + res.number = node.getNumber(); + res.path = node.getPath(); + } if (node.contents) { for(let prop in node.contents) { if (node.contents.hasOwnProperty(prop)) { diff --git a/package.json b/package.json index b2025bb..b1944fa 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "1.12.0", + "version": "1.12.1", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 46a35258fd0b9ce59a597cd85a225e3a5a24b40e Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 5 Jan 2020 12:50:34 +0100 Subject: [PATCH 125/162] version 2 --- EmberLib/Command.js | 107 +++++ EmberLib/Elements.js | 8 + EmberLib/Function.js | 95 ++++ EmberLib/FunctionArgument.js | 79 ++++ EmberLib/FunctionContent.js | 99 ++++ EmberLib/Invocation.js | 79 ++++ EmberLib/InvocationResult.js | 115 +++++ EmberLib/Label.js | 58 +++ EmberLib/Matrix.js | 436 +++++++++++++++++ EmberLib/MatrixConnection.js | 150 ++++++ EmberLib/MatrixContents.js | 151 ++++++ EmberLib/MatrixDisposition.js | 18 + EmberLib/MatrixMode.js | 8 + EmberLib/MatrixNode.js | 110 +++++ EmberLib/MatrixOperation.js | 16 + EmberLib/MatrixType.js | 10 + EmberLib/Node.js | 90 ++++ EmberLib/NodeContents.js | 80 ++++ EmberLib/Parameter.js | 115 +++++ EmberLib/ParameterAccess.js | 12 + EmberLib/ParameterContents.js | 120 +++++ EmberLib/ParameterType.js | 52 +++ EmberLib/QualifiedFunction.js | 133 ++++++ EmberLib/QualifiedMatrix.js | 161 +++++++ EmberLib/QualifiedNode.js | 144 ++++++ EmberLib/QualifiedParameter.js | 186 ++++++++ EmberLib/TreeNode.js | 497 ++++++++++++++++++++ EmberLib/constants.js | 15 + EmberLib/index.js | 164 +++++++ EmberServer.js | 827 +++++++++++++++++++++++++++++++++ client.js | 6 +- device.js | 30 +- ember.js | 94 ++-- package.json | 2 +- server.js | 13 +- test/Ember.test.js | 3 +- 36 files changed, 4222 insertions(+), 61 deletions(-) create mode 100755 EmberLib/Command.js create mode 100755 EmberLib/Elements.js create mode 100755 EmberLib/Function.js create mode 100755 EmberLib/FunctionArgument.js create mode 100755 EmberLib/FunctionContent.js create mode 100755 EmberLib/Invocation.js create mode 100755 EmberLib/InvocationResult.js create mode 100755 EmberLib/Label.js create mode 100755 EmberLib/Matrix.js create mode 100755 EmberLib/MatrixConnection.js create mode 100755 EmberLib/MatrixContents.js create mode 100755 EmberLib/MatrixDisposition.js create mode 100755 EmberLib/MatrixMode.js create mode 100755 EmberLib/MatrixNode.js create mode 100755 EmberLib/MatrixOperation.js create mode 100755 EmberLib/MatrixType.js create mode 100755 EmberLib/Node.js create mode 100755 EmberLib/NodeContents.js create mode 100755 EmberLib/Parameter.js create mode 100755 EmberLib/ParameterAccess.js create mode 100755 EmberLib/ParameterContents.js create mode 100755 EmberLib/ParameterType.js create mode 100755 EmberLib/QualifiedFunction.js create mode 100755 EmberLib/QualifiedMatrix.js create mode 100755 EmberLib/QualifiedNode.js create mode 100755 EmberLib/QualifiedParameter.js create mode 100755 EmberLib/TreeNode.js create mode 100755 EmberLib/constants.js create mode 100755 EmberLib/index.js create mode 100755 EmberServer.js diff --git a/EmberLib/Command.js b/EmberLib/Command.js new file mode 100755 index 0000000..0ee5b77 --- /dev/null +++ b/EmberLib/Command.js @@ -0,0 +1,107 @@ +"use strict"; +const Enum = require('enum'); +const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); +const BER = require('../ber.js'); + +const FieldFlags = new Enum({ + sparse: -2, + all: -1, + default: 0, + identifier: 1, + description: 2, + tree: 3, + value: 4, + connections: 5 +}); + +class Command { + constructor(number) { + if(number !== undefined) + this.number = number; + if(number == COMMAND_GETDIRECTORY) { + this.fieldFlags = FieldFlags.all; + } + } + + isCommand() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(2)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + + if (this.number === COMMAND_GETDIRECTORY && this.fieldFlags) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeInt(this.fieldFlags.value); + ber.endSequence(); + } + + if (this.number === COMMAND_INVOKE && this.invocation) { + ber.startSequence(BER.CONTEXT(2)); + this.invocation.encode(ber); + ber.endSequence(); + } + // TODO: options + + ber.endSequence(); // BER.APPLICATION(2) + } + + /** + * @returns {number} + */ + getNumber() { + return this.number; + } + + /** + * + */ + toJSON() { + return { + number: this.number, + fieldFlags: this.fieldFlags, + invocation: this.invocation == null ? null : this.invocation.toJSON() + }; + } + + /** + * + * @param {BER} ber + * @returns {Command} + */ + static decode(ber) { + var c = new Command(); + ber = ber.getSequence(BER.APPLICATION(2)); + + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + c.number = seq.readInt(); + } + else if(tag == BER.CONTEXT(1)) { + c.fieldFlags = FieldFlags.get(seq.readInt()); + } + else if(tag == BER.CONTEXT(2)) { + c.invocation = Invocation.decode(seq); + } + else { + // TODO: options + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return c; + } + +} + +module.exports = Command; \ No newline at end of file diff --git a/EmberLib/Elements.js b/EmberLib/Elements.js new file mode 100755 index 0000000..1826ae5 --- /dev/null +++ b/EmberLib/Elements.js @@ -0,0 +1,8 @@ +"use stricts"; + +class Elements { + constructor() { + this._elements = new Map(); + } + +} \ No newline at end of file diff --git a/EmberLib/Function.js b/EmberLib/Function.js new file mode 100755 index 0000000..ee53b7a --- /dev/null +++ b/EmberLib/Function.js @@ -0,0 +1,95 @@ +"use strict"; +const TreeNode = require("./TreeNode"); +const QualifiedFunction = require("./QualifiedFunction"); +const BER = require('../ber.js'); + +class Function extends TreeNode { + constructor(number, func) { + super(); + this.number = number; + this.func = func; + } + + isFunction() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(19)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.children !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.APPLICATION(4)); + for(var i=0; i { + m.addChild(new Command(COMMAND_INVOKE)) + }); + } + + /** + * @returns {QualifiedFunction} + */ + toQualified() { + const qf = new QualifiedFunction(this.getPath()); + qf.update(this); + return qf; + } + + + /** + * + * @param {BER} ber + * @returns {Function} + */ + static decode(ber) { + const f = new Function(); + ber = ber.getSequence(BER.APPLICATION(19)); + + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + f.number = seq.readInt(); + } else if(tag == BER.CONTEXT(1)) { + f.contents = FunctionContent.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + f.decodeChildren(seq); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + if (DEBUG) { console.log("Function", f); } + return f; + } +} + +module.exports = Function; \ No newline at end of file diff --git a/EmberLib/FunctionArgument.js b/EmberLib/FunctionArgument.js new file mode 100755 index 0000000..216e402 --- /dev/null +++ b/EmberLib/FunctionArgument.js @@ -0,0 +1,79 @@ +"use strict"; +const BER = require('../ber.js'); +const {ParameterType} = require("./ParameterType"); + +/* +TupleDescription ::= + SEQUENCE OF [0] TupleItemDescription +TupleItemDescription ::= + [APPLICATION 21] IMPLICIT + SEQUENCE { + type [0] ParameterType, + name [1] EmberString OPTIONAL + } +Invocation ::= + [APPLICATION 22] IMPLICIT + SEQUENCE { + invocationId [0] Integer32 OPTIONAL, + arguments [1] Tuple OPTIONAL + } +Tuple ::= + SEQUENCE OF [0] Value +*/ + +class FunctionArgument { + /** + * + * @param {ParameterType} type + * @param {number|string|null} value + * @param {string|null} name + */ + constructor (type = null, value = null, name = null) { + /** @type {ParameterType} */ + this.type = type; + this.value = value; + this.name = name; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(21)); + if (this.type != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.type.value); + ber.endSequence(); + } + if (this.name != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.name, BER.EMBER_STRING); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * + * @param {BER} ber + * @returns {FunctionArgument} + */ + static decode(ber) { + const tuple = new FunctionArgument(); + ber = ber.getSequence(BER.APPLICATION(21)); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if (tag === BER.CONTEXT(0)) { + tuple.type = ParameterType.get(seq.readInt()); + } + else if (tag === BER.CONTEXT(1)) { + tuple.name = seq.readString(BER.EMBER_STRING); + } + } + return tuple; + } +} + +module.exports = FunctionArgument; \ No newline at end of file diff --git a/EmberLib/FunctionContent.js b/EmberLib/FunctionContent.js new file mode 100755 index 0000000..aab06ee --- /dev/null +++ b/EmberLib/FunctionContent.js @@ -0,0 +1,99 @@ +"use strict"; +const BER = require('../ber.js'); +const FunctionArgument = require("./FunctionArgument"); + +class FunctionContent { + constructor() { + this.arguments = []; + this.result = []; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.EMBER_SET); + + if(this.identifier != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.identifier, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(0) + } + + if(this.description != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.arguments != null) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i = 0; i < this.arguments.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + this.arguments[i].encode(ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); // BER.CONTEXT(2) + } + + if(this.result != null) { + ber.startSequence(BER.CONTEXT(3)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i = 0; i < this.result.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + this.result[i].encode(ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); // BER.CONTEXT(3) + } + + ber.endSequence(); // BER.EMBER_SET + } + + /** + * + * @param {BER} ber + * @returns {FunctionContent} + */ + static decode(ber) { + const fc = new FunctionContent(); + ber = ber.getSequence(BER.EMBER_SET); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + fc.identifier = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + fc.description = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(2)) { + fc.arguments = []; + let dataSeq = seq.getSequence(BER.EMBER_SEQUENCE); + while(dataSeq.remain > 0) { + seq = dataSeq.getSequence(BER.CONTEXT(0)); + fc.arguments.push(FunctionArgument.decode(seq)); + } + } else if(tag == BER.CONTEXT(3)) { + fc.result = []; + while(seq.remain > 0) { + tag = seq.peek(); + let dataSeq = seq.getSequence(tag); + if (tag === BER.CONTEXT(0)) { + fc.result.push(FunctionArgument.decode(dataSeq)); + } + } + } else if(tag == BER.CONTEXT(4)) { + fc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return fc; + } +} + +module.exports = FunctionContent; \ No newline at end of file diff --git a/EmberLib/Invocation.js b/EmberLib/Invocation.js new file mode 100755 index 0000000..b7bf4f9 --- /dev/null +++ b/EmberLib/Invocation.js @@ -0,0 +1,79 @@ +"use strict"; +const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); +const BER = require('../ber.js'); + +let _id = 1; +class Invocation { + constructor(id = null) { + this.id = id == null ? _id++ : id; + this.arguments = []; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(22)); + // ber.startSequence(BER.EMBER_SEQUENCE); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.id) + ber.endSequence(); + + ber.startSequence(BER.CONTEXT(1)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i = 0; i < this.arguments.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeValue( + this.arguments[i].value, + ParameterTypetoBERTAG(this.arguments[i].type + )); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + + ber.endSequence(); // BER.APPLICATION(22) + } + + /** + * + * @param {BER} ber + * @returns {Invocation} + */ + static decode(ber) { + const invocation = null; + ber = ber.getSequence(BER.APPLICATION(22)); + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + const invocationId = seq.readInt(); + invocation = new Invocation(invocationId); + } + else if(tag == BER.CONTEXT(1)) { + if (invocation == null) { + throw new Error("Missing invocationID"); + } + invocation.arguments = []; + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + const dataSeq = seq.getSequence(BER.CONTEXT(0)); + tag = dataSeq.peek(); + const val = dataSeq.readValue(); + invocation.arguments.push( + new FunctionArgument(ParameterTypefromBERTAG(tag), val) + ); + } + } + else { + // TODO: options + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return invocation; + } +} + +module.exports = Invocation; \ No newline at end of file diff --git a/EmberLib/InvocationResult.js b/EmberLib/InvocationResult.js new file mode 100755 index 0000000..6fae4b5 --- /dev/null +++ b/EmberLib/InvocationResult.js @@ -0,0 +1,115 @@ +"use strict"; + +const BER = require('../ber.js'); + + +class InvocationResult { + /** + * + * @param {number|null} invocationId=null + */ + constructor(invocationId = null) { + this.invocationId = invocationId; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(23)); + if (this.invocationId != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.invocationId); + ber.endSequence(); + } + if (this.success != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeBoolean(this.success); + ber.endSequence(); + } + if (this.result != null && this.result.length) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.EMBER_SEQUENCE); + for (let i = 0; i < this.result.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeValue(this.result[i].value, ParameterTypetoBERTAG(this.result[i].type)); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + } + ber.endSequence(); // BER.APPLICATION(23)} + } + + /** + * + */ + setFailure() { + this.success = false; + } + + /** + * + */ + setSuccess() { + this.success = true; + } + + /** + * + * @param {} result + */ + setResult(result) { + if (!Array.isArray(result)) { + throw new Error("Invalid inovation result. Should be array"); + } + this.result = result; + } + + toQualified() { + return this; + } + + /** + * + * @param {BER} ber + * @returns {InvocationResult} + */ + static decode(ber) { + const invocationResult = new InvocationResult(); + ber = ber.getSequence(BER.APPLICATION(23)); + while(ber.remain > 0) { + tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { // invocationId + invocationResult.invocationId = seq.readInt(); + } else if(tag == BER.CONTEXT(1)) { // success + invocationResult.success = seq.readBoolean() + }else if(tag == BER.CONTEXT(2)) { + invocationResult.result = []; + let res = seq.getSequence(BER.EMBER_SEQUENCE); + while(res.remain > 0) { + tag = res.peek(); + if (tag === BER.CONTEXT(0)) { + let resTag = res.getSequence(BER.CONTEXT(0)); + tag = resTag.peek(); + invocationResult.result.push( + new FunctionArgument( + ParameterTypefromBERTAG(tag), + resTag.readValue() + )); + } + } + continue + } else { + // TODO: options + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return invocationResult; + } +} + +module.exports = InvocationResult; \ No newline at end of file diff --git a/EmberLib/Label.js b/EmberLib/Label.js new file mode 100755 index 0000000..d8fecde --- /dev/null +++ b/EmberLib/Label.js @@ -0,0 +1,58 @@ +"use strict"; + +class Label { + constructor(path, description) { + if (path) { + this.basePath = path; + } + if (description) { + this.description = description; + } + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(18)); + if (this.basePath != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeRelativeOID(this.basePath, BER.EMBER_RELATIVE_OID); + ber.endSequence(); + } + if (this.description != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * + * @param {BER} ber + * @returns {Label} + */ + static decode(ber) { + var l = new Label(); + + ber = ber.getSequence(BER.APPLICATION(18)); + + while (ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if (tag == BER.CONTEXT(0)) { + l.basePath = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); + } else if (tag == BER.CONTEXT(1)) { + l.description = seq.readString(BER.EMBER_STRING); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return l; + } +} + +module.exports = Label; \ No newline at end of file diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js new file mode 100755 index 0000000..e697b74 --- /dev/null +++ b/EmberLib/Matrix.js @@ -0,0 +1,436 @@ +"use strict"; +const MatrixConnection = require("./MatrixConnection"); +const TreeNode = require("./TreeNode"); +const BER = require('../ber.js'); + +class Matrix extends TreeNode +{ + constructor() { + super(); + this._connectedSources = {}; + this._numConnections = 0; + } + + isMatrix() { + return true; + } + + /** + * + * @param {number} targetID + * @param {number[]} sources + * @param {Operation} operation + * @returns {boolean} + */ + canConnect(targetID, sources, operation) { + return Matrix.canConnect(this, targetID, sources, operation); + } + + /** + * + * @param {Object} connections + * @returns {root} + */ + connect(connections) { + const r = this.getTreeBranch(); + const m = r.getElementByPath(this.getPath()); + m.connections = connections; + return r; + } + + /** + * + * @param {number} targetID + * @param {number[]} sources + */ + connectSources(targetID, sources) { + return Matrix.connectSources(this, targetID, sources); + } + + /** + * + * @param {number} targetID + * @param {number[]} sources + */ + disconnectSources(targetID, sources) { + return Matrix.disconnectSources(this, targetID, sources); + } + + /** + * + * @param {BER} ber + */ + encodeConnections(ber) { + if (this.connections !== undefined) { + ber.startSequence(BER.CONTEXT(5)); + ber.startSequence(BER.EMBER_SEQUENCE); + + for(var id in this.connections) { + if (this.connections.hasOwnProperty(id)) { + ber.startSequence(BER.CONTEXT(0)); + this.connections[id].encode(ber); + ber.endSequence(); + } + } + ber.endSequence(); + ber.endSequence(); + } + } + + /** + * + * @param {BER} ber + */ + encodeSources(ber) { + if (this.sources != null) { + ber.startSequence(BER.CONTEXT(4)); + ber.startSequence(BER.EMBER_SEQUENCE); + + for(var i=0; i Number(i))); + + if (matrixNode.connections[targetID].isLocked()) { + return false; + } + if (type === MatrixType.oneToN && + matrixNode.contents.maximumConnectsPerTarget == null && + matrixNode.contents.maximumConnectsPerTarget == null) { + return sMap.size < 2; + } + else if (type === MatrixType.oneToN && sMap.size >= 2) { + return false; + } + else if (type === MatrixType.oneToOne) { + if (sMap.size > 1) { + return false; + } + const sourceConnections = matrixNode._connectedSources[sources[0]]; + return sourceConnections == null || sourceConnections.size === 0 || sourceConnections.has(targetID); + } + else { + // N to N + if (matrixNode.contents.maximumConnectsPerTarget != null && + newSources.length > matrixNode.contents.maximumConnectsPerTarget) { + return false; + } + if (matrixNode.contents.maximumTotalConnects != null) { + let count = matrixNode._numConnections; + if (oldSources) { + count -= oldSources.length; + } + if (newSources) { + count += newSources.length; + } + return count <= matrixNode.contents.maximumTotalConnects; + } + + } + return true; + } + + /** + * + * @param {MatrixNode} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + static connectSources(matrix, targetID, sources) { + const target = Number(targetID); + if (matrix.connections[target] == null) { + matrix.connections[target] = new MatrixConnection(target); + } + matrix.connections[target].connectSources(sources); + if (sources != null) { + for(let source of sources) { + if (matrix._connectedSources[source] == null) { + matrix._connectedSources[source] = new Set(); + } + if (!matrix._connectedSources[source].has(target)) { + matrix._connectedSources[source].add(target); + matrix._numConnections++; + } + } + } + } + + /** + * + * @param {BER} ber + * @returns {number[]} + */ + static decodeTargets(ber) { + const targets = []; + ber = ber.getSequence(BER.EMBER_SEQUENCE); + while(ber.remain > 0) { + var seq = ber.getSequence(BER.CONTEXT(0)); + seq = seq.getSequence(BER.APPLICATION(14)); + seq = seq.getSequence(BER.CONTEXT(0)); + targets.push(seq.readInt()); + } + return targets; + } + + /** + * + * @param {BER} ber + * @returns {number[]} + */ + static decodeSources(ber) { + const sources = []; + ber = ber.getSequence(BER.EMBER_SEQUENCE); + while(ber.remain > 0) { + var seq = ber.getSequence(BER.CONTEXT(0)); + seq = seq.getSequence(BER.APPLICATION(15)); + seq = seq.getSequence(BER.CONTEXT(0)); + sources.push(seq.readInt()); + } + return sources; + }; + + /** + * + * @param {BER} ber + * @returns {Object} + */ + static decodeConnections(ber) { + let connections = {}; + let seq = ber.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + var conSeq = seq.getSequence(BER.CONTEXT(0)); + var con = MatrixConnection.decode(conSeq); + if (con.target !== undefined) { + connections[con.target] = (con); + } + } + return connections; + } + + /** + * + * @param {MatrixNode} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + static disconnectSources(matrix, targetID, sources) { + const target = Number(targetID); + if (matrix.connections[target] == null) { + matrix.connections[target] = new MatrixConnection(target); + } + matrix.connections[target].disconnectSources(sources); + if (sources != null) { + for(let source of sources) { + if (matrix._connectedSources[source] == null) { + continue; + } + if (matrix._connectedSources[source].has(target)) { + matrix._connectedSources[source].delete(target); + matrix._numConnections--; + } + } + } + } + + /** + * + * @param {MatrixNode} matrix + * @param {number} source + */ + static getSourceConnections(matrix, source) { + const targets = matrix._connectedSources[source]; + if (targets) { + return [...targets]; + } + return []; + } + + /** + * + * @param {QualifiedMatrix|MatrixNode} matrix + * @param {QualifiedMatrix|MatrixNode} newMatrix + */ + static MatrixUpdate(matrix, newMatrix) { + if (newMatrix.targets != null) { + matrix.targets = newMatrix.targets; + } + if (newMatrix.sources != null) { + matrix.sources = newMatrix.sources; + } + if (newMatrix.connections != null) { + if (matrix.connections == null) { + matrix.connections = {}; + } + for(let id in newMatrix.connections) { + if (newMatrix.connections.hasOwnProperty(id)) { + let connection = newMatrix.connections[id]; + if ((connection.target < matrix.contents.targetCount) && + (connection.target >= 0)) { + if (matrix.connections[connection.target] == null) { + matrix.connections[connection.target] = new MatrixConnection(connection.target); + } + matrix.connections[connection.target].setSources(connection.sources); + } + } + } + } + } + + /** + * + * @param {MatrixNode} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + static setSources(matrix, targetID, sources) { + const currentSource = matrix.connections[targetID] == null || matrix.connections[targetID].sources == null ? + [] : matrix.connections[targetID].sources; + if (currentSource.length > 0) { + MatrixNode.disconnectSources(matrix, targetID, currentSource) + } + Matrix.connectSources(matrix, targetID, sources); + } + + /** + * + * @param {MatrixNode} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + static validateConnection(matrixNode, targetID, sources) { + if (targetID < 0) { + throw new Error(`Invalid negative target index ${targetID}`); + } + for(let i = 0; i < sources.length; i++) { + if (sources[i] < 0) { + throw new Error(`Invalid negative source at index ${i}`); + } + } + if (matrixNode.contents.mode === MatrixMode.linear) { + if (targetID >= matrixNode.contents.targetCount) { + throw new Error(`targetID ${targetID} higher than max value ${matrixNode.contents.targetCount}`); + } + for(let i = 0; i < sources.length; i++) { + if (sources[i] >= matrixNode.contents.sourceCount) { + throw new Error(`Invalid source at index ${i}`); + } + } + } + else if ((matrixNode.targets == null) || (matrixNode.sources == null)) { + throw new Error("Non-Linear matrix should have targets and sources"); + } + else { + let found = false; + for(let i = 0; i < matrixNode.targets; i++) { + if (matrixNode.targets[i] === targetID) { + found = true; + break; + } + } + if (!found) { + throw new Error(`Unknown targetid ${targetID}`); + } + found = false; + for(let i = 0; i < sources.length; i++) { + for(let j = 0; i < matrixNode.sources; j++) { + if (matrixNode.sources[j] === sources[i]) { + found = true; + break; + } + } + if (!found) { + throw new Error(`Unknown source at index ${i}`); + } + } + } + } +} + +module.exports = Matrix; \ No newline at end of file diff --git a/EmberLib/MatrixConnection.js b/EmberLib/MatrixConnection.js new file mode 100755 index 0000000..5bd90c1 --- /dev/null +++ b/EmberLib/MatrixConnection.js @@ -0,0 +1,150 @@ +"use strict"; + +class MatrixConnection { + /** + * + * @param {number} target + */ + constructor(target) { + if (target) { + let _target = Number(target); + if (isNaN(_target)) { _target = 0; } + this.target = _target; + } + else { + this.target = 0; + } + this._locked = false; + } + + /** + * + * @param {number[]} sources + */ + connectSources(sources) { + if (sources == null) { + return; + } + let s = new Set(this.sources); + for(let item of sources) { + s.add(item); + } + this.sources = [...s].sort(); + } + + /** + * + * @param {number[]} sources + */ + disconnectSources(sources) { + if (sources == null) { + return; + } + let s = new Set(this.sources); + for(let item of sources) { + s.delete(item); + } + this.sources = [...s].sort(); + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(16)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.target); + ber.endSequence(); + + if ((this.sources !== undefined)&& (this.sources.length > 0)) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeRelativeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); + ber.endSequence(); + } + if (this.operation !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.writeInt(this.operation.value); + ber.endSequence(); + } + if (this.disposition !== undefined) { + ber.startSequence(BER.CONTEXT(3)); + ber.writeInt(this.disposition.value); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * @returns {boolean} + */ + isLocked() { + return this._locked; + } + + /** + * + */ + lock() { + this._locked = true; + } + + /** + * + * @param {number[]} sources + */ + setSources(sources) { + if (sources == null) { + delete this.sources; + return; + } + let s = new Set(sources.map(i => Number(i))); + this.sources = [...s].sort(); // sources should be an array + } + + /** + * + */ + unlock() { + this._locked = false; + } + + /** + * + * @param {BER} ber + * @returns {MatrixConnection} + */ + static decode(ber) { + const c = new MatrixConnection(); + ber = ber.getSequence(BER.APPLICATION(16)); + while (ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if (tag == BER.CONTEXT(0)) { + c.target = seq.readInt(); + } + else if (tag == BER.CONTEXT(1)) { + const sources = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); + if (sources === "") { + c .sources = []; + } + else { + c.sources = sources.split(".").map(i => Number(i)); + } + } else if (tag == BER.CONTEXT(2)) { + c.operation = MatrixOperation.get(seq.readInt()); + + } else if (tag == BER.CONTEXT(3)) { + c.disposition = MatrixDisposition.get(seq.readInt()); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return c; + } + +} + +module.exports = MatrixConnection; \ No newline at end of file diff --git a/EmberLib/MatrixContents.js b/EmberLib/MatrixContents.js new file mode 100755 index 0000000..7c11647 --- /dev/null +++ b/EmberLib/MatrixContents.js @@ -0,0 +1,151 @@ +"use strict"; + +const MatrixType = require("./MatrixType"); +const MatrixMode = require("./MatrixMode"); + +class MatrixContents { + constructor(type = MatrixType.oneToN, mode = MatrixMode.linear) { + this.type = type; + this.mode = mode; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.EMBER_SET); + if (this.identifier !== undefined) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.identifier, BER.EMBER_STRING); + ber.endSequence(); + } + if (this.description !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); + } + if (this.type !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.writeInt(this.type.value); + ber.endSequence(); + } + if (this.mode !== undefined) { + ber.startSequence(BER.CONTEXT(3)); + ber.writeInt(this.mode.value); + ber.endSequence(); + } + if (this.targetCount !== undefined) { + ber.startSequence(BER.CONTEXT(4)); + ber.writeInt(this.targetCount); + ber.endSequence(); + } + if (this.sourceCount !== undefined) { + ber.startSequence(BER.CONTEXT(5)); + ber.writeInt(this.sourceCount); + ber.endSequence(); + } + if (this.maximumTotalConnects !== undefined) { + ber.startSequence(BER.CONTEXT(6)); + ber.writeInt(this.maximumTotalConnects); + ber.endSequence(); + } + if (this.maximumConnectsPerTarget !== undefined) { + ber.startSequence(BER.CONTEXT(7)); + ber.writeInt(this.maximumConnectsPerTarget); + ber.endSequence(); + } + if (this.parametersLocation !== undefined) { + ber.startSequence(BER.CONTEXT(8)); + let param = Number(this.parametersLocation) + if (isNaN(param)) { + ber.writeRelativeOID(this.parametersLocation, BER.EMBER_RELATIVE_OID); + } + else { + ber.writeInt(param); + } + ber.endSequence(); + } + if (this.gainParameterNumber !== undefined) { + ber.startSequence(BER.CONTEXT(9)); + ber.writeInt(this.gainParameterNumber); + ber.endSequence(); + } + if (this.labels !== undefined) { + ber.startSequence(BER.CONTEXT(10)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i =0; i < this.labels.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + this.labels[i].encode(ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + } + if (this.schemaIdentifiers !== undefined) { + ber.startSequence(BER.CONTEXT(11)); + ber.writeInt(this.schemaIdentifiers, BER.EMBER_STRING); + ber.endSequence(); + } + if (this.templateReference !== undefined) { + ber.startSequence(BER.CONTEXT(12)); + ber.writeRelativeOID(this.templateReference, BER.EMBER_RELATIVE_OID); + ber.endSequence(); + } + ber.endSequence(); + } + + static decode(ber) { + const mc = new MatrixContents(); + ber = ber.getSequence(BER.EMBER_SET); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + + if(tag == BER.CONTEXT(0)) { + mc.identifier = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + mc.description = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(2)) { + mc.type = MatrixType.get(seq.readInt()); + } else if(tag == BER.CONTEXT(3)) { + mc.mode = MatrixMode.get(seq.readInt()); + } else if(tag == BER.CONTEXT(4)) { + mc.targetCount = seq.readInt(); + } else if(tag == BER.CONTEXT(5)) { + mc.sourceCount = seq.readInt(); + } else if(tag == BER.CONTEXT(6)) { + mc.maximumTotalConnects = seq.readInt(); + } else if(tag == BER.CONTEXT(7)) { + mc.maximumConnectsPerTarget = seq.readInt(); + } else if(tag == BER.CONTEXT(8)) { + tag = seq.peek(); + if (tag === BER.EMBER_RELATIVE_OID) { + mc.parametersLocation = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else { + mc.parametersLocation = seq.readInt(); + } + } else if(tag == BER.CONTEXT(9)) { + mc.gainParameterNumber = seq.readInt(); + } else if(tag == BER.CONTEXT(10)) { + mc.labels = []; + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + let lSeq = seq.getSequence(BER.CONTEXT(0)); + mc.labels.push(Label.decode(lSeq)); + } + } else if(tag == BER.CONTEXT(11)) { + mc.schemaIdentifiers = seq.readInt(); + } else if(tag == BER.CONTEXT(12)) { + mc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return mc; + }; +} + +module.exports = MatrixContents; \ No newline at end of file diff --git a/EmberLib/MatrixDisposition.js b/EmberLib/MatrixDisposition.js new file mode 100755 index 0000000..1cce450 --- /dev/null +++ b/EmberLib/MatrixDisposition.js @@ -0,0 +1,18 @@ +const Enum = require('enum'); + +// ConnectionDisposition ::= +// INTEGER { +// tally (0), -- default +// modified (1), -- sources contains new current state +// pending (2), -- sources contains future state +// locked (3) -- error: target locked. sources contains current state +// -- more tbd. +// } +const MatrixDisposition = new Enum({ + tally: 0, + modified: 1, + pending: 2, + locked: 3 +}); + +module.exports = MatrixDisposition; \ No newline at end of file diff --git a/EmberLib/MatrixMode.js b/EmberLib/MatrixMode.js new file mode 100755 index 0000000..7101df0 --- /dev/null +++ b/EmberLib/MatrixMode.js @@ -0,0 +1,8 @@ +const Enum = require('enum'); + +const MatrixMode = new Enum({ + linear: 0, + nonLinear: 1 +}); + +module.exports = MatrixMode; \ No newline at end of file diff --git a/EmberLib/MatrixNode.js b/EmberLib/MatrixNode.js new file mode 100755 index 0000000..618b2a3 --- /dev/null +++ b/EmberLib/MatrixNode.js @@ -0,0 +1,110 @@ +"use strict"; + +const Matrix = require("./Matrix"); +const MatrixContents = require("./MatrixContents"); +const QualifiedMatrix = require("./QualifiedMatrix"); +const BER = require('../ber.js'); + +class MatrixNode extends Matrix { + constructor(number = undefined) { + super(); + this.number = number; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(13)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + this.encodeTargets(ber); + this.encodeSources(ber); + this.encodeConnections(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + + + /** + * + * @param {boolean} complete + * @returns {MatrixNode} + */ + getMinimal(complete = false) { + const number = this.getNumber(); + const m = new MatrixNode(number); + if (complete) { + if (this.contents != null) { + m.contents = this.contents; + } + if (this.targets != null) { + m.targets = this.targets; + } + if (this.sources != null) { + m.sources = this.sources; + } + if (this.connections != null) { + m.connections = this.connections; + } + } + return m; + } + + /** + * @returns {QualifiedMatrix} + */ + toQualified() { + const qm = new QualifiedMatrix(this.getPath()); + qm.update(this); + return qm; + } + + /** + * + * @param {BER} ber + * @returns {MatrixNode} + */ + static decode(ber) { + const m = new MatrixNode(); + ber = ber.getSequence(BER.APPLICATION(13)); + while (ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if (tag == BER.CONTEXT(0)) { + m.number = seq.readInt(); + } + else if (tag == BER.CONTEXT(1)) { + m.contents = MatrixContents.decode(seq); + + } else if (tag == BER.CONTEXT(2)) { + m.decodeChildren(seq); + } else if (tag == BER.CONTEXT(3)) { + m.targets = Matrix.decodeTargets(seq); + } else if (tag == BER.CONTEXT(4)) { + m.sources = Matrix.decodeSources(seq); + } else if (tag == BER.CONTEXT(5)) { + m.connections = Matrix.decodeConnections(seq); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + if (DEBUG) { console.log("MatrixNode", m); } + return m; + } +} + +module.exports = MatrixNode; \ No newline at end of file diff --git a/EmberLib/MatrixOperation.js b/EmberLib/MatrixOperation.js new file mode 100755 index 0000000..c3a4879 --- /dev/null +++ b/EmberLib/MatrixOperation.js @@ -0,0 +1,16 @@ +const Enum = require('enum'); + +// ConnectionOperation ::= +// INTEGER { +// absolute (0), -- default. sources contains absolute information +// connect (1), -- nToN only. sources contains sources to add to connection +// disconnect (2) -- nToN only. sources contains sources to remove from +// connection +// } +const MatrixOperation = new Enum({ + absolute: 0, + connect: 1, + disconnect: 2 +}); + +module.exports = MatrixOperation; \ No newline at end of file diff --git a/EmberLib/MatrixType.js b/EmberLib/MatrixType.js new file mode 100755 index 0000000..8008bd4 --- /dev/null +++ b/EmberLib/MatrixType.js @@ -0,0 +1,10 @@ + +const Enum = require('enum'); + +const MatrixType = new Enum({ + oneToN: 0, + oneToOne: 1, + nToN: 2 +}); + +module.exports = MatrixType; \ No newline at end of file diff --git a/EmberLib/Node.js b/EmberLib/Node.js new file mode 100755 index 0000000..7b1d6a8 --- /dev/null +++ b/EmberLib/Node.js @@ -0,0 +1,90 @@ +"use strict"; + +const TreeNode = require("./TreeNode"); +const QualifiedNode = require("./QualifiedNode"); +const NodeContents = require("./NodeContents"); +const BER = require('../ber.js'); + +class Node extends TreeNode { + /** + * + * @param {number} number + */ + constructor(number) { + super(); + this.number = number; + } + + isNode() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(3)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + /** + * + * @param {function} callback + */ + subscribe(callback) { + if (callback != null && this.isStream()) { + this.contents._subscribers.add(callback); + } + } + + /** + * @returns {QualifiedNode} + */ + toQualified() { + const qn = new QualifiedNode(this.getPath()); + qn.update(this); + return qn; + } + + /** + * + * @param {BER} ber + * @returns {Node} + */ + static decode(ber) { + const n = new Node(); + ber = ber.getSequence(BER.APPLICATION(3)); + + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + n.number = seq.readInt(); + } else if(tag == BER.CONTEXT(1)) { + n.contents = NodeContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + n.decodeChildren(seq); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + if (DEBUG) { console.log("Node", n); } + return n; + } +}; + +module.exports = Node; diff --git a/EmberLib/NodeContents.js b/EmberLib/NodeContents.js new file mode 100755 index 0000000..a34fc48 --- /dev/null +++ b/EmberLib/NodeContents.js @@ -0,0 +1,80 @@ +"use strict"; +const BER = require('../ber.js'); + +class NodeContents{ + constructor() { + this.isOnline = true; + this._subscribers = new Set(); + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.EMBER_SET); + + if(this.identifier !== undefined) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.identifier, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(0) + } + + if(this.description !== undefined) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.isRoot !== undefined) { + ber.startSequence(BER.CONTEXT(2)); + ber.writeBoolean(this.isRoot); + ber.endSequence(); // BER.CONTEXT(2) + } + + if(this.isOnline !== undefined) { + ber.startSequence(BER.CONTEXT(3)); + ber.writeBoolean(this.isOnline); + ber.endSequence(); // BER.CONTEXT(3) + } + + if(this.schemaIdentifiers !== undefined) { + ber.startSequence(BER.CONTEXT(4)); + ber.writeString(this.schemaIdentifiers, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(4) + } + + ber.endSequence(); // BER.EMBER_SET + } + + /** + * + * @param {BER} ber + * @returns {NodeContents} + */ + static decode(ber) { + var nc = new NodeContents(); + ber = ber.getSequence(BER.EMBER_SET); + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + nc.identifier = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + nc.description = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(2)) { + nc.isRoot = seq.readBoolean(); + } else if(tag == BER.CONTEXT(3)) { + nc.isOnline = seq.readBoolean(); + } else if(tag == BER.CONTEXT(4)) { + nc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return nc; + } +} + +module.exports = NodeContents; \ No newline at end of file diff --git a/EmberLib/Parameter.js b/EmberLib/Parameter.js new file mode 100755 index 0000000..13e14d3 --- /dev/null +++ b/EmberLib/Parameter.js @@ -0,0 +1,115 @@ +"use strict"; + +const TreeNode = require("./TreeNode"); +const QualifiedParameter = require("./QualifiedParameter"); +const BER = require('../ber.js'); +const ParameterContents = require("./ParameterContents"); + +class Parameter extends TreeNode { + /** + * + * @param {number} number + */ + constructor(number) { + super(); + this.number = number; + } + + isParameter() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(1)); + + ber.writeIfDefined(this.number, ber.writeInt, 0); + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); + } + + this.encodeChildren(ber); + + ber.endSequence(); + } + + /** + * + * @param {string|number} value + * @returns {Root} + */ + setValue(value) { + return this.getTreeBranch(undefined, (m) => { + m.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); + }); + } + + /** + * @returns {QualifiedParameter} + */ + toQualified() { + let qp = new QualifiedParameter(this.getPath()); + qp.update(this); + return qp; + } + + /** + * + * @param {Parameter} other + */ + update(other) { + if ((other !== undefined) && (other.contents !== undefined)) { + if (this.contents == null) { + this.contents = other.contents; + } + else { + for (var key in other.contents) { + if (key[0] === "_") { continue; } + if (other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } + } + for(let cb of this.contents._subscribers) { + cb(this); + } + } + } + return; + } + + /** + * + * @param {BER} ber + * @returns {Parameter} + */ + static decode(ber) { + const p = new Parameter(); + ber = ber.getSequence(BER.APPLICATION(1)); + + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + p.number = seq.readInt(); + + } else if(tag == BER.CONTEXT(1)) { + p.contents = ParameterContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + p.decodeChildren(seq); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + if (DEBUG) { console.log("Parameter", p); } + return p; + } + +} + +module.exports = Parameter; \ No newline at end of file diff --git a/EmberLib/ParameterAccess.js b/EmberLib/ParameterAccess.js new file mode 100755 index 0000000..e6f7113 --- /dev/null +++ b/EmberLib/ParameterAccess.js @@ -0,0 +1,12 @@ + +const Enum = require('enum'); + +var ParameterAccess = new Enum({ + none: 0, + read: 1, + write: 2, + readWrite: 3 +}); + + +module.exports = ParameterAccess; \ No newline at end of file diff --git a/EmberLib/ParameterContents.js b/EmberLib/ParameterContents.js new file mode 100755 index 0000000..f068e46 --- /dev/null +++ b/EmberLib/ParameterContents.js @@ -0,0 +1,120 @@ +"use strict"; + +const {ParameterType} = require("./ParameterType"); +const ParameterAccess = require("./ParameterAccess"); + +const BER = require('../ber.js'); + +class ParameterContents { + constructor(value, type) { + this._subscribers = new Set(); + if(value !== undefined) { + this.value = value; + } + if(type !== undefined) { + if((type = ParameterType.get(type)) !== undefined){ + this.type = type + } + } + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.EMBER_SET); + + ber.writeIfDefined(this.identifier, ber.writeString, 0, BER.EMBER_STRING); + ber.writeIfDefined(this.description, ber.writeString, 1, BER.EMBER_STRING); + ber.writeIfDefined(this.value, ber.writeValue, 2); + ber.writeIfDefined(this.minimum, ber.writeValue, 3); + ber.writeIfDefined(this.maximum, ber.writeValue, 4); + ber.writeIfDefinedEnum(this.access, ParameterAccess, ber.writeInt, 5); + ber.writeIfDefined(this.format, ber.writeString, 6, BER.EMBER_STRING); + ber.writeIfDefined(this.enumeration, ber.writeString, 7, BER.EMBER_STRING); + ber.writeIfDefined(this.factor, ber.writeInt, 8); + ber.writeIfDefined(this.isOnline, ber.writeBoolean, 9); + ber.writeIfDefined(this.formula, ber.writeString, 10, BER.EMBER_STRING); + ber.writeIfDefined(this.step, ber.writeInt, 11); + ber.writeIfDefined(this.default, ber.writeValue, 12); + ber.writeIfDefinedEnum(this.type, ParameterType, ber.writeInt, 13); + ber.writeIfDefined(this.streamIdentifier, ber.writeInt, 14); + + if(this.emumMap !== undefined) { + ber.startSequence(BER.CONTEXT(15)); + StringIntegerCollection.encode(ber, this.enumMap); + ber.endSequence(); + } + + if(this.streamDescriptor !== undefined) { + ber.startSequence(BER.CONTEXT(16)); + this.streamDescriptor.encode(ber); + ber.endSequence(); + } + + ber.writeIfDefined(this.schemaIdentifiers, ber.writeString, 17, BER.EMBER_STRING); + + ber.endSequence(); + } + + /** + * + * @param {BER} ber + * @returns {ParameterContents} + */ + static decode(ber) { + const pc = new ParameterContents(); + ber = ber.getSequence(BER.EMBER_SET); + + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + pc.identifier = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + pc.description = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(2)) { + pc.value = seq.readValue(); + } else if(tag == BER.CONTEXT(3)) { + pc.minimum = seq.readValue(); + } else if(tag == BER.CONTEXT(4)) { + pc.maximum = seq.readValue(); + } else if(tag == BER.CONTEXT(5)) { + pc.access = ParameterAccess.get(seq.readInt()); + } else if(tag == BER.CONTEXT(6)) { + pc.format = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(7)) { + pc.enumeration = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(8)) { + pc.factor = seq.readInt(); + } else if(tag == BER.CONTEXT(9)) { + pc.isOnline = seq.readBoolean(); + } else if(tag == BER.CONTEXT(10)) { + pc.formula = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(11)) { + pc.step = seq.readInt(); + } else if(tag == BER.CONTEXT(12)) { + pc.default = seq.readValue(); + } else if(tag == BER.CONTEXT(13)) { + pc.type = ParameterType.get(seq.readInt()); + } else if(tag == BER.CONTEXT(14)) { + pc.streamIdentifier = seq.readInt(); + } else if(tag == BER.CONTEXT(15)) { + pc.enumMap = StringIntegerCollection.decode(seq); + } else if(tag == BER.CONTEXT(16)) { + pc.streamDescriptor = StreamDescription.decode(seq); + } else if(tag == BER.CONTEXT(17)) { + pc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); + } else if (tag == null) { + break; + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return pc; + } +} + +module.exports = ParameterContents; diff --git a/EmberLib/ParameterType.js b/EmberLib/ParameterType.js new file mode 100755 index 0000000..1611237 --- /dev/null +++ b/EmberLib/ParameterType.js @@ -0,0 +1,52 @@ +const Enum = require('enum'); +const BER = require('../ber.js'); + +function ParameterTypetoBERTAG(type) { + switch (type.value) { + case 1: return BER.EMBER_INTEGER; + case 2: return BER.EMBER_REAL; + case 3: return BER.EMBER_STRING; + case 4: return BER.EMBER_BOOLEAN; + case 7: return BER.EMBER_OCTETSTRING; + default: + throw new Error(`Unhandled ParameterType ${type}`); + } +} + +function ParameterTypefromBERTAG(tag) { + switch (tag) { + case BER.EMBER_INTEGER: return ParameterType.integer; + case BER.EMBER_REAL: return ParameterType.real; + case BER.EMBER_STRING: return ParameterType.string; + case BER.EMBER_BOOLEAN: return ParameterType.boolean; + case BER.EMBER_OCTETSTRING: return ParameterType.octets; + default: + throw new Error(`Unhandled BER TAB ${tag}`); + } +} + +/* +BER VAlue +Value ::= + CHOICE { + integer Integer64, + real REAL, + string EmberString, + boolean BOOLEAN, + octets OCTET STRING, + null NULL + }*/ + + var ParameterType = new Enum({ + integer: 1, + real: 2, + string: 3, + boolean: 4, + trigger: 5, + enum: 6, + octets: 7 +}); + +module.exports = { + ParameterType, ParameterTypetoBERTAG, ParameterTypefromBERTAG +}; \ No newline at end of file diff --git a/EmberLib/QualifiedFunction.js b/EmberLib/QualifiedFunction.js new file mode 100755 index 0000000..cfccc54 --- /dev/null +++ b/EmberLib/QualifiedFunction.js @@ -0,0 +1,133 @@ +"use strict"; + +const TreeNode = require("./TreeNode"); +const FunctionContent = require("./FunctionContent"); +const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); +const BER = require('../ber.js'); +const Command = require("./Command"); + +class QualifiedFunction extends TreeNode { + /** + * + * @param {string} path + * @param {function} func + */ + constructor(path, func) { + super(); + this.path = path; + this.func = func; + } + + isFunction() { + return true; + } + isQualified() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(20)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + /** + * + * @param {number} cmd + * @returns {TreeNode} + */ + getCommand(cmd) { + const r = new TreeNode(); + const qf = new QualifiedFunction(); + qf.path = this.getPath(); + r.addElement(qf); + qf.addChild(new Command(cmd)); + return r; + } + + /** + * + * @returns {TreeNode} + */ + getDirectory() { + return this.getCommand(COMMAND_GETDIRECTORY); + } + + /** + * + * @param {*} params + */ + invoke(params) { + if (this.path == null) { + throw new Error("Invalid path"); + } + var QualifiedFunctionNode = this.getCommand(COMMAND_INVOKE); + var invocation = new Invocation() + invocation.arguments = params; + QualifiedFunctionNode.getElementByPath(this.getPath()).getNumber(COMMAND_INVOKE).invocation = invocation + return QualifiedFunctionNode; + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + subscribe(callback) { + return this.getCommand(COMMAND_SUBSCRIBE); + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + unsubscribe(callback) { + return QualifiedFunctionCommand(COMMAND_UNSUBSCRIBE); + } + + + /** + * + * @param {BER} ber + * @returns {QualifiedFunction} + */ + static decode(ber) { + const qf = new QualifiedFunction(); + ber = ber.getSequence(BER.APPLICATION(20)); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qf.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qf.contents = FunctionContent.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qf.decodeChildren(seq); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return qf; + } +} + +module.exports = QualifiedFunction; \ No newline at end of file diff --git a/EmberLib/QualifiedMatrix.js b/EmberLib/QualifiedMatrix.js new file mode 100755 index 0000000..666320f --- /dev/null +++ b/EmberLib/QualifiedMatrix.js @@ -0,0 +1,161 @@ +"use strict"; + +const Matrix = require("./Matrix"); +const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); +const BER = require('../ber.js'); +const Command = require("./Command"); + +class QualifiedMatrix extends Matrix { + /** + * + * @param {string} path + */ + constructor(path) { + super(); + this.path = path; + } + + isQualified() { + return true; + } + /** + * + * @param {Object} connections + * @returns {Root} + */ + connect(connections) { + const r = new Root(); + const qn = new QualifiedMatrix(); + qn.path = this.path; + r.addElement(qn); + qn.connections = connections; + return r; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(17)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + this.encodeTargets(ber); + this.encodeSources(ber); + this.encodeConnections(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + /** + * + * @param {number} cmd + * @returns {TreeNode} + */ + getCommand(cmd) { + const r = new TreeNode(); + const qn = new QualifiedMatrix(); + qn.path = this.getPath(); + r.addElement(qn); + qn.addChild(new Command(cmd)); + return r; + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + getDirectory(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + if (callback != null && !this.isStream()) { + this.contents._subscribers.add(callback); + } + return this.getCommand(COMMAND_GETDIRECTORY); + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + subscribe(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + if (callback != null && this.isStream()) { + this.contents._subscribers.add(callback); + } + return this.getCommand(COMMAND_SUBSCRIBE); + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + unsubscribe(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + if (callback != null && this.isStream()) { + this.contents._subscribers.delete(callback); + } + return this.getCommand(COMMAND_UNSUBSCRIBE); + } + + /** + * + * @param {BER} ber + * @returns {QualifiedMatrix} + */ + static decode(ber) { + const qm = new QualifiedMatrix(); + ber = ber.getSequence(BER.APPLICATION(17)); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qm.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qm.contents = MatrixContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qm.decodeChildren(seq); + } else if (tag == BER.CONTEXT(3)) { + qm.targets = decodeTargets(seq); + } else if (tag == BER.CONTEXT(4)) { + qm.sources = decodeSources(seq); + } else if (tag == BER.CONTEXT(5)) { + qm.connections = {}; + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + let conSeq = seq.getSequence(BER.CONTEXT(0)); + let con = MatrixConnection.decode(conSeq); + if (con.target !== undefined) { + qm.connections[con.target] = con; + } + } + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + if (DEBUG) { console.log("QualifiedMatrix", qm); } + return qm; + } +} + +module.exports = QualifiedMatrix; \ No newline at end of file diff --git a/EmberLib/QualifiedNode.js b/EmberLib/QualifiedNode.js new file mode 100755 index 0000000..3ac19ba --- /dev/null +++ b/EmberLib/QualifiedNode.js @@ -0,0 +1,144 @@ +"user strict"; +const TreeNode = require("./TreeNode"); +const BER = require('../ber.js'); +const NodeContents = require("./NodeContents"); +const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); +const Command = require("./Command"); + +class QualifiedNode extends TreeNode { + constructor (path) { + super(); + if (path != undefined) { + this.path = path; + } + } + + isNode() { + return true; + } + isQualified() { + return true; + } + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(10)); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + /** + * + * @param {number} cmd + * @returns {TreeNode} + */ + getCommand(cmd) { + const r = new TreeNode(); + const qn = new QualifiedNode(); + qn.path = this.getPath(); + r.addElement(qn); + qn.addChild(new Command(cmd)); + return r; + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + getDirectory(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + if (callback != null && this.contents != null && !this.isStream()) { + this.contents._subscribers.add(callback); + } + return this.getCommand(COMMAND_GETDIRECTORY); + } + + /** + * + * @param {boolean} complete + * @returns {QualifiedNode} + */ + getMinimal(complete = false) { + const number = this.getNumber(); + const n = new Node(number); + if (complete && (this.contents != null)) { + n.contents = this.contents; + } + return n; + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + subscribe(callback) { + if (this.path == null) { + throw new Error("Invalid path"); + } + if (callback != null && this.isStream()) { + this.contents._subscribers.add(callback); + } + return this.getCommand(COMMAND_SUBSCRIBE); + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + unsubscribe(callback) { + if (this.path == null) { + throw new Error("Invalid path"); + } + if (callback != null && this.isStream()) { + this.contents._subscribers.delete(callback); + } + return this.getCommand(COMMAND_UNSUBSCRIBE); + } + + /** + * + * @param {BER} ber + * @returns {QualifiedNode} + */ + static decode(ber) { + const qn = new QualifiedNode(); + ber = ber.getSequence(BER.APPLICATION(10)); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qn.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qn.contents = NodeContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qn.decodeChildren(seq); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + if (DEBUG) { console.log("QualifiedNode", qn); } + return qn; + } +} + +module.exports = QualifiedNode; \ No newline at end of file diff --git a/EmberLib/QualifiedParameter.js b/EmberLib/QualifiedParameter.js new file mode 100755 index 0000000..05a0fe1 --- /dev/null +++ b/EmberLib/QualifiedParameter.js @@ -0,0 +1,186 @@ +"use strict"; + +const TreeNode = require("./TreeNode"); +const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); +const ParameterContents = require("./ParameterContents"); +const BER = require('../ber.js'); +const Command = require("./Command"); + + +class QualifiedParameter extends TreeNode { + /** + * + * @param {string} path + */ + constructor(path) { + super(); + this.path = path; + } + + isParameter() { + return true; + } + isQualified() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(9)); + ber.startSequence(BER.CONTEXT(0)); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + /** + * + * @param {number} cmd + * @returns {TreeNode} + */ + getCommand(cmd) { + const r = new TreeNode(); + const qp = new QualifiedParameter(); + qp.path = this.getPath(); + r.addElement(qp); + qp.addChild(new Command(cmd)); + return r; + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + getDirectory(callback) { + if (callback != null && !this.isStream()) { + this.contents._subscribers.add(callback); + } + return this.getCommand(COMMAND_GETDIRECTORY); + } + + /** + * + * @param {boolean} complete + * @returns {Parameter} + */ + getMinimal(complete = false) { + const number = this.getNumber(); + const p = new Parameter(number); + if (complete) { + if (this.contents != null) { + p = this.contents; + } + } + return p; + } + + /** + * + * @param {number|string} value + * @returns {TreeNode} + */ + setValue(value) { + let r = new TreeNode(); + let qp = new QualifiedParameter(this.path); + r.addElement(qp); + qp.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); + return r; + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + subscribe(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + if (callback != null && this.isStream()) { + this.contents._subscribers.add(callback); + } + return this.getCommand(COMMAND_SUBSCRIBE); + } + + /** + * + * @param {function} callback + * @returns {TreeNode} + */ + unsubscribe(callback) { + if (this.path === undefined) { + throw new Error("Invalid path"); + } + if (callback != null && this.isStream()) { + this.contents._subscribers.delete(callback); + } + return this.getCommand(COMMAND_UNSUBSCRIBE); + } + + /** + * + * @param {QualifiedParameter} other + */ + update(other) { + if ((other !== undefined) && (other.contents !== undefined)) { + if (this.contents == null) { + this.contents = other.contents; + } + else { + for (var key in other.contents) { + if (key[0] === "_") { + continue; + } + if (other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } + } + for(let cb of this.contents._subscribers) { + cb(this); + } + } + } + return; + } + + /** + * + * @param {BER} ber + * @returns {QualifiedParameter} + */ + static decode(ber) { + var qp = new QualifiedParameter(); + ber = ber.getSequence(BER.APPLICATION(9)); + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qp.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qp.contents = ParameterContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qp.decodeChildren(seq); + } else { + return qp; + } + } + if (DEBUG) { console.log("QualifiedParameter", qp); } + return qp; + } +} + +module.exports = QualifiedParameter; \ No newline at end of file diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js new file mode 100755 index 0000000..9992de9 --- /dev/null +++ b/EmberLib/TreeNode.js @@ -0,0 +1,497 @@ +"use strict"; +const BER = require('../ber.js'); +const Command = require("./Command"); +const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); + +class TreeNode { + constructor() { + /** @type {TreeNode} */ + this._parent = null; + } + + addChild(child) { + TreeNode.addElement(this, child); + } + + /** + * + * @param {TreeNode} element + */ + addElement(element) { + TreeNode.addElement(this, element); + } + /** + * + */ + addResult(result) { + this.result = result; + } + + /** + * + */ + clear() { + this.elements = undefined; + } + + get children() { + let it = {}; + const self = this; + it[Symbol.iterator] = function*() { + if (self.elements == null) { return null;} + for(let child of self.elements.entries()) { + yield child[1]; + } + } + return it; + } + + /** + * + * @param {BER} ber + */ + decodeChildren(ber) { + const seq = ber.getSequence(BER.APPLICATION(4)); + while(seq.remain > 0) { + const nodeSeq = seq.getSequence(BER.CONTEXT(0)); + this.addChild(TreeNode.decode(nodeSeq)); + } + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(0)); + if(this.elements != null) { + const elements = this.getChildren(); + ber.startSequence(BER.APPLICATION(11)); + for(var i=0; i < elements.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + elements[i].encode(ber); + ber.endSequence(); // BER.CONTEXT(0) + } + ber.endSequence(); + } + if (this.result != null) { + this.result.encode(ber); + } + ber.endSequence(); // BER.APPLICATION(0) + } + + /** + * + * @param {BER} ber + */ + encodeChildren(ber) { + const children = this.getChildren(); + if(children != null) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.APPLICATION(4)); + for(var i = 0; i < children.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + children[i].encode(ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + } + } + + hasChildren() { + return this.elements != null && this.elements.size > 0; + } + + isCommand() { + return false; + } + + isNode() { + return false; + } + + isMatrix() { + return false; + } + + isParameter() { + return false; + } + + isFunction() { + return false; + } + + isRoot() { + return this._parent == null; + } + + isQualified() { + return false; + } + + isStream() { + return this.contents != null && + this.contents.streamIdentifier != null; + } + + getMinimalContent() { + let obj; + if (this.isQualified()) { + obj = new this.constructor(this.path); + } + else { + obj = new this.constructor(this.number); + } + if (this.contents !== undefined) { + obj.contents= this.contents; + } + return obj; + }; + + getDuplicate() { + const obj = this.getMinimal(); + obj.update(this); + return obj; + } + + getMinimal() { + if (this.isQualified()) { + return new this.constructor(this.path); + } + else { + return new this.constructor(this.number); + } + } + + getTreeBranch(child, modifier) { + const m = this.getMinimal(); + if(child !== undefined) { + m.addChild(child); + } + + if(modifier !== undefined) { + modifier(m); + } + + if(this._parent === null) { + return m; + } + else { + return this._parent.getTreeBranch(m); + } + } + + getRoot() { + if(this._parent == null) { + return this; + } else { + return this._parent.getRoot(); + } + } + + getDirectory(callback) { + if (callback != null && this.contents != null && !this.isStream()) { + this.contents._subscribers.add(callback); + } + return this.getTreeBranch(new Command(COMMAND_GETDIRECTORY)); + } + + subscribe(callback) { + if (callback != null && this.isParameter() && this.isStream()) { + this.contents._subscribers.add(callback); + } + return this.getTreeBranch(new Command(COMMAND_SUBSCRIBE)); + } + + unsubscribe(callback) { + if (callback != null && this.isParameter() && this.isStream()) { + this.contents._subscribers.delete(callback); + } + return this.getTreeBranch(new Command(COMMAND_UNSUBSCRIBE)); + } + + /** + * @returns {TreeNode[]} + */ + getChildren() { + if(this.elements != null) { + return [...this.elements.values()]; + } + return null; + } + + /** + * @returns {number} + */ + getNumber() { + if (this.isQualified()) { + return TreeNode.path2number(this.getPath()); + } + else { + return this.number; + } + } + + /** + * @returns {TreeNode} + */ + getParent() { + return this._parent; + } + + /** + * @returns {string} + */ + getPath() { + return ""; + } + + /** + * + * @param {string} path + * @returns {TreeNode} + */ + getElementByPath(path) { + if (this.elements == null || this.elements.size === 0) { + return null; + } + const myPath = this.getPath(); + if (path == myPath) { + return this; + } + const myPathArray = this.isRoot() ? [] : myPath.split("."); + let pathArray = path.split("."); + + if (pathArray.length <= myPathArray.length) { + // We are lower in the tree than the requested path + return null; + } + + // Verify that our path matches the beginning of the requested path + for(var i = 0; i < myPathArray.length; i++) { + if (pathArray[i] != myPathArray[i]) { + return null; + } + } + //Now add child by child to get the requested path + let node = this; + while(myPathArray.length != pathArray.length) { + const number = pathArray[myPathArray.length]; + node = node.getElementByNumber(number); + if (node == null) { + return null; + } + myPathArray.push(number); + } + return node; + } + + /** + * + * @param {number} number + * @returns {TreeNode} + */ + getElementByNumber(number) { + const n = Number(number); + if (this.elements != null) { + return this.elements.get(n); + } + return null; + } + /** + * + * @param {string} identifier + * @returns {TreeNode} + */ + getElementByIdentifier(identifier) { + const children = this.getChildren(); + if (children == null) return null; + for(var i = 0; i < children.length; i++) { + if(children[i].contents !== undefined && + children[i].contents.identifier == identifier) { + return children[i]; + } + } + return null; + } + + /** + * + * @param {number|string} id + * @returns {TreeNode} + */ + getElement(id) { + if(Number.isInteger(id)) { + return this.getElementByNumber(id); + } else { + return this.getElementByIdentifier(id); + } + } + + getNodeByPath(client, path, callback) { + if(path.length === 0) { + callback(null, this); + return; + } + + const child = this.getElement(path[0]); + if(child !== null) { + child.getNodeByPath(client, path.slice(1), callback); + } else { + var cmd = self.getDirectory((error, node) => { + if(error) { + callback(error); + } + child = node.getElement(path[0]); + if(child === null) { + //let e = new Error('invalid path: "' + path[0] + '"'); + // console.log(e.message ,"Self:", self, "Node:", node); + //callback(e); + //DO NOT REJECT !!!! We could still be updating the tree. + return; + } else { + child.getNodeByPath(client, path.slice(1), callback); + } + }); + if(cmd !== null) { + client.sendBERNode(cmd); + } + } + } + + /** + * @returns {string} + */ + getPath() { + if (this.path != null) { + return this.path; + } + if(this._parent == null) { + if(this.number == null) { + return ""; + } + else { + return this.number.toString(); + } + } else { + let path = this._parent.getPath(); + if(path.length > 0) { + path = path + "."; + } + return path + this.number; + } + } + + /** + * + */ + toJSON() { + const res = {}; + const node = this; + if (this.isRoot()) { + const elements = this.getChildren(); + return { + elements: elements.map(e => e.toJSON()) + }; + } + res.number = node.getNumber(); + res.path = node.getPath(); + if (node.contents) { + for(let prop in node.contents) { + if (node.contents.hasOwnProperty(prop)) { + const type = typeof node.contents[prop]; + if ((type === "string") || (type === "number")) { + res[prop] = node.contents[prop]; + } + else if (node.contents[prop].value !== undefined) { + res[prop] = node.contents[prop].value; + } + else { + res[prop] = node.contents[prop]; + } + } + } + } + if (node.isMatrix()) { + if (node.targets) { + res.targets = node.targets.slice(0); + } + if (node.sources) { + res.sources = node.sources.slice(0); + } + if (node.connections) { + res.connections = {}; + for (let target in node.connections) { + if (node.connections.hasOwnProperty(target)) { + res.connections[target] = {target: target, sources: []}; + if (node.connections[target].sources) { + res.connections[target].sources = node.connections[target].sources.slice(0); + } + } + } + + } + } + const children = node.getChildren(); + if (children) { + res.children = []; + for(let child of children) { + res.children.push(child.toJSON()); + } + } + return res; + }; + + /** + * + * @param {TreeNode} other + */ + update(other) { + if ((other != null) && (other.contents != null)) { + if (this.contents == null) { + this.contents = other.contents; + } + else { + for (var key in other.contents) { + if (other.contents.hasOwnProperty(key)) { + this.contents[key] = other.contents[key]; + } + } + } + } + return; + } + + static decode(ber) { + + } + + /** + * + * @param {TreeNode} parent + * @param {TreeNode} element + */ + static addElement(parent, element) { + element._parent = parent; + if(parent.elements == null) { + parent.elements = new Map(); + } + parent.elements.set(element.getNumber(), element); + } + + static path2number(path) { + try { + const numbers = path.split("."); + if (numbers.length > 0) { + return Number(numbers[numbers.length - 1]); + } + } + catch(e) { + // ignore + } + } +} + +module.exports = TreeNode; \ No newline at end of file diff --git a/EmberLib/constants.js b/EmberLib/constants.js new file mode 100755 index 0000000..06a63e8 --- /dev/null +++ b/EmberLib/constants.js @@ -0,0 +1,15 @@ +const COMMAND_SUBSCRIBE = 30; +const COMMAND_UNSUBSCRIBE = 31; +const COMMAND_GETDIRECTORY = 32; +const COMMAND_INVOKE = 33; + +module.exports = { + COMMAND_SUBSCRIBE, + COMMAND_UNSUBSCRIBE, + COMMAND_GETDIRECTORY, + COMMAND_INVOKE, + Subscribe: COMMAND_SUBSCRIBE, + Unsubscribe: COMMAND_UNSUBSCRIBE, + GetDirectory: COMMAND_GETDIRECTORY, + Invoke: COMMAND_INVOKE +}; \ No newline at end of file diff --git a/EmberLib/index.js b/EmberLib/index.js new file mode 100755 index 0000000..73789ac --- /dev/null +++ b/EmberLib/index.js @@ -0,0 +1,164 @@ +const {Subscribe,COMMAND_SUBSCRIBE,Unsubscribe,COMMAND_UNSUBSCRIBE, + GetDirectory,COMMAND_GETDIRECTORY,Invoke,COMMAND_INVOKE} = require("./constants"); +const BER = require('../ber.js'); + +const TreeNode = require("./TreeNode"); +const Command = require("./Command"); +const Function = require("./Function"); +const FunctionArgument = require("./FunctionArgument"); +const FunctionContent = require("./FunctionContent"); +const Invocation = require("./Invocation"); +const InvocationResult = require("./InvocationResult"); +const Label = require("./Label"); +const MatrixNode = require("./MatrixNode"); +const MatrixMode = require("./MatrixMode"); +const MatrixType = require("./MatrixType"); +const MatrixContents = require("./MatrixContents"); +const MatrixConnection = require("./MatrixConnection"); +const Matrixoperation = require("./MatrixOperation"); +const Node = require("./Node"); +const NodeContents = require("./NodeContents"); +const Parameter = require("./Parameter"); +const ParameterContents = require("./ParameterContents"); +const ParameterAccess = require("./ParameterAccess"); +const ParameterType = require("./ParameterType").ParameterType; +const QualifiedFunction = require("./QualifiedFunction"); +const QualifiedMatrix = require("./QualifiedMatrix"); +const QualifiedNode = require("./QualifiedNode"); +const QualifiedParameter = require("./QualifiedParameter"); + +const rootDecode = function(ber) { + const r = new TreeNode(); + let tag = undefined; + while(ber.remain > 0) { + if (DEBUG) { console.log("Reading root"); } + tag = ber.peek(); + if (tag === BER.APPLICATION(0)) { + ber = ber.getSequence(BER.APPLICATION(0)); + tag = ber.peek(); + if (DEBUG) { + console.log("Application 0 start"); + } + + if (tag === BER.APPLICATION(11)) { + if (DEBUG) { + console.log("Application 11 start"); + } + const seq = ber.getSequence(BER.APPLICATION(11)); + while (seq.remain > 0) { + try { + const rootReader = seq.getSequence(BER.CONTEXT(0)); + while (rootReader.remain > 0) { + r.addElement(childDecode(rootReader)); + } + } + catch (e) { + if (DEBUG) { + console.log("Decode ERROR", e.stack); + return r; + } + throw e; + } + } + } + else if (tag === BER.APPLICATION(23)) { // InvocationResult BER.APPLICATION(23) + return InvocationResult.decode(ber) + } + else { + // StreamCollection BER.APPLICATION(6) + // InvocationResult BER.APPLICATION(23) + throw new errors.UnimplementedEmberTypeError(tag); + } + } + else if (tag === BER.CONTEXT(0)) { + // continuation of previous message + try { + var rootReader = ber.getSequence(BER.CONTEXT(0)); + return Element.decode(rootReader) + } + catch (e) { + console.log(e.stack); + return r; + } + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return r; +} + +const childDecode = function(ber) { + const tag = ber.peek(); + if (tag == BER.APPLICATION(1)) { + if (DEBUG) { console.log("Parameter decode");} + return Parameter.decode(ber); + } else if(tag == BER.APPLICATION(3)) { + if (DEBUG) { console.log("Node decode");} + return Node.decode(ber); + } else if(tag == BER.APPLICATION(2)) { + if (DEBUG) { console.log("Command decode");} + return Command.decode(ber); + } else if(tag == BER.APPLICATION(9)) { + if (DEBUG) { console.log("QualifiedParameter decode");} + return QualifiedParameter.decode(ber); + } else if(tag == BER.APPLICATION(10)) { + if (DEBUG) { console.log("QualifiedNode decode");} + return QualifiedNode.decode(ber); + } else if(tag == BER.APPLICATION(13)) { + if (DEBUG) { console.log("MatrixNode decode");} + return MatrixNode.decode(ber); + } + else if(tag == BER.APPLICATION(17)) { + if (DEBUG) { console.log("QualifiedMatrix decode");} + return QualifiedMatrix.decode(ber); + } + else if(tag == BER.APPLICATION(19)) { + if (DEBUG) { console.log("Function decode");} + return Function.decode(ber); + } else if (tag == BER.APPLICATION(20)) { + if (DEBUG) { console.log("QualifiedFunction decode");} + return QualifiedFunction.decode(ber); + } + else if(tag == BER.APPLICATION(24)) { + // Template + throw new errors.UnimplementedEmberTypeError(tag); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } +} + +TreeNode.decode = childDecode; + +module.exports = { + Command, + childDecode: childDecode, + rootDecode: rootDecode, + Root: TreeNode, + Function, + FunctionArgument, + FunctionContent, + Invocation, + InvocationResult, + Label, + MatrixNode, + MatrixMode, + MatrixType, + MatrixContents, + MatrixConnection, + Matrixoperation, + Node, + NodeContents, + Parameter, + ParameterContents, + ParameterAccess, + ParameterType, + QualifiedFunction , + QualifiedMatrix, + QualifiedNode, + QualifiedParameter, + Subscribe,COMMAND_SUBSCRIBE, + Unsubscribe,COMMAND_UNSUBSCRIBE, + GetDirectory,COMMAND_GETDIRECTORY, + Invoke,COMMAND_INVOKE +} \ No newline at end of file diff --git a/EmberServer.js b/EmberServer.js new file mode 100755 index 0000000..0a7a7e7 --- /dev/null +++ b/EmberServer.js @@ -0,0 +1,827 @@ +const EventEmitter = require('events').EventEmitter; +const util = require('util'); +const S101Server = require('./client.js').S101Server; +const ember = require('./EmberLib'); + +function TreeServer(host, port, tree) { + TreeServer.super_.call(this); + var self = this; + self._debug = false; + + self.callback = undefined; + self.timeoutValue = 2000; + self.server = new S101Server(host, port); + self.tree = tree; + self.clients = new Set(); + self.subscribers = {}; + + self.server.on('listening', () => { + if (self._debug) { console.log("listening"); } + self.emit('listening'); + if (self.callback !== undefined) { + self.callback(); + self.callback = undefined; + } + }); + + self.server.on('connection', (client) => { + if (self._debug) { console.log("ember new connection from", client.remoteAddress()); } + self.clients.add(client); + client.on("emberTree", (root) => { + if (self._debug) { console.log("ember new request from", client.remoteAddress(), root); } + // Queue the action to make sure responses are sent in order. + client.addRequest(() => { + try { + let path = self.handleRoot(client, root); + self.emit("request", {client: client.remoteAddress(), root: root, path: path}); + } + catch(e) { + if (self._debug) { console.log(e.stack); } + self.emit("error", e); + } + }); + }); + client.on("disconnected", () => { + self.clients.delete(client); + self.emit('disconnect', client.remoteAddress()); + }); + client.on("error", error => { + self.emit('clientError', { remoteAddress: client.remoteAddress(), error }); + }); + self.emit('connection', client.remoteAddress()); + }); + + self.server.on('disconnected', () => { + self.emit('disconnected', client.remoteAddress()); + }); + + self.server.on("error", (e) => { + self.emit("error", e); + if (self.callback !== undefined) { + self.callback(e); + } + }); + +} + +util.inherits(TreeServer, EventEmitter); + + +TreeServer.prototype.listen = function() { + return new Promise((resolve, reject) => { + this.callback = (e) => { + if (e == null) { + return resolve(); + } + return reject(e); + }; + this.server.listen(); + }); +}; + +TreeServer.prototype.close = function () { + return new Promise((resolve, reject) => { + this.callback = (e) => { + if (e == null) { + return resolve(); + } + return reject(e); + }; + this.server.server.close(); + }); +}; + +TreeServer.prototype.handleRoot = function(client, root) { + if ((root == null) || (root.elements == null) || (root.elements.size < 1)) { + // ignore empty requests. + return; + } + + const node = root.getChildren()[0]; + client.request = node; + + if (node.path !== undefined) { + return this.handleQualifiedNode(client, node); + } + else if (node.isCommand()) { + // Command on root element + this.handleCommand(client, this.tree, node); + return "root"; + } + else { + return this.handleNode(client, node); + } +} + +TreeServer.prototype.handleError = function(client, node) { + if (client !== undefined) { + const res = node == null ? this.tree.getMinimal() : node; + client.sendBERNode(res); + } +} + + +TreeServer.prototype.handleQualifiedNode = function(client, node) { + const path = node.path; + // Find this element in our tree + const element = this.tree.getElementByPath(path); + + if (element == null) { + this.emit("error", new Error(`unknown element at path ${path}`)); + return this.handleError(client); + } + + if (node.hasChildren()) { + for(child of node.children) { + if (child.isCommand()) { + this.handleCommand(client, element, child); + } + break; + } + } + else { + if (node.isMatrix()) { + this.handleQualifiedMatrix(client, element, node); + } + else if (node.isParameter()) { + this.handleQualifiedParameter(client, element, node); + } + } + return path; +} + + +TreeServer.prototype.handleNode = function(client, node) { + // traverse the tree + let element = node; + let path = []; + while(element !== undefined) { + if (element.number == null) { + this.emit("error", "invalid request"); + return; + } + if (element.isCommand()) { + break; + } + path.push(element.number); + + let children = element.getChildren(); + if ((! children) || (children.length === 0)) { + break; + } + element = element.children[0]; + } + let cmd = element; + + if (cmd == null) { + this.emit("error", "invalid request"); + return this.handleError(client); + } + + element = this.tree.getElementByPath(path.join(".")); + + if (element == null) { + this.emit("error", new Error(`unknown element at path ${path}`)); + return this.handleError(client); + } + + if (cmd.isCommand()) { + this.handleCommand(client, element, cmd); + } + else if ((cmd.isCommand()) && (cmd.connections !== undefined)) { + this.handleMatrixConnections(client, element, cmd.connections); + } + else if ((cmd.isParameter()) && + (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { + if (this._debug) { console.log(`setValue for element at path ${path} with value ${cmd.contents.value}`); } + this.setValue(element, cmd.contents.value, client); + let res = this.getResponse(element); + client.sendBERNode(res) + this.updateSubscribers(element.getPath(), res, client); + } + else { + this.emit("error", new Error("invalid request format")); + if (this._debug) { console.log("invalid request format"); } + return this.handleError(client, element.getTreeBranch()); + } + return path; +} + +TreeServer.prototype.handleQualifiedMatrix = function(client, element, matrix) +{ + this.handleMatrixConnections(client, element, matrix.connections); +} + +TreeServer.prototype.handleQualifiedParameter = function(client, element, parameter) +{ + if (parameter.contents.value !== undefined) { + this.setValue(element, parameter.contents.value, client); + let res = this.getQualifiedResponse(element); + client.sendBERNode(res) + this.updateSubscribers(element.getPath(), res, client); + } +} + + +TreeServer.prototype.handleMatrixConnections = function(client, matrix, connections, response = true) { + var res,conResult; + var root; // ember message root + if (this._debug) { + console.log("Handling Matrix Connection"); + } + if (client != null && client.request.isQualified()) { + root = new ember.Root(); + res = new ember.QualifiedMatrix(matrix.getPath()); + //root.elements = [res]; // do not use addchild or the element will get removed from the tree. + root.addElement(res); + } + else { + res = new ember.MatrixNode(matrix.number); + root = matrix._parent.getTreeBranch(res); + } + res.connections = {}; + for(let id in connections) { + if (!connections.hasOwnProperty(id)) { + continue; + } + let connection = connections[id]; + conResult = new ember.MatrixConnection(connection.target); + let emitType; + res.connections[connection.target] = conResult; + + if (matrix.connections[connection.target].isLocked()) { + conResult.disposition = ember.MatrixDisposition.locked; + } + else if (matrix.contents.type !== ember.MatrixType.nToN && + connection.operation !== ember.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 1) { + if (matrix.contents.type === ember.MatrixType.oneToOne) { + // if the source is being used already, disconnect it. + const targets = matrix.getSourceConnections(connection.sources[0]); + if (targets.length === 1 && targets[0] !== connection.target) { + const disconnect = new ember.MatrixConnection(targets[0]); + disconnect.setSources([]); + disconnect.disposition = ember.MatrixDisposition.modified; + res.connections[targets[0]] = disconnect; + matrix.setSources(targets[0], []); + if (response) { + this.emit("matrix-disconnect", { + target: targets[0], + sources: connection.sources, + client: client == null ? null : client.remoteAddress() + }); + } + } + } + // if the target is connected already, disconnect it + if (matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length === 1) { + if (matrix.contents.type === ember.MatrixType.oneToN) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + if (disconnectSource != null && disconnectSource != -1 && + disconnectSource != connection.sources[0]) { + connection.sources = [disconnectSource]; + } + else { + // do nothing + connection.operarion = ember.MatrixOperation.tally; + } + } + } + if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { + const source = matrix.connections[connection.target].sources[0]; + matrix.setSources(connection.target, []); + if (response) { + this.emit("matrix-disconnect", { + target: connection.target, + sources: [source], + client: client == null ? null : client.remoteAddress() + }); + } + } + else if (matrix.contents.type === ember.MatrixType.oneToOne) { + // let's change the request into a disconnect + connection.operation = ember.MatrixOperation.disconnect; + } + } + } + + if (connection.operation !== ember.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length > 0 && + matrix.canConnect(connection.target,connection.sources,connection.operation)) { + // Apply changes + if ((connection.operation == null) || + (connection.operation.value == ember.MatrixOperation.absolute)) { + matrix.setSources(connection.target, connection.sources); + emitType = "matrix-change"; + } + else if (connection.operation == ember.MatrixOperation.connect) { + matrix.connectSources(connection.target, connection.sources); + emitType = "matrix-connect"; + } + conResult.disposition = ember.MatrixDisposition.modified; + } + else if (connection.operation !== ember.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 0 && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // let's disconnect + if (response) { + this.emit("matrix-disconnect", { + target: connection.target, + sources: matrix.connections[connection.target].sources, + client: client == null ? null : client.remoteAddress() + }); + } + matrix.setSources(connection.target, []); + conResult.disposition = ember.MatrixDisposition.modified; + } + else if (connection.operation === ember.MatrixOperation.disconnect && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // Disconnect + if (matrix.contents.type === ember.MatrixType.oneToN) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + if (disconnectSource != null && disconnectSource != -1 && + disconnectSource != connection.sources[0]) { + if (response) { + this.emit("matrix-disconnect", { + target: connection.target, + sources: matrix.connections[connection.target].sources, + client: client == null ? null : client.remoteAddress() + }); + } + matrix.setSources(connection.target, [disconnectSource]); + connection.operarion = ember.MatrixOperation.modified; + } + else { + // do nothing + connection.operarion = ember.MatrixOperation.tally; + } + } + } + else { + matrix.disconnectSources(connection.target, connection.sources); + conResult.disposition = ember.MatrixDisposition.modified; + emitType = "matrix-disconnect"; + } + } + else if (conResult.disposition !== ember.MatrixDisposition.locked){ + if (this._debug) { + console.log(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); + } + conResult.disposition = ember.MatrixDisposition.tally; + } + + // Send response or update subscribers. + conResult.sources = matrix.connections[connection.target].sources; + if (response) { + // We got a request so emit something. + this.emit(emitType, { + target: connection.target, + sources: connection.sources, + client: client == null ? null : client.remoteAddress() + }); + } + } + if (client != null) { + client.sendBERNode(root); + } + + if (conResult != null && conResult.disposition !== ember.MatrixDisposition.tally) { + if (this._debug) { + console.log("Updating subscribers for matrix change"); + } + this.updateSubscribers(matrix.getPath(), root, client); + } +} + +const validateMatrixOperation = function(matrix, target, sources) { + if (matrix == null) { + throw new Error(`matrix not found with path ${path}`); + } + if (matrix.contents == null) { + throw new Error(`invalid matrix at ${path} : no contents`); + } + if (matrix.contents.targetCount == null) { + throw new Error(`invalid matrix at ${path} : no targetCount`); + } + if ((target < 0) || (target >= matrix.contents.targetCount)) { + throw new Error(`target id ${target} out of range 0 - ${matrix.contents.targetCount}`); + } + if (sources.length == null) { + throw new Error("invalid sources format"); + } +} + +const doMatrixOperation = function(server, path, target, sources, operation) { + let matrix = server.tree.getElementByPath(path); + + validateMatrixOperation(matrix, target, sources); + + let connection = new ember.MatrixConnection(target); + connection.sources = sources; + connection.operation = operation; + server.handleMatrixConnections(undefined, matrix, [connection], false); +} + +TreeServer.prototype.matrixConnect = function(path, target, sources) { + doMatrixOperation(this, path, target, sources, ember.MatrixOperation.connect); +} + +TreeServer.prototype.matrixDisconnect = function(path, target, sources) { + doMatrixOperation(this, path, target, sources, ember.MatrixOperation.disconnect); +} + +TreeServer.prototype.matrixSet = function(path, target, sources) { + doMatrixOperation(this, path, target, sources, ember.MatrixOperation.absolute); +} + +TreeServer.prototype.handleQualifiedFunction = function(client, element, node) { + +} + + +TreeServer.prototype.handleCommand = function(client, element, cmd) { + switch(cmd.number) { + case ember.COMMAND_GETDIRECTORY: + this.handleGetDirectory(client, element); + break; + case ember.COMMAND_SUBSCRIBE: + this.handleSubscribe(client, element); + break; + case ember.COMMAND_UNSUBSCRIBE: + this.handleUnSubscribe(client, element); + break; + case ember.COMMAND_INVOKE: + this.handleInvoke(client, cmd.invocation, element); + break; + default: + this.emit("error", new Error(`invalid command ${cmd.number}`)); + break; + } +} + +TreeServer.prototype.getResponse = function(element) { + return element.getTreeBranch(undefined, function(node) { + node.update(element); + let children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + node.addChild(children[i].getDuplicate()); + } + } + else if (this._debug) { + console.log("getResponse","no children"); + } + }); +} + +TreeServer.prototype.getQualifiedResponse = function(element) { + const res = new ember.Root(); + let dup; + if (element.isRoot() === false) { + dup = element.toQualified(); + } + let children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + res.addChild(children[i].toQualified().getMinimalContent()); + } + } + else { + res.addChild(dup); + } + return res; +} + +TreeServer.prototype.handleInvoke = function(client, invocation, element) { + const result = new ember.InvocationResult(); + result.invocationId = invocation.id; + if (element == null || !element.isFunction()) { + result.setFailure(); + } + else { + try { + result.setResult(element.func(invocation.arguments)); + } + catch(e){ + this.emit("error", e); + result.setFailure(); + } + } + const res = new ember.Root(); + res.addResult(result); + client.sendBERNode(res); +} + +TreeServer.prototype.handleGetDirectory = function(client, element) { + if (client !== undefined) { + if ((element.isMatrix() || element.isParameter()) && + (!element.isStream())) { + // ember spec: parameter without streamIdentifier should + // report their value changes automatically. + this.subscribe(client, element); + } + else if (element.isNode()) { + const children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if ((child.isMatrix() || child.isParameter()) && + (!child.isStream())) { + this.subscribe(client, child); + } + } + } + } + + const res = this.getQualifiedResponse(element); + if (this._debug) { + console.log("getDirectory response", res); + } + client.sendBERNode(res); + } +} + +TreeServer.prototype.handleSubscribe = function(client, element) { + if (this._debug) { + console.log("subscribe"); + } + this.subscribe(client, element); +} + +TreeServer.prototype.handleUnSubscribe = function(client, element) { + if (this._debug) { + console.log("unsubscribe"); + } + this.unsubscribe(client, element); +} + + +TreeServer.prototype.subscribe = function(client, element) { + const path = element.getPath(); + if (this.subscribers[path] == null) { + this.subscribers[path] = new Set(); + } + this.subscribers[path].add(client); +} + +TreeServer.prototype.unsubscribe = function(client, element) { + const path = element.getPath(); + if (this.subscribers[path] == null) { + return; + } + this.subscribers[path].delete(client); +} + +TreeServer.prototype.setValue = function(element, value, origin, key) { + return new Promise((resolve, reject) => { + // Change the element value if write access permitted. + if (element.contents == null) { + return resolve(); + } + if (element.isParameter()) { + if ((element.contents.access !== undefined) && + (element.contents.access.value > 1)) { + element.contents.value = value; + const res = this.getResponse(element); + this.updateSubscribers(element.getPath(),res, origin); + this.emit("value-change", element); + } + } + else if (element.isMatrix()) { + if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { + element.contents[key] = value; + const res = this.getResponse(element); + this.updateSubscribers(element.getPath(),res, origin); + this.emit("value-change", element); + } + } + return resolve(); + }); +} + +TreeServer.prototype.replaceElement = function(element) { + let path = element.getPath(); + let parent = this.tree.getElementByPath(path); + if ((parent == null)||(parent._parent == null)) { + throw new Error(`Could not find element at path ${path}`); + } + parent = parent._parent; + let children = parent.getChildren(); + let newList = []; + for(let i = 0; i <= children.length; i++) { + if (children[i] && children[i].getPath() == path) { + element._parent = parent; // move it to new tree. + children[i] = element; + let res = this.getResponse(element); + this.updateSubscribers(path,res); + return; + } + } +} + +TreeServer.prototype.getDisconnectSource = function(matrix, targetID) { + if (matrix.defaultSources) { + return matrix.defaultSources[targetID].contents.value; + } + if (matrix.contents.labels == null || matrix.contents.labels.length == 0) { + return null; + } + const basePath = matrix.contents.labels[0].basePath; + const labels = this.tree.getElementByPath(basePath); + const number = labels.getNumber() + 1; + const parent = labels.getParent(); + const children = parent.getChildren(); + for(let child of children) { + if (child.getNumber() === number) { + matrix.defaultSources = child.getChildren(); + return matrix.defaultSources[targetID].contents.value; + } + } + return null; +} + +TreeServer.prototype.updateSubscribers = function(path, response, origin) { + if (this.subscribers[path] == null) { + return; + } + + for (let client of this.subscribers[path]) { + if (client === origin) { + continue; // already sent the response to origin + } + if (this.clients.has(client)) { + client.queueMessage(response); + } + else { + // clean up subscribers - client is gone + this.subscribers[path].delete(client); + } + } +} + +const parseMatrixContent = function(matrixContent, content) { + if (content.labels) { + matrixContent.labels = []; + for(let l = 0; l < content.labels.length; l++) { + if (typeof (content.labels[l]) === "object") { + matrixContent.labels.push( + new ember.Label( + content.labels[l].basePath, + content.labels[l].description + ) + ); + } + else { + // for backward compatibility... Remove in the future + matrixContent.labels.push( + new ember.Label(content.labels[l]) + ); + } + } + delete content.labels; + } + if (content.type != null) { + if (content.type == "oneToN") { + matrixContent.type = ember.MatrixType.oneToN; + } + else if (content.type == "oneToOne") { + matrixContent.type = ember.MatrixType.oneToOne; + } + else if (content.type == "nToN") { + matrixContent.type = ember.MatrixType.nToN; + matrixContent.maximumTotalConnects = content.maximumTotalConnects == null ? + Number(content.targetCount) * Number(content.sourceCount) : Number(content.maximumTotalConnects); + matrixContent.maximumConnectsPerTarget = content.maximumConnectsPerTarget == null ? + Number(content.sourceCount) : Number(content.maximumConnectsPerTarget); + } + else { + throw new Error(`Invalid matrix type ${content.type}`); + } + delete content.type; + } + if (content.mode != null) { + if (content.mode == "linear") { + matrixContent.mode = ember.MatrixMode.linear; + } + else if (content.mode == "nonLinear") { + matrixContent.mode = ember.MatrixMode.nonLinear; + } + else { + throw new Error(`Invalid matrix mode ${content.mode}`); + } + delete content.mode; + } +} + +const parseObj = function(parent, obj) { + for(let i = 0; i < obj.length; i++) { + let emberElement; + let content = obj[i]; + let number = content.number !== undefined ? content.number : i; + delete content.number; + if (content.value != null) { + emberElement = new ember.Parameter(number); + emberElement.contents = new ember.ParameterContents(content.value); + if (content.type) { + emberElement.contents.type = ember.ParameterType.get(content.type); + delete content.type; + } + else { + emberElement.contents.type = ember.ParameterType.string; + } + if (content.access) { + emberElement.contents.access = ember.ParameterAccess.get(content.access); + delete content.access; + } + else { + emberElement.contents.access = ember.ParameterAccess.read; + } + } + else if (content.func != null) { + emberElement = new ember.Function(number, content.func); + emberElement.contents = new ember.FunctionContent(); + if (content.arguments != null) { + for(let argument of content.arguments) { + emberElement.contents.arguments.push(new ember.FunctionArgument( + argument.type, + argument.value, + argument.name + )); + } + } + if (content.result != null) { + for(let argument of content.result) { + emberElement.contents.result.push(new ember.FunctionArgument( + argument.type, + argument.value, + argument.name + )); + } + } + delete content.result; + } + else if (content.targetCount != null) { + emberElement = new ember.MatrixNode(number); + emberElement.contents = new ember.MatrixContents(); + parseMatrixContent(emberElement.contents, content); + if (content.connections) { + emberElement.connections = {}; + for (let c in content.connections) { + if (! content.connections.hasOwnProperty(c)) { + continue; + } + const t = content.connections[c].target != null ? content.connections[c].target : 0; + emberElement.setSources(t, content.connections[c].sources); + } + delete content.connections; + } + else { + emberElement.connections = {}; + for (let t = 0; t < content.targetCount; t++) { + let connection = new ember.MatrixConnection(t); + emberElement.connections[t] = connection; + } + } + } + else { + emberElement = new ember.Node(number); + emberElement.contents = new ember.NodeContents(); + } + for(let id in content) { + if (emberElement.isFunction() && id === "arguments") { + // we did it already. + continue; + } + if ((id !== "children") && (content.hasOwnProperty(id))) { + emberElement.contents[id] = content[id]; + } + else { + parseObj(emberElement, content.children); + } + } + parent.addChild(emberElement); + } +} + +TreeServer.JSONtoTree = function(obj) { + const tree = new ember.Root(); + parseObj(tree, obj); + return tree; +} + + +TreeServer.prototype.toJSON = function() { + if (this.tree == null) { + return []; + } + const elements = this.tree.getChildren(); + + return elements.map(element => element.toJSON()); +}; + +module.exports = TreeServer; diff --git a/client.js b/client.js index 20bf7ad..1f297f3 100755 --- a/client.js +++ b/client.js @@ -3,7 +3,7 @@ const util = require('util'); const winston = require('winston'); const net = require('net'); const BER = require('./ber.js'); -const ember = require('./ember.js'); +const ember = require('./EmberLib'); const S101Codec = require('./s101.js'); @@ -46,7 +46,7 @@ function S101Client(socket, server) { self.emit('emberPacket', packet); var ber = new BER.Reader(packet); try { - var root = ember.Root.decode(ber); + var root = ember.rootDecode(ber); if (root !== undefined) { self.emit('emberTree', root); } @@ -199,7 +199,7 @@ S101Socket.prototype.connect = function (timeout = 2) { var ber = new BER.Reader(packet); try { - var root = ember.Root.decode(ber); + var root = ember.rootDecode(ber); if (root !== undefined) { self.emit('emberTree', root); } diff --git a/device.js b/device.js index ba32402..a8a85a5 100755 --- a/device.js +++ b/device.js @@ -1,7 +1,7 @@ const EventEmitter = require('events').EventEmitter; const util = require('util'); const S101Client = require('./client.js').S101Socket; -const ember = require('./ember.js'); +const ember = require('./EmberLib'); const BER = require('./ber.js'); const errors = require('./errors.js'); @@ -175,15 +175,16 @@ DeviceTree.prototype.getDirectory = function (qnode, callback = null) { reject(error); return; } - if (qnode instanceof ember.Root) { - if (qnode.elements == null || qnode.elements.length === 0) { + if (qnode.isRoot()) { + const elements = qnode.getChildren(); + if (elements == null || elements.length === 0) { if (self._debug) { console.log("getDirectory response", node); } return self.callback(new Error("Invalid qnode for getDirectory")); } - const nodeElements = node == null ? null : node.elements; + const nodeElements = node == null ? null : node.getChildren(); if (nodeElements != null && nodeElements.every(el => el._parent instanceof ember.Root)) { @@ -197,8 +198,14 @@ DeviceTree.prototype.getDirectory = function (qnode, callback = null) { else { return self.callback(new Error(`Invalid response for getDirectory ${requestedPath}`)); } - } else { - const nodeElements = node == null ? null : node.elements; + } + else if (node.getElementByPath(requestedPath) != null) { + self.clearTimeout(); // clear the timeout now. The resolve below may take a while. + self.finishRequest(); + return resolve(node); // make sure the info is treated before going to next request. + } + else { + const nodeElements = node == null ? null : node.getChildren(); if (nodeElements != null && ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { @@ -207,7 +214,7 @@ DeviceTree.prototype.getDirectory = function (qnode, callback = null) { } self.clearTimeout(); // clear the timeout now. The resolve below may take a while. self.finishRequest(); - resolve(node); // make sure the info is treated before going to next request. + return resolve(node); // make sure the info is treated before going to next request. } else if (self._debug) { console.log(node); @@ -395,12 +402,13 @@ DeviceTree.prototype.handleRoot = function (root) { } self.root.update(root); if (root.elements !== undefined) { - for (var i = 0; i < root.elements.length; i++) { - if (root.elements[i].isQualified()) { - this.handleQualifiedNode(this.root, root.elements[i]); + const elements = root.getChildren(); + for (var i = 0; i < elements.length; i++) { + if (elements[i].isQualified()) { + this.handleQualifiedNode(this.root, elements[i]); } else { - this.handleNode(this.root, root.elements[i]); + this.handleNode(this.root, elements[i]); } } } diff --git a/ember.js b/ember.js index 2201ae8..1ce667b 100755 --- a/ember.js +++ b/ember.js @@ -18,14 +18,20 @@ module.exports.DEBUG = function(d) { DEBUG = d; }; +/**************************************************************************** + * Elements + */ + + function Elements() { + Elements.super_.call(this); + } + /**************************************************************************** * Root ***************************************************************************/ function Root() { Root.super_.call(this); - - //Object.defineProperty(this, '_parent', {value: null, enumerable: false}); }; util.inherits(Root, TreeNode); @@ -49,8 +55,7 @@ Root.decode = function(ber) { if (DEBUG) { console.log("Application 11 start"); } - var seq = ber.getSequence(BER.APPLICATION(11)); - r.elements = []; + var seq = ber.getSequence(BER.APPLICATION(11)); while (seq.remain > 0) { try { var rootReader = seq.getSequence(BER.CONTEXT(0)); @@ -94,12 +99,16 @@ Root.decode = function(ber) { return r; } -Root.prototype.addElement = function(ele) { - ele._parent = this; - if(this.elements === undefined) { - this.elements = []; +function addElement(parent, element) { + element._parent = parent; + if(parent.elements == null) { + parent.elements = new Map(); } - this.elements.push(ele); + parent.elements.set(element.getNumber(), element); +} + +Root.prototype.addElement = function(ele) { + addElement(this, ele); } Root.prototype.addResult = function(result) { @@ -113,10 +122,11 @@ Root.prototype.addChild = function(child) { Root.prototype.encode = function(ber) { ber.startSequence(BER.APPLICATION(0)); if(this.elements != null) { + const elements = this.getChildren(); ber.startSequence(BER.APPLICATION(11)); - for(var i=0; i element.toJSON()); }; module.exports = TreeServer; diff --git a/test/Ember.test.js b/test/Ember.test.js index e1c6e23..abe772e 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -28,5 +28,6 @@ describe("Ember", () => { it("should handle errors in message", () => { var ber = new BER.Reader(errorBuffer); expect(() => ember.Root.decode(ber)).toThrow(errors.UnimplementedEmberTypeError); - }) + }); + }); From 40c3e5dea2ac8abad2058c40bdc002857f3902f6 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 6 Jan 2020 17:18:08 +0100 Subject: [PATCH 126/162] Finalized version 2.0.0 --- EmberClient/EmberClient.js | 730 ++++++++ EmberClient/index.js | 1 + EmberLib/Command.js | 12 +- EmberLib/Element.js | 38 + EmberLib/Function.js | 37 +- EmberLib/MatrixNode.js | 1 - EmberLib/Node.js | 36 +- EmberLib/Parameter.js | 29 +- EmberLib/QualifiedElement.js | 98 + EmberLib/QualifiedFunction.js | 76 +- EmberLib/QualifiedMatrix.js | 1 - EmberLib/QualifiedNode.js | 97 +- EmberLib/QualifiedParameter.js | 92 +- EmberLib/TreeNode.js | 54 +- EmberLib/index.js | 38 +- EmberServer.js | 827 -------- EmberServer/ElementHandlers.js | 186 ++ EmberServer/EmberServer.js | 376 ++++ EmberServer/JSONParser.js | 160 ++ EmberServer/MatrixHandlers.js | 223 +++ EmberServer/QualifiedHandlers.js | 75 + EmberServer/index.js | 1 + EmberSocket/S101Client.js | 87 + EmberSocket/S101Server.js | 53 + EmberSocket/S101Socket.js | 186 ++ EmberSocket/index.js | 7 + README.md | 95 +- client.js | 308 --- device.js | 657 ------- ember.js | 3021 ------------------------------ index.js | 12 +- server.js | 823 -------- test/utils.js | 2 +- 33 files changed, 2372 insertions(+), 6067 deletions(-) create mode 100755 EmberClient/EmberClient.js create mode 100755 EmberClient/index.js create mode 100755 EmberLib/Element.js create mode 100755 EmberLib/QualifiedElement.js delete mode 100755 EmberServer.js create mode 100755 EmberServer/ElementHandlers.js create mode 100755 EmberServer/EmberServer.js create mode 100755 EmberServer/JSONParser.js create mode 100755 EmberServer/MatrixHandlers.js create mode 100755 EmberServer/QualifiedHandlers.js create mode 100755 EmberServer/index.js create mode 100755 EmberSocket/S101Client.js create mode 100755 EmberSocket/S101Server.js create mode 100755 EmberSocket/S101Socket.js create mode 100755 EmberSocket/index.js delete mode 100755 client.js delete mode 100755 device.js delete mode 100755 ember.js delete mode 100755 server.js diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js new file mode 100755 index 0000000..e139bbe --- /dev/null +++ b/EmberClient/EmberClient.js @@ -0,0 +1,730 @@ +const EventEmitter = require('events').EventEmitter; +const S101Client = require('../EmberSocket').S101Socket; +const ember = require('../EmberLib'); +const BER = require('../ber.js'); +const errors = require('../errors.js'); + +const DEFAULT_PORT = 9000; +const DEFAULT_TIMEOUT = 3000; + +/** @typedef {{ + * node: TreeNode, + * func: function + * }} REQUEST*/ +class EmberClient extends EventEmitter { + /** + * + * @param {string} host + * @param {number} port + */ + constructor(host, port = DEFAULT_PORT) { + super(); + this._debug = false; + /** @type {REQUEST[]} */ + this._pendingRequests = []; + /** @type {REQUEST} */ + this._activeRequest = null; + this._timeout = null; + this._callback = undefined; + this._requestID = 0; + this._client = new S101Client(host, port); + this.timeoutValue = DEFAULT_TIMEOUT; + /** @type {Root} */ + this.root = new ember.Root(); + + this._client.on('connecting', () => { + this.emit('connecting'); + }); + + this._client.on('connected', () => { + this.emit('connected'); + if (this._callback !== undefined) { + this._callback(); + } + }); + + this._client.on('disconnected', () => { + this.emit('disconnected'); + }); + + this._client.on("error", e => { + if (this._callback !== undefined) { + this._callback(e); + } + this.emit("error", e); + }); + + this._client.on('emberTree', root => { + try { + if (root instanceof ember.InvocationResult) { + this.emit('invocationResult', root); + if (this._debug) { + console.log("Received InvocationResult", root); + } + } else { + this._handleRoot(root); + if (this._debug) { + console.log("Received root", root); + } + } + if (this._callback) { + this._callback(undefined, root); + } + } + catch(e) { + if (this._debug) { + console.log(e, root); + } + if (this._callback) { + this._callback(e); + } + } + }); + } + + _finishRequest() { + this._clearTimeout(); + this._activeRequest = null; + try { + this._makeRequest(); + } catch(e) { + if (this._debug) {console.log(e);} + if (this._callback != null) { + this._callback(e); + } + this.emit("error", e); + } + } + + _makeRequest() { + if (this._activeRequest == null && this._pendingRequests.length > 0) { + this._activeRequest = this._pendingRequests.shift(); + const req = `${ this._requestID++} - ${this._activeRequest.node.getPath()}`; + this._activeRequest.timeoutError = new errors.EmberTimeoutError(`Request ${req} timed out`) + + if (this._debug) { + console.log(`Making request ${req}`, Date.now()); + } + this._timeout = setTimeout(() => { + this._timeoutRequest(); + }, this.timeoutValue); + this._activeRequest.func(); + } + } + + _timeoutRequest(id) { + this._activeRequest.func(this._activeRequest.timeoutError); + } + + /** + * + * @param {function} req + */ + addRequest(req) { + this._pendingRequests.push(req); + this._makeRequest(); + }; + + _clearTimeout() { + if (this._timeout != null) { + clearTimeout(this._timeout); + this._timeout = null; + } + }; + + /** + * + * @param {TreeNode} parent + * @param {TreeNode} node + */ + _handleNode(parent, node) { + var n = parent.getElementByNumber(node.getNumber()); + if (n === null) { + parent.addChild(node); + n = node; + } else { + n.update(node); + } + + var children = node.getChildren(); + if (children !== null) { + for (var i = 0; i < children.length; i++) { + this._handleNode(n, children[i]); + } + } + else { + this.emit("value-change", node); + } + return; + } + + /** + * + * @param {TreeNode} parent + * @param {TreeNode} node + */ + _handleQualifiedNode(parent, node) { + var element = parent.getElementByPath(node.path); + if (element !== null) { + this.emit("value-change", node); + element.update(node); + } + else { + var path = node.path.split("."); + if (path.length === 1) { + this.root.addChild(node); + } + else { + // Let's try to get the parent + path.pop(); + parent = this.root.getElementByPath(path.join(".")); + if (parent === null) { + return; + } + parent.addChild(node); + parent.update(parent); + } + element = node; + } + + var children = node.getChildren(); + if (children !== null) { + for (var i = 0; i < children.length; i++) { + if (children[i].isQualified()) { + this._handleQualifiedNode(element, children[i]); + } + else { + this._handleNode(element, children[i]); + } + } + } + + return; + } + + /** + * + * @param {TreeNode} root + */ + _handleRoot (root) { + if (this._debug) { + console.log("handling root", JSON.stringify(root)); + } + this.root.update(root); + if (root.elements !== undefined) { + const elements = root.getChildren(); + for (var i = 0; i < elements.length; i++) { + if (elements[i].isQualified()) { + this._handleQualifiedNode(this.root, elements[i]); + } + else { + this._handleNode(this.root, elements[i]); + } + } + } + if (this._callback) { + this._callback(null, root); + } + } + + /** + * + * @param {number} timeout + */ + connect(timeout = 2) { + return new Promise((resolve, reject) => { + this._callback = e => { + this._callback = undefined; + if (e === undefined) { + return resolve(); + } + return reject(e); + }; + if ((this._client !== undefined) && (this._client.isConnected())) { + this._client.disconnect(); + } + this._client.connect(timeout); + }); + } + + disconnect() { + if (this._client != null) { + return this._client.disconnect(); + } + }; + + /** + * + * @param {TreeNode} qnode + * @param {function} callback=null + * @returns {Promise} + */ + expand(node, callback = null) { + if (node == null) { + return Promise.reject(new Error("Invalid null node")); + } + if (node.isParameter() || node.isMatrix() || node.isFunction()) { + return this.getDirectory(node); + } + return this.getDirectory(node, callback).then((res) => { + let children = node.getChildren(); + if ((res === undefined) || (children === undefined) || (children === null)) { + if (this._debug) { + console.log("No more children for ", node); + } + return; + } + let p = Promise.resolve(); + for (let child of children) { + if (child.isParameter()) { + // Parameter can only have a single child of type Command. + continue; + } + if (this._debug) { + console.log("Expanding child", child); + } + p = p.then(() => { + return this.expand(child).catch((e) => { + // We had an error on some expansion + // let's save it on the child itthis + child.error = e; + }); + }); + } + return p; + }); + } + + /** + * + * @param {TreeNode} qnode + * @param {function} callback=null + * @returns {Promise} + */ + getDirectory(qnode, callback = null) { + if (qnode == null) { + this.root.clear(); + qnode = this.root; + } + return new Promise((resolve, reject) => { + this.addRequest({node: qnode, func: error => { + if (error) { + this._finishRequest(); + reject(error); + return; + } + + this._callback = (error, node) => { + const requestedPath = qnode.getPath(); + if (node == null) { + if (this._debug) { + console.log(`received null response for ${requestedPath}`); + } + return; + } + if (error) { + if (this._debug) { + console.log("Received getDirectory error", error); + } + this._clearTimeout(); // clear the timeout now. The resolve below may take a while. + this._finishRequest(); + reject(error); + return; + } + if (qnode.isRoot()) { + const elements = qnode.getChildren(); + if (elements == null || elements.length === 0) { + if (this._debug) { + console.log("getDirectory response", node); + } + return this._callback(new Error("Invalid qnode for getDirectory")); + } + + const nodeElements = node == null ? null : node.getChildren(); + + if (nodeElements != null + && nodeElements.every(el => el._parent instanceof ember.Root)) { + if (this._debug) { + console.log("Received getDirectory response", node); + } + this._clearTimeout(); // clear the timeout now. The resolve below may take a while. + this._finishRequest(); + resolve(node); // make sure the info is treated before going to next request. + } + else { + return this._callback(new Error(`Invalid response for getDirectory ${requestedPath}`)); + } + } + else if (node.getElementByPath(requestedPath) != null) { + this._clearTimeout(); // clear the timeout now. The resolve below may take a while. + this._finishRequest(); + return resolve(node); // make sure the info is treated before going to next request. + } + else { + const nodeElements = node == null ? null : node.getChildren(); + if (nodeElements != null && + ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || + (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { + if (this._debug) { + console.log("Received getDirectory response", node); + } + this._clearTimeout(); // clear the timeout now. The resolve below may take a while. + this._finishRequest(); + return resolve(node); // make sure the info is treated before going to next request. + } + else if (this._debug) { + console.log(node); + console.log(new Error(requestedPath)); + } + } + }; + + if (this._debug) { + console.log("Sending getDirectory", qnode); + } + this._client.sendBERNode(qnode.getDirectory(callback)); + }}); + }); + } + + /** + * + * @param {string} path ie: "path/to/destination" + * @param {function} callback=null + * @returns {Promise} + */ + getNodeByPath(path, callback = null) { + if (typeof path === 'string') { + path = path.split('/'); + } + var pathError = new Error(`Failed path discovery at ${path.slice(0, pos + 1).join("/")}`); + var pos = 0; + var lastMissingPos = -1; + var currentNode = this.root; + const getNext = () => { + return Promise.resolve() + .then(() => { + const children = currentNode.getChildren(); + const identifier = path[pos]; + if (children != null) { + for (let i = 0; i < children.length; i++) { + var node = children[i]; + if (node.contents != null && node.contents.identifier === identifier) { + // We have this part already. + pos++; + if (pos >= path.length) { + return node; + } + currentNode = node; + return getNext(); + } + } + } + // We do not have that node yet. + if (lastMissingPos === pos) { + throw pathError; + } + lastMissingPos = pos; + return this.getDirectory(currentNode, callback).then(() => getNext()); + }); + } + return getNext(); + } + + /** + * + * @param {string|number[]} path ie: 1.0.2 + * @param {function} callback=null + * @returns {Promise} + */ + getNodeByPathnum(path, callback = null) { + if (typeof path === 'string') { + path = path.split('.'); + } + var pathnumError = new Error(`Failed path discovery at ${path.slice(0, pos).join("/")}`); + var pos = 0; + var lastMissingPos = -1; + var currentNode = this.root; + const getNext = () => { + return Promise.resolve() + .then(() => { + const children = currentNode.getChildren(); + const number = Number(path[pos]); + if (children != null) { + for (let i = 0; i < children.length; i++) { + var node = children[i]; + if (node.getNumber() === number) { + // We have this part already. + pos++; + if (pos >= path.length) { + return node; + } + currentNode = node; + return getNext(); + } + } + } + // We do not have that node yet. + if (lastMissingPos === pos) { + throw pathnumError; + } + lastMissingPos = pos; + return this.getDirectory(currentNode, callback).then(() => getNext()); + }); + } + return getNext(); + } + + /** + * + * @param {TreeNode} fnNode + * @param {FunctionArgument[]} params + */ + invokeFunction(fnNode, params) { + return new Promise((resolve, reject) => { + this.addRequest({node: fnNode, func: (error) => { + if (error) { + reject(error); + this._finishRequest(); + return; + } + + const cb = (error, result) => { + this._clearTimeout(); + if (error) { + reject(error); + } + else { + if (this._debug) { + console.log("InvocationResult", result); + } + resolve(result); + } + // cleaning callback and making next request. + this._finishRequest(); + }; + + if (this._debug) { + console.log("Invocking function", fnNode); + } + this._callback = cb; + this._client.sendBERNode(fnNode.invoke(params)); + }}); + }) + } + + /** + * @returns {boolean} + */ + isConnected() { + return ((this._client !== undefined) && (this._client.isConnected())); + } + + /** + * + * @param {Matrix} matrixNode + * @param {number} targetID + * @param {number[]} sources + * @param {MatrixOperation} operation + */ + matrixOPeration(matrixNode, targetID, sources, operation = ember.MatrixOperation.connect) { + return new Promise((resolve, reject) => { + if (!Array.isArray(sources)) { + return reject(new Error("Sources should be an array")); + } + try { + matrixNode.validateConnection(targetID, sources); + } + catch(e) { + return reject(e); + } + const connections = {} + const targetConnection = new ember.MatrixConnection(targetID); + targetConnection.operation = operation; + targetConnection.setSources(sources); + connections[targetID] = targetConnection; + + this.addRequest({node: matrixNode, func: (error) => { + if (error) { + this._finishRequest(); + reject(error); + return; + } + + this._callback = (error, node) => { + const requestedPath = matrixNode.getPath(); + if (node == null) { + if (this._debug) { + console.log(`received null response for ${requestedPath}`); + } + return; + } + if (error) { + if (this._debug) { + console.log("Received getDirectory error", error); + } + this._clearTimeout(); // clear the timeout now. The resolve below may take a while. + this._finishRequest(); + reject(error); + return; + } + let matrix = null; + if (node != null) { + matrix = node.elements[0]; + } + if (matrix != null && matrix.isMatrix() && matrix.getPath() === requestedPath) { + this._clearTimeout(); // clear the timeout now. The resolve below may take a while. + this._finishRequest(); + resolve(matrix); + } + else { + if (this._debug) { + console.log(`unexpected node response during matrix connect ${requestedPath}`, + JSON.stringify(matrix.toJSON(), null, 4)); + } + } + } + this._client.sendBERNode(matrixNode.connect(connections)); + }}); + }); + } + + /** + * + * @param {Matrix} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + matrixConnect(matrixNode, targetID, sources) { + return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.connect) + } + + /** + * + * @param {Matrix} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + matrixDisconnect(matrixNode, targetID, sources) { + return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.disconnect) + } + + /** + * + * @param {Matrix} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + matrixSetConnection(matrixNode, targetID, sources) { + return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.absolute) + } + + /** + * + * @param {function} f + */ + saveTree(f) { + const writer = new BER.Writer(); + this.root.encode(writer); + f(writer.buffer); + } + + /** + * + * @param {TreeNode} node + * @param {string|number} value + * @returns {Promise} + */ + setValue(node, value) { + return new Promise((resolve, reject) => { + if ((!(node instanceof ember.Parameter)) && + (!(node instanceof ember.QualifiedParameter))) { + reject(new errors.EmberAccessError('not a property')); + } + else { + // if (this._debug) { console.log('setValue', node.getPath(), value); } + this.addRequest({node: node, func: error => { + if (error) { + this._finishRequest(); + reject(error); + return; + } + + let cb = (error, node) => { + this._clearTimeout(); + this._finishRequest(); + if (error) { + reject(error); + } + else { + resolve(node); + } + }; + + this._callback = cb; + if (this._debug) { + console.log('setValue sending ...', node.getPath(), value); + } + this._client.sendBERNode(node.setValue(value)); + }}); + } + }); + } + + /** + * + * @param {TreeNode} qnode + * @param {function} callback + */ + subscribe(qnode, callback) { + if ((qnode.isParameter() || qnode.isMatrix()) && qnode.isStream()) { + if (qnode == null) { + this.root.clear(); + qnode = this.root; + } + return new Promise((resolve, reject) => { + this.addRequest({node: qnode, func: (error) => { + if (this._debug) { + console.log("Sending subscribe", qnode); + } + this._client.sendBERNode(qnode.subscribe(callback)); + this._finishRequest(); + resolve(); + }}); + }); + } else { + node.addCallback(callback); + } + } + + /** + * + * @param {TreeNode} qnode + * @param {function} callback + */ + unsubscribe(qnode, callback) { + if (qnode.isParameter() && qnode.isStream()) { + if (qnode == null) { + this.root.clear(); + qnode = this.root; + } + return new Promise((resolve, reject) => { + this.addRequest({node: qnode, func: (error) => { + if (this._debug) { + console.log("Sending subscribe", qnode); + } + this._client.sendBERNode(qnode.unsubscribe(callback)); + this._finishRequest(); + resolve(); + }}); + }); + } + } +} + +function isDirectSubPathOf(path, parent) { + return path === parent || (path.lastIndexOf('.') === parent.length && path.startsWith(parent)); +} + +module.exports = EmberClient; + diff --git a/EmberClient/index.js b/EmberClient/index.js new file mode 100755 index 0000000..964a37d --- /dev/null +++ b/EmberClient/index.js @@ -0,0 +1 @@ +module.exports = require("./EmberClient"); \ No newline at end of file diff --git a/EmberLib/Command.js b/EmberLib/Command.js index 0ee5b77..5d76c2b 100755 --- a/EmberLib/Command.js +++ b/EmberLib/Command.js @@ -15,14 +15,20 @@ const FieldFlags = new Enum({ }); class Command { + /** + * + * @param {number} number + */ constructor(number) { - if(number !== undefined) - this.number = number; + this.number = number; if(number == COMMAND_GETDIRECTORY) { - this.fieldFlags = FieldFlags.all; + this.fieldFlags = FieldFlags.all; } } + /** + * @returns {boolean} + */ isCommand() { return true; } diff --git a/EmberLib/Element.js b/EmberLib/Element.js new file mode 100755 index 0000000..26bfa9d --- /dev/null +++ b/EmberLib/Element.js @@ -0,0 +1,38 @@ +"use strict"; +const TreeNode = require("./TreeNode"); +const BER = require('../ber.js'); + +class Element extends TreeNode { + /** + * + * @param {number} number + */ + constructor(number) { + super(); + this.number = number; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(this._seqID); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } +} + +module.exports = Element; \ No newline at end of file diff --git a/EmberLib/Function.js b/EmberLib/Function.js index ee53b7a..3ccb79b 100755 --- a/EmberLib/Function.js +++ b/EmberLib/Function.js @@ -8,42 +8,14 @@ class Function extends TreeNode { super(); this.number = number; this.func = func; - } - - isFunction() { - return true; + this._seqID = BER.APPLICATION(19); } /** - * - * @param {BER} ber + * @returns {boolean} */ - encode(ber) { - ber.startSequence(BER.APPLICATION(19)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0; } + /** + * @returns {boolean} + */ isCommand() { return false; } - + /** + * @returns {boolean} + */ isNode() { return false; } - + /** + * @returns {boolean} + */ isMatrix() { return false; } - + /** + * @returns {boolean} + */ isParameter() { return false; } - + /** + * @returns {boolean} + */ isFunction() { return false; } - + /** + * @returns {boolean} + */ isRoot() { return this._parent == null; } - + /** + * @returns {boolean} + */ isQualified() { return false; } - + /** + * @returns {boolean} + */ isStream() { return this.contents != null && this.contents.streamIdentifier != null; } - + /** + * @returns {TreeNode} + */ getMinimalContent() { let obj; if (this.isQualified()) { @@ -149,7 +178,9 @@ class TreeNode { } return obj; }; - + /** + * @returns {TreeNode} + */ getDuplicate() { const obj = this.getMinimal(); obj.update(this); @@ -388,7 +419,7 @@ class TreeNode { * */ toJSON() { - const res = {}; + const res = {nodeType: this.constructor.name}; const node = this; if (this.isRoot()) { const elements = this.getChildren(); @@ -400,6 +431,9 @@ class TreeNode { res.path = node.getPath(); if (node.contents) { for(let prop in node.contents) { + if (prop[0] == "_") { + continue; + } if (node.contents.hasOwnProperty(prop)) { const type = typeof node.contents[prop]; if ((type === "string") || (type === "number")) { diff --git a/EmberLib/index.js b/EmberLib/index.js index 73789ac..10ebba6 100755 --- a/EmberLib/index.js +++ b/EmberLib/index.js @@ -1,7 +1,7 @@ const {Subscribe,COMMAND_SUBSCRIBE,Unsubscribe,COMMAND_UNSUBSCRIBE, GetDirectory,COMMAND_GETDIRECTORY,Invoke,COMMAND_INVOKE} = require("./constants"); const BER = require('../ber.js'); - +const errors = require("../errors"); const TreeNode = require("./TreeNode"); const Command = require("./Command"); const Function = require("./Function"); @@ -31,19 +31,12 @@ const rootDecode = function(ber) { const r = new TreeNode(); let tag = undefined; while(ber.remain > 0) { - if (DEBUG) { console.log("Reading root"); } tag = ber.peek(); if (tag === BER.APPLICATION(0)) { ber = ber.getSequence(BER.APPLICATION(0)); tag = ber.peek(); - if (DEBUG) { - console.log("Application 0 start"); - } if (tag === BER.APPLICATION(11)) { - if (DEBUG) { - console.log("Application 11 start"); - } const seq = ber.getSequence(BER.APPLICATION(11)); while (seq.remain > 0) { try { @@ -53,10 +46,6 @@ const rootDecode = function(ber) { } } catch (e) { - if (DEBUG) { - console.log("Decode ERROR", e.stack); - return r; - } throw e; } } @@ -77,7 +66,6 @@ const rootDecode = function(ber) { return Element.decode(rootReader) } catch (e) { - console.log(e.stack); return r; } } @@ -91,36 +79,24 @@ const rootDecode = function(ber) { const childDecode = function(ber) { const tag = ber.peek(); if (tag == BER.APPLICATION(1)) { - if (DEBUG) { console.log("Parameter decode");} return Parameter.decode(ber); } else if(tag == BER.APPLICATION(3)) { - if (DEBUG) { console.log("Node decode");} return Node.decode(ber); } else if(tag == BER.APPLICATION(2)) { - if (DEBUG) { console.log("Command decode");} return Command.decode(ber); } else if(tag == BER.APPLICATION(9)) { - if (DEBUG) { console.log("QualifiedParameter decode");} return QualifiedParameter.decode(ber); } else if(tag == BER.APPLICATION(10)) { - if (DEBUG) { console.log("QualifiedNode decode");} return QualifiedNode.decode(ber); } else if(tag == BER.APPLICATION(13)) { - if (DEBUG) { console.log("MatrixNode decode");} return MatrixNode.decode(ber); - } - else if(tag == BER.APPLICATION(17)) { - if (DEBUG) { console.log("QualifiedMatrix decode");} + } else if(tag == BER.APPLICATION(17)) { return QualifiedMatrix.decode(ber); - } - else if(tag == BER.APPLICATION(19)) { - if (DEBUG) { console.log("Function decode");} + } else if(tag == BER.APPLICATION(19)) { return Function.decode(ber); } else if (tag == BER.APPLICATION(20)) { - if (DEBUG) { console.log("QualifiedFunction decode");} return QualifiedFunction.decode(ber); - } - else if(tag == BER.APPLICATION(24)) { + } else if(tag == BER.APPLICATION(24)) { // Template throw new errors.UnimplementedEmberTypeError(tag); } else { @@ -130,10 +106,16 @@ const childDecode = function(ber) { TreeNode.decode = childDecode; +const DecodeBuffer = function (packet) { + const ber = new BER.Reader(packet); + return TreeNode.decode(ber); +}; + module.exports = { Command, childDecode: childDecode, rootDecode: rootDecode, + DecodeBuffer, Root: TreeNode, Function, FunctionArgument, diff --git a/EmberServer.js b/EmberServer.js deleted file mode 100755 index 0a7a7e7..0000000 --- a/EmberServer.js +++ /dev/null @@ -1,827 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const S101Server = require('./client.js').S101Server; -const ember = require('./EmberLib'); - -function TreeServer(host, port, tree) { - TreeServer.super_.call(this); - var self = this; - self._debug = false; - - self.callback = undefined; - self.timeoutValue = 2000; - self.server = new S101Server(host, port); - self.tree = tree; - self.clients = new Set(); - self.subscribers = {}; - - self.server.on('listening', () => { - if (self._debug) { console.log("listening"); } - self.emit('listening'); - if (self.callback !== undefined) { - self.callback(); - self.callback = undefined; - } - }); - - self.server.on('connection', (client) => { - if (self._debug) { console.log("ember new connection from", client.remoteAddress()); } - self.clients.add(client); - client.on("emberTree", (root) => { - if (self._debug) { console.log("ember new request from", client.remoteAddress(), root); } - // Queue the action to make sure responses are sent in order. - client.addRequest(() => { - try { - let path = self.handleRoot(client, root); - self.emit("request", {client: client.remoteAddress(), root: root, path: path}); - } - catch(e) { - if (self._debug) { console.log(e.stack); } - self.emit("error", e); - } - }); - }); - client.on("disconnected", () => { - self.clients.delete(client); - self.emit('disconnect', client.remoteAddress()); - }); - client.on("error", error => { - self.emit('clientError', { remoteAddress: client.remoteAddress(), error }); - }); - self.emit('connection', client.remoteAddress()); - }); - - self.server.on('disconnected', () => { - self.emit('disconnected', client.remoteAddress()); - }); - - self.server.on("error", (e) => { - self.emit("error", e); - if (self.callback !== undefined) { - self.callback(e); - } - }); - -} - -util.inherits(TreeServer, EventEmitter); - - -TreeServer.prototype.listen = function() { - return new Promise((resolve, reject) => { - this.callback = (e) => { - if (e == null) { - return resolve(); - } - return reject(e); - }; - this.server.listen(); - }); -}; - -TreeServer.prototype.close = function () { - return new Promise((resolve, reject) => { - this.callback = (e) => { - if (e == null) { - return resolve(); - } - return reject(e); - }; - this.server.server.close(); - }); -}; - -TreeServer.prototype.handleRoot = function(client, root) { - if ((root == null) || (root.elements == null) || (root.elements.size < 1)) { - // ignore empty requests. - return; - } - - const node = root.getChildren()[0]; - client.request = node; - - if (node.path !== undefined) { - return this.handleQualifiedNode(client, node); - } - else if (node.isCommand()) { - // Command on root element - this.handleCommand(client, this.tree, node); - return "root"; - } - else { - return this.handleNode(client, node); - } -} - -TreeServer.prototype.handleError = function(client, node) { - if (client !== undefined) { - const res = node == null ? this.tree.getMinimal() : node; - client.sendBERNode(res); - } -} - - -TreeServer.prototype.handleQualifiedNode = function(client, node) { - const path = node.path; - // Find this element in our tree - const element = this.tree.getElementByPath(path); - - if (element == null) { - this.emit("error", new Error(`unknown element at path ${path}`)); - return this.handleError(client); - } - - if (node.hasChildren()) { - for(child of node.children) { - if (child.isCommand()) { - this.handleCommand(client, element, child); - } - break; - } - } - else { - if (node.isMatrix()) { - this.handleQualifiedMatrix(client, element, node); - } - else if (node.isParameter()) { - this.handleQualifiedParameter(client, element, node); - } - } - return path; -} - - -TreeServer.prototype.handleNode = function(client, node) { - // traverse the tree - let element = node; - let path = []; - while(element !== undefined) { - if (element.number == null) { - this.emit("error", "invalid request"); - return; - } - if (element.isCommand()) { - break; - } - path.push(element.number); - - let children = element.getChildren(); - if ((! children) || (children.length === 0)) { - break; - } - element = element.children[0]; - } - let cmd = element; - - if (cmd == null) { - this.emit("error", "invalid request"); - return this.handleError(client); - } - - element = this.tree.getElementByPath(path.join(".")); - - if (element == null) { - this.emit("error", new Error(`unknown element at path ${path}`)); - return this.handleError(client); - } - - if (cmd.isCommand()) { - this.handleCommand(client, element, cmd); - } - else if ((cmd.isCommand()) && (cmd.connections !== undefined)) { - this.handleMatrixConnections(client, element, cmd.connections); - } - else if ((cmd.isParameter()) && - (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { - if (this._debug) { console.log(`setValue for element at path ${path} with value ${cmd.contents.value}`); } - this.setValue(element, cmd.contents.value, client); - let res = this.getResponse(element); - client.sendBERNode(res) - this.updateSubscribers(element.getPath(), res, client); - } - else { - this.emit("error", new Error("invalid request format")); - if (this._debug) { console.log("invalid request format"); } - return this.handleError(client, element.getTreeBranch()); - } - return path; -} - -TreeServer.prototype.handleQualifiedMatrix = function(client, element, matrix) -{ - this.handleMatrixConnections(client, element, matrix.connections); -} - -TreeServer.prototype.handleQualifiedParameter = function(client, element, parameter) -{ - if (parameter.contents.value !== undefined) { - this.setValue(element, parameter.contents.value, client); - let res = this.getQualifiedResponse(element); - client.sendBERNode(res) - this.updateSubscribers(element.getPath(), res, client); - } -} - - -TreeServer.prototype.handleMatrixConnections = function(client, matrix, connections, response = true) { - var res,conResult; - var root; // ember message root - if (this._debug) { - console.log("Handling Matrix Connection"); - } - if (client != null && client.request.isQualified()) { - root = new ember.Root(); - res = new ember.QualifiedMatrix(matrix.getPath()); - //root.elements = [res]; // do not use addchild or the element will get removed from the tree. - root.addElement(res); - } - else { - res = new ember.MatrixNode(matrix.number); - root = matrix._parent.getTreeBranch(res); - } - res.connections = {}; - for(let id in connections) { - if (!connections.hasOwnProperty(id)) { - continue; - } - let connection = connections[id]; - conResult = new ember.MatrixConnection(connection.target); - let emitType; - res.connections[connection.target] = conResult; - - if (matrix.connections[connection.target].isLocked()) { - conResult.disposition = ember.MatrixDisposition.locked; - } - else if (matrix.contents.type !== ember.MatrixType.nToN && - connection.operation !== ember.MatrixOperation.disconnect && - connection.sources != null && connection.sources.length === 1) { - if (matrix.contents.type === ember.MatrixType.oneToOne) { - // if the source is being used already, disconnect it. - const targets = matrix.getSourceConnections(connection.sources[0]); - if (targets.length === 1 && targets[0] !== connection.target) { - const disconnect = new ember.MatrixConnection(targets[0]); - disconnect.setSources([]); - disconnect.disposition = ember.MatrixDisposition.modified; - res.connections[targets[0]] = disconnect; - matrix.setSources(targets[0], []); - if (response) { - this.emit("matrix-disconnect", { - target: targets[0], - sources: connection.sources, - client: client == null ? null : client.remoteAddress() - }); - } - } - } - // if the target is connected already, disconnect it - if (matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length === 1) { - if (matrix.contents.type === ember.MatrixType.oneToN) { - const disconnectSource = this.getDisconnectSource(matrix, connection.target); - if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { - if (disconnectSource != null && disconnectSource != -1 && - disconnectSource != connection.sources[0]) { - connection.sources = [disconnectSource]; - } - else { - // do nothing - connection.operarion = ember.MatrixOperation.tally; - } - } - } - if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { - const source = matrix.connections[connection.target].sources[0]; - matrix.setSources(connection.target, []); - if (response) { - this.emit("matrix-disconnect", { - target: connection.target, - sources: [source], - client: client == null ? null : client.remoteAddress() - }); - } - } - else if (matrix.contents.type === ember.MatrixType.oneToOne) { - // let's change the request into a disconnect - connection.operation = ember.MatrixOperation.disconnect; - } - } - } - - if (connection.operation !== ember.MatrixOperation.disconnect && - connection.sources != null && connection.sources.length > 0 && - matrix.canConnect(connection.target,connection.sources,connection.operation)) { - // Apply changes - if ((connection.operation == null) || - (connection.operation.value == ember.MatrixOperation.absolute)) { - matrix.setSources(connection.target, connection.sources); - emitType = "matrix-change"; - } - else if (connection.operation == ember.MatrixOperation.connect) { - matrix.connectSources(connection.target, connection.sources); - emitType = "matrix-connect"; - } - conResult.disposition = ember.MatrixDisposition.modified; - } - else if (connection.operation !== ember.MatrixOperation.disconnect && - connection.sources != null && connection.sources.length === 0 && - matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length > 0) { - // let's disconnect - if (response) { - this.emit("matrix-disconnect", { - target: connection.target, - sources: matrix.connections[connection.target].sources, - client: client == null ? null : client.remoteAddress() - }); - } - matrix.setSources(connection.target, []); - conResult.disposition = ember.MatrixDisposition.modified; - } - else if (connection.operation === ember.MatrixOperation.disconnect && - matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length > 0) { - // Disconnect - if (matrix.contents.type === ember.MatrixType.oneToN) { - const disconnectSource = this.getDisconnectSource(matrix, connection.target); - if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { - if (disconnectSource != null && disconnectSource != -1 && - disconnectSource != connection.sources[0]) { - if (response) { - this.emit("matrix-disconnect", { - target: connection.target, - sources: matrix.connections[connection.target].sources, - client: client == null ? null : client.remoteAddress() - }); - } - matrix.setSources(connection.target, [disconnectSource]); - connection.operarion = ember.MatrixOperation.modified; - } - else { - // do nothing - connection.operarion = ember.MatrixOperation.tally; - } - } - } - else { - matrix.disconnectSources(connection.target, connection.sources); - conResult.disposition = ember.MatrixDisposition.modified; - emitType = "matrix-disconnect"; - } - } - else if (conResult.disposition !== ember.MatrixDisposition.locked){ - if (this._debug) { - console.log(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); - } - conResult.disposition = ember.MatrixDisposition.tally; - } - - // Send response or update subscribers. - conResult.sources = matrix.connections[connection.target].sources; - if (response) { - // We got a request so emit something. - this.emit(emitType, { - target: connection.target, - sources: connection.sources, - client: client == null ? null : client.remoteAddress() - }); - } - } - if (client != null) { - client.sendBERNode(root); - } - - if (conResult != null && conResult.disposition !== ember.MatrixDisposition.tally) { - if (this._debug) { - console.log("Updating subscribers for matrix change"); - } - this.updateSubscribers(matrix.getPath(), root, client); - } -} - -const validateMatrixOperation = function(matrix, target, sources) { - if (matrix == null) { - throw new Error(`matrix not found with path ${path}`); - } - if (matrix.contents == null) { - throw new Error(`invalid matrix at ${path} : no contents`); - } - if (matrix.contents.targetCount == null) { - throw new Error(`invalid matrix at ${path} : no targetCount`); - } - if ((target < 0) || (target >= matrix.contents.targetCount)) { - throw new Error(`target id ${target} out of range 0 - ${matrix.contents.targetCount}`); - } - if (sources.length == null) { - throw new Error("invalid sources format"); - } -} - -const doMatrixOperation = function(server, path, target, sources, operation) { - let matrix = server.tree.getElementByPath(path); - - validateMatrixOperation(matrix, target, sources); - - let connection = new ember.MatrixConnection(target); - connection.sources = sources; - connection.operation = operation; - server.handleMatrixConnections(undefined, matrix, [connection], false); -} - -TreeServer.prototype.matrixConnect = function(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.connect); -} - -TreeServer.prototype.matrixDisconnect = function(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.disconnect); -} - -TreeServer.prototype.matrixSet = function(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.absolute); -} - -TreeServer.prototype.handleQualifiedFunction = function(client, element, node) { - -} - - -TreeServer.prototype.handleCommand = function(client, element, cmd) { - switch(cmd.number) { - case ember.COMMAND_GETDIRECTORY: - this.handleGetDirectory(client, element); - break; - case ember.COMMAND_SUBSCRIBE: - this.handleSubscribe(client, element); - break; - case ember.COMMAND_UNSUBSCRIBE: - this.handleUnSubscribe(client, element); - break; - case ember.COMMAND_INVOKE: - this.handleInvoke(client, cmd.invocation, element); - break; - default: - this.emit("error", new Error(`invalid command ${cmd.number}`)); - break; - } -} - -TreeServer.prototype.getResponse = function(element) { - return element.getTreeBranch(undefined, function(node) { - node.update(element); - let children = element.getChildren(); - if (children != null) { - for (let i = 0; i < children.length; i++) { - node.addChild(children[i].getDuplicate()); - } - } - else if (this._debug) { - console.log("getResponse","no children"); - } - }); -} - -TreeServer.prototype.getQualifiedResponse = function(element) { - const res = new ember.Root(); - let dup; - if (element.isRoot() === false) { - dup = element.toQualified(); - } - let children = element.getChildren(); - if (children != null) { - for (let i = 0; i < children.length; i++) { - res.addChild(children[i].toQualified().getMinimalContent()); - } - } - else { - res.addChild(dup); - } - return res; -} - -TreeServer.prototype.handleInvoke = function(client, invocation, element) { - const result = new ember.InvocationResult(); - result.invocationId = invocation.id; - if (element == null || !element.isFunction()) { - result.setFailure(); - } - else { - try { - result.setResult(element.func(invocation.arguments)); - } - catch(e){ - this.emit("error", e); - result.setFailure(); - } - } - const res = new ember.Root(); - res.addResult(result); - client.sendBERNode(res); -} - -TreeServer.prototype.handleGetDirectory = function(client, element) { - if (client !== undefined) { - if ((element.isMatrix() || element.isParameter()) && - (!element.isStream())) { - // ember spec: parameter without streamIdentifier should - // report their value changes automatically. - this.subscribe(client, element); - } - else if (element.isNode()) { - const children = element.getChildren(); - if (children != null) { - for (let i = 0; i < children.length; i++) { - const child = children[i]; - if ((child.isMatrix() || child.isParameter()) && - (!child.isStream())) { - this.subscribe(client, child); - } - } - } - } - - const res = this.getQualifiedResponse(element); - if (this._debug) { - console.log("getDirectory response", res); - } - client.sendBERNode(res); - } -} - -TreeServer.prototype.handleSubscribe = function(client, element) { - if (this._debug) { - console.log("subscribe"); - } - this.subscribe(client, element); -} - -TreeServer.prototype.handleUnSubscribe = function(client, element) { - if (this._debug) { - console.log("unsubscribe"); - } - this.unsubscribe(client, element); -} - - -TreeServer.prototype.subscribe = function(client, element) { - const path = element.getPath(); - if (this.subscribers[path] == null) { - this.subscribers[path] = new Set(); - } - this.subscribers[path].add(client); -} - -TreeServer.prototype.unsubscribe = function(client, element) { - const path = element.getPath(); - if (this.subscribers[path] == null) { - return; - } - this.subscribers[path].delete(client); -} - -TreeServer.prototype.setValue = function(element, value, origin, key) { - return new Promise((resolve, reject) => { - // Change the element value if write access permitted. - if (element.contents == null) { - return resolve(); - } - if (element.isParameter()) { - if ((element.contents.access !== undefined) && - (element.contents.access.value > 1)) { - element.contents.value = value; - const res = this.getResponse(element); - this.updateSubscribers(element.getPath(),res, origin); - this.emit("value-change", element); - } - } - else if (element.isMatrix()) { - if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { - element.contents[key] = value; - const res = this.getResponse(element); - this.updateSubscribers(element.getPath(),res, origin); - this.emit("value-change", element); - } - } - return resolve(); - }); -} - -TreeServer.prototype.replaceElement = function(element) { - let path = element.getPath(); - let parent = this.tree.getElementByPath(path); - if ((parent == null)||(parent._parent == null)) { - throw new Error(`Could not find element at path ${path}`); - } - parent = parent._parent; - let children = parent.getChildren(); - let newList = []; - for(let i = 0; i <= children.length; i++) { - if (children[i] && children[i].getPath() == path) { - element._parent = parent; // move it to new tree. - children[i] = element; - let res = this.getResponse(element); - this.updateSubscribers(path,res); - return; - } - } -} - -TreeServer.prototype.getDisconnectSource = function(matrix, targetID) { - if (matrix.defaultSources) { - return matrix.defaultSources[targetID].contents.value; - } - if (matrix.contents.labels == null || matrix.contents.labels.length == 0) { - return null; - } - const basePath = matrix.contents.labels[0].basePath; - const labels = this.tree.getElementByPath(basePath); - const number = labels.getNumber() + 1; - const parent = labels.getParent(); - const children = parent.getChildren(); - for(let child of children) { - if (child.getNumber() === number) { - matrix.defaultSources = child.getChildren(); - return matrix.defaultSources[targetID].contents.value; - } - } - return null; -} - -TreeServer.prototype.updateSubscribers = function(path, response, origin) { - if (this.subscribers[path] == null) { - return; - } - - for (let client of this.subscribers[path]) { - if (client === origin) { - continue; // already sent the response to origin - } - if (this.clients.has(client)) { - client.queueMessage(response); - } - else { - // clean up subscribers - client is gone - this.subscribers[path].delete(client); - } - } -} - -const parseMatrixContent = function(matrixContent, content) { - if (content.labels) { - matrixContent.labels = []; - for(let l = 0; l < content.labels.length; l++) { - if (typeof (content.labels[l]) === "object") { - matrixContent.labels.push( - new ember.Label( - content.labels[l].basePath, - content.labels[l].description - ) - ); - } - else { - // for backward compatibility... Remove in the future - matrixContent.labels.push( - new ember.Label(content.labels[l]) - ); - } - } - delete content.labels; - } - if (content.type != null) { - if (content.type == "oneToN") { - matrixContent.type = ember.MatrixType.oneToN; - } - else if (content.type == "oneToOne") { - matrixContent.type = ember.MatrixType.oneToOne; - } - else if (content.type == "nToN") { - matrixContent.type = ember.MatrixType.nToN; - matrixContent.maximumTotalConnects = content.maximumTotalConnects == null ? - Number(content.targetCount) * Number(content.sourceCount) : Number(content.maximumTotalConnects); - matrixContent.maximumConnectsPerTarget = content.maximumConnectsPerTarget == null ? - Number(content.sourceCount) : Number(content.maximumConnectsPerTarget); - } - else { - throw new Error(`Invalid matrix type ${content.type}`); - } - delete content.type; - } - if (content.mode != null) { - if (content.mode == "linear") { - matrixContent.mode = ember.MatrixMode.linear; - } - else if (content.mode == "nonLinear") { - matrixContent.mode = ember.MatrixMode.nonLinear; - } - else { - throw new Error(`Invalid matrix mode ${content.mode}`); - } - delete content.mode; - } -} - -const parseObj = function(parent, obj) { - for(let i = 0; i < obj.length; i++) { - let emberElement; - let content = obj[i]; - let number = content.number !== undefined ? content.number : i; - delete content.number; - if (content.value != null) { - emberElement = new ember.Parameter(number); - emberElement.contents = new ember.ParameterContents(content.value); - if (content.type) { - emberElement.contents.type = ember.ParameterType.get(content.type); - delete content.type; - } - else { - emberElement.contents.type = ember.ParameterType.string; - } - if (content.access) { - emberElement.contents.access = ember.ParameterAccess.get(content.access); - delete content.access; - } - else { - emberElement.contents.access = ember.ParameterAccess.read; - } - } - else if (content.func != null) { - emberElement = new ember.Function(number, content.func); - emberElement.contents = new ember.FunctionContent(); - if (content.arguments != null) { - for(let argument of content.arguments) { - emberElement.contents.arguments.push(new ember.FunctionArgument( - argument.type, - argument.value, - argument.name - )); - } - } - if (content.result != null) { - for(let argument of content.result) { - emberElement.contents.result.push(new ember.FunctionArgument( - argument.type, - argument.value, - argument.name - )); - } - } - delete content.result; - } - else if (content.targetCount != null) { - emberElement = new ember.MatrixNode(number); - emberElement.contents = new ember.MatrixContents(); - parseMatrixContent(emberElement.contents, content); - if (content.connections) { - emberElement.connections = {}; - for (let c in content.connections) { - if (! content.connections.hasOwnProperty(c)) { - continue; - } - const t = content.connections[c].target != null ? content.connections[c].target : 0; - emberElement.setSources(t, content.connections[c].sources); - } - delete content.connections; - } - else { - emberElement.connections = {}; - for (let t = 0; t < content.targetCount; t++) { - let connection = new ember.MatrixConnection(t); - emberElement.connections[t] = connection; - } - } - } - else { - emberElement = new ember.Node(number); - emberElement.contents = new ember.NodeContents(); - } - for(let id in content) { - if (emberElement.isFunction() && id === "arguments") { - // we did it already. - continue; - } - if ((id !== "children") && (content.hasOwnProperty(id))) { - emberElement.contents[id] = content[id]; - } - else { - parseObj(emberElement, content.children); - } - } - parent.addChild(emberElement); - } -} - -TreeServer.JSONtoTree = function(obj) { - const tree = new ember.Root(); - parseObj(tree, obj); - return tree; -} - - -TreeServer.prototype.toJSON = function() { - if (this.tree == null) { - return []; - } - const elements = this.tree.getChildren(); - - return elements.map(element => element.toJSON()); -}; - -module.exports = TreeServer; diff --git a/EmberServer/ElementHandlers.js b/EmberServer/ElementHandlers.js new file mode 100755 index 0000000..c30f888 --- /dev/null +++ b/EmberServer/ElementHandlers.js @@ -0,0 +1,186 @@ +"use strict"; +const QualifiedHandlers = require("./QualifiedHandlers"); +const ember = require('../EmberLib'); + +class ElementHandlers extends QualifiedHandlers{ + /** + * + * @param {EmberServer} server + */ + constructor(server) { + super(server); + } + /** + * + * @param {S101Client} client + * @param {TreeNode} root + * @param {Command} cmd + */ + handleCommand(client, element, cmd) { + switch(cmd.number) { + case ember.COMMAND_GETDIRECTORY: + this.handleGetDirectory(client, element); + break; + case ember.COMMAND_SUBSCRIBE: + this.handleSubscribe(client, element); + break; + case ember.COMMAND_UNSUBSCRIBE: + this.handleUnSubscribe(client, element); + break; + case ember.COMMAND_INVOKE: + this.handleInvoke(client, cmd.invocation, element); + break; + default: + this.server.emit("error", new Error(`invalid command ${cmd.number}`)); + break; + } + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleGetDirectory(client, element) { + if (client !== undefined) { + if ((element.isMatrix() || element.isParameter()) && + (!element.isStream())) { + // ember spec: parameter without streamIdentifier should + // report their value changes automatically. + this.server.subscribe(client, element); + } + else if (element.isNode()) { + const children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if ((child.isMatrix() || child.isParameter()) && + (!child.isStream())) { + this.server.subscribe(client, child); + } + } + } + } + + const res = this.server.getQualifiedResponse(element); + if (this.server._debug) { + console.log("getDirectory response", res); + } + client.sendBERNode(res); + } + } + + /** + * + * @param {S101Client} client + * @param {Invocation} invocation + * @param {TreeNode} element + */ + handleInvoke(client, invocation, element) { + const result = new ember.InvocationResult(); + result.invocationId = invocation.id; + if (element == null || !element.isFunction()) { + result.setFailure(); + } + else { + try { + result.setResult(element.func(invocation.arguments)); + } + catch(e){ + this.server.emit("error", e); + result.setFailure(); + } + } + const res = new ember.Root(); + res.addResult(result); + client.sendBERNode(res); + } + + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleNode(client, node) { + // traverse the tree + let element = node; + let path = []; + while(element !== undefined) { + if (element.number == null) { + this.server.emit("error", "invalid request"); + return; + } + if (element.isCommand()) { + break; + } + path.push(element.number); + + let children = element.getChildren(); + if ((! children) || (children.length === 0)) { + break; + } + element = element.children[0]; + } + let cmd = element; + + if (cmd == null) { + this.server.emit("error", "invalid request"); + return this.server.handleError(client); + } + + element = this.server.tree.getElementByPath(path.join(".")); + + if (element == null) { + this.server.emit("error", new Error(`unknown element at path ${path}`)); + return this.server.handleError(client); + } + + if (cmd.isCommand()) { + this.handleCommand(client, element, cmd); + } + else if ((cmd.isCommand()) && (cmd.connections !== undefined)) { + this.handleMatrixConnections(client, element, cmd.connections); + } + else if ((cmd.isParameter()) && + (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { + if (this.server._debug) { console.log(`setValue for element at path ${path} with value ${cmd.contents.value}`); } + this.setValue(element, cmd.contents.value, client); + let res = this.server.getResponse(element); + client.sendBERNode(res) + this.server.updateSubscribers(element.getPath(), res, client); + } + else { + this.server.emit("error", new Error("invalid request format")); + if (this.server._debug) { console.log("invalid request format"); } + return this.server.handleError(client, element.getTreeBranch()); + } + return path; + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleSubscribe(client, element) { + if (this.server._debug) { + console.log("subscribe"); + } + this.server.subscribe(client, element); + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleUnSubscribe(client, element) { + if (this.server._debug) { + console.log("unsubscribe"); + } + this.server.unsubscribe(client, element); + } +} + +module.exports = ElementHandlers; \ No newline at end of file diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js new file mode 100755 index 0000000..5407b46 --- /dev/null +++ b/EmberServer/EmberServer.js @@ -0,0 +1,376 @@ +const EventEmitter = require('events').EventEmitter; +const S101Server = require('../EmberSocket').S101Server; +const ember = require('../EmberLib'); +const JSONParser = require("./JSONParser"); +const ElementHandlers = require("./ElementHandlers"); + +class TreeServer extends EventEmitter{ + /** + * + * @param {string} host + * @param {number} port + * @param {TreeNode} tree + */ + constructor(host, port, tree) { + super(); + this._debug = false; + this.callback = undefined; + this.timeoutValue = 2000; + this.server = new S101Server(host, port); + this.tree = tree; + this.clients = new Set(); + this.subscribers = {}; + this._handlers = new ElementHandlers(this); + + this.server.on('listening', () => { + if (this._debug) { console.log("listening"); } + this.emit('listening'); + if (this.callback !== undefined) { + this.callback(); + this.callback = undefined; + } + }); + + this.server.on('connection', client => { + if (this._debug) { console.log("ember new connection from", client.remoteAddress()); } + this.clients.add(client); + client.on("emberTree", (root) => { + if (this._debug) { console.log("ember new request from", client.remoteAddress(), root); } + // Queue the action to make sure responses are sent in order. + client.addRequest(() => { + try { + let path = this.handleRoot(client, root); + this.emit("request", {client: client.remoteAddress(), root: root, path: path}); + } + catch(e) { + if (this._debug) { console.log(e.stack); } + this.emit("error", e); + } + }); + }); + client.on("disconnected", () => { + this.clients.delete(client); + this.emit('disconnect', client.remoteAddress()); + }); + client.on("error", error => { + this.emit('clientError', { remoteAddress: client.remoteAddress(), error }); + }); + this.emit('connection', client.remoteAddress()); + }); + + this.server.on('disconnected', () => { + this.emit('disconnected', client.remoteAddress()); + }); + + this.server.on("error", (e) => { + this.emit("error", e); + if (this.callback !== undefined) { + this.callback(e); + } + }); + } + + /** + * @returns {Promise} + */ + close() { + return new Promise((resolve, reject) => { + this.callback = (e) => { + if (e == null) { + return resolve(); + } + return reject(e); + }; + this.server.server.close(); + }); + }; + + /** + * + * @param {TreeNode} element + */ + getResponse(element) { + return element.getTreeBranch(undefined, node => { + node.update(element); + let children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + node.addChild(children[i].getDuplicate()); + } + } + else if (this._debug) { + console.log("getResponse","no children"); + } + }); + } + + /** + * + * @param {TreeNode} element + */ + getQualifiedResponse(element) { + const res = new ember.Root(); + let dup; + if (element.isRoot() === false) { + dup = element.toQualified(); + } + let children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + res.addChild(children[i].toQualified().getMinimalContent()); + } + } + else { + res.addChild(dup); + } + return res; + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleError(client, node) { + if (client !== undefined) { + const res = node == null ? this.tree.getMinimal() : node; + client.sendBERNode(res); + } + } + + /** + * + * @param {S101Socket} client + * @param {TreeNode} root + */ + handleRoot(client, root) { + if ((root == null) || (root.elements == null) || (root.elements.size < 1)) { + // ignore empty requests. + return; + } + + const node = root.getChildren()[0]; + client.request = node; + + if (node.path !== undefined) { + return this._handlers.handleQualifiedNode(client, node); + } + else if (node.isCommand()) { + // Command on root element + this._handlers.handleCommand(client, this.tree, node); + return "root"; + } + else { + return this._handlers.handleNode(client, node); + } + } + + /** + * @returns {Promise} + */ + listen() { + return new Promise((resolve, reject) => { + this.callback = (e) => { + if (e == null) { + return resolve(); + } + return reject(e); + }; + this.server.listen(); + }); + }; + + /** + * + * @param {string} path + * @param {number} target + * @param {number[]} sources + */ + matrixConnect(path, target, sources) { + doMatrixOperation(this, path, target, sources, ember.MatrixOperation.connect); + } + + /** + * + * @param {string} path + * @param {number} target + * @param {number[]} sources + */ + matrixDisconnect(path, target, sources) { + doMatrixOperation(this, path, target, sources, ember.MatrixOperation.disconnect); + } + + /** + * + * @param {string} path + * @param {number} target + * @param {number[]} sources + */ + matrixSet(path, target, sources) { + doMatrixOperation(this, path, target, sources, ember.MatrixOperation.absolute); + } + + /** + * + * @param {TreeNode} element + */ + replaceElement(element) { + let path = element.getPath(); + let parent = this.tree.getElementByPath(path); + if ((parent == null)||(parent._parent == null)) { + throw new Error(`Could not find element at path ${path}`); + } + parent = parent._parent; + let children = parent.getChildren(); + let newList = []; + for(let i = 0; i <= children.length; i++) { + if (children[i] && children[i].getPath() == path) { + element._parent = parent; // move it to new tree. + children[i] = element; + let res = this.getResponse(element); + this.updateSubscribers(path,res); + return; + } + } + } + + /** + * + * @param {TreeNode} element + * @param {string|number} value + * @param {S101Socket} origin + * @param {string} key + */ + setValue(element, value, origin, key) { + return new Promise((resolve, reject) => { + // Change the element value if write access permitted. + if (element.contents == null) { + return resolve(); + } + if (element.isParameter()) { + if ((element.contents.access !== undefined) && + (element.contents.access.value > 1)) { + element.contents.value = value; + const res = this.getResponse(element); + this.updateSubscribers(element.getPath(),res, origin); + this.emit("value-change", element); + } + } + else if (element.isMatrix()) { + if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { + element.contents[key] = value; + const res = this.getResponse(element); + this.updateSubscribers(element.getPath(),res, origin); + this.emit("value-change", element); + } + } + return resolve(); + }); + } + + /** + * + * @param {S101Socket} client + * @param {TreeNode} root + */ + subscribe(client, element) { + const path = element.getPath(); + if (this.subscribers[path] == null) { + this.subscribers[path] = new Set(); + } + this.subscribers[path].add(client); + } + + /** + * @returns {Object} + */ + toJSON() { + if (this.tree == null) { + return []; + } + const elements = this.tree.getChildren(); + + return elements.map(element => element.toJSON()); + }; + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + unsubscribe(client, element) { + const path = element.getPath(); + if (this.subscribers[path] == null) { + return; + } + this.subscribers[path].delete(client); + } + + /** + * + * @param {string} path + * @param {TreeNode} response + * @param {S101Socket} origin + */ + updateSubscribers(path, response, origin) { + if (this.subscribers[path] == null) { + return; + } + + for (let client of this.subscribers[path]) { + if (client === origin) { + continue; // already sent the response to origin + } + if (this.clients.has(client)) { + client.queueMessage(response); + } + else { + // clean up subscribers - client is gone + this.subscribers[path].delete(client); + } + } + } + + /** + * + * @param {object} obj + * @returns {TreeNode} + */ + static JSONtoTree(obj) { + const tree = new ember.Root(); + JSONParser.parseObj(tree, obj); + return tree; + } +} + + +const validateMatrixOperation = function(matrix, target, sources) { + if (matrix == null) { + throw new Error(`matrix not found with path ${path}`); + } + if (matrix.contents == null) { + throw new Error(`invalid matrix at ${path} : no contents`); + } + if (matrix.contents.targetCount == null) { + throw new Error(`invalid matrix at ${path} : no targetCount`); + } + if ((target < 0) || (target >= matrix.contents.targetCount)) { + throw new Error(`target id ${target} out of range 0 - ${matrix.contents.targetCount}`); + } + if (sources.length == null) { + throw new Error("invalid sources format"); + } +} + +const doMatrixOperation = function(server, path, target, sources, operation) { + let matrix = server.tree.getElementByPath(path); + + validateMatrixOperation(matrix, target, sources); + + let connection = new ember.MatrixConnection(target); + connection.sources = sources; + connection.operation = operation; + server.handleMatrixConnections(undefined, matrix, [connection], false); +} + +module.exports = TreeServer; diff --git a/EmberServer/JSONParser.js b/EmberServer/JSONParser.js new file mode 100755 index 0000000..1908b11 --- /dev/null +++ b/EmberServer/JSONParser.js @@ -0,0 +1,160 @@ +"use strict"; +const ember = require('../EmberLib'); + +class JSONParser { + /** + * + * @param {MatrixContent} matrixContent + * @param {object} content + */ + static parseMatrixContent(matrixContent, content) { + if (content.labels) { + matrixContent.labels = []; + for(let l = 0; l < content.labels.length; l++) { + if (typeof (content.labels[l]) === "object") { + matrixContent.labels.push( + new ember.Label( + content.labels[l].basePath, + content.labels[l].description + ) + ); + } + else { + // for backward compatibility... Remove in the future + matrixContent.labels.push( + new ember.Label(content.labels[l]) + ); + } + } + delete content.labels; + } + if (content.type != null) { + if (content.type == "oneToN") { + matrixContent.type = ember.MatrixType.oneToN; + } + else if (content.type == "oneToOne") { + matrixContent.type = ember.MatrixType.oneToOne; + } + else if (content.type == "nToN") { + matrixContent.type = ember.MatrixType.nToN; + matrixContent.maximumTotalConnects = content.maximumTotalConnects == null ? + Number(content.targetCount) * Number(content.sourceCount) : Number(content.maximumTotalConnects); + matrixContent.maximumConnectsPerTarget = content.maximumConnectsPerTarget == null ? + Number(content.sourceCount) : Number(content.maximumConnectsPerTarget); + } + else { + throw new Error(`Invalid matrix type ${content.type}`); + } + delete content.type; + } + if (content.mode != null) { + if (content.mode == "linear") { + matrixContent.mode = ember.MatrixMode.linear; + } + else if (content.mode == "nonLinear") { + matrixContent.mode = ember.MatrixMode.nonLinear; + } + else { + throw new Error(`Invalid matrix mode ${content.mode}`); + } + delete content.mode; + } + } + + /** + * + * @param {TreeNode} parent + * @param {object} obj + */ + static parseObj(parent, obj) { + for(let i = 0; i < obj.length; i++) { + let emberElement; + let content = obj[i]; + let number = content.number !== undefined ? content.number : i; + delete content.number; + if (content.value != null) { + emberElement = new ember.Parameter(number); + emberElement.contents = new ember.ParameterContents(content.value); + if (content.type) { + emberElement.contents.type = ember.ParameterType.get(content.type); + delete content.type; + } + else { + emberElement.contents.type = ember.ParameterType.string; + } + if (content.access) { + emberElement.contents.access = ember.ParameterAccess.get(content.access); + delete content.access; + } + else { + emberElement.contents.access = ember.ParameterAccess.read; + } + } + else if (content.func != null) { + emberElement = new ember.Function(number, content.func); + emberElement.contents = new ember.FunctionContent(); + if (content.arguments != null) { + for(let argument of content.arguments) { + emberElement.contents.arguments.push(new ember.FunctionArgument( + argument.type, + argument.value, + argument.name + )); + } + } + if (content.result != null) { + for(let argument of content.result) { + emberElement.contents.result.push(new ember.FunctionArgument( + argument.type, + argument.value, + argument.name + )); + } + } + delete content.result; + } + else if (content.targetCount != null) { + emberElement = new ember.MatrixNode(number); + emberElement.contents = new ember.MatrixContents(); + this.parseMatrixContent(emberElement.contents, content); + if (content.connections) { + emberElement.connections = {}; + for (let c in content.connections) { + if (! content.connections.hasOwnProperty(c)) { + continue; + } + const t = content.connections[c].target != null ? content.connections[c].target : 0; + emberElement.setSources(t, content.connections[c].sources); + } + delete content.connections; + } + else { + emberElement.connections = {}; + for (let t = 0; t < content.targetCount; t++) { + let connection = new ember.MatrixConnection(t); + emberElement.connections[t] = connection; + } + } + } + else { + emberElement = new ember.Node(number); + emberElement.contents = new ember.NodeContents(); + } + for(let id in content) { + if (emberElement.isFunction() && id === "arguments") { + // we did it already. + continue; + } + if ((id !== "children") && (content.hasOwnProperty(id))) { + emberElement.contents[id] = content[id]; + } + else { + this.parseObj(emberElement, content.children); + } + } + parent.addChild(emberElement); + } + } +} + +module.exports = JSONParser; \ No newline at end of file diff --git a/EmberServer/MatrixHandlers.js b/EmberServer/MatrixHandlers.js new file mode 100755 index 0000000..58e7391 --- /dev/null +++ b/EmberServer/MatrixHandlers.js @@ -0,0 +1,223 @@ +"use strict"; +const ember = require('../EmberLib'); + +class MatrixHandlers { + /** + * + * @param {EmberServer} server + */ + constructor(server) { + this.server = server; + } + + /** + * + * @param {Matrix} matrix + * @param {number} targetID + */ + getDisconnectSource(matrix, targetID) { + if (matrix.defaultSources) { + return matrix.defaultSources[targetID].contents.value; + } + if (matrix.contents.labels == null || matrix.contents.labels.length == 0) { + return null; + } + const basePath = matrix.contents.labels[0].basePath; + const labels = this.server.tree.getElementByPath(basePath); + const number = labels.getNumber() + 1; + const parent = labels.getParent(); + const children = parent.getChildren(); + for(let child of children) { + if (child.getNumber() === number) { + matrix.defaultSources = child.getChildren(); + return matrix.defaultSources[targetID].contents.value; + } + } + return null; + } + + /** + * + * @param {S101Client} client + * @param {Matrix} matrix + * @param {Object} connections + * @param {boolean} response=true + */ + handleMatrixConnections(client, matrix, connections, response = true) { + var res,conResult; + var root; // ember message root + if (this.server._debug) { + console.log("Handling Matrix Connection"); + } + if (client != null && client.request.isQualified()) { + root = new ember.Root(); + res = new ember.QualifiedMatrix(matrix.getPath()); + //root.elements = [res]; // do not use addchild or the element will get removed from the tree. + root.addElement(res); + } + else { + res = new ember.MatrixNode(matrix.number); + root = matrix._parent.getTreeBranch(res); + } + res.connections = {}; + for(let id in connections) { + if (!connections.hasOwnProperty(id)) { + continue; + } + let connection = connections[id]; + conResult = new ember.MatrixConnection(connection.target); + let emitType; + res.connections[connection.target] = conResult; + + if (matrix.connections[connection.target].isLocked()) { + conResult.disposition = ember.MatrixDisposition.locked; + } + else if (matrix.contents.type !== ember.MatrixType.nToN && + connection.operation !== ember.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 1) { + if (matrix.contents.type === ember.MatrixType.oneToOne) { + // if the source is being used already, disconnect it. + const targets = matrix.getSourceConnections(connection.sources[0]); + if (targets.length === 1 && targets[0] !== connection.target) { + const disconnect = new ember.MatrixConnection(targets[0]); + disconnect.setSources([]); + disconnect.disposition = ember.MatrixDisposition.modified; + res.connections[targets[0]] = disconnect; + matrix.setSources(targets[0], []); + if (response) { + this.server.emit("matrix-disconnect", { + target: targets[0], + sources: connection.sources, + client: client == null ? null : client.remoteAddress() + }); + } + } + } + // if the target is connected already, disconnect it + if (matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length === 1) { + if (matrix.contents.type === ember.MatrixType.oneToN) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + if (disconnectSource != null && disconnectSource != -1 && + disconnectSource != connection.sources[0]) { + connection.sources = [disconnectSource]; + } + else { + // do nothing + connection.operarion = ember.MatrixOperation.tally; + } + } + } + if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { + const source = matrix.connections[connection.target].sources[0]; + matrix.setSources(connection.target, []); + if (response) { + this.server.emit("matrix-disconnect", { + target: connection.target, + sources: [source], + client: client == null ? null : client.remoteAddress() + }); + } + } + else if (matrix.contents.type === ember.MatrixType.oneToOne) { + // let's change the request into a disconnect + connection.operation = ember.MatrixOperation.disconnect; + } + } + } + + if (connection.operation !== ember.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length > 0 && + matrix.canConnect(connection.target,connection.sources,connection.operation)) { + // Apply changes + if ((connection.operation == null) || + (connection.operation.value == ember.MatrixOperation.absolute)) { + matrix.setSources(connection.target, connection.sources); + emitType = "matrix-change"; + } + else if (connection.operation == ember.MatrixOperation.connect) { + matrix.connectSources(connection.target, connection.sources); + emitType = "matrix-connect"; + } + conResult.disposition = ember.MatrixDisposition.modified; + } + else if (connection.operation !== ember.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 0 && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // let's disconnect + if (response) { + this.server.emit("matrix-disconnect", { + target: connection.target, + sources: matrix.connections[connection.target].sources, + client: client == null ? null : client.remoteAddress() + }); + } + matrix.setSources(connection.target, []); + conResult.disposition = ember.MatrixDisposition.modified; + } + else if (connection.operation === ember.MatrixOperation.disconnect && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // Disconnect + if (matrix.contents.type === ember.MatrixType.oneToN) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + if (disconnectSource != null && disconnectSource != -1 && + disconnectSource != connection.sources[0]) { + if (response) { + this.server.emit("matrix-disconnect", { + target: connection.target, + sources: matrix.connections[connection.target].sources, + client: client == null ? null : client.remoteAddress() + }); + } + matrix.setSources(connection.target, [disconnectSource]); + connection.operarion = ember.MatrixOperation.modified; + } + else { + // do nothing + connection.operarion = ember.MatrixOperation.tally; + } + } + } + else { + matrix.disconnectSources(connection.target, connection.sources); + conResult.disposition = ember.MatrixDisposition.modified; + emitType = "matrix-disconnect"; + } + } + else if (conResult.disposition !== ember.MatrixDisposition.locked){ + if (this.server._debug) { + console.log(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); + } + conResult.disposition = ember.MatrixDisposition.tally; + } + + // Send response or update subscribers. + conResult.sources = matrix.connections[connection.target].sources; + if (response) { + // We got a request so emit something. + this.server.emit(emitType, { + target: connection.target, + sources: connection.sources, + client: client == null ? null : client.remoteAddress() + }); + } + } + if (client != null) { + client.sendBERNode(root); + } + + if (conResult != null && conResult.disposition !== ember.MatrixDisposition.tally) { + if (this.server._debug) { + console.log("Updating subscribers for matrix change"); + } + this.server.updateSubscribers(matrix.getPath(), root, client); + } + } + +} + +module.exports = MatrixHandlers; \ No newline at end of file diff --git a/EmberServer/QualifiedHandlers.js b/EmberServer/QualifiedHandlers.js new file mode 100755 index 0000000..03915ff --- /dev/null +++ b/EmberServer/QualifiedHandlers.js @@ -0,0 +1,75 @@ +"use strict"; +const MatrixHandlers = require("./MatrixHandlers"); + +class QualifiedHandlers extends MatrixHandlers { + /** + * + * @param {EmberServer} server + */ + constructor(server) { + super(server); + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} element + * @param {QualifiedMatrix} matrix + */ + handleQualifiedMatrix(client, element, matrix) + { + this.handleMatrixConnections(client, element, matrix.connections); + } + + /** + * + * @param {S101Client} client + * @param {QualifiedNode} root + */ + handleQualifiedNode(client, node) { + const path = node.path; + // Find this element in our tree + const element = this.server.tree.getElementByPath(path); + + if (element == null) { + this.server.emit("error", new Error(`unknown element at path ${path}`)); + return this.server.handleError(client); + } + + if (node.hasChildren()) { + for(let child of node.children) { + if (child.isCommand()) { + this.handleCommand(client, element, child); + } + break; + } + } + else { + if (node.isMatrix()) { + this.handleQualifiedMatrix(client, element, node); + } + else if (node.isParameter()) { + this.handleQualifiedParameter(client, element, node); + } + } + return path; + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} element + * @param {QualifiedParameter} parameter + */ + handleQualifiedParameter(client, element, parameter) + { + if (parameter.contents.value !== undefined) { + this.server.setValue(element, parameter.contents.value, client); + let res = this.server.getQualifiedResponse(element); + client.sendBERNode(res) + this.server.updateSubscribers(element.getPath(), res, client); + } + } +} + +module.exports = QualifiedHandlers; diff --git a/EmberServer/index.js b/EmberServer/index.js new file mode 100755 index 0000000..17080bd --- /dev/null +++ b/EmberServer/index.js @@ -0,0 +1 @@ +module.exports = require("./EmberServer"); \ No newline at end of file diff --git a/EmberSocket/S101Client.js b/EmberSocket/S101Client.js new file mode 100755 index 0000000..b4c3f76 --- /dev/null +++ b/EmberSocket/S101Client.js @@ -0,0 +1,87 @@ +"use strict"; + +const S101Socket = require("./S101Socket"); +const S101Codec = require('../s101.js'); +const BER = require('../ber.js'); +const ember = require("../EmberLib"); + +class S101Client extends S101Socket { + constructor(socket, server) { + super() + this.request = null; + this.server = server; + this.socket = socket; + + this.pendingRequests = []; + this.activeRequest = null; + + this.status = "connected"; + + this.codec = new S101Codec(); + this.codec.on('keepaliveReq', () => { + this.sendKeepaliveResponse(); + }); + + this.codec.on('emberPacket', (packet) => { + this.emit('emberPacket', packet); + const ber = new BER.Reader(packet); + try { + const root = ember.rootDecode(ber); + if (root !== undefined) { + this.emit('emberTree', root); + } + } catch (e) { + this.emit("error", e); + } + }); + + if (socket != null) { + this.socket.on('data', (data) => { + this.codec.dataIn(data); + }); + + this.socket.on('close', () => { + this.emit('disconnected'); + this.status = "disconnected"; + this.socket = null; + }); + + this.socket.on('error', (e) => { + this.emit("error", e); + }); + } + } + + addRequest(cb) { + this.pendingRequests.push(cb); + this._makeRequest(); + } + + _makeRequest() { + if (this.activeRequest === null && this.pendingRequests.length > 0) { + this.activeRequest = this.pendingRequests.shift(); + this.activeRequest(); + this.activeRequest = null; + } + }; + + /** + * + * @param {TreeNode} node + */ + queueMessage(node) { + this.addRequest(() => this.sendBERNode(node)); + } + + /** + * @returns {string} - ie: "10.1.1.1:9000" + */ + remoteAddress() { + if (this.socket === undefined) { + return; + } + return `${this.socket.remoteAddress}:${this.socket.remotePort}` + } +} + +module.exports = S101Client; diff --git a/EmberSocket/S101Server.js b/EmberSocket/S101Server.js new file mode 100755 index 0000000..67dcb84 --- /dev/null +++ b/EmberSocket/S101Server.js @@ -0,0 +1,53 @@ +"use strict"; +const EventEmitter = require('events').EventEmitter; +const S101Client = require("./S101Client"); +const net = require('net'); + +class S101Server extends EventEmitter { + /** + * + * @param {string} address + * @param {number} port + */ + constructor(address, port) { + super(); + this.address = address; + this.port = Number(port); + this.server = null; + this.status = "disconnected"; + } + /** + * + * @param {Socket} socket + */ + addClient(socket) { + var client = new S101Client(socket, this); + this.emit("connection", client); + } + /** + * + */ + listen () { + if (this.status !== "disconnected") { + return; + } + + this.server = net.createServer((socket) => { + this.addClient(socket); + }); + + this.server.on("error", (e) => { + this.emit("error", e); + }); + + this.server.on("listening", () => { + this.emit("listening"); + this.status = "listening"; + }); + + this.server.listen(this.port, this.address); + } + +} + +module.exports = S101Server; \ No newline at end of file diff --git a/EmberSocket/S101Socket.js b/EmberSocket/S101Socket.js new file mode 100755 index 0000000..a38b51d --- /dev/null +++ b/EmberSocket/S101Socket.js @@ -0,0 +1,186 @@ +"use strict"; + +const EventEmitter = require('events').EventEmitter; +const net = require('net'); +const BER = require('../ber.js'); +const ember = require('../EmberLib'); +const S101Codec = require('../s101.js'); + + +class S101Socket extends EventEmitter{ + constructor(address, port) { + super(); + this.address = address; + this.port = port; + this.socket = null; + this.keepaliveInterval = 10; + this.codec = null; + this.status = "disconnected"; + } + + /** + * + * @param {number} timeout + */ + connect(timeout = 2) { + if (this.status !== "disconnected") { + return; + } + + this.emit('connecting'); + + this.codec = new S101Codec(); + + const connectTimeoutListener = () => { + this.socket.destroy(); + this.emit("error", new Error(`Could not connect to ${this.address}:${this.port} after a timeout of ${timeout} seconds`)); + }; + + this.socket = net.createConnection({ + port: this.port, + host: this.address, + timeout: 1000 * timeout + }, + () => { + // Disable connect timeout to hand-over to keepalive mechanism + this.socket.removeListener("timeout", connectTimeoutListener); + this.socket.setTimeout(0); + + this.keepaliveIntervalTimer = setInterval(() => { + try { + this.sendKeepaliveRequest(); + } catch (e) { + this.emit("error", e); + } + }, 1000 * this.keepaliveInterval); + + this.codec.on('keepaliveReq', () => { + this.sendKeepaliveResponse(); + }); + + this.codec.on('emberPacket', (packet) => { + this.emit('emberPacket', packet); + + const ber = new BER.Reader(packet); + try { + const root = ember.rootDecode(ber); + if (root !== undefined) { + this.emit('emberTree', root); + } + } catch (e) { + this.emit("error", e); + } + }); + + this.emit('connected'); + }) + .on('error', (e) => { + this.emit("error", e); + }) + .once("timeout", connectTimeoutListener) + .on('data', (data) => { + if (this.isConnected()) { + this.codec.dataIn(data); + } + }) + .on('close', this.handleClose) + .on("end", this.handleClose); + } + + /** + * + */ + disconnect() { + if (!this.isConnected()) { + return Promise.resolve(); + } + return new Promise((resolve, reject) => { + this.socket.once('close', () => { + this.codec = null; + this.socket = null; + resolve(); + }); + this.socket.once('error', reject); + clearInterval(this.keepaliveIntervalTimer); + this.socket.end(); + this.status = "disconnected"; + } + ); + }; + + /** + * + */ + handleClose() { + this.socket = null; + clearInterval(this.keepaliveIntervalTimer); + this.status = "disconnected"; + this.emit('disconnected'); + }; + + /** + * @returns {boolean} + */ + isConnected() { + return ((this.socket !== null) && (this.socket !== undefined)); + } + + /** + * + * @param {Buffer} data + */ + sendBER(data) { + if (this.isConnected()) { + try { + const frames = this.codec.encodeBER(data); + for (let i = 0; i < frames.length; i++) { + this.socket.write(frames[i]); + } + } + catch(e){ + this.handleClose(); + } + } + } + + /** + * + */ + sendKeepaliveRequest() { + if (this.isConnected()) { + try { + this.socket.write(this.codec.keepAliveRequest()); + } + catch(e){ + this.handleClose(); + } + } + } + + /** + * + */ + sendKeepaliveResponse() { + if (this.isConnected()) { + try { + this.socket.write(this.codec.keepAliveResponse()); + } + catch(e){ + this.handleClose(); + } + } + } + + /** + * + * @param {TreeNode} node + */ + sendBERNode(node) { + if (!node) return; + const writer = new BER.Writer(); + node.encode(writer); + this.sendBER(writer.buffer); + } +} + +module.exports = S101Socket; \ No newline at end of file diff --git a/EmberSocket/index.js b/EmberSocket/index.js new file mode 100755 index 0000000..d755a5b --- /dev/null +++ b/EmberSocket/index.js @@ -0,0 +1,7 @@ +const S101Socket = require("./S101Socket"); +const S101Server = require("./S101Server"); +const S101Client = require("./S101Client"); + +module.exports = { + S101Client, S101Server, S101Socket +}; \ No newline at end of file diff --git a/README.md b/README.md index 5b3d203..332997f 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # node-emberplus -This is an implementation of [Lawo's -Ember+](https://github.com/Lawo/ember-plus) control protocol for Node. One of -Node's great strengths is the ready availability of frameworks for various +This is version 2 of emberplus library. +An implementation of [Lawo's Ember+](https://github.com/Lawo/ember-plus) control protocol for Node. +One of Node's great strengths is the ready availability of frameworks for various communication protocols and user interfaces; this module allows those to be integrated with Ember+ somewhat more easily than the reference libember C++ implementation. @@ -22,48 +22,36 @@ Server has been added in version 1.6.0. ### Client Get Full tree: ```javascript -const DeviceTree = require('emberplus').DeviceTree; -var root; -var tree = new DeviceTree("10.9.8.7", 9000); -tree.on("error", e => { +const EmberClient = require('node-emberplus').EmberClient; +const client = new EmberClient("10.9.8.7", 9000); +client.on("error", e => { console.log(e); }); -tree.connect() - .then(() => tree.getDirectory()) - .then((r) => { - root = r ; - return tree.expand(r.elements[0]); +client.connect() + // Get Root info + .then(() => client.getDirectory()) + // Get a Specific Node + .then(() => client.getNodeByPathnum("0.0.2")) + .then(node => { + console.log(node); }) - .then(() => { - console.log("done"); + // Get a node by its path identifiers + .then(() => client.getNodeByPath("path/to/node")) + .then(node => { + console.log(node); }) + // Expand entire tree under node 0 + .then(() => client.expand(client.root.getElementByNumber(0))) .catch((e) => { console.log(e.stack); }); ``` -Get Specific Node: -```javascript -const DeviceTree = require('emberplus').DeviceTree; -const ember = require("emberplus").Ember; - -const client = new DeviceTree(HOST, PORT); -client.connect()) - .then(() => client.getDirectory()) - .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) - .then(() => client.getNodeByPath("scoreMaster/router/labels/group 1")) - .then(() => client.getNodeByPathnum("0.2")) - .then(node => { - console.log(JSON.stringify(node.toJSON(), null, 4)); - }); -``` - Subsribe to changes ```javascript -const DeviceTree = require('emberplus').DeviceTree; -const ember = require("emberplus").Ember; +const {EmberClient, EmberLib} = require('node-emberplus'); -const client = new DeviceTree(HOST, PORT); +const client = new EmberClient(HOST, PORT); client.connect()) .then(() => client.getDirectory()) .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) @@ -89,36 +77,29 @@ client.connect()) ### Invoking Function ```javascript -const DeviceTree = require('emberplus').DeviceTree; -const ember = require("emberplus").Ember; +const {EmberClient, EmberLib} = require('node-emberplus'); -const client = new DeviceTree(HOST, PORT); +const client = new EmberClient(HOST, PORT); client.connect()) .then(() => client.getDirectory()) .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) - .then(() => client.expand(client.root.elements[0])) + .then(() => client.expand(client.root.getElementByNumber(0))) .then(() => { - console.log(JSON.stringify(client.root.elements[0].toJSON(), null, 4)); - let func; - if (TINYEMBER) { - func = client.root.elements[0].children[4].children[0]; - } - else { - func = client.root.elements[0].children[2]; - } - return client.invokeFunction(func, [ - new ember.FunctionArgument(ember.ParameterType.integer, 1), - new ember.FunctionArgument(ember.ParameterType.integer, 7) + console.log(JSON.stringify(client.root.getElementByNumber(0).toJSON(), null, 4)); + const funcNode = client.root.getElementByNumber(0).getElementByNumber(5).getElementByNumber(0); + return client.invokeFunction(funcNode, [ + new ember.FunctionArgument(EmberLib.ParameterType.integer, 1), + new ember.FunctionArgument(EmberLib.ParameterType.integer, 7) ]); }); ``` ### Matrix Connection ```javascript -const DeviceTree = require('emberplus').DeviceTree; -const ember = require("emberplus").Ember; +const {EmberClient, EmberLib} = require('node-emberplus'); + -const client = new DeviceTree(HOST, PORT); +const client = new EmberClient(HOST, PORT); client.connect() .then(() => client.getDirectory()) .then(() => client.getNodeByPathnum("0.1.0")) @@ -136,7 +117,7 @@ client.connect() ### Packet decoder ```javascript // Simple packet decoder -const Decoder = require('emberplus').Decoder; +const Decoder = require('node-emberplus').Decoder; const fs = require("fs"); fs.readFile("tree.ember", (e,data) => { @@ -147,8 +128,8 @@ fs.readFile("tree.ember", (e,data) => { ```javascript // Server -const TreeServer = require("emberplus").TreeServer; -const server = new TreeServer("127.0.0.1", 9000, root); +const EmberServer = require("node-emberplus").EmberServer; +const server = new EmberServer("127.0.0.1", 9000, root); server.on("error", e => { console.log("Server Error", e); }); @@ -169,8 +150,8 @@ server.listen().then(() => { console.log("listening"); }).catch((e) => { console ### Construct Tree ```javascript -const TreeServer = require("emberplus").TreeServer; -const {ParameterType, FunctionArgument} = require("emberplus").Ember; +const EmberServer = require("node-emberplus").EmberServer; +const {ParameterType, FunctionArgument} = require("node-emberplus").EmberLib; const targets = [ "tgt1", "tgt2", "tgt3" ]; const sources = [ "src1", "src2", "src3" ]; @@ -291,6 +272,6 @@ const jsonTree = [ ] } ]; -const root = TreeServer.JSONtoTree(jsonTree); +const root = EmberServer.JSONtoTree(jsonTree); ``` diff --git a/client.js b/client.js deleted file mode 100755 index 1f297f3..0000000 --- a/client.js +++ /dev/null @@ -1,308 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const winston = require('winston'); -const net = require('net'); -const BER = require('./ber.js'); -const ember = require('./EmberLib'); - -const S101Codec = require('./s101.js'); - - -function S101Socket(address, port) { - var self = this; - S101Socket.super_.call(this); - - self.address = address; - self.port = port; - self.socket = null; - self.keepaliveInterval = 10; - self.codec = null; - self.status = "disconnected"; - -} - -util.inherits(S101Socket, EventEmitter); - - -function S101Client(socket, server) { - var self = this; - S101Client.super_.call(this); - - self.request = null; - self.server = server; - self.socket = socket; - - self.pendingRequests = []; - self.activeRequest = null; - - self.status = "connected"; - - self.codec = new S101Codec(); - self.codec.on('keepaliveReq', () => { - self.sendKeepaliveResponse(); - }); - - self.codec.on('emberPacket', (packet) => { - self.emit('emberPacket', packet); - var ber = new BER.Reader(packet); - try { - var root = ember.rootDecode(ber); - if (root !== undefined) { - self.emit('emberTree', root); - } - } catch (e) { - self.emit("error", e); - } - }); - - if (socket !== undefined) { - self.socket.on('data', (data) => { - self.codec.dataIn(data); - }); - - self.socket.on('close', () => { - self.emit('disconnected'); - self.status = "disconnected"; - self.socket = null; - }); - - self.socket.on('error', (e) => { - self.emit("error", e); - }); - } -} - -util.inherits(S101Client, S101Socket); - - -/********************************************** - * SERVER - **********************************************/ - -function S101Server(address, port) { - var self = this; - S101Server.super_.call(this); - - self.address = address; - self.port = port; - self.server = null; - self.status = "disconnected"; -} - -util.inherits(S101Server, EventEmitter); - -S101Server.prototype.listen = function () { - var self = this; - if (self.status !== "disconnected") { - return; - } - - self.server = net.createServer((socket) => { - self.addClient(socket); - }); - - self.server.on("error", (e) => { - self.emit("error", e); - }); - - self.server.on("listening", () => { - self.emit("listening"); - self.status = "listening"; - }); - - self.server.listen(self.port, self.address); -} - - -S101Server.prototype.addClient = function (socket) { - var client = new S101Client(socket, this); - this.emit("connection", client); -} - - -/***************************************************** - * Client - *****************************************************/ - -S101Client.prototype.remoteAddress = function () { - if (this.socket === undefined) { - return; - } - return `${this.socket.remoteAddress}:${this.socket.remotePort}` -} - -S101Client.prototype.queueMessage = function (node) { - const self = this; - this.addRequest(() => { - self.sendBERNode(node); - }); -} - -S101Client.prototype.makeRequest = function () { - if (this.activeRequest === null && this.pendingRequests.length > 0) { - this.activeRequest = this.pendingRequests.shift(); - this.activeRequest(); - this.activeRequest = null; - } -}; - -S101Client.prototype.addRequest = function (cb) { - this.pendingRequests.push(cb); - this.makeRequest(); -} - - -/***************************************************** - * Socket - *****************************************************/ - -S101Socket.prototype.connect = function (timeout = 2) { - var self = this; - if (self.status !== "disconnected") { - return; - } - - self.emit('connecting'); - - self.codec = new S101Codec(); - - const connectTimeoutListener = () => { - self.socket.destroy(); - self.emit("error", new Error(`Could not connect to ${self.address}:${self.port} after a timeout of ${timeout} seconds`)); - }; - - self.socket = net.createConnection({ - port: self.port, - host: self.address, - timeout: 1000 * timeout - }, - () => { - winston.debug('socket connected'); - // Disable connect timeout to hand-over to keepalive mechanism - self.socket.removeListener("timeout", connectTimeoutListener); - self.socket.setTimeout(0); - - self.keepaliveIntervalTimer = setInterval(() => { - try { - self.sendKeepaliveRequest(); - } catch (e) { - self.emit("error", e); - } - }, 1000 * self.keepaliveInterval); - - self.codec.on('keepaliveReq', () => { - self.sendKeepaliveResponse(); - }); - - self.codec.on('emberPacket', (packet) => { - self.emit('emberPacket', packet); - - var ber = new BER.Reader(packet); - try { - var root = ember.rootDecode(ber); - if (root !== undefined) { - self.emit('emberTree', root); - } - } catch (e) { - self.emit("error", e); - } - }); - - self.emit('connected'); - }) - .on('error', (e) => { - self.emit("error", e); - }) - .once("timeout", connectTimeoutListener) - .on('data', (data) => { - if (self.isConnected()) { - self.codec.dataIn(data); - } - }) - .on('close', self.handleClose) - .on("end", self.handleClose); -} - -S101Socket.prototype.handleClose = function() { - this.socket = null; - clearInterval(this.keepaliveIntervalTimer); - this.status = "disconnected"; - this.emit('disconnected'); -}; - -S101Socket.prototype.isConnected = function () { - return ((this.socket !== null) && (this.socket !== undefined)); -} - -S101Socket.prototype.disconnect = function () { - var self = this; - - if (!self.isConnected()) { - return Promise.resolve(); - } - return new Promise((resolve, reject) => { - self.socket.once('close', () => { - self.codec = null; - self.socket = null; - resolve(); - }); - self.socket.once('error', reject); - clearInterval(self.keepaliveIntervalTimer); - self.socket.end(); - self.status = "disconnected"; - } - ); -}; - -S101Socket.prototype.sendKeepaliveRequest = function () { - var self = this; - if (self.isConnected()) { - try { - self.socket.write(self.codec.keepAliveRequest()); - winston.debug('sent keepalive request'); - } - catch(e){ - self.handleClose(); - } - } -} - -S101Socket.prototype.sendKeepaliveResponse = function () { - var self = this; - if (self.isConnected()) { - try { - self.socket.write(self.codec.keepAliveResponse()); - } - catch(e){ - self.handleClose(); - } - winston.debug('sent keepalive response'); - } -} - -S101Socket.prototype.sendBER = function (data) { - var self = this; - if (self.isConnected()) { - try { - var frames = self.codec.encodeBER(data); - for (var i = 0; i < frames.length; i++) { - self.socket.write(frames[i]); - } - } - catch(e){ - self.handleClose(); - } - } -} - -S101Socket.prototype.sendBERNode = function (node) { - var self = this; - if (!node) return; - var writer = new BER.Writer(); - node.encode(writer); - self.sendBER(writer.buffer); -} - - -module.exports = { S101Socket, S101Server, S101Client }; - diff --git a/device.js b/device.js deleted file mode 100755 index a8a85a5..0000000 --- a/device.js +++ /dev/null @@ -1,657 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const S101Client = require('./client.js').S101Socket; -const ember = require('./EmberLib'); -const BER = require('./ber.js'); -const errors = require('./errors.js'); - - -function DeviceTree(host, port = 9000) { - DeviceTree.super_.call(this); - var self = this; - self._debug = false; - self.timeoutValue = 3000; - self.client = new S101Client(host, port); - self.root = new ember.Root(); - self.pendingRequests = []; - self.activeRequest = null; - self.timeout = null; - self.callback = undefined; - self.requestID = 0; - - self.client.on('connecting', () => { - self.emit('connecting'); - }); - - self.client.on('connected', () => { - self.emit('connected'); - if (self.callback !== undefined) { - self.callback(); - } - }); - - self.client.on('disconnected', () => { - self.emit('disconnected'); - }); - - self.client.on("error", (e) => { - if (self.callback !== undefined) { - self.callback(e); - } - self.emit("error", e); - }); - - self.client.on('emberTree', (root) => { - try { - if (root instanceof ember.InvocationResult) { - self.emit('invocationResult', root); - if (self._debug) { - console.log("Received InvocationResult", root); - } - } else { - self.handleRoot(root); - if (self._debug) { - console.log("Received root", root); - } - } - if (self.callback) { - self.callback(undefined, root); - } - } - catch(e) { - if (self._debug) { - console.log(e, root); - } - if (self.callback) { - self.callback(e); - } - } - }); -} - -util.inherits(DeviceTree, EventEmitter); - - -var DecodeBuffer = function (packet) { - var ber = new BER.Reader(packet); - return ember.Root.decode(ber); -}; - -DeviceTree.prototype.saveTree = function (f) { - var writer = new BER.Writer(); - this.root.encode(writer); - f(writer.buffer); -}; - -DeviceTree.prototype.isConnected = function () { - return ((this.client !== undefined) && (this.client.isConnected())); -}; - -DeviceTree.prototype.connect = function (timeout = 2) { - return new Promise((resolve, reject) => { - this.callback = (e) => { - this.callback = undefined; - if (e === undefined) { - return resolve(); - } - return reject(e); - }; - if ((this.client !== undefined) && (this.client.isConnected())) { - this.client.disconnect(); - } - this.client.connect(timeout); - }); -}; - -DeviceTree.prototype.expand = function (node, callback = null) { - let self = this; - if (node == null) { - return Promise.reject(new Error("Invalid null node")); - } - if (node.isParameter() || node.isMatrix() || node.isFunction()) { - return self.getDirectory(node); - } - return self.getDirectory(node, callback).then((res) => { - let children = node.getChildren(); - if ((res === undefined) || (children === undefined) || (children === null)) { - if (self._debug) { - console.log("No more children for ", node); - } - return; - } - let p = Promise.resolve(); - for (let child of children) { - if (child.isParameter()) { - // Parameter can only have a single child of type Command. - continue; - } - if (self._debug) { - console.log("Expanding child", child); - } - p = p.then(() => { - return self.expand(child).catch((e) => { - // We had an error on some expansion - // let's save it on the child itself - child.error = e; - }); - }); - } - return p; - }); -}; - -function isDirectSubPathOf(path, parent) { - return path === parent || (path.lastIndexOf('.') === parent.length && path.startsWith(parent)); -} - -DeviceTree.prototype.getDirectory = function (qnode, callback = null) { - var self = this; - if (qnode == null) { - self.root.clear(); - qnode = self.root; - } - return new Promise((resolve, reject) => { - self.addRequest({node: qnode, func: (error) => { - if (error) { - self.finishRequest(); - reject(error); - return; - } - - self.callback = (error, node) => { - const requestedPath = qnode.getPath(); - if (node == null) { - if (self._debug) { - console.log(`received null response for ${requestedPath}`); - } - return; - } - if (error) { - if (self._debug) { - console.log("Received getDirectory error", error); - } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(); - reject(error); - return; - } - if (qnode.isRoot()) { - const elements = qnode.getChildren(); - if (elements == null || elements.length === 0) { - if (self._debug) { - console.log("getDirectory response", node); - } - return self.callback(new Error("Invalid qnode for getDirectory")); - } - - const nodeElements = node == null ? null : node.getChildren(); - - if (nodeElements != null - && nodeElements.every(el => el._parent instanceof ember.Root)) { - if (self._debug) { - console.log("Received getDirectory response", node); - } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(); - resolve(node); // make sure the info is treated before going to next request. - } - else { - return self.callback(new Error(`Invalid response for getDirectory ${requestedPath}`)); - } - } - else if (node.getElementByPath(requestedPath) != null) { - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(); - return resolve(node); // make sure the info is treated before going to next request. - } - else { - const nodeElements = node == null ? null : node.getChildren(); - if (nodeElements != null && - ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || - (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { - if (self._debug) { - console.log("Received getDirectory response", node); - } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(); - return resolve(node); // make sure the info is treated before going to next request. - } - else if (self._debug) { - console.log(node); - console.log(new Error(requestedPath)); - } - } - }; - - if (self._debug) { - console.log("Sending getDirectory", qnode); - } - self.client.sendBERNode(qnode.getDirectory(callback)); - }}); - }); -}; - -DeviceTree.prototype.matrixOPeration = function(matrixNode, targetID, sources, operation = ember.MatrixOperation.connect) { - return new Promise((resolve, reject) => { - if (!Array.isArray(sources)) { - return reject(new Error("Sources should be an array")); - } - try { - matrixNode.validateConnection(targetID, sources); - } - catch(e) { - return reject(e); - } - const connections = {} - const targetConnection = new ember.MatrixConnection(targetID); - targetConnection.operation = operation; - targetConnection.setSources(sources); - connections[targetID] = targetConnection; - - this.addRequest({node: matrixNode, func: (error) => { - if (error) { - this.finishRequest(); - reject(error); - return; - } - - this.callback = (error, node) => { - const requestedPath = matrixNode.getPath(); - if (node == null) { - if (this._debug) { - console.log(`received null response for ${requestedPath}`); - } - return; - } - if (error) { - if (this._debug) { - console.log("Received getDirectory error", error); - } - this.clearTimeout(); // clear the timeout now. The resolve below may take a while. - this.finishRequest(); - reject(error); - return; - } - let matrix = null; - if (node != null) { - matrix = node.elements[0]; - } - if (matrix != null && matrix.isMatrix() && matrix.getPath() === requestedPath) { - this.clearTimeout(); // clear the timeout now. The resolve below may take a while. - this.finishRequest(); - resolve(matrix); - } - else { - if (this._debug) { - console.log(`unexpected node response during matrix connect ${requestedPath}`, - JSON.stringify(matrix.toJSON(), null, 4)); - } - } - } - this.client.sendBERNode(matrixNode.connect(connections)); - }}); - }); -} - -DeviceTree.prototype.matrixConnect = function(matrixNode, targetID, sources) { - return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.connect) -} - -DeviceTree.prototype.matrixDisconnect = function(matrixNode, targetID, sources) { - return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.disconnect) -} - -DeviceTree.prototype.matrixSetConnection = function(matrixNode, targetID, sources) { - return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.absolute) -} - -DeviceTree.prototype.invokeFunction = function (fnNode, params) { - var self = this; - return new Promise((resolve, reject) => { - self.addRequest({node: fnNode, func: (error) => { - if (error) { - reject(error); - self.finishRequest(); - return; - } - - let cb = (error, result) => { - self.clearTimeout(); - if (error) { - reject(error); - } - else { - if (self._debug) { - console.log("InvocationResult", result); - } - resolve(result); - } - // cleaning callback and making next request. - self.finishRequest(); - }; - - if (self._debug) { - console.log("Invocking function", fnNode); - } - self.callback = cb; - self.client.sendBERNode(fnNode.invoke(params)); - }}); - }) -}; - -DeviceTree.prototype.disconnect = function () { - if (this.client !== undefined) { - return this.client.disconnect(); - } -}; - -DeviceTree.prototype.makeRequest = function () { - const self = this; - if (self.activeRequest === null && self.pendingRequests.length > 0) { - self.activeRequest = self.pendingRequests.shift(); - const req = `${ self.requestID++} - ${self.activeRequest.node.getPath()}`; - self.activeRequest.timeoutError = new errors.EmberTimeoutError(`Request ${req} timed out`) - - if (self._debug) { - console.log(`Making request ${req}`, Date.now()); - } - self.timeout = setTimeout(() => { - self.timeoutRequest(); - }, self.timeoutValue); - self.activeRequest.func(); - } -}; - -DeviceTree.prototype.addRequest = function (req) { - var self = this; - self.pendingRequests.push(req); - self.makeRequest(); -}; - -DeviceTree.prototype.clearTimeout = function () { - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; - } -}; - -DeviceTree.prototype.finishRequest = function () { - var self = this; - self.clearTimeout(); - self.activeRequest = null; - try { - self.makeRequest(); - } catch(e) { - if (self._debug) {console.log(e);} - if (self.callback != null) { - self.callback(e); - } - self.emit("error", e); - } -}; - -DeviceTree.prototype.timeoutRequest = function (id) { - this.activeRequest.func(this.activeRequest.timeoutError); -}; - -DeviceTree.prototype.handleRoot = function (root) { - var self = this; - - if (self._debug) { - console.log("handling root", JSON.stringify(root)); - } - self.root.update(root); - if (root.elements !== undefined) { - const elements = root.getChildren(); - for (var i = 0; i < elements.length; i++) { - if (elements[i].isQualified()) { - this.handleQualifiedNode(this.root, elements[i]); - } - else { - this.handleNode(this.root, elements[i]); - } - } - } - if (self.callback) { - self.callback(null, root); - } -}; - -DeviceTree.prototype.handleQualifiedNode = function (parent, node) { - var self = this; - var element = parent.getElementByPath(node.path); - if (element !== null) { - self.emit("value-change", node); - element.update(node); - } - else { - var path = node.path.split("."); - if (path.length === 1) { - this.root.addChild(node); - } - else { - // Let's try to get the parent - path.pop(); - parent = this.root.getElementByPath(path.join(".")); - if (parent === null) { - return; - } - parent.addChild(node); - parent.update(parent); - } - element = node; - } - - var children = node.getChildren(); - if (children !== null) { - for (var i = 0; i < children.length; i++) { - if (children[i].isQualified()) { - this.handleQualifiedNode(element, children[i]); - } - else { - this.handleNode(element, children[i]); - } - } - } - - return; -}; - -DeviceTree.prototype.handleNode = function (parent, node) { - var self = this; - var n = parent.getElementByNumber(node.getNumber()); - if (n === null) { - parent.addChild(node); - n = node; - } else { - n.update(node); - } - - var children = node.getChildren(); - if (children !== null) { - for (var i = 0; i < children.length; i++) { - this.handleNode(n, children[i]); - } - } - else { - self.emit("value-change", node); - } - return; -}; - -DeviceTree.prototype.getNodeByPathnum = function (path, callback = null) { - var self = this; - if (typeof path === 'string') { - path = path.split('.'); - } - var pathnumError = new Error(`Failed path discovery at ${path.slice(0, pos).join("/")}`); - var pos = 0; - var lastMissingPos = -1; - var currentNode = this.root; - const getNext = () => { - return Promise.resolve() - .then(() => { - const children = currentNode.getChildren(); - const number = Number(path[pos]); - if (children != null) { - for (let i = 0; i < children.length; i++) { - var node = children[i]; - if (node.getNumber() === number) { - // We have this part already. - pos++; - if (pos >= path.length) { - return node; - } - currentNode = node; - return getNext(); - } - } - } - // We do not have that node yet. - if (lastMissingPos === pos) { - throw pathnumError; - } - lastMissingPos = pos; - return this.getDirectory(currentNode, callback).then(() => getNext()); - }); - } - return getNext(); -}; - -DeviceTree.prototype.getNodeByPath = function (path, callback = null) { - var self = this; - if (typeof path === 'string') { - path = path.split('/'); - } - var pathError = new Error(`Failed path discovery at ${path.slice(0, pos + 1).join("/")}`); - var pos = 0; - var lastMissingPos = -1; - var currentNode = this.root; - const getNext = () => { - return Promise.resolve() - .then(() => { - const children = currentNode.getChildren(); - const identifier = path[pos]; - if (children != null) { - for (let i = 0; i < children.length; i++) { - var node = children[i]; - if (node.contents != null && node.contents.identifier === identifier) { - // We have this part already. - pos++; - if (pos >= path.length) { - return node; - } - currentNode = node; - return getNext(); - } - } - } - // We do not have that node yet. - if (lastMissingPos === pos) { - throw pathError; - } - lastMissingPos = pos; - return this.getDirectory(currentNode, callback).then(() => getNext()); - }); - } - return getNext(); -}; - -DeviceTree.prototype.subscribe = function (qnode, callback) { - if ((qnode.isParameter() || qnode.isMatrix()) && qnode.isStream()) { - var self = this; - if (qnode == null) { - self.root.clear(); - qnode = self.root; - } - return new Promise((resolve, reject) => { - self.addRequest({node: qnode, func: (error) => { - if (self._debug) { - console.log("Sending subscribe", qnode); - } - self.client.sendBERNode(qnode.subscribe(callback)); - self.finishRequest(); - resolve(); - }}); - }); - } else { - node.addCallback(callback); - } -}; - -DeviceTree.prototype.unsubscribe = function (qnode, callback) { - if (qnode.isParameter() && qnode.isStream()) { - var self = this; - if (qnode == null) { - self.root.clear(); - qnode = self.root; - } - return new Promise((resolve, reject) => { - self.addRequest({node: qnode, func: (error) => { - if (self._debug) { - console.log("Sending subscribe", qnode); - } - self.client.sendBERNode(qnode.unsubscribe(callback)); - self.finishRequest(); - resolve(); - }}); - }); - } -}; - -DeviceTree.prototype.setValue = function (node, value) { - var self = this; - return new Promise((resolve, reject) => { - if ((!(node instanceof ember.Parameter)) && - (!(node instanceof ember.QualifiedParameter))) { - reject(new errors.EmberAccessError('not a property')); - } - else { - // if (this._debug) { console.log('setValue', node.getPath(), value); } - self.addRequest({node: node, func: (error) => { - if (error) { - self.finishRequest(); - reject(error); - return; - } - - let cb = (error, node) => { - //console.log('setValue complete...', node.getPath(), value); - self.finishRequest(); - if (error) { - reject(error); - } - else { - resolve(node); - } - }; - - self.callback = cb; - if (this._debug) { - console.log('setValue sending ...', node.getPath(), value); - } - self.client.sendBERNode(node.setValue(value)); - }}); - } - }); -}; - -function TreePath(path) { - this.identifiers = []; - this.numbers = []; - - if (path !== undefined) { - for (var i = 0; i < path.length; i++) { - if (Number.isInteger(path[i])) { - this.numbers.push(path[i]); - this.identifiers.push(null); - } else { - this.identifiers.push(path[i]); - this.numbers.push(null); - } - } - } -} - - -module.exports = { DeviceTree, DecodeBuffer }; diff --git a/ember.js b/ember.js deleted file mode 100755 index 1ce667b..0000000 --- a/ember.js +++ /dev/null @@ -1,3021 +0,0 @@ -const BER = require('./ber.js'); -const errors = require('./errors.js'); -const util = require('util'); -const Enum = require('enum'); - -const COMMAND_SUBSCRIBE = 30; -const COMMAND_UNSUBSCRIBE = 31; -const COMMAND_GETDIRECTORY = 32; -const COMMAND_INVOKE = 33; -module.exports.Subscribe = COMMAND_SUBSCRIBE; -module.exports.Unsubscribe = COMMAND_UNSUBSCRIBE; -module.exports.GetDirectory = COMMAND_GETDIRECTORY; -module.exports.Invoke = COMMAND_INVOKE; - -DEBUG = false; - -module.exports.DEBUG = function(d) { - DEBUG = d; -}; - -/**************************************************************************** - * Elements - */ - - function Elements() { - Elements.super_.call(this); - } - -/**************************************************************************** - * Root - ***************************************************************************/ - -function Root() { - Root.super_.call(this); -}; - -util.inherits(Root, TreeNode); - - -Root.decode = function(ber) { - let r = new Root(); - let tag = undefined; - - while(ber.remain > 0) { - if (DEBUG) { console.log("Reading root"); } - tag = ber.peek(); - if (tag === BER.APPLICATION(0)) { - ber = ber.getSequence(BER.APPLICATION(0)); - tag = ber.peek(); - if (DEBUG) { - console.log("Application 0 start"); - } - - if (tag === BER.APPLICATION(11)) { - if (DEBUG) { - console.log("Application 11 start"); - } - var seq = ber.getSequence(BER.APPLICATION(11)); - while (seq.remain > 0) { - try { - var rootReader = seq.getSequence(BER.CONTEXT(0)); - while (rootReader.remain > 0) { - r.addElement(RootElement.decode(rootReader)); - } - } - catch (e) { - if (DEBUG) { - console.log("Decode ERROR", e.stack); - return r; - } - throw e; - } - } - } - else if (tag === BER.APPLICATION(23)) { // InvocationResult BER.APPLICATION(23) - return InvocationResult.decode(ber) - } - else { - // StreamCollection BER.APPLICATION(6) - // InvocationResult BER.APPLICATION(23) - throw new errors.UnimplementedEmberTypeError(tag); - } - } - else if (tag === BER.CONTEXT(0)) { - // continuation of previous message - try { - var rootReader = ber.getSequence(BER.CONTEXT(0)); - return RootElement.decode(rootReader) - } - catch (e) { - console.log(e.stack); - return r; - } - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return r; -} - -function addElement(parent, element) { - element._parent = parent; - if(parent.elements == null) { - parent.elements = new Map(); - } - parent.elements.set(element.getNumber(), element); -} - -Root.prototype.addElement = function(ele) { - addElement(this, ele); -} - -Root.prototype.addResult = function(result) { - this.result = result; -} - -Root.prototype.addChild = function(child) { - this.addElement(child); -} - -Root.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(0)); - if(this.elements != null) { - const elements = this.getChildren(); - ber.startSequence(BER.APPLICATION(11)); - for(var i=0; i < elements.length; i++) { - ber.startSequence(BER.CONTEXT(0)); - elements[i].encode(ber); - ber.endSequence(); // BER.CONTEXT(0) - } - ber.endSequence(); - } - if (this.result != null) { - this.result.encode(ber); - } - ber.endSequence(); // BER.APPLICATION(0) -} - -Root.prototype.clear = function() { - this.elements = undefined; -} - -Root.prototype.getPath = function() { - throw new Error("old lib"); -} - -Root.prototype.getChildren = function() { - if(this.elements != null) { - return [...this.elements.values()]; - } - return null; -} - -module.exports.Root = Root; - -/**************************************************************************** - * TreeNode (abstract) - ***************************************************************************/ - -function TreeNode() { - Object.defineProperty(this, '_parent', {value: null, enumerable: false, writable: true}); -} - -TreeNode.prototype.addChild = function(child) { - addElement(this, child); -} - -TreeNode.prototype.isCommand = function() { - return this instanceof Command; -} - -TreeNode.prototype.isNode = function() { - return ((this instanceof Node) || (this instanceof QualifiedNode)); -} - -TreeNode.prototype.isMatrix = function() { - return ((this instanceof MatrixNode) || (this instanceof QualifiedMatrix)); -} - -TreeNode.prototype.isParameter = function() { - return ((this instanceof Parameter) || (this instanceof QualifiedParameter)); -} - -TreeNode.prototype.isFunction = function() { - return ((this instanceof QualifiedFunction) || (this instanceof Function)); -} - -TreeNode.prototype.isRoot = function() { - return this._parent == null; -} - -TreeNode.prototype.isQualified = function() { - return ((this instanceof QualifiedParameter)|| - (this instanceof QualifiedNode) || - (this instanceof QualifiedMatrix) || - (this instanceof QualifiedFunction)); -} - -TreeNode.prototype.isStream = function() { - return this.contents != null && - this.contents.streamIdentifier != null; -} - -TreeNode.prototype.getMinimalContent = function() { - let obj; - if (this.isQualified()) { - obj = new this.constructor(this.path); - } - else { - obj = new this.constructor(this.number); - } - if (this.contents !== undefined) { - obj.contents= this.contents; - } - return obj; -}; - -TreeNode.prototype.getDuplicate = function() { - let obj = this.getMinimal(); - obj.update(this); - return obj; -} - -TreeNode.prototype.getMinimal = function() { - if (this.isQualified()) { - return new this.constructor(this.path); - } - else { - return new this.constructor(this.number); - } -} - -TreeNode.prototype.getTreeBranch = function(child, modifier) { - var m = this.getMinimal(); - if(child !== undefined) { - m.addChild(child); - } - - if(modifier !== undefined) { - modifier(m); - } - - if(this._parent === null) { - return m; - } - else { - var p = this._parent.getTreeBranch(m); - return p; - } -} - -TreeNode.prototype.getRoot = function() { - if(this._parent === null) { - return this; - } else { - return this._parent.getRoot(); - } -} - -TreeNode.prototype.getDirectory = function(callback) { - if (callback != null && this.contents != null && !this.isStream()) { - this.contents._subscribers.add(callback); - } - return this.getTreeBranch(new Command(COMMAND_GETDIRECTORY)); -} - -TreeNode.prototype.subscribe = function(callback) { - if (callback != null && this.isParameter() && this.isStream()) { - this.contents._subscribers.add(callback); - } - return this.getTreeBranch(new Command(COMMAND_SUBSCRIBE)); -} - -TreeNode.prototype.unsubscribe = function(callback) { - if (callback != null && this.isParameter() && this.isStream()) { - this.contents._subscribers.delete(callback); - } - return this.getTreeBranch(new Command(COMMAND_UNSUBSCRIBE)); -} - -TreeNode.prototype.getChildren = function() { - if(this.children !== undefined) { - return this.children; - } - return null; -} - -function path2number(path) { - try { - let numbers = path.split("."); - if (numbers.length > 0) { - return Number(numbers[numbers.length - 1]); - } - } - catch(e) { - // ignore - } -} - -TreeNode.prototype.getNumber = function() { - if (this.isQualified()) { - return path2number(this.getPath()); - } - else { - return this.number; - } -} - -_getElementByPath = function(children, pathArray, path) { - if ((children === null)||(children === undefined)||(pathArray.length < 1)) { - return null; - } - var number = pathArray[pathArray.length - 1]; - - if (children[i].getNumber() == number) { - if (path.length === 0) { - return children[i]; - } - pathArray.push(path.splice(0,1)); - return _getElementByPath(children[i].getChildren(), pathArray, path); - } - - return null; -} - -TreeNode.prototype.toJSON = function() { - let res = {}; - const node = this; - if (!this.isRoot()) { - res.number = node.getNumber(); - res.path = node.getPath(); - } - if (node.contents) { - for(let prop in node.contents) { - if (node.contents.hasOwnProperty(prop)) { - let type = typeof node.contents[prop]; - if ((type === "string") || (type === "number")) { - res[prop] = node.contents[prop]; - } - else if (node.contents[prop].value !== undefined) { - res[prop] = node.contents[prop].value; - } - else { - res[prop] = node.contents[prop]; - } - } - } - } - if (node.isMatrix()) { - if (node.targets) { - res.targets = node.targets.slice(0); - } - if (node.sources) { - res.sources = node.sources.slice(0); - } - if (node.connections) { - res.connections = {}; - for (let target in node.connections) { - if (node.connections.hasOwnProperty(target)) { - res.connections[target] = {target: target, sources: []}; - if (node.connections[target].sources) { - res.connections[target].sources = node.connections[target].sources.slice(0); - } - } - } - - } - } - let children = node.getChildren(); - if (children) { - res.children = []; - for(let child of children) { - res.children.push(child.toJSON()); - } - } - return res; -}; - -TreeNode.prototype.getParent = function() { - return this._parent; -} - -TreeNode.prototype.getElementByPath = function(path) { - var children = this.getChildren(); - if (children == null) { - return null; - } - - var myPath = this.getPath(); - if (path == myPath) { - return this; - } - var myPathArray = []; - if (this._parent) { - myPathArray = myPath.split("."); - } - path = path.split("."); - - if (path.length > myPathArray.length) { - pathArray = path.splice(0, myPath.length + 1); - for(var i = 0; i < pathArray.length - 1; i++) { - if (pathArray[i] != myPathArray[i]) { - return null; - } - } - } - else { - return null; - } - return _getElementByPath(children, pathArray, path); -} - -TreeNode.prototype.getElementByNumber = function(number) { - if (this.elements != null) { - return this.elements.get(number); - } - return null; -} - -TreeNode.prototype.getElementByIdentifier = function(identifier) { - var children = this.getChildren(); - if (children == null) return null; - for(var i = 0; i < children.length; i++) { - if(children[i].contents !== undefined && - children[i].contents.identifier == identifier) { - return children[i]; - } - } - return null; -} - -TreeNode.prototype.getElement = function(id) { - if(Number.isInteger(id)) { - return this.getElementByNumber(id); - } else { - return this.getElementByIdentifier(id); - } -} - -TreeNode.prototype.update = function(other) { - return; -} - -TreeNode.prototype.getNodeByPath = function(client, path, callback) { - var self=this; - - if(path.length == 0) { - callback(null, self); - return; - } - - var child = self.getElement(path[0]); - if(child !== null) { - child.getNodeByPath(client, path.slice(1), callback); - } else { - var cmd = self.getDirectory((error, node) => { - if(error) { - callback(error); - } - child = node.getElement(path[0]); - if(child === null) { - //let e = new Error('invalid path: "' + path[0] + '"'); - // console.log(e.message ,"Self:", self, "Node:", node); - //callback(e); - //DO NOT REJECT !!!! We could still be updating the tree. - return; - } else { - child.getNodeByPath(client, path.slice(1), callback); - } - }); - if(cmd !== null) { - client.sendBERNode(cmd); - } - } -} - -TreeNode.prototype.getPath = function() { - if (this.path !== undefined) { - return this.path; - } - if(this._parent == null) { - if(this.number === undefined) { - return ""; - } else { - return this.number.toString(); - } - } else { - var path = this._parent.getPath(); - if(path.length > 0) { - path = path + "."; - } - return path + this.number; - } -} - -/**************************************************************************** - * RootElement - ***************************************************************************/ - -function RootElement() {}; - -RootElement.decode = function(ber) { - return Element.decode(ber); - - // TODO: handle qualified types -} - -/**************************************************************************** - * Element - ***************************************************************************/ - -function Element() {}; - -Element.decode = function(ber) { - var tag = ber.peek(); - if(tag == BER.APPLICATION(1)) { - if (DEBUG) { console.log("Parameter decode");} - return Parameter.decode(ber); - } else if(tag == BER.APPLICATION(3)) { - if (DEBUG) { console.log("Node decode");} - return Node.decode(ber); - } else if(tag == BER.APPLICATION(2)) { - if (DEBUG) { console.log("Command decode");} - return Command.decode(ber); - } else if(tag == BER.APPLICATION(9)) { - if (DEBUG) { console.log("QualifiedParameter decode");} - return QualifiedParameter.decode(ber); - } else if(tag == BER.APPLICATION(10)) { - if (DEBUG) { console.log("QualifiedNode decode");} - return QualifiedNode.decode(ber); - } else if(tag == BER.APPLICATION(13)) { - if (DEBUG) { console.log("MatrixNode decode");} - return MatrixNode.decode(ber); - } - else if(tag == BER.APPLICATION(17)) { - if (DEBUG) { console.log("QualifiedMatrix decode");} - return QualifiedMatrix.decode(ber); - } - else if(tag == BER.APPLICATION(19)) { - if (DEBUG) { console.log("Function decode");} - return Function.decode(ber); - } else if (tag == BER.APPLICATION(20)) { - if (DEBUG) { console.log("QualifiedFunction decode");} - return QualifiedFunction.decode(ber); - } - else if(tag == BER.APPLICATION(24)) { - // Template - throw new errors.UnimplementedEmberTypeError(tag); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } -} - -/**************************************************************************** - * ElementCollection - ***************************************************************************/ - - - -/**************************************************************************** - * QualifiedNode - ***************************************************************************/ - -function QualifiedNode(path) { - QualifiedNode.super_.call(this); - if (path != undefined) { - this.path = path; - } -} - -util.inherits(QualifiedNode, TreeNode); - - -QualifiedNode.decode = function(ber) { - var qn = new QualifiedNode(); - ber = ber.getSequence(BER.APPLICATION(10)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - qn.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else if(tag == BER.CONTEXT(1)) { - qn.contents = NodeContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - qn.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - qn.addChild(Element.decode(nodeSeq)); - } - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("QualifiedNode", qn); } - return qn; -} - -QualifiedNode.prototype.getMinimal = function(complete = false) { - const number = this.getNumber(); - const n = new Node(number); - if (complete && (this.contents != null)) { - n.contents = this.contents; - } - return n; -} - -QualifiedNode.prototype.update = function(other) { - QualifiedNode.super_.prototype.update.apply(this); - if((other !== undefined) && (other.contents !== undefined)) { - if (this.contents == null) { - this.contents = other.contents; - } - else { - for(var key in other.contents) { - if(other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; - } - } - } - } - return; -} - -function QualifiedNodeCommand(self, cmd) { - var r = new Root(); - var qn = new QualifiedNode(); - qn.path = self.path; - r.addElement(qn); - qn.addChild(new Command(cmd)); - return r; -} - -QualifiedNode.prototype.getDirectory = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - if (callback != null && this.contents != null && !this.isStream()) { - this.contents._subscribers.add(callback); - } - return QualifiedNodeCommand(this, COMMAND_GETDIRECTORY) -} - -QualifiedNode.prototype.subscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - if (callback != null && this.isStream()) { - this.contents._subscribers.add(callback); - } - return QualifiedNodeCommand(this, COMMAND_SUBSCRIBE) -} - -QualifiedNode.prototype.unsubscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - if (callback != null && this.isStream()) { - this.contents._subscribers.delete(callback); - } - return QualifiedNodeCommand(this, COMMAND_UNSUBSCRIBE) -} - -QualifiedNode.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(10)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - n.number = seq.readInt(); - } else if(tag == BER.CONTEXT(1)) { - n.contents = NodeContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - seq = seq.getSequence(BER.APPLICATION(4)); - n.children = []; - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - n.addChild(Element.decode(nodeSeq)); - } - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("Node", n); } - return n; -} - -Node.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(3)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i Number(i))); - - if (matrixNode.connections[targetID].isLocked()) { - return false; - } - if (type === MatrixType.oneToN && - matrixNode.contents.maximumConnectsPerTarget == null && - matrixNode.contents.maximumConnectsPerTarget == null) { - return sMap.size < 2; - } - else if (type === MatrixType.oneToN && sMap.size >= 2) { - return false; - } - else if (type === MatrixType.oneToOne) { - if (sMap.size > 1) { - return false; - } - const sourceConnections = matrixNode._connectedSources[sources[0]]; - return sourceConnections == null || sourceConnections.size === 0 || sourceConnections.has(targetID); - } - else { - // N to N - if (matrixNode.contents.maximumConnectsPerTarget != null && - newSources.length > matrixNode.contents.maximumConnectsPerTarget) { - return false; - } - if (matrixNode.contents.maximumTotalConnects != null) { - let count = matrixNode._numConnections; - if (oldSources) { - count -= oldSources.length; - } - if (newSources) { - count += newSources.length; - } - return count <= matrixNode.contents.maximumTotalConnects; - } - - } - return true; -} - -function validateConnection(matrixNode, targetID, sources) { - if (targetID < 0) { - throw new Error(`Invalid negative target index ${targetID}`); - } - for(let i = 0; i < sources.length; i++) { - if (sources[i] < 0) { - throw new Error(`Invalid negative source at index ${i}`); - } - } - if (matrixNode.contents.mode === MatrixMode.linear) { - if (targetID >= matrixNode.contents.targetCount) { - throw new Error(`targetID ${targetID} higher than max value ${matrixNode.contents.targetCount}`); - } - for(let i = 0; i < sources.length; i++) { - if (sources[i] >= matrixNode.contents.sourceCount) { - throw new Error(`Invalid source at index ${i}`); - } - } - } - else if ((matrixNode.targets == null) || (matrixNode.sources == null)) { - throw new Error("Non-Linear matrix should have targets and sources"); - } - else { - let found = false; - for(let i = 0; i < matrixNode.targets; i++) { - if (matrixNode.targets[i] === targetID) { - found = true; - break; - } - } - if (!found) { - throw new Error(`Unknown targetid ${targetID}`); - } - found = false; - for(let i = 0; i < sources.length; i++) { - for(let j = 0; i < matrixNode.sources; j++) { - if (matrixNode.sources[j] === sources[i]) { - found = true; - break; - } - } - if (!found) { - throw new Error(`Unknown source at index ${i}`); - } - } - } -} - - -MatrixNode.decode = function(ber) { - var m = new MatrixNode(); - ber = ber.getSequence(BER.APPLICATION(13)); - while (ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if (tag == BER.CONTEXT(0)) { - m.number = seq.readInt(); - } - else if (tag == BER.CONTEXT(1)) { - m.contents = MatrixContents.decode(seq); - - } else if (tag == BER.CONTEXT(2)) { - m.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while (seq.remain > 0) { - var childSeq = seq.getSequence(BER.CONTEXT(0)); - m.addChild(Element.decode(childSeq)); - } - } else if (tag == BER.CONTEXT(3)) { - m.targets = decodeTargets(seq); - } else if (tag == BER.CONTEXT(4)) { - m.sources = decodeSources(seq); - } else if (tag == BER.CONTEXT(5)) { - m.connections = decodeConnections(seq); - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("MatrixNode", m); } - return m; -}; - -MatrixNode.prototype.validateConnection = function(targetID, sources) { - validateConnection(this, targetID, sources); -} - -MatrixNode.prototype.canConnect = function(targetID, sources, operation) { - return canConnect(this, targetID, sources, operation); -} - -MatrixNode.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(13)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - MatrixNode.disconnectSources(matrix, targetID, currentSource) - } - MatrixNode.connectSources(matrix, targetID, sources); -} - -MatrixNode.connectSources = function(matrix, targetID, sources) { - const target = Number(targetID); - if (matrix.connections[target] == null) { - matrix.connections[target] = new MatrixConnection(target); - } - matrix.connections[target].connectSources(sources); - if (sources != null) { - for(let source of sources) { - if (matrix._connectedSources[source] == null) { - matrix._connectedSources[source] = new Set(); - } - if (!matrix._connectedSources[source].has(target)) { - matrix._connectedSources[source].add(target); - matrix._numConnections++; - } - } - } -} - -MatrixNode.disconnectSources = function(matrix, targetID, sources) { - const target = Number(targetID); - if (matrix.connections[target] == null) { - matrix.connections[target] = new MatrixConnection(target); - } - matrix.connections[target].disconnectSources(sources); - if (sources != null) { - for(let source of sources) { - if (matrix._connectedSources[source] == null) { - continue; - } - if (matrix._connectedSources[source].has(target)) { - matrix._connectedSources[source].delete(target); - matrix._numConnections--; - } - } - } -} - -MatrixNode.prototype.update = function(other) { - MatrixNode.super_.prototype.update.apply(this); - MatrixUpdate(this, other); - return; -} - -MatrixNode.prototype.toQualified = function() { - let qm = new QualifiedMatrix(this.getPath()); - qm.update(this); - return qm; -} - -MatrixNode.prototype.connect = function(connections) { - let r = this.getTreeBranch(); - let m = r.getElementByPath(this.getPath()); - m.connections = connections; - return r; -} - -util.inherits(MatrixNode, TreeNode); - -module.exports.MatrixNode = MatrixNode; - -function MatrixContents() { - this.type = MatrixType.oneToN; - this.mode = MatrixMode.linear; -} - -MatrixContents.decode = function(ber) { - var mc = new MatrixContents(); - - ber = ber.getSequence(BER.EMBER_SET); - - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - - if(tag == BER.CONTEXT(0)) { - mc.identifier = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - mc.description = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(2)) { - mc.type = MatrixType.get(seq.readInt()); - } else if(tag == BER.CONTEXT(3)) { - mc.mode = MatrixMode.get(seq.readInt()); - } else if(tag == BER.CONTEXT(4)) { - mc.targetCount = seq.readInt(); - } else if(tag == BER.CONTEXT(5)) { - mc.sourceCount = seq.readInt(); - } else if(tag == BER.CONTEXT(6)) { - mc.maximumTotalConnects = seq.readInt(); - } else if(tag == BER.CONTEXT(7)) { - mc.maximumConnectsPerTarget = seq.readInt(); - } else if(tag == BER.CONTEXT(8)) { - tag = seq.peek(); - if (tag === BER.EMBER_RELATIVE_OID) { - mc.parametersLocation = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else { - mc.parametersLocation = seq.readInt(); - } - } else if(tag == BER.CONTEXT(9)) { - mc.gainParameterNumber = seq.readInt(); - } else if(tag == BER.CONTEXT(10)) { - mc.labels = []; - seq = seq.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - var lSeq = seq.getSequence(BER.CONTEXT(0)); - mc.labels.push(Label.decode(lSeq)); - } - } else if(tag == BER.CONTEXT(11)) { - mc.schemaIdentifiers = seq.readInt(); - } else if(tag == BER.CONTEXT(12)) { - mc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return mc; -}; - -MatrixContents.prototype.encode = function(ber) { - ber.startSequence(BER.EMBER_SET); - if (this.identifier !== undefined) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeString(this.identifier, BER.EMBER_STRING); - ber.endSequence(); - } - if (this.description !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); - } - if (this.type !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.writeInt(this.type.value); - ber.endSequence(); - } - if (this.mode !== undefined) { - ber.startSequence(BER.CONTEXT(3)); - ber.writeInt(this.mode.value); - ber.endSequence(); - } - if (this.targetCount !== undefined) { - ber.startSequence(BER.CONTEXT(4)); - ber.writeInt(this.targetCount); - ber.endSequence(); - } - if (this.sourceCount !== undefined) { - ber.startSequence(BER.CONTEXT(5)); - ber.writeInt(this.sourceCount); - ber.endSequence(); - } - if (this.maximumTotalConnects !== undefined) { - ber.startSequence(BER.CONTEXT(6)); - ber.writeInt(this.maximumTotalConnects); - ber.endSequence(); - } - if (this.maximumConnectsPerTarget !== undefined) { - ber.startSequence(BER.CONTEXT(7)); - ber.writeInt(this.maximumConnectsPerTarget); - ber.endSequence(); - } - if (this.parametersLocation !== undefined) { - ber.startSequence(BER.CONTEXT(8)); - let param = Number(this.parametersLocation) - if (isNaN(param)) { - ber.writeRelativeOID(this.parametersLocation, BER.EMBER_RELATIVE_OID); - } - else { - ber.writeInt(param); - } - ber.endSequence(); - } - if (this.gainParameterNumber !== undefined) { - ber.startSequence(BER.CONTEXT(9)); - ber.writeInt(this.gainParameterNumber); - ber.endSequence(); - } - if (this.labels !== undefined) { - ber.startSequence(BER.CONTEXT(10)); - ber.startSequence(BER.EMBER_SEQUENCE); - for(var i =0; i < this.labels.length; i++) { - ber.startSequence(BER.CONTEXT(0)); - this.labels[i].encode(ber); - ber.endSequence(); - } - ber.endSequence(); - ber.endSequence(); - } - if (this.schemaIdentifiers !== undefined) { - ber.startSequence(BER.CONTEXT(11)); - ber.writeInt(this.schemaIdentifiers, BER.EMBER_STRING); - ber.endSequence(); - } - if (this.templateReference !== undefined) { - ber.startSequence(BER.CONTEXT(12)); - ber.writeRelativeOID(this.templateReference, BER.EMBER_RELATIVE_OID); - ber.endSequence(); - } - ber.endSequence(); -} - -decodeTargets = function(ber) { - let targets = []; - - ber = ber.getSequence(BER.EMBER_SEQUENCE); - - - while(ber.remain > 0) { - var seq = ber.getSequence(BER.CONTEXT(0)); - seq = seq.getSequence(BER.APPLICATION(14)); - seq = seq.getSequence(BER.CONTEXT(0)); - targets.push(seq.readInt()); - } - - return targets; -} - -decodeSources = function(ber) { - let sources = []; - - ber = ber.getSequence(BER.EMBER_SEQUENCE); - - while(ber.remain > 0) { - var seq = ber.getSequence(BER.CONTEXT(0)); - seq = seq.getSequence(BER.APPLICATION(15)); - seq = seq.getSequence(BER.CONTEXT(0)); - sources.push(seq.readInt()); - } - - return sources; -}; - -decodeConnections = function(ber) { - let connections = {}; - - let seq = ber.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - var conSeq = seq.getSequence(BER.CONTEXT(0)); - var con = MatrixConnection.decode(conSeq); - if (con.target !== undefined) { - connections[con.target] = (con); - } - } - return connections; -} - -module.exports.MatrixContents = MatrixContents; - - - -function MatrixConnection(target) { - if (target) { - target = Number(target); - if (isNaN(target)) { target = 0; } - this.target = target; - } - else { - this.target = 0; - } - this._locked = false; -} - -// ConnectionOperation ::= -// INTEGER { -// absolute (0), -- default. sources contains absolute information -// connect (1), -- nToN only. sources contains sources to add to connection -// disconnect (2) -- nToN only. sources contains sources to remove from -// connection -// } -var MatrixOperation = new Enum({ - absolute: 0, - connect: 1, - disconnect: 2 -}); - -// ConnectionDisposition ::= -// INTEGER { -// tally (0), -- default -// modified (1), -- sources contains new current state -// pending (2), -- sources contains future state -// locked (3) -- error: target locked. sources contains current state -// -- more tbd. -// } -var MatrixDisposition = new Enum({ - tally: 0, - modified: 1, - pending: 2, - locked: 3 -}); - -module.exports.MatrixOperation = MatrixOperation; -module.exports.MatrixDisposition = MatrixDisposition; - -MatrixConnection.prototype.setSources = function(sources) { - if (sources === undefined) { - delete this.sources; - return; - } - let s = new Set(sources.map(i => Number(i))); - this.sources = [...s].sort(); // sources should be an array -} - -MatrixConnection.prototype.connectSources = function(sources) { - if (sources === undefined) { - return; - } - let s = new Set(this.sources); - for(let item of sources) { - s.add(item); - } - this.sources = [...s].sort(); -} - -MatrixConnection.prototype.lock = function() { - this._locked = true; -} -MatrixConnection.prototype.unlock = function() { - this._locked = false; -} -MatrixConnection.prototype.isLocked = function() { - return this._locked; -} - -MatrixConnection.prototype.disconnectSources = function(sources) { - if (sources === undefined) { - return; - } - let s = new Set(this.sources); - for(let item of sources) { - s.delete(item); - } - this.sources = [...s].sort(); -} - -MatrixConnection.decode = function(ber) { - var c = new MatrixConnection(); - ber = ber.getSequence(BER.APPLICATION(16)); - while (ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if (tag == BER.CONTEXT(0)) { - c.target = seq.readInt(); - } - else if (tag == BER.CONTEXT(1)) { - const sources = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); - if (sources === "") { - c .sources = []; - } - else { - c.sources = sources.split(".").map(i => Number(i)); - } - } else if (tag == BER.CONTEXT(2)) { - c.operation = MatrixOperation.get(seq.readInt()); - - } else if (tag == BER.CONTEXT(3)) { - c.disposition = MatrixDisposition.get(seq.readInt()); - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return c; -} - -MatrixConnection.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(16)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.target); - ber.endSequence(); - - if ((this.sources !== undefined)&& (this.sources.length > 0)) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeRelativeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); - ber.endSequence(); - } - if (this.operation !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.writeInt(this.operation.value); - ber.endSequence(); - } - if (this.disposition !== undefined) { - ber.startSequence(BER.CONTEXT(3)); - ber.writeInt(this.disposition.value); - ber.endSequence(); - } - ber.endSequence(); -} - -module.exports.MatrixConnection = MatrixConnection; - -function Label(path, description) { - if (path) { - this.basePath = path; - } - if (description) { - this.description = description; - } -} - -Label.decode = function(ber) { - var l = new Label(); - - ber = ber.getSequence(BER.APPLICATION(18)); - - while (ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if (tag == BER.CONTEXT(0)) { - l.basePath = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); - } else if (tag == BER.CONTEXT(1)) { - l.description = seq.readString(BER.EMBER_STRING); - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return l; -}; - -Label.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(18)); - if (this.basePath != null) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.basePath, BER.EMBER_RELATIVE_OID); - ber.endSequence(); - } - if (this.description != null) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); - } - ber.endSequence(); -} - -module.exports.Label = Label; - - -function ParametersLocation() { -} - -ParametersLocation.decode = function(ber) { - var tag = ber.peek(); - ber = ber.getSequence(tag); - this.value = ber.readValue(); -} - -module.exports.ParametersLocation = ParametersLocation; - - - -var MatrixType = new Enum({ - oneToN: 0, - oneToOne: 1, - nToN: 2 -}); - - -module.exports.MatrixType = MatrixType; - - -var MatrixMode = new Enum({ - linear: 0, - nonLinear: 1 -}); - - -module.exports.MatrixMode = MatrixMode; - - -/**************************************************************************** - * QualifiedMatrix - ***************************************************************************/ - -function QualifiedMatrix(path) { - QualifiedMatrix.super_.call(this); - if (path != undefined) { - this.path = path; - } - this._connectedSources = {}; - this._numConnections = 0; -} - -util.inherits(QualifiedMatrix, TreeNode); - -QualifiedMatrix.prototype.getMinimal = function(complete = false) { - const number = this.getNumber(); - const m = new MatrixNode(number); - if (complete) { - if (this.contents != null) { - m.contents = this.contents; - } - if (this.targets != null) { - m.targets = this.targets; - } - if (this.sources != null) { - m.sources = this.sources; - } - if (this.connections != null) { - m.connections = this.connections; - } - } - return m; -} - -QualifiedMatrix.prototype.validateConnection = function(targetID, sources) { - validateConnection(this, targetID, sources); -} - -QualifiedMatrix.prototype.canConnect = function(targetID, sources, operation) { - return canConnect(this, targetID, sources, operation); -} - -QualifiedMatrix.prototype.setSources = function(targetID, sources) { - return MatrixNode.setSources(this, targetID, sources); -} -QualifiedMatrix.prototype.connectSources = function(targetID, sources) { - return MatrixNode.connectSources(this, targetID, sources); -} -QualifiedMatrix.prototype.disconnectSources = function(targetID, sources) { - return MatrixNode.disconnectSources(this, targetID, sources); -} -QualifiedMatrix.prototype.getSourceConnections = function(source) { - return MatrixNode.getSourceConnections(this, source); -} - -QualifiedMatrix.decode = function(ber) { - var qm = new QualifiedMatrix(); - ber = ber.getSequence(BER.APPLICATION(17)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - qm.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else if(tag == BER.CONTEXT(1)) { - qm.contents = MatrixContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - qm.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - qm.addChild(Element.decode(nodeSeq)); - } - } else if (tag == BER.CONTEXT(3)) { - qm.targets = decodeTargets(seq); - } else if (tag == BER.CONTEXT(4)) { - qm.sources = decodeSources(seq); - } else if (tag == BER.CONTEXT(5)) { - qm.connections = {}; - seq = seq.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - var conSeq = seq.getSequence(BER.CONTEXT(0)); - var con = MatrixConnection.decode(conSeq); - if (con.target !== undefined) { - qm.connections[con.target] = con; - } - } - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("QualifiedMatrix", qm); } - return qm; -} - -function MatrixUpdate(matrix, newMatrix) { - if (newMatrix != null) { - if (newMatrix.contents != null) { - if (matrix.contents == null) { - matrix.contents = newMatrix.contents; - } - else { - for (var key in newMatrix.contents) { - if (newMatrix.contents.hasOwnProperty(key)) { - matrix.contents[key] = newMatrix.contents[key]; - } - } - } - } - if (newMatrix.targets != null) { - matrix.targets = newMatrix.targets; - } - if (newMatrix.sources != null) { - matrix.sources = newMatrix.sources; - } - if (newMatrix.connections != null) { - if (matrix.connections == null) { - matrix.connections = {}; - } - for(let id in newMatrix.connections) { - if (newMatrix.connections.hasOwnProperty(id)) { - let connection = newMatrix.connections[id]; - if ((connection.target < matrix.contents.targetCount) && - (connection.target >= 0)) { - if (matrix.connections[connection.target] == null) { - matrix.connections[connection.target] = new MatrixConnection(connection.target); - } - matrix.connections[connection.target].setSources(connection.sources); - } - } - } - } - } -} - -QualifiedMatrix.prototype.update = function(other) { - QualifiedMatrix.super_.prototype.update.apply(this); - MatrixUpdate(this, other); - return; -} - -function QualifiedMatrixCommand(self, cmd) { - var r = new Root(); - var qn = new QualifiedMatrix(); - qn.path = self.path; - r.addElement(qn); - qn.addChild(new Command(cmd)); - return r; -} - -QualifiedMatrix.prototype.getDirectory = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - if (callback != null && !this.isStream()) { - this.contents._subscribers.add(callback); - } - return QualifiedMatrixCommand(this, COMMAND_GETDIRECTORY); -} - -QualifiedMatrix.prototype.subscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - if (callback != null && this.isStream()) { - this.contents._subscribers.add(callback); - } - return QualifiedMatrixCommand(this, COMMAND_SUBSCRIBE); -} - -QualifiedMatrix.prototype.unsubscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - if (callback != null && this.isStream()) { - this.contents._subscribers.delete(callback); - } - return QualifiedMatrixCommand(this, COMMAND_UNSUBSCRIBE); -} - -QualifiedMatrix.prototype.connect = function(connections) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - var r = new Root(); - var qn = new QualifiedMatrix(); - qn.path = this.path; - r.addElement(qn); - qn.connections = connections; - return r; -} - -QualifiedMatrix.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(17)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - tag = ber.peek(); - var seq = ber.getSequence(tag); - if (tag === BER.CONTEXT(0)) { - tuple.type = ParameterType.get(seq.readInt()); - } - else if (tag === BER.CONTEXT(1)) { - tuple.name = seq.readString(BER.EMBER_STRING); - } - } - return tuple; -} - -module.exports.FunctionArgument = FunctionArgument; - -/**************************************************************************** - * FunctionContent - ***************************************************************************/ - -function FunctionContent() { - this.arguments = []; - this.result = []; -} - - -FunctionContent.decode = function(ber) { - var fc = new FunctionContent(); - ber = ber.getSequence(BER.EMBER_SET); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - fc.identifier = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - fc.description = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(2)) { - fc.arguments = []; - var dataSeq = seq.getSequence(BER.EMBER_SEQUENCE); - while(dataSeq.remain > 0) { - seq = dataSeq.getSequence(BER.CONTEXT(0)); - fc.arguments.push(FunctionArgument.decode(seq)); - } - } else if(tag == BER.CONTEXT(3)) { - fc.result = []; - while(seq.remain > 0) { - tag = seq.peek(); - var dataSeq = seq.getSequence(tag); - if (tag === BER.CONTEXT(0)) { - fc.result.push(FunctionArgument.decode(dataSeq)); - } - } - } else if(tag == BER.CONTEXT(4)) { - fc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return fc; -} - -FunctionContent.prototype.encode = function(ber) { - ber.startSequence(BER.EMBER_SET); - - if(this.identifier != null) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeString(this.identifier, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(0) - } - - if(this.description != null) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.arguments != null) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.EMBER_SEQUENCE); - for(var i = 0; i < this.arguments.length; i++) { - ber.startSequence(BER.CONTEXT(0)); - this.arguments[i].encode(ber); - ber.endSequence(); - } - ber.endSequence(); - ber.endSequence(); // BER.CONTEXT(2) - } - - if(this.result != null) { - ber.startSequence(BER.CONTEXT(3)); - ber.startSequence(BER.EMBER_SEQUENCE); - for(var i = 0; i < this.result.length; i++) { - ber.startSequence(BER.CONTEXT(0)); - this.result[i].encode(ber); - ber.endSequence(); - } - ber.endSequence(); - ber.endSequence(); // BER.CONTEXT(3) - } - - ber.endSequence(); // BER.EMBER_SET -} - -module.exports.FunctionContent = FunctionContent; - -/**************************************************************************** - * QualifiedFunction - ***************************************************************************/ - -function QualifiedFunction(path, func) { - QualifiedFunction.super_.call(this); - if (path != undefined) { - this.path = path; - } - this.func = func; -} - -util.inherits(QualifiedFunction, TreeNode); - - -QualifiedFunction.decode = function(ber) { - var qf = new QualifiedFunction(); - ber = ber.getSequence(BER.APPLICATION(20)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - qf.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else if(tag == BER.CONTEXT(1)) { - qf.contents = FunctionContent.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - qf.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - qf.addChild(Element.decode(nodeSeq)); - } - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return qf; -} - -QualifiedFunction.prototype.update = function(other) { - QualifiedFunction.super_.prototype.update.apply(this); - if ((other !== undefined) && (other.contents !== undefined)) { - if (this.contents == null) { - this.contents = other.contents; - } - else { - for (var key in other.contents) { - if (other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; - } - } - } - } - return; -} - -function QualifiedFunctionCommand(self, cmd) { - var r = new Root(); - var qf = new QualifiedFunction(); - qf.path = self.path; - r.addElement(qf); - qf.addChild(new Command(cmd)); - return r; -} - -QualifiedFunction.prototype.invoke = function(params) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - var QualifiedFunctionNode = QualifiedFunctionCommand(this, COMMAND_INVOKE); - var invocation = new Invocation() - invocation.arguments = params; - QualifiedFunctionNode.getElementByPath(this.getPath()).getNumber(COMMAND_INVOKE).invocation = invocation - return QualifiedFunctionNode -} - -QualifiedFunction.prototype.getDirectory = function() { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedFunctionCommand(this, COMMAND_GETDIRECTORY); -} - -QualifiedFunction.prototype.subscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedFunctionCommand(this, COMMAND_SUBSCRIBE); -} - -QualifiedFunction.prototype.unsubscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedFunctionCommand(this, COMMAND_UNSUBSCRIBE); -} - -QualifiedFunction.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(20)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - f.number = seq.readInt(); - } else if(tag == BER.CONTEXT(1)) { - f.contents = FunctionContent.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - seq = seq.getSequence(BER.APPLICATION(4)); - f.children = []; - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - f.addChild(Element.decode(nodeSeq)); - } - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("Function", f); } - return f; -} - -Function.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(19)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i { - m.addChild(new Command(COMMAND_INVOKE)) - }); -} - -Function.prototype.update = function(other) { - Function.super_.prototype.update.apply(this); - if ((other !== undefined) && (other.contents !== undefined)) { - if (this.contents == null) { - this.contents = other.contents; - } - else { - for (var key in other.contents) { - if (other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; - } - } - } - } - return; -} - -module.exports.Function = Function; - - -/**************************************************************************** - * NodeContents - ***************************************************************************/ - -function NodeContents() { - this.isOnline = true; - this._subscribers = new Set(); -}; - - - -NodeContents.decode = function(ber) { - var nc = new NodeContents(); - ber = ber.getSequence(BER.EMBER_SET); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - nc.identifier = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - nc.description = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(2)) { - nc.isRoot = seq.readBoolean(); - } else if(tag == BER.CONTEXT(3)) { - nc.isOnline = seq.readBoolean(); - } else if(tag == BER.CONTEXT(4)) { - nc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return nc; -} - -NodeContents.prototype.encode = function(ber) { - ber.startSequence(BER.EMBER_SET); - - if(this.identifier !== undefined) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeString(this.identifier, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(0) - } - - if(this.description !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.isRoot !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.writeBoolean(this.isRoot); - ber.endSequence(); // BER.CONTEXT(2) - } - - if(this.isOnline !== undefined) { - ber.startSequence(BER.CONTEXT(3)); - ber.writeBoolean(this.isOnline); - ber.endSequence(); // BER.CONTEXT(3) - } - - if(this.schemaIdentifiers !== undefined) { - ber.startSequence(BER.CONTEXT(4)); - ber.writeString(this.schemaIdentifiers, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(4) - } - - ber.endSequence(); // BER.EMBER_SET -} - -module.exports.NodeContents = NodeContents; - -/**************************************************************************** - * Command - ***************************************************************************/ - -function Command(number) { - if(number !== undefined) - this.number = number; - if(number == COMMAND_GETDIRECTORY) { - this.fieldFlags = FieldFlags.all; - } -} - -var FieldFlags = new Enum({ - sparse: -2, - all: -1, - default: 0, - identifier: 1, - description: 2, - tree: 3, - value: 4, - connections: 5 -}); - -Command.decode = function(ber) { - var c = new Command(); - ber = ber.getSequence(BER.APPLICATION(2)); - - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - c.number = seq.readInt(); - } - else if(tag == BER.CONTEXT(1)) { - c.fieldFlags = FieldFlags.get(seq.readInt()); - } - else if(tag == BER.CONTEXT(2)) { - c.invocation = Invocation.decode(seq); - } - else { - // TODO: options - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return c; -} - -Command.prototype.toJSON = function() { - return { - number: this.number, - fieldFlags: this.fieldFlags, - invocation: this.invocation == null ? null : this.invocation.toJSON() - }; -} - -Command.prototype.isCommand = function() { - throw new Error("old ember lib"); -} - -Command.prototype.getNumber = function() { - return this.number; -} - -Command.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(2)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) - - if (this.number === COMMAND_GETDIRECTORY && this.fieldFlags) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeInt(this.fieldFlags.value); - ber.endSequence(); - } - - if (this.number === COMMAND_INVOKE && this.invocation) { - ber.startSequence(BER.CONTEXT(2)); - this.invocation.encode(ber); - ber.endSequence(); - } - // TODO: options - - ber.endSequence(); // BER.APPLICATION(2) -} - -module.exports.Command = Command; - -/**************************************************************************** - * Invocation - ***************************************************************************/ -function Invocation(id = null) { - this.id = id == null ? Invocation._id++ : id; - this.arguments = []; -} - -Invocation._id = 1 - -Invocation.decode = function(ber) { - let invocation = null; - ber = ber.getSequence(BER.APPLICATION(22)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - const invocationId = seq.readInt(); - invocation = new Invocation(invocationId); - } - else if(tag == BER.CONTEXT(1)) { - if (invocation == null) { - throw new Error("Missing invocationID"); - } - invocation.arguments = []; - seq = seq.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - const dataSeq = seq.getSequence(BER.CONTEXT(0)); - tag = dataSeq.peek(); - const val = dataSeq.readValue(); - invocation.arguments.push( - new FunctionArgument(ParameterTypefromBERTAG(tag), val) - ); - } - } - else { - // TODO: options - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return invocation; -} - - -Invocation.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(22)); - // ber.startSequence(BER.EMBER_SEQUENCE); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.id) - ber.endSequence(); - - ber.startSequence(BER.CONTEXT(1)); - ber.startSequence(BER.EMBER_SEQUENCE); - for(var i = 0; i < this.arguments.length; i++) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeValue( - this.arguments[i].value, - ParameterTypetoBERTAG(this.arguments[i].type - )); - ber.endSequence(); - } - ber.endSequence(); - ber.endSequence(); - - ber.endSequence(); // BER.APPLICATION(22) -} -/**************************************************************************** - * InvocationResult - ***************************************************************************/ - - function InvocationResult(invocationId = null) { - this.invocationId = invocationId; -} - -util.inherits(InvocationResult, TreeNode); -module.exports.InvocationResult = InvocationResult; - -InvocationResult.prototype.setFailure = function() { - this.success = false; -} - -InvocationResult.prototype.setSuccess = function() { - this.success = true; -} - -/** - * @param{} - */ -InvocationResult.prototype.setResult = function(result) { - if (!Array.isArray(result)) { - throw new Error("Invalid inovation result. Should be array"); - } - this.result = result; -} - -InvocationResult.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(23)); - if (this.invocationId != null) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.invocationId); - ber.endSequence(); - } - if (this.success != null) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeBoolean(this.success); - ber.endSequence(); - } - if (this.result != null && this.result.length) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.EMBER_SEQUENCE); - for (let i = 0; i < this.result.length; i++) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeValue(this.result[i].value, ParameterTypetoBERTAG(this.result[i].type)); - ber.endSequence(); - } - ber.endSequence(); - ber.endSequence(); - } - ber.endSequence(); // BER.APPLICATION(23)} -} - -InvocationResult.prototype.toQualified = function() { - return this; -} - -InvocationResult.decode = function(ber) { - let invocationResult = new InvocationResult(); - ber = ber.getSequence(BER.APPLICATION(23)); - while(ber.remain > 0) { - tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { // invocationId - invocationResult.invocationId = seq.readInt(); - } else if(tag == BER.CONTEXT(1)) { // success - invocationResult.success = seq.readBoolean() - }else if(tag == BER.CONTEXT(2)) { - invocationResult.result = []; - let res = seq.getSequence(BER.EMBER_SEQUENCE); - while(res.remain > 0) { - tag = res.peek(); - if (tag === BER.CONTEXT(0)) { - var resTag = res.getSequence(BER.CONTEXT(0)); - tag = resTag.peek(); - invocationResult.result.push( - new FunctionArgument( - ParameterTypefromBERTAG(tag), - resTag.readValue() - )); - } - } - continue - } else { - // TODO: options - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return invocationResult; -} - - - -/**************************************************************************** - * QualifiedParameter - ***************************************************************************/ - -function QualifiedParameter(path) { - QualifiedParameter.super_.call(this); - if(path !== undefined) - this.path = path; -} - -util.inherits(QualifiedParameter, TreeNode); -module.exports.QualifiedParameter = QualifiedParameter; - -QualifiedParameter.prototype.getMinimal = function(complete = false) { - const number = this.getNumber(); - const p = new Parameter(number); - if (complete) { - if (this.contents != null) { - p = this.contents; - } - } - return p; -} - - -QualifiedParameter.decode = function(ber) { - var qp = new QualifiedParameter(); - ber = ber.getSequence(BER.APPLICATION(9)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - qp.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else if(tag == BER.CONTEXT(1)) { - qp.contents = ParameterContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - qp.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - qp.addChild(Element.decode(nodeSeq)); - } - } else { - return qp; - } - } - if (DEBUG) { console.log("QualifiedParameter", qp); } - return qp; -} - -QualifiedParameter.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(9)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - p.number = seq.readInt(); - - } else if(tag == BER.CONTEXT(1)) { - p.contents = ParameterContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - seq = seq.getSequence(BER.APPLICATION(4)); - p.children = []; - while(seq.remain > 0) { - var paramSeq = seq.getSequence(BER.CONTEXT(0)); - p.addChild(Element.decode(paramSeq)); - } - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("Parameter", p); } - return p; -} - -Parameter.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(1)); - - ber.writeIfDefined(this.number, ber.writeInt, 0); - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i { - m.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); - }); -} - -Parameter.prototype.toQualified = function() { - let qp = new QualifiedParameter(this.getPath()); - qp.update(this); - return qp; -} - -Parameter.prototype.update = function(other) { - Parameter.super_.prototype.update.apply(this); - if ((other !== undefined) && (other.contents !== undefined)) { - if (this.contents == null) { - this.contents = other.contents; - } - else { - for (var key in other.contents) { - if (key[0] === "_") { continue; } - if (other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; - } - } - for(let cb of this.contents._subscribers) { - cb(this); - } - } - } - return; -} - - -var ParameterAccess = new Enum({ - none: 0, - read: 1, - write: 2, - readWrite: 3 -}); - - -/* -BER VAlue -Value ::= - CHOICE { - integer Integer64, - real REAL, - string EmberString, - boolean BOOLEAN, - octets OCTET STRING, - null NULL - }*/ - -var ParameterType = new Enum({ - integer: 1, - real: 2, - string: 3, - boolean: 4, - trigger: 5, - enum: 6, - octets: 7 -}); - -function ParameterTypetoBERTAG(type) { - switch (type.value) { - case 1: return BER.EMBER_INTEGER; - case 2: return BER.EMBER_REAL; - case 3: return BER.EMBER_STRING; - case 4: return BER.EMBER_BOOLEAN; - case 7: return BER.EMBER_OCTETSTRING; - default: - throw new Error(`Unhandled ParameterType ${type}`); - } -} - -function ParameterTypefromBERTAG(tag) { - switch (tag) { - case BER.EMBER_INTEGER: return ParameterType.integer; - case BER.EMBER_REAL: return ParameterType.real; - case BER.EMBER_STRING: return ParameterType.string; - case BER.EMBER_BOOLEAN: return ParameterType.boolean; - case BER.EMBER_OCTETSTRING: return ParameterType.octets; - default: - throw new Error(`Unhandled BER TAB ${tag}`); - } -} - -module.exports.ParameterAccess = ParameterAccess; -module.exports.ParameterType = ParameterType; - -function ParameterContents(value, type) { - this._subscribers = new Set(); - if(value !== undefined) { - this.value = value; - } - if(type !== undefined) { - if((type = ParameterType.get(type)) !== undefined){ - this.type = type - } - } -}; - -module.exports.ParameterContents = ParameterContents; - -ParameterContents.decode = function(ber) { - var pc = new ParameterContents(); - ber = ber.getSequence(BER.EMBER_SET); - - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - pc.identifier = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - pc.description = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(2)) { - pc.value = seq.readValue(); - } else if(tag == BER.CONTEXT(3)) { - pc.minimum = seq.readValue(); - } else if(tag == BER.CONTEXT(4)) { - pc.maximum = seq.readValue(); - } else if(tag == BER.CONTEXT(5)) { - pc.access = ParameterAccess.get(seq.readInt()); - } else if(tag == BER.CONTEXT(6)) { - pc.format = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(7)) { - pc.enumeration = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(8)) { - pc.factor = seq.readInt(); - } else if(tag == BER.CONTEXT(9)) { - pc.isOnline = seq.readBoolean(); - } else if(tag == BER.CONTEXT(10)) { - pc.formula = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(11)) { - pc.step = seq.readInt(); - } else if(tag == BER.CONTEXT(12)) { - pc.default = seq.readValue(); - } else if(tag == BER.CONTEXT(13)) { - pc.type = ParameterType.get(seq.readInt()); - } else if(tag == BER.CONTEXT(14)) { - pc.streamIdentifier = seq.readInt(); - } else if(tag == BER.CONTEXT(15)) { - pc.enumMap = StringIntegerCollection.decode(seq); - } else if(tag == BER.CONTEXT(16)) { - pc.streamDescriptor = StreamDescription.decode(seq); - } else if(tag == BER.CONTEXT(17)) { - pc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); - } else if (tag == null) { - break; - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return pc; -} - -ParameterContents.prototype.encode = function(ber) { - ber.startSequence(BER.EMBER_SET); - - ber.writeIfDefined(this.identifier, ber.writeString, 0, BER.EMBER_STRING); - ber.writeIfDefined(this.description, ber.writeString, 1, BER.EMBER_STRING); - ber.writeIfDefined(this.value, ber.writeValue, 2); - ber.writeIfDefined(this.minimum, ber.writeValue, 3); - ber.writeIfDefined(this.maximum, ber.writeValue, 4); - ber.writeIfDefinedEnum(this.access, ParameterAccess, ber.writeInt, 5); - ber.writeIfDefined(this.format, ber.writeString, 6, BER.EMBER_STRING); - ber.writeIfDefined(this.enumeration, ber.writeString, 7, BER.EMBER_STRING); - ber.writeIfDefined(this.factor, ber.writeInt, 8); - ber.writeIfDefined(this.isOnline, ber.writeBoolean, 9); - ber.writeIfDefined(this.formula, ber.writeString, 10, BER.EMBER_STRING); - ber.writeIfDefined(this.step, ber.writeInt, 11); - ber.writeIfDefined(this.default, ber.writeValue, 12); - ber.writeIfDefinedEnum(this.type, ParameterType, ber.writeInt, 13); - ber.writeIfDefined(this.streamIdentifier, ber.writeInt, 14); - - if(this.emumMap !== undefined) { - ber.startSequence(BER.CONTEXT(15)); - StringIntegerCollection.encode(ber, this.enumMap); - ber.endSequence(); - } - - if(this.streamDescriptor !== undefined) { - ber.startSequence(BER.CONTEXT(16)); - this.streamDescriptor.encode(ber); - ber.endSequence(); - } - - ber.writeIfDefined(this.schemaIdentifiers, ber.writeString, 17, BER.EMBER_STRING); - - ber.endSequence(); -} - -/**************************************************************************** - * StringIntegerCollection - ***************************************************************************/ - -// This is untested, VPB doesn't seem to use this that I've seen so far - -function StringIntegerCollection() {}; - -StringIntegerCollection.decode = function(ber) { - var enumMap = {}; - ber = ber.getSequence(BER.APPLICATION(8)); - while(ber.remain > 0) { - var seq = ber.getSequence(BER.CONTEXT(0)); - seq = seq.getSequence(BER.APPLICATION(7)); - var entryString, entryInteger; - while(seq.remain > 0) { - var tag = seq.peek(); - var dataSeq = seq.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - entryString = dataSeq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - entryInteger = dataSeq.readInt(); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - enumMap[entryString] = entryInteger; - } - - return new Enum(enumMap); -} - -StringIntegerCollection.encode = function(ber, e) { - ber.startSequence(BER.APPLICATION(8)); - ber.startSequence(BER.CONTEXT(0)); - e.enums.forEach((item) => { - ber.startSequence(BER.APPLICATION(7)); - ber.startSequence(BER.CONTEXT(0)); - ber.writeString(item.key, BER.EMBER_STRING); - ber.endSequence(); - ber.startSequence(BER.CONTEXT(1)); - ber.writeInt(item.value); - ber.endSequence(); - ber.endSequence(); - }); - ber.endSequence(); - ber.endSequence(); -} - -/**************************************************************************** - * StreamDescription - ***************************************************************************/ - -var StreamFormat = new Enum({ - unsignedInt8: 0, - unsignedInt16BigEndian: 2, - unsignedInt16LittleEndian: 3, - unsignedInt32BigEndian: 4, - unsignedInt32LittleEndian: 5, - unsignedInt64BigEndian: 6, - unsignedInt64LittleENdian: 7, - signedInt8: 8, - signedInt16BigEndian: 10, - signedInt16LittleEndian: 11, - signedInt32BigEndian: 12, - signedInt32LittleEndian: 13, - signedInt64BigEndian: 14, - signedInt64LittleEndian: 15, - ieeeFloat32BigEndian: 20, - ieeeFloat32LittleEndian: 21, - ieeeFloat64BigEndian: 22, - ieeeFloat64LittleEndian: 23 -}); - -function StreamDescription() {}; - -StreamDescription.decode = function(ber) { - var sd = new StreamDescription(); - ber = ber.getSequence(BER.APPLICATION(12)); - - while(ber.remain > 0) { - var tag = ber.peek(); - var seq =ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - sd.format = StreamFormat.get(seq.readInt()); - } else if(tag == BER.CONTEXT(1)) { - sd.offset = seq.readInt(); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return sd; -} - -StreamDescription.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(12)); - - ber.writeIfDefinedEnum(this.format, StreamFormat, ber.writeInt, 0); - ber.writeIfDefined(this.offset, ber.writeInt, 1); - - ber.endSequence(); -} - - diff --git a/index.js b/index.js index 924f192..7b60331 100755 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ -const DeviceTree = require('./device.js').DeviceTree; -const Decoder = require('./device.js').DecodeBuffer; -const Ember = require("./ember.js"); +const EmberClient = require('./EmberClient'); +const EmberLib = require("./EmberLib"); +const Decoder = EmberLib.DecodeBuffer; const S101 = require("./s101"); -const TreeServer = require("./server"); -const {S101Client} = require("./client"); -module.exports = {DeviceTree, Decoder, Ember, TreeServer, S101, S101Client}; +const EmberServer = require("./EmberServer"); +const {S101Client} = require("./EmberSocket"); +module.exports = {EmberClient, Decoder, EmberLib, EmberServer, S101, S101Client}; diff --git a/server.js b/server.js deleted file mode 100755 index 9210a69..0000000 --- a/server.js +++ /dev/null @@ -1,823 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const S101Server = require('./client.js').S101Server; -const ember = require('./ember.js'); - -function TreeServer(host, port, tree) { - TreeServer.super_.call(this); - var self = this; - self._debug = false; - - self.callback = undefined; - self.timeoutValue = 2000; - self.server = new S101Server(host, port); - self.tree = tree; - self.clients = new Set(); - self.subscribers = {}; - - self.server.on('listening', () => { - if (self._debug) { console.log("listening"); } - self.emit('listening'); - if (self.callback !== undefined) { - self.callback(); - self.callback = undefined; - } - }); - - self.server.on('connection', (client) => { - if (self._debug) { console.log("ember new connection from", client.remoteAddress()); } - self.clients.add(client); - client.on("emberTree", (root) => { - if (self._debug) { console.log("ember new request from", client.remoteAddress(), root); } - // Queue the action to make sure responses are sent in order. - client.addRequest(() => { - try { - let path = self.handleRoot(client, root); - self.emit("request", {client: client.remoteAddress(), root: root, path: path}); - } - catch(e) { - if (self._debug) { console.log(e.stack); } - self.emit("error", e); - } - }); - }); - client.on("disconnected", () => { - self.clients.delete(client); - self.emit('disconnect', client.remoteAddress()); - }); - client.on("error", error => { - self.emit('clientError', { remoteAddress: client.remoteAddress(), error }); - }); - self.emit('connection', client.remoteAddress()); - }); - - self.server.on('disconnected', () => { - self.emit('disconnected', client.remoteAddress()); - }); - - self.server.on("error", (e) => { - self.emit("error", e); - if (self.callback !== undefined) { - self.callback(e); - } - }); - -} - -util.inherits(TreeServer, EventEmitter); - - -TreeServer.prototype.listen = function() { - return new Promise((resolve, reject) => { - this.callback = (e) => { - if (e == null) { - return resolve(); - } - return reject(e); - }; - this.server.listen(); - }); -}; - -TreeServer.prototype.close = function () { - return new Promise((resolve, reject) => { - this.callback = (e) => { - if (e == null) { - return resolve(); - } - return reject(e); - }; - this.server.server.close(); - }); -}; - -TreeServer.prototype.handleRoot = function(client, root) { - if ((root == null) || (root.elements == null) || (root.elements.size < 1)) { - // ignore empty requests. - return; - } - - const node = root.getChildren()[0]; - client.request = node; - - if (node.path !== undefined) { - return this.handleQualifiedNode(client, node); - } - else if (node instanceof ember.Command) { - // Command on root element - this.handleCommand(client, this.tree, node); - return "root"; - } - else { - return this.handleNode(client, node); - } -} - -TreeServer.prototype.handleError = function(client, node) { - if (client !== undefined) { - let res = node == null ? this.tree._root.getMinimal() : node; - client.sendBERNode(res); - } -} - - -TreeServer.prototype.handleQualifiedNode = function(client, node) { - const path = node.path; - // Find this element in our tree - const element = this.tree.getElementByPath(path); - - if (element == null) { - this.emit("error", new Error(`unknown element at path ${path}`)); - return this.handleError(client); - } - - if ((node.children != null) && (node.children.length === 1) && - (node.children[0] instanceof ember.Command)) { - this.handleCommand(client, element, node.children[0]); - } - else { - if (node instanceof ember.QualifiedMatrix) { - this.handleQualifiedMatrix(client, element, node); - } - else if (node instanceof ember.QualifiedParameter) { - this.handleQualifiedParameter(client, element, node); - } - } - return path; -} - - -TreeServer.prototype.handleNode = function(client, node) { - // traverse the tree - let element = node; - let path = []; - while(element !== undefined) { - if (element.number == null) { - this.emit("error", "invalid request"); - return; - } - if (element instanceof ember.Command) { - break; - } - path.push(element.number); - - let children = element.getChildren(); - if ((! children) || (children.length === 0)) { - break; - } - element = element.children[0]; - } - let cmd = element; - - if (cmd == null) { - this.emit("error", "invalid request"); - return this.handleError(client); - } - - element = this.tree.getElementByPath(path.join(".")); - - if (element == null) { - this.emit("error", new Error(`unknown element at path ${path}`)); - return this.handleError(client); - } - - if (cmd instanceof ember.Command) { - this.handleCommand(client, element, cmd); - } - else if ((cmd instanceof ember.MatrixNode) && (cmd.connections !== undefined)) { - this.handleMatrixConnections(client, element, cmd.connections); - } - else if ((cmd instanceof ember.Parameter) && - (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { - if (this._debug) { console.log(`setValue for element at path ${path} with value ${cmd.contents.value}`); } - this.setValue(element, cmd.contents.value, client); - let res = this.getResponse(element); - client.sendBERNode(res) - this.updateSubscribers(element.getPath(), res, client); - } - else { - this.emit("error", new Error("invalid request format")); - if (this._debug) { console.log("invalid request format"); } - return this.handleError(client, element.getTreeBranch()); - } - return path; -} - -TreeServer.prototype.handleQualifiedMatrix = function(client, element, matrix) -{ - this.handleMatrixConnections(client, element, matrix.connections); -} - -TreeServer.prototype.handleQualifiedParameter = function(client, element, parameter) -{ - if (parameter.contents.value !== undefined) { - this.setValue(element, parameter.contents.value, client); - let res = this.getQualifiedResponse(element); - client.sendBERNode(res) - this.updateSubscribers(element.getPath(), res, client); - } -} - - -TreeServer.prototype.handleMatrixConnections = function(client, matrix, connections, response = true) { - var res,conResult; - var root; // ember message root - if (this._debug) { - console.log("Handling Matrix Connection"); - } - if (client != null && client.request.isQualified()) { - root = new ember.Root(); - res = new ember.QualifiedMatrix(matrix.getPath()); - //root.elements = [res]; // do not use addchild or the element will get removed from the tree. - root.addElement(res); - } - else { - res = new ember.MatrixNode(matrix.number); - root = matrix._parent.getTreeBranch(res); - } - res.connections = {}; - for(let id in connections) { - if (!connections.hasOwnProperty(id)) { - continue; - } - let connection = connections[id]; - conResult = new ember.MatrixConnection(connection.target); - let emitType; - res.connections[connection.target] = conResult; - - if (matrix.connections[connection.target].isLocked()) { - conResult.disposition = ember.MatrixDisposition.locked; - } - else if (matrix.contents.type !== ember.MatrixType.nToN && - connection.operation !== ember.MatrixOperation.disconnect && - connection.sources != null && connection.sources.length === 1) { - if (matrix.contents.type === ember.MatrixType.oneToOne) { - // if the source is being used already, disconnect it. - const targets = matrix.getSourceConnections(connection.sources[0]); - if (targets.length === 1 && targets[0] !== connection.target) { - const disconnect = new ember.MatrixConnection(targets[0]); - disconnect.setSources([]); - disconnect.disposition = ember.MatrixDisposition.modified; - res.connections[targets[0]] = disconnect; - matrix.setSources(targets[0], []); - if (response) { - this.emit("matrix-disconnect", { - target: targets[0], - sources: connection.sources, - client: client == null ? null : client.remoteAddress() - }); - } - } - } - // if the target is connected already, disconnect it - if (matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length === 1) { - if (matrix.contents.type === ember.MatrixType.oneToN) { - const disconnectSource = this.getDisconnectSource(matrix, connection.target); - if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { - if (disconnectSource != null && disconnectSource != -1 && - disconnectSource != connection.sources[0]) { - connection.sources = [disconnectSource]; - } - else { - // do nothing - connection.operarion = ember.MatrixOperation.tally; - } - } - } - if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { - const source = matrix.connections[connection.target].sources[0]; - matrix.setSources(connection.target, []); - if (response) { - this.emit("matrix-disconnect", { - target: connection.target, - sources: [source], - client: client == null ? null : client.remoteAddress() - }); - } - } - else if (matrix.contents.type === ember.MatrixType.oneToOne) { - // let's change the request into a disconnect - connection.operation = ember.MatrixOperation.disconnect; - } - } - } - - if (connection.operation !== ember.MatrixOperation.disconnect && - connection.sources != null && connection.sources.length > 0 && - matrix.canConnect(connection.target,connection.sources,connection.operation)) { - // Apply changes - if ((connection.operation == null) || - (connection.operation.value == ember.MatrixOperation.absolute)) { - matrix.setSources(connection.target, connection.sources); - emitType = "matrix-change"; - } - else if (connection.operation == ember.MatrixOperation.connect) { - matrix.connectSources(connection.target, connection.sources); - emitType = "matrix-connect"; - } - conResult.disposition = ember.MatrixDisposition.modified; - } - else if (connection.operation !== ember.MatrixOperation.disconnect && - connection.sources != null && connection.sources.length === 0 && - matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length > 0) { - // let's disconnect - if (response) { - this.emit("matrix-disconnect", { - target: connection.target, - sources: matrix.connections[connection.target].sources, - client: client == null ? null : client.remoteAddress() - }); - } - matrix.setSources(connection.target, []); - conResult.disposition = ember.MatrixDisposition.modified; - } - else if (connection.operation === ember.MatrixOperation.disconnect && - matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length > 0) { - // Disconnect - if (matrix.contents.type === ember.MatrixType.oneToN) { - const disconnectSource = this.getDisconnectSource(matrix, connection.target); - if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { - if (disconnectSource != null && disconnectSource != -1 && - disconnectSource != connection.sources[0]) { - if (response) { - this.emit("matrix-disconnect", { - target: connection.target, - sources: matrix.connections[connection.target].sources, - client: client == null ? null : client.remoteAddress() - }); - } - matrix.setSources(connection.target, [disconnectSource]); - connection.operarion = ember.MatrixOperation.modified; - } - else { - // do nothing - connection.operarion = ember.MatrixOperation.tally; - } - } - } - else { - matrix.disconnectSources(connection.target, connection.sources); - conResult.disposition = ember.MatrixDisposition.modified; - emitType = "matrix-disconnect"; - } - } - else if (conResult.disposition !== ember.MatrixDisposition.locked){ - if (this._debug) { - console.log(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); - } - conResult.disposition = ember.MatrixDisposition.tally; - } - - // Send response or update subscribers. - conResult.sources = matrix.connections[connection.target].sources; - if (response) { - // We got a request so emit something. - this.emit(emitType, { - target: connection.target, - sources: connection.sources, - client: client == null ? null : client.remoteAddress() - }); - } - } - if (client != null) { - client.sendBERNode(root); - } - - if (conResult != null && conResult.disposition !== ember.MatrixDisposition.tally) { - if (this._debug) { - console.log("Updating subscribers for matrix change"); - } - this.updateSubscribers(matrix.getPath(), root, client); - } -} - -const validateMatrixOperation = function(matrix, target, sources) { - if (matrix == null) { - throw new Error(`matrix not found with path ${path}`); - } - if (matrix.contents == null) { - throw new Error(`invalid matrix at ${path} : no contents`); - } - if (matrix.contents.targetCount == null) { - throw new Error(`invalid matrix at ${path} : no targetCount`); - } - if ((target < 0) || (target >= matrix.contents.targetCount)) { - throw new Error(`target id ${target} out of range 0 - ${matrix.contents.targetCount}`); - } - if (sources.length == null) { - throw new Error("invalid sources format"); - } -} - -const doMatrixOperation = function(server, path, target, sources, operation) { - let matrix = server.tree.getElementByPath(path); - - validateMatrixOperation(matrix, target, sources); - - let connection = new ember.MatrixConnection(target); - connection.sources = sources; - connection.operation = operation; - server.handleMatrixConnections(undefined, matrix, [connection], false); -} - -TreeServer.prototype.matrixConnect = function(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.connect); -} - -TreeServer.prototype.matrixDisconnect = function(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.disconnect); -} - -TreeServer.prototype.matrixSet = function(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.absolute); -} - -TreeServer.prototype.handleQualifiedFunction = function(client, element, node) { - -} - - -TreeServer.prototype.handleCommand = function(client, element, cmd) { - switch(cmd.number) { - case ember.GetDirectory: - this.handleGetDirectory(client, element); - break; - case ember.Subscribe: - this.handleSubscribe(client, element); - break; - case ember.Unsubscribe: - this.handleUnSubscribe(client, element); - break; - case ember.Invoke: - this.handleInvoke(client, cmd.invocation, element); - break; - default: - this.emit("error", new Error(`invalid command ${cmd.number}`)); - break; - } -} - -TreeServer.prototype.getResponse = function(element) { - return element.getTreeBranch(undefined, function(node) { - node.update(element); - let children = element.getChildren(); - if (children != null) { - for (let i = 0; i < children.length; i++) { - node.addChild(children[i].getDuplicate()); - } - } - else if (this._debug) { - console.log("getResponse","no children"); - } - }); -} - -TreeServer.prototype.getQualifiedResponse = function(element) { - const res = new ember.Root(); - let dup; - if (element.isRoot() === false) { - dup = element.toQualified(); - } - let children = element.getChildren(); - if (children != null) { - for (let i = 0; i < children.length; i++) { - res.addChild(children[i].toQualified().getMinimalContent()); - } - } - else { - res.addChild(dup); - } - return res; -} - -TreeServer.prototype.handleInvoke = function(client, invocation, element) { - const result = new ember.InvocationResult(); - result.invocationId = invocation.id; - if (element == null || !element.isFunction()) { - result.setFailure(); - } - else { - try { - result.setResult(element.func(invocation.arguments)); - } - catch(e){ - this.emit("error", e); - result.setFailure(); - } - } - const res = new ember.Root(); - res.addResult(result); - client.sendBERNode(res); -} - -TreeServer.prototype.handleGetDirectory = function(client, element) { - if (client !== undefined) { - if ((element.isMatrix() || element.isParameter()) && - (!element.isStream())) { - // ember spec: parameter without streamIdentifier should - // report their value changes automatically. - this.subscribe(client, element); - } - else if (element.isNode()) { - const children = element.getChildren(); - if (children != null) { - for (let i = 0; i < children.length; i++) { - const child = children[i]; - if ((child.isMatrix() || child.isParameter()) && - (!child.isStream())) { - this.subscribe(client, child); - } - } - } - } - - const res = this.getQualifiedResponse(element); - if (this._debug) { - console.log("getDirectory response", res); - } - client.sendBERNode(res); - } -} - -TreeServer.prototype.handleSubscribe = function(client, element) { - if (this._debug) { - console.log("subscribe"); - } - this.subscribe(client, element); -} - -TreeServer.prototype.handleUnSubscribe = function(client, element) { - if (this._debug) { - console.log("unsubscribe"); - } - this.unsubscribe(client, element); -} - - -TreeServer.prototype.subscribe = function(client, element) { - const path = element.getPath(); - if (this.subscribers[path] == null) { - this.subscribers[path] = new Set(); - } - this.subscribers[path].add(client); -} - -TreeServer.prototype.unsubscribe = function(client, element) { - const path = element.getPath(); - if (this.subscribers[path] == null) { - return; - } - this.subscribers[path].delete(client); -} - -TreeServer.prototype.setValue = function(element, value, origin, key) { - return new Promise((resolve, reject) => { - // Change the element value if write access permitted. - if (element.contents == null) { - return resolve(); - } - if (element.isParameter()) { - if ((element.contents.access !== undefined) && - (element.contents.access.value > 1)) { - element.contents.value = value; - const res = this.getResponse(element); - this.updateSubscribers(element.getPath(),res, origin); - this.emit("value-change", element); - } - } - else if (element.isMatrix()) { - if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { - element.contents[key] = value; - const res = this.getResponse(element); - this.updateSubscribers(element.getPath(),res, origin); - this.emit("value-change", element); - } - } - return resolve(); - }); -} - -TreeServer.prototype.replaceElement = function(element) { - let path = element.getPath(); - let parent = this.tree.getElementByPath(path); - if ((parent == null)||(parent._parent == null)) { - throw new Error(`Could not find element at path ${path}`); - } - parent = parent._parent; - let children = parent.getChildren(); - let newList = []; - for(let i = 0; i <= children.length; i++) { - if (children[i] && children[i].getPath() == path) { - element._parent = parent; // move it to new tree. - children[i] = element; - let res = this.getResponse(element); - this.updateSubscribers(path,res); - return; - } - } -} - -TreeServer.prototype.getDisconnectSource = function(matrix, targetID) { - if (matrix.defaultSources) { - return matrix.defaultSources[targetID].contents.value; - } - if (matrix.contents.labels == null || matrix.contents.labels.length == 0) { - return null; - } - const basePath = matrix.contents.labels[0].basePath; - const labels = this.tree.getElementByPath(basePath); - const number = labels.getNumber() + 1; - const parent = labels.getParent(); - const children = parent.getChildren(); - for(let child of children) { - if (child.getNumber() === number) { - matrix.defaultSources = child.getChildren(); - return matrix.defaultSources[targetID].contents.value; - } - } - return null; -} - -TreeServer.prototype.updateSubscribers = function(path, response, origin) { - if (this.subscribers[path] == null) { - return; - } - - for (let client of this.subscribers[path]) { - if (client === origin) { - continue; // already sent the response to origin - } - if (this.clients.has(client)) { - client.queueMessage(response); - } - else { - // clean up subscribers - client is gone - this.subscribers[path].delete(client); - } - } -} - -const parseMatrixContent = function(matrixContent, content) { - if (content.labels) { - matrixContent.labels = []; - for(let l = 0; l < content.labels.length; l++) { - if (typeof (content.labels[l]) === "object") { - matrixContent.labels.push( - new ember.Label( - content.labels[l].basePath, - content.labels[l].description - ) - ); - } - else { - // for backward compatibility... Remove in the future - matrixContent.labels.push( - new ember.Label(content.labels[l]) - ); - } - } - delete content.labels; - } - if (content.type != null) { - if (content.type == "oneToN") { - matrixContent.type = ember.MatrixType.oneToN; - } - else if (content.type == "oneToOne") { - matrixContent.type = ember.MatrixType.oneToOne; - } - else if (content.type == "nToN") { - matrixContent.type = ember.MatrixType.nToN; - matrixContent.maximumTotalConnects = content.maximumTotalConnects == null ? - Number(content.targetCount) * Number(content.sourceCount) : Number(content.maximumTotalConnects); - matrixContent.maximumConnectsPerTarget = content.maximumConnectsPerTarget == null ? - Number(content.sourceCount) : Number(content.maximumConnectsPerTarget); - } - else { - throw new Error(`Invalid matrix type ${content.type}`); - } - delete content.type; - } - if (content.mode != null) { - if (content.mode == "linear") { - matrixContent.mode = ember.MatrixMode.linear; - } - else if (content.mode == "nonLinear") { - matrixContent.mode = ember.MatrixMode.nonLinear; - } - else { - throw new Error(`Invalid matrix mode ${content.mode}`); - } - delete content.mode; - } -} - -const parseObj = function(parent, obj) { - for(let i = 0; i < obj.length; i++) { - let emberElement; - let content = obj[i]; - let number = content.number !== undefined ? content.number : i; - delete content.number; - if (content.value != null) { - emberElement = new ember.Parameter(number); - emberElement.contents = new ember.ParameterContents(content.value); - if (content.type) { - emberElement.contents.type = ember.ParameterType.get(content.type); - delete content.type; - } - else { - emberElement.contents.type = ember.ParameterType.string; - } - if (content.access) { - emberElement.contents.access = ember.ParameterAccess.get(content.access); - delete content.access; - } - else { - emberElement.contents.access = ember.ParameterAccess.read; - } - } - else if (content.func != null) { - emberElement = new ember.Function(number, content.func); - emberElement.contents = new ember.FunctionContent(); - if (content.arguments != null) { - for(let argument of content.arguments) { - emberElement.contents.arguments.push(new ember.FunctionArgument( - argument.type, - argument.value, - argument.name - )); - } - } - if (content.result != null) { - for(let argument of content.result) { - emberElement.contents.result.push(new ember.FunctionArgument( - argument.type, - argument.value, - argument.name - )); - } - } - delete content.result; - } - else if (content.targetCount != null) { - emberElement = new ember.MatrixNode(number); - emberElement.contents = new ember.MatrixContents(); - parseMatrixContent(emberElement.contents, content); - if (content.connections) { - emberElement.connections = {}; - for (let c in content.connections) { - if (! content.connections.hasOwnProperty(c)) { - continue; - } - const t = content.connections[c].target != null ? content.connections[c].target : 0; - emberElement.setSources(t, content.connections[c].sources); - } - delete content.connections; - } - else { - emberElement.connections = {}; - for (let t = 0; t < content.targetCount; t++) { - let connection = new ember.MatrixConnection(t); - emberElement.connections[t] = connection; - } - } - } - else { - emberElement = new ember.Node(number); - emberElement.contents = new ember.NodeContents(); - } - for(let id in content) { - if (emberElement.isFunction() && id === "arguments") { - // we did it already. - continue; - } - if ((id !== "children") && (content.hasOwnProperty(id))) { - emberElement.contents[id] = content[id]; - } - else { - parseObj(emberElement, content.children); - } - } - parent.addChild(emberElement); - } -} - -TreeServer.JSONtoTree = function(obj) { - let tree = new ember.Root(); - parseObj(tree, obj); - return tree; -} - - -TreeServer.prototype.toJSON = function() { - if (this.tree == null) { - return []; - } - const elements = this.tree.getChildren(); - - return elements.map(element => element.toJSON()); -}; - -module.exports = TreeServer; diff --git a/test/utils.js b/test/utils.js index 5393953..7fb6f65 100755 --- a/test/utils.js +++ b/test/utils.js @@ -1,4 +1,4 @@ -const {ParameterType, FunctionArgument} = require("../ember"); +const {ParameterType, FunctionArgument} = require("../EmberLib"); const init = function(_src,_tgt) { From a6fb9ca03db06bca5b22acb5e80769f80fcd7ffa Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 7 Jan 2020 10:57:34 +0100 Subject: [PATCH 127/162] version 2 final fixes and good to go --- EmberClient/EmberClient.js | 4 +- EmberLib/Command.js | 7 ++- EmberLib/Invocation.js | 15 +++-- EmberLib/InvocationResult.js | 6 +- EmberLib/Label.js | 1 + EmberLib/Matrix.js | 6 +- EmberLib/MatrixConnection.js | 3 + EmberLib/MatrixContents.js | 2 + EmberLib/MatrixOperation.js | 1 + EmberLib/ParameterContents.js | 9 ++- EmberLib/QualifiedElement.js | 12 +++- EmberLib/QualifiedFunction.js | 17 +++--- EmberLib/QualifiedMatrix.js | 6 +- EmberLib/QualifiedParameter.js | 2 +- EmberLib/TreeNode.js | 5 +- EmberLib/index.js | 10 +++- test/DeviceTree.test.js | 22 ++++---- test/Ember.test.js | 4 +- test/Server.test.js | 100 +++++++++++++++++---------------- 19 files changed, 129 insertions(+), 103 deletions(-) diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index e139bbe..4ab22c2 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -570,7 +570,7 @@ class EmberClient extends EventEmitter { } let matrix = null; if (node != null) { - matrix = node.elements[0]; + matrix = node.getElementByPath(requestedPath); } if (matrix != null && matrix.isMatrix() && matrix.getPath() === requestedPath) { this._clearTimeout(); // clear the timeout now. The resolve below may take a while. @@ -580,7 +580,7 @@ class EmberClient extends EventEmitter { else { if (this._debug) { console.log(`unexpected node response during matrix connect ${requestedPath}`, - JSON.stringify(matrix.toJSON(), null, 4)); + matrix == null ? null : JSON.stringify(matrix.toJSON(), null, 4)); } } } diff --git a/EmberLib/Command.js b/EmberLib/Command.js index 5d76c2b..c3247b0 100755 --- a/EmberLib/Command.js +++ b/EmberLib/Command.js @@ -2,6 +2,7 @@ const Enum = require('enum'); const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); const BER = require('../ber.js'); +const Invocation = require("./Invocation"); const FieldFlags = new Enum({ sparse: -2, @@ -84,12 +85,12 @@ class Command { * @returns {Command} */ static decode(ber) { - var c = new Command(); + const c = new Command(); ber = ber.getSequence(BER.APPLICATION(2)); while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); + let tag = ber.peek(); + let seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { c.number = seq.readInt(); } diff --git a/EmberLib/Invocation.js b/EmberLib/Invocation.js index b7bf4f9..e12130b 100755 --- a/EmberLib/Invocation.js +++ b/EmberLib/Invocation.js @@ -1,6 +1,7 @@ "use strict"; const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); const BER = require('../ber.js'); +const FunctionArgument = require("./FunctionArgument"); let _id = 1; class Invocation { @@ -43,19 +44,17 @@ class Invocation { * @returns {Invocation} */ static decode(ber) { - const invocation = null; + let invocation = null; ber = ber.getSequence(BER.APPLICATION(22)); while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); + let tag = ber.peek(); + let seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - const invocationId = seq.readInt(); - invocation = new Invocation(invocationId); + // Create the invocation with the id received otherwise we will + // increment the internal id counter. + invocation = new Invocation(seq.readInt()); } else if(tag == BER.CONTEXT(1)) { - if (invocation == null) { - throw new Error("Missing invocationID"); - } invocation.arguments = []; seq = seq.getSequence(BER.EMBER_SEQUENCE); while(seq.remain > 0) { diff --git a/EmberLib/InvocationResult.js b/EmberLib/InvocationResult.js index 6fae4b5..5e9505d 100755 --- a/EmberLib/InvocationResult.js +++ b/EmberLib/InvocationResult.js @@ -1,6 +1,8 @@ "use strict"; const BER = require('../ber.js'); +const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); +const FunctionArgument = require("./FunctionArgument"); class InvocationResult { @@ -80,8 +82,8 @@ class InvocationResult { const invocationResult = new InvocationResult(); ber = ber.getSequence(BER.APPLICATION(23)); while(ber.remain > 0) { - tag = ber.peek(); - var seq = ber.getSequence(tag); + let tag = ber.peek(); + let seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { // invocationId invocationResult.invocationId = seq.readInt(); } else if(tag == BER.CONTEXT(1)) { // success diff --git a/EmberLib/Label.js b/EmberLib/Label.js index d8fecde..069e84d 100755 --- a/EmberLib/Label.js +++ b/EmberLib/Label.js @@ -1,4 +1,5 @@ "use strict"; +const BER = require('../ber.js'); class Label { constructor(path, description) { diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js index e697b74..c7b9b79 100755 --- a/EmberLib/Matrix.js +++ b/EmberLib/Matrix.js @@ -2,6 +2,10 @@ const MatrixConnection = require("./MatrixConnection"); const TreeNode = require("./TreeNode"); const BER = require('../ber.js'); +const MatrixMode = require("./MatrixMode"); +const MatrixOperation = require("./MatrixOperation"); +const MatrixType = require("./MatrixType"); +const MatrixNode = require("./MatrixNode"); class Matrix extends TreeNode { @@ -373,7 +377,7 @@ class Matrix extends TreeNode const currentSource = matrix.connections[targetID] == null || matrix.connections[targetID].sources == null ? [] : matrix.connections[targetID].sources; if (currentSource.length > 0) { - MatrixNode.disconnectSources(matrix, targetID, currentSource) + this.disconnectSources(matrix, targetID, currentSource) } Matrix.connectSources(matrix, targetID, sources); } diff --git a/EmberLib/MatrixConnection.js b/EmberLib/MatrixConnection.js index 5bd90c1..4e78e45 100755 --- a/EmberLib/MatrixConnection.js +++ b/EmberLib/MatrixConnection.js @@ -1,4 +1,7 @@ "use strict"; +const BER = require('../ber.js'); +const MatrixOperation = require("./MatrixOperation"); +const MatrixDisposition = require("./MatrixDisposition"); class MatrixConnection { /** diff --git a/EmberLib/MatrixContents.js b/EmberLib/MatrixContents.js index 7c11647..1e8df10 100755 --- a/EmberLib/MatrixContents.js +++ b/EmberLib/MatrixContents.js @@ -2,6 +2,8 @@ const MatrixType = require("./MatrixType"); const MatrixMode = require("./MatrixMode"); +const BER = require('../ber.js'); +const Label = require("./Label"); class MatrixContents { constructor(type = MatrixType.oneToN, mode = MatrixMode.linear) { diff --git a/EmberLib/MatrixOperation.js b/EmberLib/MatrixOperation.js index c3a4879..f211a0b 100755 --- a/EmberLib/MatrixOperation.js +++ b/EmberLib/MatrixOperation.js @@ -7,6 +7,7 @@ const Enum = require('enum'); // disconnect (2) -- nToN only. sources contains sources to remove from // connection // } + const MatrixOperation = new Enum({ absolute: 0, connect: 1, diff --git a/EmberLib/ParameterContents.js b/EmberLib/ParameterContents.js index f068e46..b213299 100755 --- a/EmberLib/ParameterContents.js +++ b/EmberLib/ParameterContents.js @@ -2,6 +2,7 @@ const {ParameterType} = require("./ParameterType"); const ParameterAccess = require("./ParameterAccess"); +const StringIntegerCollection = require("./StringIntegerCollection"); const BER = require('../ber.js'); @@ -41,10 +42,8 @@ class ParameterContents { ber.writeIfDefinedEnum(this.type, ParameterType, ber.writeInt, 13); ber.writeIfDefined(this.streamIdentifier, ber.writeInt, 14); - if(this.emumMap !== undefined) { - ber.startSequence(BER.CONTEXT(15)); - StringIntegerCollection.encode(ber, this.enumMap); - ber.endSequence(); + if(this.stringIntegerCollection !== undefined) { + this.stringIntegerCollection.encode(ber); } if(this.streamDescriptor !== undefined) { @@ -101,7 +100,7 @@ class ParameterContents { } else if(tag == BER.CONTEXT(14)) { pc.streamIdentifier = seq.readInt(); } else if(tag == BER.CONTEXT(15)) { - pc.enumMap = StringIntegerCollection.decode(seq); + pc.stringIntegerCollection = StringIntegerCollection.decode(seq); } else if(tag == BER.CONTEXT(16)) { pc.streamDescriptor = StreamDescription.decode(seq); } else if(tag == BER.CONTEXT(17)) { diff --git a/EmberLib/QualifiedElement.js b/EmberLib/QualifiedElement.js index b0ef471..59ab929 100755 --- a/EmberLib/QualifiedElement.js +++ b/EmberLib/QualifiedElement.js @@ -46,15 +46,21 @@ class QualifiedElement extends TreeNode { /** * - * @param {number} cmd + * @param {number} cmd + * @param {string} key + * @param {string} value * @returns {TreeNode} */ - getCommand(cmd) { + getCommand(cmd, key, value) { const r = this.getNewTree(); const qn = new this.constructor(); qn.path = this.getPath(); r.addElement(qn); - qn.addChild(new Command(cmd)); + const command = new Command(cmd); + if (key != null) { + command[key] = value; + } + qn.addChild(command); return r; } diff --git a/EmberLib/QualifiedFunction.js b/EmberLib/QualifiedFunction.js index 04dbc42..37cab00 100755 --- a/EmberLib/QualifiedFunction.js +++ b/EmberLib/QualifiedFunction.js @@ -2,9 +2,9 @@ const QualifiedElement = require("./QualifiedElement"); const FunctionContent = require("./FunctionContent"); -const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); +const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); const BER = require('../ber.js'); -const Command = require("./Command"); +const Invocation = require("./Invocation"); class QualifiedFunction extends QualifiedElement { /** @@ -37,15 +37,12 @@ class QualifiedFunction extends QualifiedElement { * * @param {*} params */ - invoke(params) { - if (this.path == null) { - throw new Error("Invalid path"); - } - var QualifiedFunctionNode = this.getCommand(COMMAND_INVOKE); - var invocation = new Invocation() + invoke(params) { + const invocation = new Invocation() invocation.arguments = params; - QualifiedFunctionNode.getElementByPath(this.getPath()).getNumber(COMMAND_INVOKE).invocation = invocation - return QualifiedFunctionNode; + const qualifiedFunctionNode = this.getCommand(COMMAND_INVOKE, "invocation", invocation); + //qualifiedFunctionNode.getElementByNumber(this.getNumber()).getElementByNumber(COMMAND_INVOKE).invocation = invocation + return qualifiedFunctionNode; } diff --git a/EmberLib/QualifiedMatrix.js b/EmberLib/QualifiedMatrix.js index 86314f6..538e76c 100755 --- a/EmberLib/QualifiedMatrix.js +++ b/EmberLib/QualifiedMatrix.js @@ -4,6 +4,8 @@ const Matrix = require("./Matrix"); const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); const BER = require('../ber.js'); const Command = require("./Command"); +const MatrixContents = require("./MatrixContents"); +const MatrixConnection = require("./MatrixConnection"); class QualifiedMatrix extends Matrix { /** @@ -24,7 +26,7 @@ class QualifiedMatrix extends Matrix { * @returns {Root} */ connect(connections) { - const r = new Root(); + const r = this.getNewTree(); const qn = new QualifiedMatrix(); qn.path = this.path; r.addElement(qn); @@ -63,7 +65,7 @@ class QualifiedMatrix extends Matrix { * @returns {TreeNode} */ getCommand(cmd) { - const r = new TreeNode(); + const r = this.getNewTree(); const qn = new QualifiedMatrix(); qn.path = this.getPath(); r.addElement(qn); diff --git a/EmberLib/QualifiedParameter.js b/EmberLib/QualifiedParameter.js index 2e3e3d0..85a748f 100755 --- a/EmberLib/QualifiedParameter.js +++ b/EmberLib/QualifiedParameter.js @@ -46,7 +46,7 @@ class QualifiedParameter extends QualifiedElement { * @returns {TreeNode} */ setValue(value) { - let r = new TreeNode(); + let r = this.getNewTree(); let qp = new QualifiedParameter(this.path); r.addElement(qp); qp.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index d09cdf4..c07849a 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -313,7 +313,10 @@ class TreeNode { node = node.getElementByNumber(number); if (node == null) { return null; - } + } + if (node.isQualified() && node.path == path) { + return node; + } myPathArray.push(number); } return node; diff --git a/EmberLib/index.js b/EmberLib/index.js index 10ebba6..1767ac5 100755 --- a/EmberLib/index.js +++ b/EmberLib/index.js @@ -15,7 +15,8 @@ const MatrixMode = require("./MatrixMode"); const MatrixType = require("./MatrixType"); const MatrixContents = require("./MatrixContents"); const MatrixConnection = require("./MatrixConnection"); -const Matrixoperation = require("./MatrixOperation"); +const MatrixOperation = require("./MatrixOperation"); +const MatrixDisposition = require("./MatrixDisposition"); const Node = require("./Node"); const NodeContents = require("./NodeContents"); const Parameter = require("./Parameter"); @@ -26,6 +27,7 @@ const QualifiedFunction = require("./QualifiedFunction"); const QualifiedMatrix = require("./QualifiedMatrix"); const QualifiedNode = require("./QualifiedNode"); const QualifiedParameter = require("./QualifiedParameter"); +const StringIntegerCollection = require("./StringIntegerCollection"); const rootDecode = function(ber) { const r = new TreeNode(); @@ -108,7 +110,7 @@ TreeNode.decode = childDecode; const DecodeBuffer = function (packet) { const ber = new BER.Reader(packet); - return TreeNode.decode(ber); + return rootDecode(ber); }; module.exports = { @@ -128,7 +130,8 @@ module.exports = { MatrixType, MatrixContents, MatrixConnection, - Matrixoperation, + MatrixDisposition, + MatrixOperation, Node, NodeContents, Parameter, @@ -139,6 +142,7 @@ module.exports = { QualifiedMatrix, QualifiedNode, QualifiedParameter, + StringIntegerCollection, Subscribe,COMMAND_SUBSCRIBE, Unsubscribe,COMMAND_UNSUBSCRIBE, GetDirectory,COMMAND_GETDIRECTORY, diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index 20c3995..c072418 100755 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -1,16 +1,16 @@ const fs = require("fs"); const sinon = require("sinon"); -const Decoder = require('../').Decoder; -const DeviceTree = require("../").DeviceTree; -const TreeServer = require("../").TreeServer; +const Decoder = require('../EmberLib').DecodeBuffer; +const EmberClient = require("../EmberClient"); +const EmberServer = require("../EmberServer"); const LOCALHOST = "127.0.0.1"; const UNKNOWN_HOST = "192.168.99.99"; const PORT = 9008; -describe("DeviceTree", () => { +describe("EmberClient", () => { describe("With server", () => { - /** @type {TreeServer} */ + /** @type {EmberServer} */ let server; beforeAll(() => { return Promise.resolve() @@ -23,7 +23,7 @@ describe("DeviceTree", () => { }); })) .then(root => { - server = new TreeServer(LOCALHOST, PORT, root); + server = new EmberServer(LOCALHOST, PORT, root); return server.listen(); }); }); @@ -31,7 +31,7 @@ describe("DeviceTree", () => { it("should gracefully connect and disconnect", () => { return Promise.resolve() .then(() => { - let tree = new DeviceTree(LOCALHOST, PORT); + let tree = new EmberClient(LOCALHOST, PORT); return Promise.resolve() .then(() => tree.connect()) .then(() => tree.getDirectory()) @@ -45,7 +45,7 @@ describe("DeviceTree", () => { it("should not disconnect after 5 seconds of inactivity", () => { return Promise.resolve() .then(() => { - let tree = new DeviceTree(LOCALHOST, PORT); + let tree = new EmberClient(LOCALHOST, PORT); tree.on("error", error => { throw error; @@ -59,7 +59,7 @@ describe("DeviceTree", () => { }, 7000); it("timeout should be taken into account when connecting to unknown host", () => { - let tree = new DeviceTree(UNKNOWN_HOST, PORT); + let tree = new EmberClient(UNKNOWN_HOST, PORT); tree.on("error", () => { }); const expectedTimeoutInSec = 2; @@ -78,11 +78,11 @@ describe("DeviceTree", () => { it("should gracefully connect and getDirectory", () => { - let tree = new DeviceTree(LOCALHOST, PORT); + let tree = new EmberClient(LOCALHOST, PORT); tree.on("error", e => { console.log(e); }) - let stub = sinon.stub(tree.client, "sendBER"); + let stub = sinon.stub(tree._client, "sendBER"); tree._debug = true; server._debug = true; stub.onFirstCall().returns(); diff --git a/test/Ember.test.js b/test/Ember.test.js index abe772e..f9ea636 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -1,8 +1,8 @@ const expect = require("expect"); -const { S101Client } = require("../index"); +const { S101Client } = require("../EmberSocket"); const s101Buffer = Buffer.from("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); -const ember = require("../ember"); +const ember = require("../EmberLib"); const BER = require('../ber.js'); const errors = require('../errors.js'); diff --git a/test/Server.test.js b/test/Server.test.js index 898ef55..4c9ac92 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -1,8 +1,9 @@ const expect = require("expect"); -const TreeServer = require("../server"); -const DeviceTree = require("../").DeviceTree; -const ember = require("../ember"); +const EmberServer = require("../EmberServer"); +const EmberClient = require("../EmberClient"); +const ember = require("../EmberLib"); const {jsonRoot} = require("./utils"); +const MatrixHandlers = require("../EmberServer/MatrixHandlers"); const LOCALHOST = "127.0.0.1"; const PORT = 9009; @@ -20,13 +21,13 @@ describe("server", function() { jsonTree = jsonRoot(); }); it("should generate an ember tree from json", function() { - const root = TreeServer.JSONtoTree(jsonTree); + const root = EmberServer.JSONtoTree(jsonTree); expect(root).toBeDefined(); expect(root.elements).toBeDefined(); - expect(root.elements.length).toBe(1); - console.log("root", root.elements[0].contents); - expect(root.elements[0].contents.identifier).toBe("scoreMaster"); - expect(root.elements[0].children.length).toBe(jsonTree[0].children.length); + expect(root.elements.size).toBe(1); + console.log("root", root.getElementByNumber(0).contents); + expect(root.getElementByNumber(0).contents.identifier).toBe("scoreMaster"); + expect(root.getElementByNumber(0).elements.size).toBe(jsonTree[0].children.length); }); }); @@ -34,8 +35,8 @@ describe("server", function() { let server,client; beforeAll(function() { jsonTree = jsonRoot(); - const root = TreeServer.JSONtoTree(jsonTree); - server = new TreeServer(LOCALHOST, PORT, root); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); server.on("error", e => { console.log(e); }); @@ -51,7 +52,7 @@ describe("server", function() { return server.close(); }); it("should receive and decode the full tree", function () { - client = new DeviceTree(LOCALHOST, PORT); + client = new EmberClient(LOCALHOST, PORT); //client._debug = true; return Promise.resolve() .then(() => client.connect()) @@ -62,53 +63,53 @@ describe("server", function() { .then(() => { expect(client.root).toBeDefined(); expect(client.root.elements).toBeDefined(); - expect(client.root.elements.length).toBe(1); - expect(client.root.elements[0].contents.identifier).toBe("scoreMaster"); - return client.getDirectory(client.root.elements[0]); + expect(client.root.elements.size).toBe(1); + expect(client.root.getElementByNumber(0).contents.identifier).toBe("scoreMaster"); + return client.getDirectory(client.root.getElementByNumber(0)); }) .then(() => { - expect(client.root.elements[0].children.length).toBe(jsonTree[0].children.length); - return client.getDirectory(client.root.elements[0].children[0]); + expect(client.root.getElementByNumber(0).elements.size).toBe(jsonTree[0].children.length); + return client.getDirectory(client.root.getElementByNumber(0).getElementByNumber(0)); }) .then(() => { - expect(client.root.elements[0].children[0].children.length).toBe(4); - expect(client.root.elements[0].children[0].children[3].contents.identifier).toBe("author"); - // Issue #33 TreeServer.handleGetDirectory does not subscribe to child parameters + expect(client.root.getElementByNumber(0).getElementByNumber(0).elements.size).toBe(4); + expect(client.root.getElementByNumber(0).getElementByNumber(0).getElementByNumber(3).contents.identifier).toBe("author"); + // Issue #33 EmberServer.handleGetDirectory does not subscribe to child parameters expect(server.subscribers["0.0.0"]).toBeDefined(); // Keepalive return client.disconnect(); }); }); it("should be able to modify a parameter", () => { - client = new DeviceTree(LOCALHOST, PORT); + client = new EmberClient(LOCALHOST, PORT); //client._debug = true; return Promise.resolve() .then(() => client.connect()) .then(() => { return client.getDirectory(); }) - .then(() => client.expand(client.root.elements[0])) + .then(() => client.expand(client.root.getElementByNumber(0))) .then(() => { - expect(server.tree.elements[0].children[0].children[1].contents.value).not.toBe("gdnet"); - return client.setValue(client.root.elements[0].children[0].children[1], "gdnet"); + expect(server.tree.getElementByNumber(0).getElementByNumber(0).getElementByNumber(1).contents.value).not.toBe("gdnet"); + return client.setValue(client.root.getElementByNumber(0).getElementByNumber(0).getElementByNumber(1), "gdnet"); }) .then(() => { - expect(server.tree.elements[0].children[0].children[1].contents.value).toBe("gdnet"); + expect(server.tree.getElementByNumber(0).getElementByNumber(0).getElementByNumber(1).contents.value).toBe("gdnet"); return client.disconnect(); }); }); it("should be able to call a function with parameters", () => { - client = new DeviceTree(LOCALHOST, PORT); + client = new EmberClient(LOCALHOST, PORT); //client._debug = true; return Promise.resolve() .then(() => client.connect()) .then(() => { return client.getDirectory(); }) - .then(() => client.expand(client.root.elements[0])) + .then(() => client.expand(client.root.getElementByNumber(0))) .then(() => { - const func = client.root.elements[0].children[2]; + const func = client.root.getElementByNumber(0).getElementByNumber(2); return client.invokeFunction(func, [ new ember.FunctionArgument(ember.ParameterType.integer, 1), new ember.FunctionArgument(ember.ParameterType.integer, 7) @@ -126,7 +127,7 @@ describe("server", function() { it("should be able to get child with tree.getNodeByPath", function() { //server._debug = true; - client = new DeviceTree(LOCALHOST, PORT); + client = new EmberClient(LOCALHOST, PORT); //client._debug = true; //client._debug = true; return Promise.resolve() @@ -147,7 +148,7 @@ describe("server", function() { }); it("should throw an error if getNodeByPath for unknown path", function() { //server._debug = true; - client = new DeviceTree(LOCALHOST, PORT); + client = new EmberClient(LOCALHOST, PORT); return Promise.resolve() .then(() => client.connect()) .then(() => { @@ -166,7 +167,7 @@ describe("server", function() { }); }); it("should be able to make a matrix connection", () => { - client = new DeviceTree(LOCALHOST, PORT); + client = new EmberClient(LOCALHOST, PORT); //client._debug = true; return Promise.resolve() .then(() => client.connect()) @@ -195,8 +196,8 @@ describe("server", function() { let server; beforeEach(function() { jsonTree = jsonRoot(); - const root = TreeServer.JSONtoTree(jsonTree); - server = new TreeServer(LOCALHOST, PORT, root); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); }); it("should verify if connection allowed in 1-to-N", function() { let disconnectCount = 0; @@ -204,7 +205,7 @@ describe("server", function() { disconnectCount++; } server.on("matrix-disconnect", handleDisconnect.bind(this)); - const matrix = server.tree.elements[0].children[1].children[0]; + const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); let connection = new ember.MatrixConnection(0); connection.setSources([1]); connection.operation = ember.MatrixOperation.connect; @@ -217,14 +218,15 @@ describe("server", function() { res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); // We can't connect. But server will disconnect existing source and connect new one. - server.handleMatrixConnections(null, matrix, {0: connection}); + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); expect(matrix.connections[0].sources[0]).toBe(1); expect(disconnectCount).toBe(1); // But if connecting same source and dest this is a disconnect. But not possible in 1toN. // instead connect with defaultSource or do nothing - server.getDisconnectSource(matrix, 0); + const matrixHandlers = new MatrixHandlers(server); + matrixHandlers.getDisconnectSource(matrix, 0); matrix.defaultSources[0].contents.value = 222; - server.handleMatrixConnections(null, matrix, {0: connection}); + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); expect(disconnectCount).toBe(2); expect(matrix.connections[0].sources[0]).toBe(222); matrix.setSources(0, [0]); @@ -235,7 +237,7 @@ describe("server", function() { expect(res).toBeTruthy(); }); it("should verify if connection allowed in 1-to-1", function() { - const matrix = server.tree.elements[0].children[1].children[0]; + const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); let disconnectCount = 0; const handleDisconnect = info => { disconnectCount++; @@ -251,11 +253,11 @@ describe("server", function() { res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); // We can't connect but in 1-on-1 server should disconnect existing source and connect new one. - server.handleMatrixConnections(null, matrix, {0: connection}); + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); expect(matrix.connections[0].sources[0]).toBe(1); expect(disconnectCount).toBe(1); // But if connecting same source and dest. just disconnect and do not reconnect. - server.handleMatrixConnections(null, matrix, {0: connection}); + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); expect(disconnectCount).toBe(2); connection.operation = ember.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); @@ -267,7 +269,7 @@ describe("server", function() { server.off("matrix-disconnect", handleDisconnect); }); it("should disconnect if trying to connect same source and target in 1-to-1", function() { - const matrix = server.tree.elements[0].children[1].children[0]; + const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); let disconnectCount = 0; const handleDisconnect = info => { disconnectCount++; @@ -278,12 +280,12 @@ describe("server", function() { const connection = new ember.MatrixConnection(0); connection.setSources([1]); connection.operation = ember.MatrixOperation.connect; - server.handleMatrixConnections(null, matrix, {0: connection}); + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); expect(matrix.connections[0].sources.length).toBe(0); expect(disconnectCount).toBe(1); }); it("should be able to lock a connection", function() { - const matrix = server.tree.elements[0].children[1].children[0]; + const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); let disconnectCount = 0; const handleDisconnect = info => { disconnectCount++; @@ -295,13 +297,13 @@ describe("server", function() { const connection = new ember.MatrixConnection(0); connection.setSources([0]); connection.operation = ember.MatrixOperation.connect; - server.handleMatrixConnections(null, matrix, {0: connection}); + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); expect(matrix.connections[0].sources.length).toBe(1); expect(matrix.connections[0].sources[0]).toBe(1); expect(disconnectCount).toBe(0); }); it("should verify if connection allowed in N-to-N", function() { - const matrix = server.tree.elements[0].children[1].children[0]; + const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); matrix.contents.type = ember.MatrixType.nToN; matrix.contents.maximumTotalConnects = 2; matrix.setSources(0, [0,1]); @@ -362,7 +364,7 @@ describe("server", function() { //server._debug = true; return server.listen() .then(() => { - client = new DeviceTree(LOCALHOST, PORT); + client = new EmberClient(LOCALHOST, PORT); return Promise.resolve() }) .then(() => client.connect()) @@ -391,8 +393,8 @@ describe("server", function() { let server; beforeAll(function() { jsonTree = jsonRoot(); - const root = TreeServer.JSONtoTree(jsonTree); - server = new TreeServer(LOCALHOST, PORT, root); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); server.on("error", e => { console.log(e); }); @@ -405,13 +407,13 @@ describe("server", function() { return server.close(); }); it("should not auto subscribe stream parameter", function() { - const parameter = server.tree.elements[0].children[0].children[2]; + const parameter = server.tree.getElementByNumber(0).getElementByNumber(0).getElementByNumber(2); console.log(parameter); expect(parameter.isStream()).toBeTruthy(); expect(server.subscribers["0.0.2"]).not.toBeDefined(); }); it("should be able subscribe to parameter changes", function() { - const client = new DeviceTree(LOCALHOST, PORT); + const client = new EmberClient(LOCALHOST, PORT); const cb = () => { return "updated"; } From 080c7087f368679901482824bbc1a746c5611d9f Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 7 Jan 2020 14:32:19 +0100 Subject: [PATCH 128/162] Fixing missing EmberLib file. Added server events --- EmberClient/EmberClient.js | 19 ++++- EmberLib/StringIntegerCollection.js | 72 ++++++++++++++++ EmberLib/constants.js | 10 ++- EmberLib/index.js | 3 +- EmberServer/ElementHandlers.js | 27 +++--- EmberServer/EmberServer.js | 15 ++-- EmberServer/MatrixHandlers.js | 1 + README.md | 19 +++-- package.json | 2 +- test/Server.test.js | 128 ++++++++++++++++++++++------ 10 files changed, 241 insertions(+), 55 deletions(-) create mode 100755 EmberLib/StringIntegerCollection.js diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index 4ab22c2..ead404f 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -388,7 +388,22 @@ class EmberClient extends EventEmitter { } /** - * + * @deprecated + * @param {string} path ie: "path/to/destination" + * @param {function} callback=null + * @returns {Promise} + */ + getElementByPath(path, callback=null) { + if (path.indexOf("/") >= 0) { + return this.getNodeByPath(path, callback); + } + else { + return this.getNodeByPathnum(path, callback); + } + } + + /** + * @deprecated * @param {string} path ie: "path/to/destination" * @param {function} callback=null * @returns {Promise} @@ -432,7 +447,7 @@ class EmberClient extends EventEmitter { } /** - * + * @deprecated * @param {string|number[]} path ie: 1.0.2 * @param {function} callback=null * @returns {Promise} diff --git a/EmberLib/StringIntegerCollection.js b/EmberLib/StringIntegerCollection.js new file mode 100755 index 0000000..5bd00ea --- /dev/null +++ b/EmberLib/StringIntegerCollection.js @@ -0,0 +1,72 @@ +"use strict"; +const Element = require("./Element"); +const BER = require('../ber.js'); + +class StringIntegerCollection extends Element { + constructor() { + super(); + this._seqID = BER.APPLICATION(8); + this._collection = new Map(); + } + + addEntry(key, value) { + this._collection.set(key, value); + } + + get(key) { + return this._collection.get(key); + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.CONTEXT(15)); + ber.startSequence(BER.APPLICATION(8)); + ber.startSequence(BER.CONTEXT(0)); + for(let [key,value] of this._collection) { + ber.startSequence(BER.APPLICATION(7)); + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(key, BER.EMBER_STRING); + ber.endSequence(); + ber.startSequence(BER.CONTEXT(1)); + ber.writeInt(value); + ber.endSequence(); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + ber.endSequence(); + } + + /** + * + * @param {BER} ber + */ + static decode(ber) { + const sc = new StringIntegerCollection(); + ber = ber.getSequence(BER.APPLICATION(8)); + while(ber.remain > 0) { + var seq = ber.getSequence(BER.CONTEXT(0)); + seq = seq.getSequence(BER.APPLICATION(7)); + var entryString, entryInteger; + while(seq.remain > 0) { + var tag = seq.peek(); + var dataSeq = seq.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + entryString = dataSeq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + entryInteger = dataSeq.readInt(); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + sc.addEntry(entryString,entryInteger); + } + return sc; + } +} + +module.exports = StringIntegerCollection; \ No newline at end of file diff --git a/EmberLib/constants.js b/EmberLib/constants.js index 06a63e8..e49d071 100755 --- a/EmberLib/constants.js +++ b/EmberLib/constants.js @@ -3,6 +3,13 @@ const COMMAND_UNSUBSCRIBE = 31; const COMMAND_GETDIRECTORY = 32; const COMMAND_INVOKE = 33; +const COMMAND_STRINGS = { + [COMMAND_SUBSCRIBE]: "subscribe", + [COMMAND_UNSUBSCRIBE]: "unsubscribe", + [COMMAND_GETDIRECTORY]: "getdirectory", + [COMMAND_INVOKE]: "invoke" +}; + module.exports = { COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE, @@ -11,5 +18,6 @@ module.exports = { Subscribe: COMMAND_SUBSCRIBE, Unsubscribe: COMMAND_UNSUBSCRIBE, GetDirectory: COMMAND_GETDIRECTORY, - Invoke: COMMAND_INVOKE + Invoke: COMMAND_INVOKE, + COMMAND_STRINGS }; \ No newline at end of file diff --git a/EmberLib/index.js b/EmberLib/index.js index 1767ac5..d3a9695 100755 --- a/EmberLib/index.js +++ b/EmberLib/index.js @@ -1,5 +1,5 @@ const {Subscribe,COMMAND_SUBSCRIBE,Unsubscribe,COMMAND_UNSUBSCRIBE, - GetDirectory,COMMAND_GETDIRECTORY,Invoke,COMMAND_INVOKE} = require("./constants"); + GetDirectory,COMMAND_GETDIRECTORY,Invoke,COMMAND_INVOKE, COMMAND_STRINGS} = require("./constants"); const BER = require('../ber.js'); const errors = require("../errors"); const TreeNode = require("./TreeNode"); @@ -115,6 +115,7 @@ const DecodeBuffer = function (packet) { module.exports = { Command, + COMMAND_STRINGS, childDecode: childDecode, rootDecode: rootDecode, DecodeBuffer, diff --git a/EmberServer/ElementHandlers.js b/EmberServer/ElementHandlers.js index c30f888..8cbfcce 100755 --- a/EmberServer/ElementHandlers.js +++ b/EmberServer/ElementHandlers.js @@ -1,6 +1,6 @@ "use strict"; const QualifiedHandlers = require("./QualifiedHandlers"); -const ember = require('../EmberLib'); +const EmberLib = require('../EmberLib'); class ElementHandlers extends QualifiedHandlers{ /** @@ -17,23 +17,30 @@ class ElementHandlers extends QualifiedHandlers{ * @param {Command} cmd */ handleCommand(client, element, cmd) { + switch(cmd.number) { - case ember.COMMAND_GETDIRECTORY: + case EmberLib.COMMAND_GETDIRECTORY: this.handleGetDirectory(client, element); break; - case ember.COMMAND_SUBSCRIBE: + case EmberLib.COMMAND_SUBSCRIBE: this.handleSubscribe(client, element); break; - case ember.COMMAND_UNSUBSCRIBE: + case EmberLib.COMMAND_UNSUBSCRIBE: this.handleUnSubscribe(client, element); break; - case ember.COMMAND_INVOKE: + case EmberLib.COMMAND_INVOKE: this.handleInvoke(client, cmd.invocation, element); break; default: this.server.emit("error", new Error(`invalid command ${cmd.number}`)); - break; + return; + } + let identifier = "root" + if (!element.isRoot()) { + const node = this.server.tree.getElementByPath(element.getPath()); + identifier = node == null || node.contents == null || node.contents.identifier == null ? "unknown" : node.contents.identifier; } + this.server.emit("event", `${EmberLib.COMMAND_STRINGS[cmd.number]} to ${identifier}(path: ${element.getPath()})`); } /** @@ -77,7 +84,7 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} element */ handleInvoke(client, invocation, element) { - const result = new ember.InvocationResult(); + const result = new EmberLib.InvocationResult(); result.invocationId = invocation.id; if (element == null || !element.isFunction()) { result.setFailure(); @@ -91,7 +98,7 @@ class ElementHandlers extends QualifiedHandlers{ result.setFailure(); } } - const res = new ember.Root(); + const res = new EmberLib.Root(); res.addResult(result); client.sendBERNode(res); } @@ -145,8 +152,8 @@ class ElementHandlers extends QualifiedHandlers{ else if ((cmd.isParameter()) && (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { if (this.server._debug) { console.log(`setValue for element at path ${path} with value ${cmd.contents.value}`); } - this.setValue(element, cmd.contents.value, client); - let res = this.server.getResponse(element); + this.server.setValue(element, cmd.contents.value, client); + const res = this.server.getResponse(element); client.sendBERNode(res) this.server.updateSubscribers(element.getPath(), res, client); } diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 5407b46..6b248a4 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -247,22 +247,21 @@ class TreeServer extends EventEmitter{ if (element.contents == null) { return resolve(); } - if (element.isParameter()) { - if ((element.contents.access !== undefined) && + if (element.isParameter() || element.isMatrix()) { + if (element.isParameter() && + (element.contents.access !== undefined) && (element.contents.access.value > 1)) { element.contents.value = value; const res = this.getResponse(element); - this.updateSubscribers(element.getPath(),res, origin); - this.emit("value-change", element); + this.updateSubscribers(element.getPath(),res, origin); } - } - else if (element.isMatrix()) { - if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { + else if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { element.contents[key] = value; const res = this.getResponse(element); this.updateSubscribers(element.getPath(),res, origin); - this.emit("value-change", element); } + this.emit("value-change", element); + this.emit("event", `set value for ${element.contents.identifier}(${element.getPath()})` ); } return resolve(); }); diff --git a/EmberServer/MatrixHandlers.js b/EmberServer/MatrixHandlers.js index 58e7391..28dc3fd 100755 --- a/EmberServer/MatrixHandlers.js +++ b/EmberServer/MatrixHandlers.js @@ -65,6 +65,7 @@ class MatrixHandlers { continue; } let connection = connections[id]; + this.server.emit("event", `Matrix connection to ${matrix.contents.identifier}(path: ${matrix.getPath()}) target ${id} connections: ${connection.sources.toString()}`); conResult = new ember.MatrixConnection(connection.target); let emitType; res.connections[connection.target] = conResult; diff --git a/README.md b/README.md index 332997f..39c937b 100755 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ client.connect() // Get Root info .then(() => client.getDirectory()) // Get a Specific Node - .then(() => client.getNodeByPathnum("0.0.2")) + .then(() => client.getElementByPath("0.0.2")) .then(node => { console.log(node); }) // Get a node by its path identifiers - .then(() => client.getNodeByPath("path/to/node")) + .then(() => client.getElementByPath("path/to/node")) .then(node => { console.log(node); }) @@ -55,23 +55,23 @@ const client = new EmberClient(HOST, PORT); client.connect()) .then(() => client.getDirectory()) .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) - .then(() => client.getNodeByPath("scoreMaster/router/labels/group 1")) + .then(() => client.getElementByPath("scoreMaster/router/labels/group 1")) .then(node => { // For streams, use subscribe return client.subscribe(node, update => { console.log(udpate); }); }) - .then(() => client.getNodeByPathnum("0.2")) + .then(() => client.getElementByPath("0.2")) .then(node => { // For non-streams a getDirectory will automatically subscribe for update return client.getDirectory(node, update => { console.log(udpate); }); }) - // You can also provide a callback to the getNodeNyPath + // You can also provide a callback to the getElementByPath // Be carefull that subscription will be done for all elements in the path - .then(() => client.getNodeByPathnum("0.3", update => {console.log(update);})) + .then(() => client.getElementByPath("0.3", update => {console.log(update);})) ; ``` @@ -102,14 +102,14 @@ const {EmberClient, EmberLib} = require('node-emberplus'); const client = new EmberClient(HOST, PORT); client.connect() .then(() => client.getDirectory()) - .then(() => client.getNodeByPathnum("0.1.0")) + .then(() => client.getElementByPath("0.1.0")) .then(matrix => { console.log("Connecting source 1 to target 0); return client.matrixConnect(matrix, 0, [1]); }) .then(() => client.matrixDisconnect(matrix, 0, [1])) .then(() => client.matrixSetConnection(matrix, 0, [0,1])) - .then(matrix => client.getNodeByPathnum(matrix.getPath())) + .then(matrix => client.getElementByPath(matrix.getPath())) .then(() => client.disconnect()); ``` @@ -145,6 +145,9 @@ server.on("matrix-connect", info => { server.on("matrix-change", info => { console.log(`Client ${info.client} changed ${info.target} and ${info.sources}`); } +server.on("event", txt => { + console.log("event: " + txt); +}) server.listen().then(() => { console.log("listening"); }).catch((e) => { console.log(e.stack); }); ``` diff --git a/package.json b/package.json index bce4fa1..7f634e0 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.0.0", + "version": "2.1.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/test/Server.test.js b/test/Server.test.js index 4c9ac92..d11a682 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -69,11 +69,11 @@ describe("server", function() { }) .then(() => { expect(client.root.getElementByNumber(0).elements.size).toBe(jsonTree[0].children.length); - return client.getDirectory(client.root.getElementByNumber(0).getElementByNumber(0)); + return client.getDirectory(client.root.getElementByPath("0.0")); }) .then(() => { - expect(client.root.getElementByNumber(0).getElementByNumber(0).elements.size).toBe(4); - expect(client.root.getElementByNumber(0).getElementByNumber(0).getElementByNumber(3).contents.identifier).toBe("author"); + expect(client.root.getElementByPath("0.0").elements.size).toBe(4); + expect(client.root.getElementByPath("0.0.3").contents.identifier).toBe("author"); // Issue #33 EmberServer.handleGetDirectory does not subscribe to child parameters expect(server.subscribers["0.0.0"]).toBeDefined(); // Keepalive @@ -90,11 +90,11 @@ describe("server", function() { }) .then(() => client.expand(client.root.getElementByNumber(0))) .then(() => { - expect(server.tree.getElementByNumber(0).getElementByNumber(0).getElementByNumber(1).contents.value).not.toBe("gdnet"); - return client.setValue(client.root.getElementByNumber(0).getElementByNumber(0).getElementByNumber(1), "gdnet"); + expect(server.tree.getElementByPath("0.0.1").contents.value).not.toBe("gdnet"); + return client.setValue(client.root.getElementByPath("0.0.1"), "gdnet"); }) .then(() => { - expect(server.tree.getElementByNumber(0).getElementByNumber(0).getElementByNumber(1).contents.value).toBe("gdnet"); + expect(server.tree.getElementByPath("0.0.1").contents.value).toBe("gdnet"); return client.disconnect(); }); }); @@ -109,7 +109,7 @@ describe("server", function() { }) .then(() => client.expand(client.root.getElementByNumber(0))) .then(() => { - const func = client.root.getElementByNumber(0).getElementByNumber(2); + const func = client.root.getElementByPath("0.2"); return client.invokeFunction(func, [ new ember.FunctionArgument(ember.ParameterType.integer, 1), new ember.FunctionArgument(ember.ParameterType.integer, 7) @@ -146,6 +146,27 @@ describe("server", function() { return client.disconnect(); }); }); + it("should be able to get child with getElementByPath", function() { + //server._debug = true; + client = new EmberClient(LOCALHOST, PORT); + //client._debug = true; + //client._debug = true; + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + console.log("client connected"); + return client.getDirectory(); + }) + .then(() => client.getElementByPath("scoreMaster/identity/product")) + .then(child => { + console.log(child); + return client.getElementByPath("scoreMaster/router/labels/group 1"); + }) + .then(child => { + console.log("router/labels", child); + return client.disconnect(); + }); + }); it("should throw an error if getNodeByPath for unknown path", function() { //server._debug = true; client = new EmberClient(LOCALHOST, PORT); @@ -168,27 +189,86 @@ describe("server", function() { }); it("should be able to make a matrix connection", () => { client = new EmberClient(LOCALHOST, PORT); - //client._debug = true; return Promise.resolve() .then(() => client.connect()) .then(() => { return client.getDirectory(); }) - .then(() => client.getNodeByPathnum("0.1.0")) - .then(matrix => { - console.log(matrix); - client._debug = true; - server._debug = true; - return client.matrixConnect(matrix, 0, [1]); + .then(() => client.getElementByPath("0.1.0")) + .then(matrix => client.matrixConnect(matrix, 0, [1])) + .then(matrix => { + return client.getElementByPath(matrix.getPath()); }) - .then(matrix => client.getNodeByPathnum(matrix.getPath())) .then(matrix => { console.log(matrix); expect(matrix.connections['0'].sources).toBeDefined(); expect(matrix.connections['0'].sources.length).toBe(1); - expect(matrix.connections['0'].sources[0]).toBe(1); - return client.disconnect(); - }); + expect(matrix.connections['0'].sources[0]).toBe(1); + }) + .then(() => client.disconnect()); + }); + it("should generate events on command and matrix connection", () => { + client = new EmberClient(LOCALHOST, PORT); + let count = 0; + let receivedEvent = null; + const eventHandler = event => { + count++; + receivedEvent = event; + } + return Promise.resolve() + .then(() => client.connect()) + .then(() => { + count = 0; + server.on("event", eventHandler); + return client.getDirectory(); + }) + .then(() => { + expect(count).toBe(1); + expect(receivedEvent).toMatch(/getdirectory to root/); + return client.getElementByPath("0.1.0"); + }) + .then(matrix => { + count = 0; + return client.matrixConnect(matrix, 0, [1]); + }) + .then(() => { + expect(count).toBe(1); + expect(receivedEvent).toMatch(/Matrix connection to matrix/); + }) + .then(() => { + count = 0; + const func = client.root.getElementByPath("0.2"); + return client.invokeFunction(func, [ + new ember.FunctionArgument(ember.ParameterType.integer, 1), + new ember.FunctionArgument(ember.ParameterType.integer, 7) + ]); + }) + .then(() => { + expect(count).toBe(1); + expect(receivedEvent).toMatch(/invoke to /); + }) + .then(() => client.getNodeByPathnum("0.0.2")) + .then(parameter => { + server._subscribe = server.subscribe; + let _resolve; + const p = new Promise((resolve, reject) => { + _resolve = resolve; + }); + server.subscribe = (c,e) => { + server._subscribe(c,e); + _resolve(); + }; + count = 0; + return client.subscribe(parameter).then(() => (p)) + }) + .then(() => { + expect(count).toBe(1); + expect(receivedEvent).toMatch(/subscribe to version/); + }) + .then(() => { + server.off("event", eventHandler); + }) + .then(() => client.disconnect()); }); }); describe("Matrix Connect", function() { @@ -205,7 +285,7 @@ describe("server", function() { disconnectCount++; } server.on("matrix-disconnect", handleDisconnect.bind(this)); - const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); + const matrix = server.tree.getElementByPath("0.1.0"); let connection = new ember.MatrixConnection(0); connection.setSources([1]); connection.operation = ember.MatrixOperation.connect; @@ -237,7 +317,7 @@ describe("server", function() { expect(res).toBeTruthy(); }); it("should verify if connection allowed in 1-to-1", function() { - const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); + const matrix = server.tree.getElementByPath("0.1.0"); let disconnectCount = 0; const handleDisconnect = info => { disconnectCount++; @@ -269,7 +349,7 @@ describe("server", function() { server.off("matrix-disconnect", handleDisconnect); }); it("should disconnect if trying to connect same source and target in 1-to-1", function() { - const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); + const matrix = server.tree.getElementByPath("0.1.0"); let disconnectCount = 0; const handleDisconnect = info => { disconnectCount++; @@ -285,7 +365,7 @@ describe("server", function() { expect(disconnectCount).toBe(1); }); it("should be able to lock a connection", function() { - const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); + const matrix = server.tree.getElementByPath("0.1.0"); let disconnectCount = 0; const handleDisconnect = info => { disconnectCount++; @@ -303,7 +383,7 @@ describe("server", function() { expect(disconnectCount).toBe(0); }); it("should verify if connection allowed in N-to-N", function() { - const matrix = server.tree.getElementByNumber(0).getElementByNumber(1).getElementByNumber(0); + const matrix = server.tree.getElementByPath("0.1.0"); matrix.contents.type = ember.MatrixType.nToN; matrix.contents.maximumTotalConnects = 2; matrix.setSources(0, [0,1]); @@ -407,7 +487,7 @@ describe("server", function() { return server.close(); }); it("should not auto subscribe stream parameter", function() { - const parameter = server.tree.getElementByNumber(0).getElementByNumber(0).getElementByNumber(2); + const parameter = server.tree.getElementByPath("0.0.2"); console.log(parameter); expect(parameter.isStream()).toBeTruthy(); expect(server.subscribers["0.0.2"]).not.toBeDefined(); From 8be7a6217f9da806a30c0e77d781be165061d74a Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 7 Jan 2020 14:51:33 +0100 Subject: [PATCH 129/162] Change server event format to include source ip:port --- EmberServer/ElementHandlers.js | 3 ++- EmberServer/EmberServer.js | 3 ++- EmberServer/MatrixHandlers.js | 3 ++- package.json | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/EmberServer/ElementHandlers.js b/EmberServer/ElementHandlers.js index 8cbfcce..db51289 100755 --- a/EmberServer/ElementHandlers.js +++ b/EmberServer/ElementHandlers.js @@ -40,7 +40,8 @@ class ElementHandlers extends QualifiedHandlers{ const node = this.server.tree.getElementByPath(element.getPath()); identifier = node == null || node.contents == null || node.contents.identifier == null ? "unknown" : node.contents.identifier; } - this.server.emit("event", `${EmberLib.COMMAND_STRINGS[cmd.number]} to ${identifier}(path: ${element.getPath()})`); + const src = client == null ? "local" : `${client.socket.remoteAddress}:${client.socket.remotePort}`; + this.server.emit("event", `${EmberLib.COMMAND_STRINGS[cmd.number]} to ${identifier}(path: ${element.getPath()}) from ${src}`); } /** diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 6b248a4..5a5e33c 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -260,8 +260,9 @@ class TreeServer extends EventEmitter{ const res = this.getResponse(element); this.updateSubscribers(element.getPath(),res, origin); } + const src = origin == null ? "local" : `${origin.socket.remoteAddress}:${origin.socket.remotePort}`; this.emit("value-change", element); - this.emit("event", `set value for ${element.contents.identifier}(${element.getPath()})` ); + this.emit("event", `set value for ${element.contents.identifier}(${element.getPath()}) from ${src}` ); } return resolve(); }); diff --git a/EmberServer/MatrixHandlers.js b/EmberServer/MatrixHandlers.js index 28dc3fd..6ce6bd1 100755 --- a/EmberServer/MatrixHandlers.js +++ b/EmberServer/MatrixHandlers.js @@ -65,7 +65,8 @@ class MatrixHandlers { continue; } let connection = connections[id]; - this.server.emit("event", `Matrix connection to ${matrix.contents.identifier}(path: ${matrix.getPath()}) target ${id} connections: ${connection.sources.toString()}`); + const src = client == null ? "local" : `${client.socket.remoteAddress}:${client.socket.remotePort}`; + this.server.emit("event", `Matrix connection to ${matrix.contents.identifier}(path: ${matrix.getPath()}) target ${id} connections: ${connection.sources.toString()} from ${src}`); conResult = new ember.MatrixConnection(connection.target); let emitType; res.connections[connection.target] = conResult; diff --git a/package.json b/package.json index 7f634e0..ef0dd57 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.1.0", + "version": "2.2.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 1106a8b1ee4c24cdc74093b0e8e332b5ac4c8d62 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 7 Jan 2020 14:54:57 +0100 Subject: [PATCH 130/162] Fix setValue event to be inline with the other events --- EmberServer/EmberServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 5a5e33c..f9d8bc9 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -262,7 +262,7 @@ class TreeServer extends EventEmitter{ } const src = origin == null ? "local" : `${origin.socket.remoteAddress}:${origin.socket.remotePort}`; this.emit("value-change", element); - this.emit("event", `set value for ${element.contents.identifier}(${element.getPath()}) from ${src}` ); + this.emit("event", `set value for ${element.contents.identifier}(path: ${element.getPath()}) from ${src}` ); } return resolve(); }); From 1939824c33d177ae869318a631806dce22e28c37 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 8 Jan 2020 11:02:06 +0100 Subject: [PATCH 131/162] Fixed eslint errors --- EmberClient/EmberClient.js | 147 +++++++++++++--------------- EmberLib/Command.js | 1 + EmberLib/Elements.js | 8 -- EmberLib/Function.js | 4 + EmberLib/FunctionContent.js | 3 +- EmberLib/Invocation.js | 1 + EmberLib/InvocationResult.js | 1 + EmberLib/Label.js | 1 + EmberLib/Matrix.js | 6 +- EmberLib/MatrixConnection.js | 1 + EmberLib/MatrixContents.js | 3 +- EmberLib/MatrixNode.js | 1 + EmberLib/Node.js | 3 +- EmberLib/NodeContents.js | 1 + EmberLib/Parameter.js | 1 + EmberLib/ParameterContents.js | 3 +- EmberLib/QualifiedFunction.js | 3 +- EmberLib/QualifiedMatrix.js | 5 +- EmberLib/QualifiedNode.js | 4 +- EmberLib/QualifiedParameter.js | 6 +- EmberLib/StringIntegerCollection.js | 1 + EmberLib/TreeNode.js | 21 +--- EmberLib/index.js | 6 +- EmberServer/ElementHandlers.js | 19 ++-- EmberServer/EmberServer.js | 60 +++++++++--- EmberServer/MatrixHandlers.js | 16 +-- EmberSocket/S101Client.js | 2 +- EmberSocket/S101Socket.js | 4 +- package.json | 2 +- 29 files changed, 171 insertions(+), 163 deletions(-) delete mode 100755 EmberLib/Elements.js diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index ead404f..8420c6e 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -3,6 +3,7 @@ const S101Client = require('../EmberSocket').S101Socket; const ember = require('../EmberLib'); const BER = require('../ber.js'); const errors = require('../errors.js'); +const {Logger, LogLevel} = require("../Logger"); const DEFAULT_PORT = 9000; const DEFAULT_TIMEOUT = 3000; @@ -31,6 +32,14 @@ class EmberClient extends EventEmitter { this.timeoutValue = DEFAULT_TIMEOUT; /** @type {Root} */ this.root = new ember.Root(); + this.logger = new Logger(); + this.logLevel = LogLevel.INFO; + this._loggers = { + debug: (...args) => this._log(LogLevel.DEBUG, ...args), + error: (...args) => this._log(LogLevel.ERROR, ...args), + info: (...args) => this._log(LogLevel.INFO, ...args), + warn: (...args) => this._log(LogLevel.WARN, ...args) + }; this._client.on('connecting', () => { this.emit('connecting'); @@ -58,23 +67,17 @@ class EmberClient extends EventEmitter { try { if (root instanceof ember.InvocationResult) { this.emit('invocationResult', root); - if (this._debug) { - console.log("Received InvocationResult", root); - } + this.log.debug("Received InvocationResult", root); } else { this._handleRoot(root); - if (this._debug) { - console.log("Received root", root); - } + this.log.debug("Received root", root); } if (this._callback) { this._callback(undefined, root); } } catch(e) { - if (this._debug) { - console.log(e, root); - } + this.log.debug(e, root); if (this._callback) { this._callback(e); } @@ -82,13 +85,33 @@ class EmberClient extends EventEmitter { }); } + /** + * + * @param {Array} params + * @private + */ + _log(...params) { + if ((params.length > 1) && (Number(params[0]) <= this.logLevel)) { + const msg = params.slice(1); + this.logger[Logger.LogLevel[params[0]]](`[${Logger.LogLevel[params[0]]}]:`, ...msg); + } + } + + /** + * + * @returns {{debug: (function(...[*]): void), error: (function(...[*]): void), info: (function(...[*]): void), warn: (function(...[*]): void)}|*} + */ + get log() { + return this._loggers; + } + _finishRequest() { this._clearTimeout(); this._activeRequest = null; try { this._makeRequest(); } catch(e) { - if (this._debug) {console.log(e);} + this.log.debug(e); if (this._callback != null) { this._callback(e); } @@ -102,9 +125,7 @@ class EmberClient extends EventEmitter { const req = `${ this._requestID++} - ${this._activeRequest.node.getPath()}`; this._activeRequest.timeoutError = new errors.EmberTimeoutError(`Request ${req} timed out`) - if (this._debug) { - console.log(`Making request ${req}`, Date.now()); - } + this.log.debug(`Making request ${req}`, Date.now()); this._timeout = setTimeout(() => { this._timeoutRequest(); }, this.timeoutValue); @@ -112,7 +133,7 @@ class EmberClient extends EventEmitter { } } - _timeoutRequest(id) { + _timeoutRequest() { this._activeRequest.func(this._activeRequest.timeoutError); } @@ -123,14 +144,14 @@ class EmberClient extends EventEmitter { addRequest(req) { this._pendingRequests.push(req); this._makeRequest(); - }; + } _clearTimeout() { if (this._timeout != null) { clearTimeout(this._timeout); this._timeout = null; } - }; + } /** * @@ -207,9 +228,7 @@ class EmberClient extends EventEmitter { * @param {TreeNode} root */ _handleRoot (root) { - if (this._debug) { - console.log("handling root", JSON.stringify(root)); - } + this.log.debug("handling root", JSON.stringify(root)); this.root.update(root); if (root.elements !== undefined) { const elements = root.getChildren(); @@ -247,11 +266,14 @@ class EmberClient extends EventEmitter { }); } + /** + * + */ disconnect() { if (this._client != null) { return this._client.disconnect(); } - }; + } /** * @@ -269,9 +291,7 @@ class EmberClient extends EventEmitter { return this.getDirectory(node, callback).then((res) => { let children = node.getChildren(); if ((res === undefined) || (children === undefined) || (children === null)) { - if (this._debug) { - console.log("No more children for ", node); - } + this.log.debug("No more children for ", node); return; } let p = Promise.resolve(); @@ -280,9 +300,7 @@ class EmberClient extends EventEmitter { // Parameter can only have a single child of type Command. continue; } - if (this._debug) { - console.log("Expanding child", child); - } + this.log.debug("Expanding child", child); p = p.then(() => { return this.expand(child).catch((e) => { // We had an error on some expansion @@ -317,15 +335,11 @@ class EmberClient extends EventEmitter { this._callback = (error, node) => { const requestedPath = qnode.getPath(); if (node == null) { - if (this._debug) { - console.log(`received null response for ${requestedPath}`); - } + this.log.debug(`received null response for ${requestedPath}`); return; } if (error) { - if (this._debug) { - console.log("Received getDirectory error", error); - } + this.log.debug("Received getDirectory error", error); this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); reject(error); @@ -334,9 +348,7 @@ class EmberClient extends EventEmitter { if (qnode.isRoot()) { const elements = qnode.getChildren(); if (elements == null || elements.length === 0) { - if (this._debug) { - console.log("getDirectory response", node); - } + this.log.debug("getDirectory response", node); return this._callback(new Error("Invalid qnode for getDirectory")); } @@ -344,9 +356,7 @@ class EmberClient extends EventEmitter { if (nodeElements != null && nodeElements.every(el => el._parent instanceof ember.Root)) { - if (this._debug) { - console.log("Received getDirectory response", node); - } + this.log.debug("Received getDirectory response", node); this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); resolve(node); // make sure the info is treated before going to next request. @@ -365,23 +375,18 @@ class EmberClient extends EventEmitter { if (nodeElements != null && ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { - if (this._debug) { - console.log("Received getDirectory response", node); - } + this.log.debug("Received getDirectory response", node); this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); return resolve(node); // make sure the info is treated before going to next request. } - else if (this._debug) { - console.log(node); - console.log(new Error(requestedPath)); + else { + this.log.debug(node); + this.log.debug(new Error(requestedPath)); } } }; - - if (this._debug) { - console.log("Sending getDirectory", qnode); - } + this.log.debug("Sending getDirectory", qnode); this._client.sendBERNode(qnode.getDirectory(callback)); }}); }); @@ -503,25 +508,19 @@ class EmberClient extends EventEmitter { this._finishRequest(); return; } - const cb = (error, result) => { this._clearTimeout(); if (error) { reject(error); } else { - if (this._debug) { - console.log("InvocationResult", result); - } + this.log.debug("InvocationResult", result); resolve(result); } // cleaning callback and making next request. this._finishRequest(); }; - - if (this._debug) { - console.log("Invocking function", fnNode); - } + this.log.debug("Invocking function", fnNode); this._callback = cb; this._client.sendBERNode(fnNode.invoke(params)); }}); @@ -569,15 +568,11 @@ class EmberClient extends EventEmitter { this._callback = (error, node) => { const requestedPath = matrixNode.getPath(); if (node == null) { - if (this._debug) { - console.log(`received null response for ${requestedPath}`); - } + this.log.debug(`received null response for ${requestedPath}`); return; } if (error) { - if (this._debug) { - console.log("Received getDirectory error", error); - } + this.log.debug("Received getDirectory error", error); this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); reject(error); @@ -593,10 +588,8 @@ class EmberClient extends EventEmitter { resolve(matrix); } else { - if (this._debug) { - console.log(`unexpected node response during matrix connect ${requestedPath}`, + this.log.debug(`unexpected node response during matrix connect ${requestedPath}`, matrix == null ? null : JSON.stringify(matrix.toJSON(), null, 4)); - } } } this._client.sendBERNode(matrixNode.connect(connections)); @@ -677,9 +670,7 @@ class EmberClient extends EventEmitter { }; this._callback = cb; - if (this._debug) { - console.log('setValue sending ...', node.getPath(), value); - } + this.log.debug('setValue sending ...', node.getPath(), value); this._client.sendBERNode(node.setValue(value)); }}); } @@ -698,17 +689,18 @@ class EmberClient extends EventEmitter { qnode = this.root; } return new Promise((resolve, reject) => { - this.addRequest({node: qnode, func: (error) => { - if (this._debug) { - console.log("Sending subscribe", qnode); - } + this.addRequest({node: qnode, func: error => { + if (error != null) { + return reject(error); + } + this.log.debug("Sending subscribe", qnode); this._client.sendBERNode(qnode.subscribe(callback)); this._finishRequest(); resolve(); }}); }); } else { - node.addCallback(callback); + qnode.addCallback(callback); } } @@ -724,10 +716,11 @@ class EmberClient extends EventEmitter { qnode = this.root; } return new Promise((resolve, reject) => { - this.addRequest({node: qnode, func: (error) => { - if (this._debug) { - console.log("Sending subscribe", qnode); - } + this.addRequest({node: qnode, func: (error) => { + if (error != null) { + return reject(error); + } + this.log.debug("Sending subscribe", qnode); this._client.sendBERNode(qnode.unsubscribe(callback)); this._finishRequest(); resolve(); diff --git a/EmberLib/Command.js b/EmberLib/Command.js index c3247b0..3ff9d6a 100755 --- a/EmberLib/Command.js +++ b/EmberLib/Command.js @@ -3,6 +3,7 @@ const Enum = require('enum'); const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); const BER = require('../ber.js'); const Invocation = require("./Invocation"); +const errors = require("../errors"); const FieldFlags = new Enum({ sparse: -2, diff --git a/EmberLib/Elements.js b/EmberLib/Elements.js deleted file mode 100755 index 1826ae5..0000000 --- a/EmberLib/Elements.js +++ /dev/null @@ -1,8 +0,0 @@ -"use stricts"; - -class Elements { - constructor() { - this._elements = new Map(); - } - -} \ No newline at end of file diff --git a/EmberLib/Function.js b/EmberLib/Function.js index 3ccb79b..ddc4aa4 100755 --- a/EmberLib/Function.js +++ b/EmberLib/Function.js @@ -2,6 +2,10 @@ const TreeNode = require("./TreeNode"); const QualifiedFunction = require("./QualifiedFunction"); const BER = require('../ber.js'); +const Command = require("./Command"); +const {COMMAND_INVOKE} = require("./constants"); +const FunctionContent = require("./FunctionContent"); +const errors = require("../errors"); class Function extends TreeNode { constructor(number, func) { diff --git a/EmberLib/FunctionContent.js b/EmberLib/FunctionContent.js index aab06ee..f5e040b 100755 --- a/EmberLib/FunctionContent.js +++ b/EmberLib/FunctionContent.js @@ -1,6 +1,7 @@ "use strict"; const BER = require('../ber.js'); const FunctionArgument = require("./FunctionArgument"); +const errors = require("../errors"); class FunctionContent { constructor() { @@ -42,7 +43,7 @@ class FunctionContent { if(this.result != null) { ber.startSequence(BER.CONTEXT(3)); ber.startSequence(BER.EMBER_SEQUENCE); - for(var i = 0; i < this.result.length; i++) { + for(let i = 0; i < this.result.length; i++) { ber.startSequence(BER.CONTEXT(0)); this.result[i].encode(ber); ber.endSequence(); diff --git a/EmberLib/Invocation.js b/EmberLib/Invocation.js index e12130b..b114e0a 100755 --- a/EmberLib/Invocation.js +++ b/EmberLib/Invocation.js @@ -2,6 +2,7 @@ const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); const BER = require('../ber.js'); const FunctionArgument = require("./FunctionArgument"); +const errors = require("../errors"); let _id = 1; class Invocation { diff --git a/EmberLib/InvocationResult.js b/EmberLib/InvocationResult.js index 5e9505d..901c60d 100755 --- a/EmberLib/InvocationResult.js +++ b/EmberLib/InvocationResult.js @@ -3,6 +3,7 @@ const BER = require('../ber.js'); const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); const FunctionArgument = require("./FunctionArgument"); +const errors = require("../errors"); class InvocationResult { diff --git a/EmberLib/Label.js b/EmberLib/Label.js index 069e84d..3c6d648 100755 --- a/EmberLib/Label.js +++ b/EmberLib/Label.js @@ -1,5 +1,6 @@ "use strict"; const BER = require('../ber.js'); +const errors = require("../errors"); class Label { constructor(path, description) { diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js index c7b9b79..e0c7980 100755 --- a/EmberLib/Matrix.js +++ b/EmberLib/Matrix.js @@ -5,7 +5,6 @@ const BER = require('../ber.js'); const MatrixMode = require("./MatrixMode"); const MatrixOperation = require("./MatrixOperation"); const MatrixType = require("./MatrixType"); -const MatrixNode = require("./MatrixNode"); class Matrix extends TreeNode { @@ -176,8 +175,7 @@ class Matrix extends TreeNode */ static canConnect(matrixNode, targetID, sources, operation) { const type = matrixNode.contents.type == null ? MatrixType.oneToN : matrixNode.contents.type; - const mode = matrixNode.contents.mode == null ? MatrixConnection.linear : matrixNode.contents.mode; - const connection = matrixNode.connections[targetID];; + const connection = matrixNode.connections[targetID]; const oldSources = connection == null || connection.sources == null ? [] : connection.sources.slice(); const newSources = operation === MatrixOperation.absolute ? sources : oldSources.concat(sources); const sMap = new Set(newSources.map(i => Number(i))); @@ -278,7 +276,7 @@ class Matrix extends TreeNode sources.push(seq.readInt()); } return sources; - }; + } /** * diff --git a/EmberLib/MatrixConnection.js b/EmberLib/MatrixConnection.js index 4e78e45..51a817a 100755 --- a/EmberLib/MatrixConnection.js +++ b/EmberLib/MatrixConnection.js @@ -2,6 +2,7 @@ const BER = require('../ber.js'); const MatrixOperation = require("./MatrixOperation"); const MatrixDisposition = require("./MatrixDisposition"); +const errors = require("../errors"); class MatrixConnection { /** diff --git a/EmberLib/MatrixContents.js b/EmberLib/MatrixContents.js index 1e8df10..1f1d1a5 100755 --- a/EmberLib/MatrixContents.js +++ b/EmberLib/MatrixContents.js @@ -4,6 +4,7 @@ const MatrixType = require("./MatrixType"); const MatrixMode = require("./MatrixMode"); const BER = require('../ber.js'); const Label = require("./Label"); +const errors = require("../errors"); class MatrixContents { constructor(type = MatrixType.oneToN, mode = MatrixMode.linear) { @@ -147,7 +148,7 @@ class MatrixContents { } } return mc; - }; + } } module.exports = MatrixContents; \ No newline at end of file diff --git a/EmberLib/MatrixNode.js b/EmberLib/MatrixNode.js index 8db2f84..5f309e2 100755 --- a/EmberLib/MatrixNode.js +++ b/EmberLib/MatrixNode.js @@ -4,6 +4,7 @@ const Matrix = require("./Matrix"); const MatrixContents = require("./MatrixContents"); const QualifiedMatrix = require("./QualifiedMatrix"); const BER = require('../ber.js'); +const errors = require("../errors"); class MatrixNode extends Matrix { constructor(number = undefined) { diff --git a/EmberLib/Node.js b/EmberLib/Node.js index 68fe845..1617b28 100755 --- a/EmberLib/Node.js +++ b/EmberLib/Node.js @@ -4,6 +4,7 @@ const Element = require("./Element"); const QualifiedNode = require("./QualifiedNode"); const NodeContents = require("./NodeContents"); const BER = require('../ber.js'); +const errors = require("../errors"); class Node extends Element { /** @@ -65,6 +66,6 @@ class Node extends Element { } return n; } -}; +} module.exports = Node; diff --git a/EmberLib/NodeContents.js b/EmberLib/NodeContents.js index a34fc48..9260ed9 100755 --- a/EmberLib/NodeContents.js +++ b/EmberLib/NodeContents.js @@ -1,5 +1,6 @@ "use strict"; const BER = require('../ber.js'); +const errors = require("../errors"); class NodeContents{ constructor() { diff --git a/EmberLib/Parameter.js b/EmberLib/Parameter.js index f89d3f6..7357754 100755 --- a/EmberLib/Parameter.js +++ b/EmberLib/Parameter.js @@ -4,6 +4,7 @@ const Element = require("./Element"); const QualifiedParameter = require("./QualifiedParameter"); const BER = require('../ber.js'); const ParameterContents = require("./ParameterContents"); +const errors = require("../errors"); class Parameter extends Element { /** diff --git a/EmberLib/ParameterContents.js b/EmberLib/ParameterContents.js index b213299..7e79bbd 100755 --- a/EmberLib/ParameterContents.js +++ b/EmberLib/ParameterContents.js @@ -3,8 +3,9 @@ const {ParameterType} = require("./ParameterType"); const ParameterAccess = require("./ParameterAccess"); const StringIntegerCollection = require("./StringIntegerCollection"); - +const StreamDescription = require("./StreamDescription"); const BER = require('../ber.js'); +const errors = require("../errors"); class ParameterContents { constructor(value, type) { diff --git a/EmberLib/QualifiedFunction.js b/EmberLib/QualifiedFunction.js index 37cab00..96966ab 100755 --- a/EmberLib/QualifiedFunction.js +++ b/EmberLib/QualifiedFunction.js @@ -5,6 +5,7 @@ const FunctionContent = require("./FunctionContent"); const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); const BER = require('../ber.js'); const Invocation = require("./Invocation"); +const errors = require("../errors"); class QualifiedFunction extends QualifiedElement { /** @@ -22,7 +23,7 @@ class QualifiedFunction extends QualifiedElement { * * @returns {TreeNode} */ - getDirectory(callback) { + getDirectory() { return this.getCommand(COMMAND_GETDIRECTORY); } diff --git a/EmberLib/QualifiedMatrix.js b/EmberLib/QualifiedMatrix.js index 538e76c..7c15ed6 100755 --- a/EmberLib/QualifiedMatrix.js +++ b/EmberLib/QualifiedMatrix.js @@ -6,6 +6,7 @@ const BER = require('../ber.js'); const Command = require("./Command"); const MatrixContents = require("./MatrixContents"); const MatrixConnection = require("./MatrixConnection"); +const errors = require("../errors"); class QualifiedMatrix extends Matrix { /** @@ -137,9 +138,9 @@ class QualifiedMatrix extends Matrix { } else if(tag == BER.CONTEXT(2)) { qm.decodeChildren(seq); } else if (tag == BER.CONTEXT(3)) { - qm.targets = decodeTargets(seq); + qm.targets = Matrix.decodeTargets(seq); } else if (tag == BER.CONTEXT(4)) { - qm.sources = decodeSources(seq); + qm.sources = Matrix.decodeSources(seq); } else if (tag == BER.CONTEXT(5)) { qm.connections = {}; seq = seq.getSequence(BER.EMBER_SEQUENCE); diff --git a/EmberLib/QualifiedNode.js b/EmberLib/QualifiedNode.js index 0776cff..7dce7a5 100755 --- a/EmberLib/QualifiedNode.js +++ b/EmberLib/QualifiedNode.js @@ -2,8 +2,8 @@ const QualifiedElement = require("./QualifiedElement"); const BER = require('../ber.js'); const NodeContents = require("./NodeContents"); -const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); -const Command = require("./Command"); +const Node = require("./Node"); +const errors = require("../errors"); class QualifiedNode extends QualifiedElement { constructor (path) { diff --git a/EmberLib/QualifiedParameter.js b/EmberLib/QualifiedParameter.js index 85a748f..717c8d7 100755 --- a/EmberLib/QualifiedParameter.js +++ b/EmberLib/QualifiedParameter.js @@ -1,11 +1,9 @@ "use strict"; const QualifiedElement = require("./QualifiedElement"); -const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); const ParameterContents = require("./ParameterContents"); const BER = require('../ber.js'); -const Command = require("./Command"); - +const Parameter = require("./Parameter"); class QualifiedParameter extends QualifiedElement { /** @@ -34,7 +32,7 @@ class QualifiedParameter extends QualifiedElement { const p = new Parameter(number); if (complete) { if (this.contents != null) { - p = this.contents; + p.contents = this.contents; } } return p; diff --git a/EmberLib/StringIntegerCollection.js b/EmberLib/StringIntegerCollection.js index 5bd00ea..a341145 100755 --- a/EmberLib/StringIntegerCollection.js +++ b/EmberLib/StringIntegerCollection.js @@ -1,6 +1,7 @@ "use strict"; const Element = require("./Element"); const BER = require('../ber.js'); +const errors = require("../errors"); class StringIntegerCollection extends Element { constructor() { diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index c07849a..94caf34 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -177,7 +177,7 @@ class TreeNode { obj.contents= this.contents; } return obj; - }; + } /** * @returns {TreeNode} */ @@ -271,13 +271,6 @@ class TreeNode { getParent() { return this._parent; } - - /** - * @returns {string} - */ - getPath() { - return ""; - } /** * @@ -370,11 +363,11 @@ class TreeNode { return; } - const child = this.getElement(path[0]); + let child = this.getElement(path[0]); if(child !== null) { child.getNodeByPath(client, path.slice(1), callback); } else { - var cmd = self.getDirectory((error, node) => { + const cmd = this.getDirectory((error, node) => { if(error) { callback(error); } @@ -479,7 +472,7 @@ class TreeNode { } } return res; - }; + } /** * @@ -498,11 +491,7 @@ class TreeNode { } } } - return; - } - - static decode(ber) { - + return; } /** diff --git a/EmberLib/index.js b/EmberLib/index.js index d3a9695..92bf00c 100755 --- a/EmberLib/index.js +++ b/EmberLib/index.js @@ -28,6 +28,8 @@ const QualifiedMatrix = require("./QualifiedMatrix"); const QualifiedNode = require("./QualifiedNode"); const QualifiedParameter = require("./QualifiedParameter"); const StringIntegerCollection = require("./StringIntegerCollection"); +const StreamFormat = require("./StreamFormat"); +const StreamDescription = require("./StreamDescription"); const rootDecode = function(ber) { const r = new TreeNode(); @@ -65,7 +67,7 @@ const rootDecode = function(ber) { // continuation of previous message try { var rootReader = ber.getSequence(BER.CONTEXT(0)); - return Element.decode(rootReader) + return childDecode(rootReader) } catch (e) { return r; @@ -143,6 +145,8 @@ module.exports = { QualifiedMatrix, QualifiedNode, QualifiedParameter, + StreamFormat, + StreamDescription, StringIntegerCollection, Subscribe,COMMAND_SUBSCRIBE, Unsubscribe,COMMAND_UNSUBSCRIBE, diff --git a/EmberServer/ElementHandlers.js b/EmberServer/ElementHandlers.js index db51289..c8d7079 100755 --- a/EmberServer/ElementHandlers.js +++ b/EmberServer/ElementHandlers.js @@ -16,8 +16,7 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} root * @param {Command} cmd */ - handleCommand(client, element, cmd) { - + handleCommand(client, element, cmd) { switch(cmd.number) { case EmberLib.COMMAND_GETDIRECTORY: this.handleGetDirectory(client, element); @@ -71,9 +70,7 @@ class ElementHandlers extends QualifiedHandlers{ } const res = this.server.getQualifiedResponse(element); - if (this.server._debug) { - console.log("getDirectory response", res); - } + this.server.log.debug("getDirectory response", res); client.sendBERNode(res); } } @@ -152,7 +149,7 @@ class ElementHandlers extends QualifiedHandlers{ } else if ((cmd.isParameter()) && (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { - if (this.server._debug) { console.log(`setValue for element at path ${path} with value ${cmd.contents.value}`); } + this.server.log.debug(`setValue for element at path ${path} with value ${cmd.contents.value}`); this.server.setValue(element, cmd.contents.value, client); const res = this.server.getResponse(element); client.sendBERNode(res) @@ -160,7 +157,7 @@ class ElementHandlers extends QualifiedHandlers{ } else { this.server.emit("error", new Error("invalid request format")); - if (this.server._debug) { console.log("invalid request format"); } + this.server.log.debug("invalid request format"); return this.server.handleError(client, element.getTreeBranch()); } return path; @@ -172,9 +169,7 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} root */ handleSubscribe(client, element) { - if (this.server._debug) { - console.log("subscribe"); - } + this.server.log.debug("subscribe"); this.server.subscribe(client, element); } @@ -184,9 +179,7 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} root */ handleUnSubscribe(client, element) { - if (this.server._debug) { - console.log("unsubscribe"); - } + this.server.log.debug("unsubscribe"); this.server.unsubscribe(client, element); } } diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index f9d8bc9..516e142 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -3,6 +3,7 @@ const S101Server = require('../EmberSocket').S101Server; const ember = require('../EmberLib'); const JSONParser = require("./JSONParser"); const ElementHandlers = require("./ElementHandlers"); +const {Logger, LogLevel} = require("../Logger"); class TreeServer extends EventEmitter{ /** @@ -21,9 +22,17 @@ class TreeServer extends EventEmitter{ this.clients = new Set(); this.subscribers = {}; this._handlers = new ElementHandlers(this); + this.logger = new Logger(); + this.logLevel = LogLevel.INFO; + this._loggers = { + debug: (...args) => this._log(LogLevel.DEBUG, ...args), + error: (...args) => this._log(LogLevel.ERROR, ...args), + info: (...args) => this._log(LogLevel.INFO, ...args), + warn: (...args) => this._log(LogLevel.WARN, ...args) + }; this.server.on('listening', () => { - if (this._debug) { console.log("listening"); } + this.log.debug("listening"); this.emit('listening'); if (this.callback !== undefined) { this.callback(); @@ -32,10 +41,10 @@ class TreeServer extends EventEmitter{ }); this.server.on('connection', client => { - if (this._debug) { console.log("ember new connection from", client.remoteAddress()); } + this.log.debug("ember new connection from", client.remoteAddress()); this.clients.add(client); client.on("emberTree", (root) => { - if (this._debug) { console.log("ember new request from", client.remoteAddress(), root); } + this.log.debug("ember new request from", client.remoteAddress(), root); // Queue the action to make sure responses are sent in order. client.addRequest(() => { try { @@ -43,7 +52,7 @@ class TreeServer extends EventEmitter{ this.emit("request", {client: client.remoteAddress(), root: root, path: path}); } catch(e) { - if (this._debug) { console.log(e.stack); } + this.log.debug(e.stack) this.emit("error", e); } }); @@ -59,7 +68,7 @@ class TreeServer extends EventEmitter{ }); this.server.on('disconnected', () => { - this.emit('disconnected', client.remoteAddress()); + this.emit('disconnected'); }); this.server.on("error", (e) => { @@ -70,6 +79,26 @@ class TreeServer extends EventEmitter{ }); } + /** + * + * @param {Array} params + * @private + */ + _log(...params) { + if ((params.length > 1) && (Number(params[0]) <= this.logLevel)) { + const msg = params.slice(1); + this.logger[Logger.LogLevel[params[0]]](`[${Logger.LogLevel[params[0]]}]:`, ...msg); + } + } + + /** + * + * @returns {{debug: (function(...[*]): void), error: (function(...[*]): void), info: (function(...[*]): void), warn: (function(...[*]): void)}|*} + */ + get log() { + return this._loggers; + } + /** * @returns {Promise} */ @@ -83,7 +112,7 @@ class TreeServer extends EventEmitter{ }; this.server.server.close(); }); - }; + } /** * @@ -98,8 +127,8 @@ class TreeServer extends EventEmitter{ node.addChild(children[i].getDuplicate()); } } - else if (this._debug) { - console.log("getResponse","no children"); + else { + this.log.debug("getResponse","no children"); } }); } @@ -178,7 +207,7 @@ class TreeServer extends EventEmitter{ }; this.server.listen(); }); - }; + } /** * @@ -221,8 +250,7 @@ class TreeServer extends EventEmitter{ throw new Error(`Could not find element at path ${path}`); } parent = parent._parent; - let children = parent.getChildren(); - let newList = []; + const children = parent.getChildren(); for(let i = 0; i <= children.length; i++) { if (children[i] && children[i].getPath() == path) { element._parent = parent; // move it to new tree. @@ -242,7 +270,7 @@ class TreeServer extends EventEmitter{ * @param {string} key */ setValue(element, value, origin, key) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { // Change the element value if write access permitted. if (element.contents == null) { return resolve(); @@ -291,7 +319,7 @@ class TreeServer extends EventEmitter{ const elements = this.tree.getChildren(); return elements.map(element => element.toJSON()); - }; + } /** * @@ -346,13 +374,13 @@ class TreeServer extends EventEmitter{ const validateMatrixOperation = function(matrix, target, sources) { if (matrix == null) { - throw new Error(`matrix not found with path ${path}`); + throw new Error(`matrix not found`); } if (matrix.contents == null) { - throw new Error(`invalid matrix at ${path} : no contents`); + throw new Error(`invalid matrix at ${matrix.getPath()} : no contents`); } if (matrix.contents.targetCount == null) { - throw new Error(`invalid matrix at ${path} : no targetCount`); + throw new Error(`invalid matrix at ${matrix.getPath()} : no targetCount`); } if ((target < 0) || (target >= matrix.contents.targetCount)) { throw new Error(`target id ${target} out of range 0 - ${matrix.contents.targetCount}`); diff --git a/EmberServer/MatrixHandlers.js b/EmberServer/MatrixHandlers.js index 6ce6bd1..d96e254 100755 --- a/EmberServer/MatrixHandlers.js +++ b/EmberServer/MatrixHandlers.js @@ -44,11 +44,9 @@ class MatrixHandlers { * @param {boolean} response=true */ handleMatrixConnections(client, matrix, connections, response = true) { - var res,conResult; - var root; // ember message root - if (this.server._debug) { - console.log("Handling Matrix Connection"); - } + let res,conResult; + let root; // ember message root + this.server.log.debug("Handling Matrix Connection"); if (client != null && client.request.isQualified()) { root = new ember.Root(); res = new ember.QualifiedMatrix(matrix.getPath()); @@ -191,9 +189,7 @@ class MatrixHandlers { } } else if (conResult.disposition !== ember.MatrixDisposition.locked){ - if (this.server._debug) { - console.log(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); - } + this.server.log.debug(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); conResult.disposition = ember.MatrixDisposition.tally; } @@ -213,9 +209,7 @@ class MatrixHandlers { } if (conResult != null && conResult.disposition !== ember.MatrixDisposition.tally) { - if (this.server._debug) { - console.log("Updating subscribers for matrix change"); - } + this.server.log.debug("Updating subscribers for matrix change"); this.server.updateSubscribers(matrix.getPath(), root, client); } } diff --git a/EmberSocket/S101Client.js b/EmberSocket/S101Client.js index b4c3f76..5e69b25 100755 --- a/EmberSocket/S101Client.js +++ b/EmberSocket/S101Client.js @@ -63,7 +63,7 @@ class S101Client extends S101Socket { this.activeRequest(); this.activeRequest = null; } - }; + } /** * diff --git a/EmberSocket/S101Socket.js b/EmberSocket/S101Socket.js index a38b51d..7a2da2c 100755 --- a/EmberSocket/S101Socket.js +++ b/EmberSocket/S101Socket.js @@ -106,7 +106,7 @@ class S101Socket extends EventEmitter{ this.status = "disconnected"; } ); - }; + } /** * @@ -116,7 +116,7 @@ class S101Socket extends EventEmitter{ clearInterval(this.keepaliveIntervalTimer); this.status = "disconnected"; this.emit('disconnected'); - }; + } /** * @returns {boolean} diff --git a/package.json b/package.json index ef0dd57..5a931cb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.2.0", + "version": "2.2.1", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From b9f7d2cad4dff068e197105047187f96e1f5a1fa Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 11 Jan 2020 21:19:21 +0100 Subject: [PATCH 132/162] Adding Streams. Fixing bugs. Event/Errors using static value --- EmberClient/EmberClient.js | 104 ++++++----------- EmberLib/Function.js | 4 +- EmberLib/FunctionContent.js | 4 +- EmberLib/InvocationResult.js | 6 +- EmberLib/Matrix.js | 21 ++-- EmberLib/MatrixConnection.js | 6 +- EmberLib/MatrixContents.js | 26 ++--- EmberLib/MatrixNode.js | 2 +- EmberLib/Node.js | 2 + EmberLib/NodeContents.js | 19 ++- EmberLib/Parameter.js | 2 +- EmberLib/ParameterContents.js | 10 +- EmberLib/ParameterType.js | 5 +- EmberLib/QualifiedMatrix.js | 11 +- EmberLib/QualifiedParameter.js | 2 +- EmberLib/StreamDescription.js | 47 ++++++++ EmberLib/StreamFormat.js | 25 ++++ EmberLib/TreeNode.js | 17 ++- EmberServer/ElementHandlers.js | 52 +++++---- EmberServer/EmberServer.js | 76 +++++------- EmberServer/JSONParser.js | 7 +- EmberServer/MatrixHandlers.js | 12 +- EmberServer/QualifiedHandlers.js | 5 +- EmberServer/ServerEvents.js | 123 +++++++++++++++++++ EmberServer/index.js | 5 +- EmberSocket/S101Client.js | 13 ++- EmberSocket/S101Server.js | 2 +- EmberSocket/S101Socket.js | 4 +- ber.js | 8 +- errors.js | 195 +++++++++++++++++++++++++------ index.js | 4 +- package.json | 4 +- test/DeviceTree.test.js | 2 +- test/Ember.test.js | 125 +++++++++++++++++--- test/Server.test.js | 19 +-- 35 files changed, 672 insertions(+), 297 deletions(-) create mode 100755 EmberLib/StreamDescription.js create mode 100755 EmberLib/StreamFormat.js create mode 100755 EmberServer/ServerEvents.js diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index 8420c6e..8ac03f5 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -1,9 +1,9 @@ const EventEmitter = require('events').EventEmitter; -const S101Client = require('../EmberSocket').S101Socket; +const S101Socket = require('../EmberSocket').S101Socket; const ember = require('../EmberLib'); const BER = require('../ber.js'); const errors = require('../errors.js'); -const {Logger, LogLevel} = require("../Logger"); +const winston = require("winston"); const DEFAULT_PORT = 9000; const DEFAULT_TIMEOUT = 3000; @@ -28,18 +28,10 @@ class EmberClient extends EventEmitter { this._timeout = null; this._callback = undefined; this._requestID = 0; - this._client = new S101Client(host, port); + this._client = new S101Socket(host, port); this.timeoutValue = DEFAULT_TIMEOUT; /** @type {Root} */ this.root = new ember.Root(); - this.logger = new Logger(); - this.logLevel = LogLevel.INFO; - this._loggers = { - debug: (...args) => this._log(LogLevel.DEBUG, ...args), - error: (...args) => this._log(LogLevel.ERROR, ...args), - info: (...args) => this._log(LogLevel.INFO, ...args), - warn: (...args) => this._log(LogLevel.WARN, ...args) - }; this._client.on('connecting', () => { this.emit('connecting'); @@ -47,7 +39,7 @@ class EmberClient extends EventEmitter { this._client.on('connected', () => { this.emit('connected'); - if (this._callback !== undefined) { + if (this._callback != null) { this._callback(); } }); @@ -57,7 +49,7 @@ class EmberClient extends EventEmitter { }); this._client.on("error", e => { - if (this._callback !== undefined) { + if (this._callback != null) { this._callback(e); } this.emit("error", e); @@ -67,17 +59,17 @@ class EmberClient extends EventEmitter { try { if (root instanceof ember.InvocationResult) { this.emit('invocationResult', root); - this.log.debug("Received InvocationResult", root); + winston.debug("Received InvocationResult", root); } else { this._handleRoot(root); - this.log.debug("Received root", root); + winston.debug("Received root", root); } if (this._callback) { this._callback(undefined, root); } } catch(e) { - this.log.debug(e, root); + winston.debug(e, root); if (this._callback) { this._callback(e); } @@ -85,33 +77,13 @@ class EmberClient extends EventEmitter { }); } - /** - * - * @param {Array} params - * @private - */ - _log(...params) { - if ((params.length > 1) && (Number(params[0]) <= this.logLevel)) { - const msg = params.slice(1); - this.logger[Logger.LogLevel[params[0]]](`[${Logger.LogLevel[params[0]]}]:`, ...msg); - } - } - - /** - * - * @returns {{debug: (function(...[*]): void), error: (function(...[*]): void), info: (function(...[*]): void), warn: (function(...[*]): void)}|*} - */ - get log() { - return this._loggers; - } - _finishRequest() { this._clearTimeout(); this._activeRequest = null; try { this._makeRequest(); } catch(e) { - this.log.debug(e); + winston.debug(e); if (this._callback != null) { this._callback(e); } @@ -125,7 +97,7 @@ class EmberClient extends EventEmitter { const req = `${ this._requestID++} - ${this._activeRequest.node.getPath()}`; this._activeRequest.timeoutError = new errors.EmberTimeoutError(`Request ${req} timed out`) - this.log.debug(`Making request ${req}`, Date.now()); + winston.debug(`Making request ${req}`, Date.now()); this._timeout = setTimeout(() => { this._timeoutRequest(); }, this.timeoutValue); @@ -228,9 +200,9 @@ class EmberClient extends EventEmitter { * @param {TreeNode} root */ _handleRoot (root) { - this.log.debug("handling root", JSON.stringify(root)); + winston.debug("handling root", JSON.stringify(root)); this.root.update(root); - if (root.elements !== undefined) { + if (root.elements != null) { const elements = root.getChildren(); for (var i = 0; i < elements.length; i++) { if (elements[i].isQualified()) { @@ -259,7 +231,7 @@ class EmberClient extends EventEmitter { } return reject(e); }; - if ((this._client !== undefined) && (this._client.isConnected())) { + if ((this._client != null) && (this._client.isConnected())) { this._client.disconnect(); } this._client.connect(timeout); @@ -283,7 +255,7 @@ class EmberClient extends EventEmitter { */ expand(node, callback = null) { if (node == null) { - return Promise.reject(new Error("Invalid null node")); + return Promise.reject(new errors.InvalidEmberNode("Invalid null node")); } if (node.isParameter() || node.isMatrix() || node.isFunction()) { return this.getDirectory(node); @@ -291,7 +263,7 @@ class EmberClient extends EventEmitter { return this.getDirectory(node, callback).then((res) => { let children = node.getChildren(); if ((res === undefined) || (children === undefined) || (children === null)) { - this.log.debug("No more children for ", node); + winston.debug("No more children for ", node); return; } let p = Promise.resolve(); @@ -300,7 +272,7 @@ class EmberClient extends EventEmitter { // Parameter can only have a single child of type Command. continue; } - this.log.debug("Expanding child", child); + winston.debug("Expanding child", child); p = p.then(() => { return this.expand(child).catch((e) => { // We had an error on some expansion @@ -335,11 +307,11 @@ class EmberClient extends EventEmitter { this._callback = (error, node) => { const requestedPath = qnode.getPath(); if (node == null) { - this.log.debug(`received null response for ${requestedPath}`); + winston.debug(`received null response for ${requestedPath}`); return; } if (error) { - this.log.debug("Received getDirectory error", error); + winston.debug("Received getDirectory error", error); this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); reject(error); @@ -348,21 +320,21 @@ class EmberClient extends EventEmitter { if (qnode.isRoot()) { const elements = qnode.getChildren(); if (elements == null || elements.length === 0) { - this.log.debug("getDirectory response", node); - return this._callback(new Error("Invalid qnode for getDirectory")); + winston.debug("getDirectory response", node); + return this._callback(new errors.InvalidEmberNode()); } const nodeElements = node == null ? null : node.getChildren(); if (nodeElements != null && nodeElements.every(el => el._parent instanceof ember.Root)) { - this.log.debug("Received getDirectory response", node); + winston.debug("Received getDirectory response", node); this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); resolve(node); // make sure the info is treated before going to next request. } else { - return this._callback(new Error(`Invalid response for getDirectory ${requestedPath}`)); + return this._callback(new errors.InvalidEmberResponse(`getDirectory ${requestedPath}`)); } } else if (node.getElementByPath(requestedPath) != null) { @@ -375,18 +347,18 @@ class EmberClient extends EventEmitter { if (nodeElements != null && ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { - this.log.debug("Received getDirectory response", node); + winston.debug("Received getDirectory response", node); this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); return resolve(node); // make sure the info is treated before going to next request. } else { - this.log.debug(node); - this.log.debug(new Error(requestedPath)); + winston.debug(node); + winston.debug(new Error(requestedPath)); } } }; - this.log.debug("Sending getDirectory", qnode); + winston.debug("Sending getDirectory", qnode); this._client.sendBERNode(qnode.getDirectory(callback)); }}); }); @@ -417,7 +389,7 @@ class EmberClient extends EventEmitter { if (typeof path === 'string') { path = path.split('/'); } - var pathError = new Error(`Failed path discovery at ${path.slice(0, pos + 1).join("/")}`); + var pathError = new errors.PathDiscoveryFailure(path.slice(0, pos + 1).join("/")); var pos = 0; var lastMissingPos = -1; var currentNode = this.root; @@ -461,7 +433,7 @@ class EmberClient extends EventEmitter { if (typeof path === 'string') { path = path.split('.'); } - var pathnumError = new Error(`Failed path discovery at ${path.slice(0, pos).join("/")}`); + var pathnumError = new errors.PathDiscoveryFailure(path.slice(0, pos).join("/")); var pos = 0; var lastMissingPos = -1; var currentNode = this.root; @@ -514,13 +486,13 @@ class EmberClient extends EventEmitter { reject(error); } else { - this.log.debug("InvocationResult", result); + winston.debug("InvocationResult", result); resolve(result); } // cleaning callback and making next request. this._finishRequest(); }; - this.log.debug("Invocking function", fnNode); + winston.debug("Invocking function", fnNode); this._callback = cb; this._client.sendBERNode(fnNode.invoke(params)); }}); @@ -531,7 +503,7 @@ class EmberClient extends EventEmitter { * @returns {boolean} */ isConnected() { - return ((this._client !== undefined) && (this._client.isConnected())); + return ((this._client != null) && (this._client.isConnected())); } /** @@ -544,7 +516,7 @@ class EmberClient extends EventEmitter { matrixOPeration(matrixNode, targetID, sources, operation = ember.MatrixOperation.connect) { return new Promise((resolve, reject) => { if (!Array.isArray(sources)) { - return reject(new Error("Sources should be an array")); + return reject(new errors.InvalidSourcesFormat()); } try { matrixNode.validateConnection(targetID, sources); @@ -568,11 +540,11 @@ class EmberClient extends EventEmitter { this._callback = (error, node) => { const requestedPath = matrixNode.getPath(); if (node == null) { - this.log.debug(`received null response for ${requestedPath}`); + winston.debug(`received null response for ${requestedPath}`); return; } if (error) { - this.log.debug("Received getDirectory error", error); + winston.debug("Received getDirectory error", error); this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); reject(error); @@ -588,7 +560,7 @@ class EmberClient extends EventEmitter { resolve(matrix); } else { - this.log.debug(`unexpected node response during matrix connect ${requestedPath}`, + winston.debug(`unexpected node response during matrix connect ${requestedPath}`, matrix == null ? null : JSON.stringify(matrix.toJSON(), null, 4)); } } @@ -670,7 +642,7 @@ class EmberClient extends EventEmitter { }; this._callback = cb; - this.log.debug('setValue sending ...', node.getPath(), value); + winston.debug('setValue sending ...', node.getPath(), value); this._client.sendBERNode(node.setValue(value)); }}); } @@ -693,7 +665,7 @@ class EmberClient extends EventEmitter { if (error != null) { return reject(error); } - this.log.debug("Sending subscribe", qnode); + winston.debug("Sending subscribe", qnode); this._client.sendBERNode(qnode.subscribe(callback)); this._finishRequest(); resolve(); @@ -720,7 +692,7 @@ class EmberClient extends EventEmitter { if (error != null) { return reject(error); } - this.log.debug("Sending subscribe", qnode); + winston.debug("Sending subscribe", qnode); this._client.sendBERNode(qnode.unsubscribe(callback)); this._finishRequest(); resolve(); diff --git a/EmberLib/Function.js b/EmberLib/Function.js index ddc4aa4..0c54eab 100755 --- a/EmberLib/Function.js +++ b/EmberLib/Function.js @@ -1,5 +1,5 @@ "use strict"; -const TreeNode = require("./TreeNode"); +const Element = require("./Element"); const QualifiedFunction = require("./QualifiedFunction"); const BER = require('../ber.js'); const Command = require("./Command"); @@ -7,7 +7,7 @@ const {COMMAND_INVOKE} = require("./constants"); const FunctionContent = require("./FunctionContent"); const errors = require("../errors"); -class Function extends TreeNode { +class Function extends Element { constructor(number, func) { super(); this.number = number; diff --git a/EmberLib/FunctionContent.js b/EmberLib/FunctionContent.js index f5e040b..dacab9e 100755 --- a/EmberLib/FunctionContent.js +++ b/EmberLib/FunctionContent.js @@ -4,9 +4,11 @@ const FunctionArgument = require("./FunctionArgument"); const errors = require("../errors"); class FunctionContent { - constructor() { + constructor(identifier=null, description=null) { this.arguments = []; this.result = []; + this.identifier = identifier; + this.description = description; } /** diff --git a/EmberLib/InvocationResult.js b/EmberLib/InvocationResult.js index 901c60d..bfd9f00 100755 --- a/EmberLib/InvocationResult.js +++ b/EmberLib/InvocationResult.js @@ -3,7 +3,7 @@ const BER = require('../ber.js'); const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); const FunctionArgument = require("./FunctionArgument"); -const errors = require("../errors"); +const Errors = require("../errors"); class InvocationResult { @@ -65,7 +65,7 @@ class InvocationResult { */ setResult(result) { if (!Array.isArray(result)) { - throw new Error("Invalid inovation result. Should be array"); + throw new Errors.InvalidResultFormat(); } this.result = result; } @@ -107,7 +107,7 @@ class InvocationResult { continue } else { // TODO: options - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js index e0c7980..a8e3cdf 100755 --- a/EmberLib/Matrix.js +++ b/EmberLib/Matrix.js @@ -5,6 +5,7 @@ const BER = require('../ber.js'); const MatrixMode = require("./MatrixMode"); const MatrixOperation = require("./MatrixOperation"); const MatrixType = require("./MatrixType"); +const Errors = require("../errors"); class Matrix extends TreeNode { @@ -64,7 +65,7 @@ class Matrix extends TreeNode * @param {BER} ber */ encodeConnections(ber) { - if (this.connections !== undefined) { + if (this.connections != null) { ber.startSequence(BER.CONTEXT(5)); ber.startSequence(BER.EMBER_SEQUENCE); @@ -109,7 +110,7 @@ class Matrix extends TreeNode * @param {BER} ber */ encodeTargets(ber) { - if (this.targets !== undefined) { + if (this.targets != null) { ber.startSequence(BER.CONTEXT(3)); ber.startSequence(BER.EMBER_SEQUENCE); @@ -289,7 +290,7 @@ class Matrix extends TreeNode while(seq.remain > 0) { var conSeq = seq.getSequence(BER.CONTEXT(0)); var con = MatrixConnection.decode(conSeq); - if (con.target !== undefined) { + if (con.target != null) { connections[con.target] = (con); } } @@ -388,25 +389,25 @@ class Matrix extends TreeNode */ static validateConnection(matrixNode, targetID, sources) { if (targetID < 0) { - throw new Error(`Invalid negative target index ${targetID}`); + throw new Errors.InvalidEmberNode(matrixNode.getPath(), `Invalid negative target index ${targetID}`); } for(let i = 0; i < sources.length; i++) { if (sources[i] < 0) { - throw new Error(`Invalid negative source at index ${i}`); + throw new Errors.InvalidEmberNode(matrixNode.getPath(),`Invalid negative source at index ${i}`); } } if (matrixNode.contents.mode === MatrixMode.linear) { if (targetID >= matrixNode.contents.targetCount) { - throw new Error(`targetID ${targetID} higher than max value ${matrixNode.contents.targetCount}`); + throw Errors.InvalidEmberNode(matrixNode.getPath(),`targetID ${targetID} higher than max value ${matrixNode.contents.targetCount}`); } for(let i = 0; i < sources.length; i++) { if (sources[i] >= matrixNode.contents.sourceCount) { - throw new Error(`Invalid source at index ${i}`); + throw new Errors.InvalidEmberNode(matrixNode.getPath(),`Invalid source at index ${i}`); } } } else if ((matrixNode.targets == null) || (matrixNode.sources == null)) { - throw new Error("Non-Linear matrix should have targets and sources"); + throw new Errors.InvalidEmberNode(matrixNode.getPath(),"Non-Linear matrix should have targets and sources"); } else { let found = false; @@ -417,7 +418,7 @@ class Matrix extends TreeNode } } if (!found) { - throw new Error(`Unknown targetid ${targetID}`); + throw new Errors.InvalidEmberNode(matrixNode.getPath(),`Unknown targetid ${targetID}`); } found = false; for(let i = 0; i < sources.length; i++) { @@ -428,7 +429,7 @@ class Matrix extends TreeNode } } if (!found) { - throw new Error(`Unknown source at index ${i}`); + throw new Errors.InvalidEmberNode(matrixNode.getPath(),`Unknown source at index ${i}`); } } } diff --git a/EmberLib/MatrixConnection.js b/EmberLib/MatrixConnection.js index 51a817a..693eee2 100755 --- a/EmberLib/MatrixConnection.js +++ b/EmberLib/MatrixConnection.js @@ -62,17 +62,17 @@ class MatrixConnection { ber.writeInt(this.target); ber.endSequence(); - if ((this.sources !== undefined)&& (this.sources.length > 0)) { + if ((this.sources != null)&& (this.sources.length > 0)) { ber.startSequence(BER.CONTEXT(1)); ber.writeRelativeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); ber.endSequence(); } - if (this.operation !== undefined) { + if (this.operation != null) { ber.startSequence(BER.CONTEXT(2)); ber.writeInt(this.operation.value); ber.endSequence(); } - if (this.disposition !== undefined) { + if (this.disposition != null) { ber.startSequence(BER.CONTEXT(3)); ber.writeInt(this.disposition.value); ber.endSequence(); diff --git a/EmberLib/MatrixContents.js b/EmberLib/MatrixContents.js index 1f1d1a5..e75643d 100755 --- a/EmberLib/MatrixContents.js +++ b/EmberLib/MatrixContents.js @@ -18,47 +18,47 @@ class MatrixContents { */ encode(ber) { ber.startSequence(BER.EMBER_SET); - if (this.identifier !== undefined) { + if (this.identifier != null) { ber.startSequence(BER.CONTEXT(0)); ber.writeString(this.identifier, BER.EMBER_STRING); ber.endSequence(); } - if (this.description !== undefined) { + if (this.description != null) { ber.startSequence(BER.CONTEXT(1)); ber.writeString(this.description, BER.EMBER_STRING); ber.endSequence(); } - if (this.type !== undefined) { + if (this.type != null) { ber.startSequence(BER.CONTEXT(2)); ber.writeInt(this.type.value); ber.endSequence(); } - if (this.mode !== undefined) { + if (this.mode != null) { ber.startSequence(BER.CONTEXT(3)); ber.writeInt(this.mode.value); ber.endSequence(); } - if (this.targetCount !== undefined) { + if (this.targetCount != null) { ber.startSequence(BER.CONTEXT(4)); ber.writeInt(this.targetCount); ber.endSequence(); } - if (this.sourceCount !== undefined) { + if (this.sourceCount != null) { ber.startSequence(BER.CONTEXT(5)); ber.writeInt(this.sourceCount); ber.endSequence(); } - if (this.maximumTotalConnects !== undefined) { + if (this.maximumTotalConnects != null) { ber.startSequence(BER.CONTEXT(6)); ber.writeInt(this.maximumTotalConnects); ber.endSequence(); } - if (this.maximumConnectsPerTarget !== undefined) { + if (this.maximumConnectsPerTarget != null) { ber.startSequence(BER.CONTEXT(7)); ber.writeInt(this.maximumConnectsPerTarget); ber.endSequence(); } - if (this.parametersLocation !== undefined) { + if (this.parametersLocation != null) { ber.startSequence(BER.CONTEXT(8)); let param = Number(this.parametersLocation) if (isNaN(param)) { @@ -69,12 +69,12 @@ class MatrixContents { } ber.endSequence(); } - if (this.gainParameterNumber !== undefined) { + if (this.gainParameterNumber != null) { ber.startSequence(BER.CONTEXT(9)); ber.writeInt(this.gainParameterNumber); ber.endSequence(); } - if (this.labels !== undefined) { + if (this.labels != null) { ber.startSequence(BER.CONTEXT(10)); ber.startSequence(BER.EMBER_SEQUENCE); for(var i =0; i < this.labels.length; i++) { @@ -85,12 +85,12 @@ class MatrixContents { ber.endSequence(); ber.endSequence(); } - if (this.schemaIdentifiers !== undefined) { + if (this.schemaIdentifiers != null) { ber.startSequence(BER.CONTEXT(11)); ber.writeInt(this.schemaIdentifiers, BER.EMBER_STRING); ber.endSequence(); } - if (this.templateReference !== undefined) { + if (this.templateReference != null) { ber.startSequence(BER.CONTEXT(12)); ber.writeRelativeOID(this.templateReference, BER.EMBER_RELATIVE_OID); ber.endSequence(); diff --git a/EmberLib/MatrixNode.js b/EmberLib/MatrixNode.js index 5f309e2..0c86a76 100755 --- a/EmberLib/MatrixNode.js +++ b/EmberLib/MatrixNode.js @@ -23,7 +23,7 @@ class MatrixNode extends Matrix { ber.writeInt(this.number); ber.endSequence(); // BER.CONTEXT(0) - if(this.contents !== undefined) { + if(this.contents != null) { ber.startSequence(BER.CONTEXT(1)); this.contents.encode(ber); ber.endSequence(); // BER.CONTEXT(1) diff --git a/EmberLib/Node.js b/EmberLib/Node.js index 1617b28..6af1c6a 100755 --- a/EmberLib/Node.js +++ b/EmberLib/Node.js @@ -14,6 +14,8 @@ class Node extends Element { constructor(number) { super(number); this._seqID = BER.APPLICATION(3); + /** @type {NodeContents} */ + this.contents = null; } /** diff --git a/EmberLib/NodeContents.js b/EmberLib/NodeContents.js index 9260ed9..68aeede 100755 --- a/EmberLib/NodeContents.js +++ b/EmberLib/NodeContents.js @@ -3,8 +3,15 @@ const BER = require('../ber.js'); const errors = require("../errors"); class NodeContents{ - constructor() { + /** + * + * @param {string} identifier + * @param {string} description + */ + constructor(identifier=null, description=null) { this.isOnline = true; + this.identifier = identifier; + this.description = description; this._subscribers = new Set(); } @@ -15,31 +22,31 @@ class NodeContents{ encode(ber) { ber.startSequence(BER.EMBER_SET); - if(this.identifier !== undefined) { + if(this.identifier != null) { ber.startSequence(BER.CONTEXT(0)); ber.writeString(this.identifier, BER.EMBER_STRING); ber.endSequence(); // BER.CONTEXT(0) } - if(this.description !== undefined) { + if(this.description != null) { ber.startSequence(BER.CONTEXT(1)); ber.writeString(this.description, BER.EMBER_STRING); ber.endSequence(); // BER.CONTEXT(1) } - if(this.isRoot !== undefined) { + if(this.isRoot != null) { ber.startSequence(BER.CONTEXT(2)); ber.writeBoolean(this.isRoot); ber.endSequence(); // BER.CONTEXT(2) } - if(this.isOnline !== undefined) { + if(this.isOnline != null) { ber.startSequence(BER.CONTEXT(3)); ber.writeBoolean(this.isOnline); ber.endSequence(); // BER.CONTEXT(3) } - if(this.schemaIdentifiers !== undefined) { + if(this.schemaIdentifiers != null) { ber.startSequence(BER.CONTEXT(4)); ber.writeString(this.schemaIdentifiers, BER.EMBER_STRING); ber.endSequence(); // BER.CONTEXT(4) diff --git a/EmberLib/Parameter.js b/EmberLib/Parameter.js index 7357754..bad3eaf 100755 --- a/EmberLib/Parameter.js +++ b/EmberLib/Parameter.js @@ -49,7 +49,7 @@ class Parameter extends Element { * @param {Parameter} other */ update(other) { - if ((other !== undefined) && (other.contents !== undefined)) { + if ((other != null) && (other.contents != null)) { if (this.contents == null) { this.contents = other.contents; } diff --git a/EmberLib/ParameterContents.js b/EmberLib/ParameterContents.js index 7e79bbd..93a5345 100755 --- a/EmberLib/ParameterContents.js +++ b/EmberLib/ParameterContents.js @@ -10,11 +10,11 @@ const errors = require("../errors"); class ParameterContents { constructor(value, type) { this._subscribers = new Set(); - if(value !== undefined) { + if(value != null) { this.value = value; } - if(type !== undefined) { - if((type = ParameterType.get(type)) !== undefined){ + if(type != null) { + if((type = ParameterType.get(type)) != null){ this.type = type } } @@ -43,11 +43,11 @@ class ParameterContents { ber.writeIfDefinedEnum(this.type, ParameterType, ber.writeInt, 13); ber.writeIfDefined(this.streamIdentifier, ber.writeInt, 14); - if(this.stringIntegerCollection !== undefined) { + if(this.stringIntegerCollection != null) { this.stringIntegerCollection.encode(ber); } - if(this.streamDescriptor !== undefined) { + if(this.streamDescriptor != null) { ber.startSequence(BER.CONTEXT(16)); this.streamDescriptor.encode(ber); ber.endSequence(); diff --git a/EmberLib/ParameterType.js b/EmberLib/ParameterType.js index 1611237..6b9fb04 100755 --- a/EmberLib/ParameterType.js +++ b/EmberLib/ParameterType.js @@ -1,5 +1,6 @@ const Enum = require('enum'); const BER = require('../ber.js'); +const Errors = require("../errors"); function ParameterTypetoBERTAG(type) { switch (type.value) { @@ -9,7 +10,7 @@ function ParameterTypetoBERTAG(type) { case 4: return BER.EMBER_BOOLEAN; case 7: return BER.EMBER_OCTETSTRING; default: - throw new Error(`Unhandled ParameterType ${type}`); + throw new Errors.InvalidBERFormat(`Unhandled ParameterType ${type}`); } } @@ -21,7 +22,7 @@ function ParameterTypefromBERTAG(tag) { case BER.EMBER_BOOLEAN: return ParameterType.boolean; case BER.EMBER_OCTETSTRING: return ParameterType.octets; default: - throw new Error(`Unhandled BER TAB ${tag}`); + throw new Errors.InvalidBERFormat(`Unhandled BER TAB ${tag}`); } } diff --git a/EmberLib/QualifiedMatrix.js b/EmberLib/QualifiedMatrix.js index 7c15ed6..a746846 100755 --- a/EmberLib/QualifiedMatrix.js +++ b/EmberLib/QualifiedMatrix.js @@ -80,9 +80,6 @@ class QualifiedMatrix extends Matrix { * @returns {TreeNode} */ getDirectory(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } if (callback != null && !this.isStream()) { this.contents._subscribers.add(callback); } @@ -95,9 +92,6 @@ class QualifiedMatrix extends Matrix { * @returns {TreeNode} */ subscribe(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } if (callback != null && this.isStream()) { this.contents._subscribers.add(callback); } @@ -110,9 +104,6 @@ class QualifiedMatrix extends Matrix { * @returns {TreeNode} */ unsubscribe(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } if (callback != null && this.isStream()) { this.contents._subscribers.delete(callback); } @@ -147,7 +138,7 @@ class QualifiedMatrix extends Matrix { while(seq.remain > 0) { let conSeq = seq.getSequence(BER.CONTEXT(0)); let con = MatrixConnection.decode(conSeq); - if (con.target !== undefined) { + if (con.target != null) { qm.connections[con.target] = con; } } diff --git a/EmberLib/QualifiedParameter.js b/EmberLib/QualifiedParameter.js index 717c8d7..aec7990 100755 --- a/EmberLib/QualifiedParameter.js +++ b/EmberLib/QualifiedParameter.js @@ -56,7 +56,7 @@ class QualifiedParameter extends QualifiedElement { * @param {QualifiedParameter} other */ update(other) { - if ((other !== undefined) && (other.contents !== undefined)) { + if ((other != null) && (other.contents != null)) { if (this.contents == null) { this.contents = other.contents; } diff --git a/EmberLib/StreamDescription.js b/EmberLib/StreamDescription.js new file mode 100755 index 0000000..08087b3 --- /dev/null +++ b/EmberLib/StreamDescription.js @@ -0,0 +1,47 @@ +"use strict"; +const Element = require("./Element"); +const BER = require('../ber.js'); +const StreamFormat = require("./StreamFormat"); +const errors = require("../errors"); + +class StreamDescription extends Element{ + /** + * + */ + constructor() { + super(); + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(12)); + + ber.writeIfDefinedEnum(this.format, StreamFormat, ber.writeInt, 0); + ber.writeIfDefined(this.offset, ber.writeInt, 1); + + ber.endSequence(); + } + + static decode(ber) { + const sd = new StreamDescription(); + ber = ber.getSequence(BER.APPLICATION(12)); + + while(ber.remain > 0) { + var tag = ber.peek(); + var seq =ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + sd.format = StreamFormat.get(seq.readInt()); + } else if(tag == BER.CONTEXT(1)) { + sd.offset = seq.readInt(); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return sd; + } +} + +module.exports = StreamDescription; \ No newline at end of file diff --git a/EmberLib/StreamFormat.js b/EmberLib/StreamFormat.js new file mode 100755 index 0000000..ed3435d --- /dev/null +++ b/EmberLib/StreamFormat.js @@ -0,0 +1,25 @@ +"use strict"; +const Enum = require('enum'); + +const StreamFormat = new Enum({ + unsignedInt8: 0, + unsignedInt16BigEndian: 2, + unsignedInt16LittleEndian: 3, + unsignedInt32BigEndian: 4, + unsignedInt32LittleEndian: 5, + unsignedInt64BigEndian: 6, + unsignedInt64LittleENdian: 7, + signedInt8: 8, + signedInt16BigEndian: 10, + signedInt16LittleEndian: 11, + signedInt32BigEndian: 12, + signedInt32LittleEndian: 13, + signedInt64BigEndian: 14, + signedInt64LittleEndian: 15, + ieeeFloat32BigEndian: 20, + ieeeFloat32LittleEndian: 21, + ieeeFloat64BigEndian: 22, + ieeeFloat64LittleEndian: 23 +}); + +module.exports = StreamFormat; \ No newline at end of file diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index 94caf34..508da87 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -173,7 +173,7 @@ class TreeNode { else { obj = new this.constructor(this.number); } - if (this.contents !== undefined) { + if (this.contents != null) { obj.contents= this.contents; } return obj; @@ -198,11 +198,11 @@ class TreeNode { getTreeBranch(child, modifier) { const m = this.getMinimal(); - if(child !== undefined) { + if(child != null) { m.addChild(child); } - if(modifier !== undefined) { + if(modifier != null) { modifier(m); } @@ -336,7 +336,7 @@ class TreeNode { const children = this.getChildren(); if (children == null) return null; for(var i = 0; i < children.length; i++) { - if(children[i].contents !== undefined && + if(children[i].contents != null && children[i].contents.identifier == identifier) { return children[i]; } @@ -373,10 +373,7 @@ class TreeNode { } child = node.getElement(path[0]); if(child === null) { - //let e = new Error('invalid path: "' + path[0] + '"'); - // console.log(e.message ,"Self:", self, "Node:", node); - //callback(e); - //DO NOT REJECT !!!! We could still be updating the tree. + //DO NOT REJECT !!!! We could still be updating the tree. return; } else { child.getNodeByPath(client, path.slice(1), callback); @@ -427,7 +424,7 @@ class TreeNode { res.path = node.getPath(); if (node.contents) { for(let prop in node.contents) { - if (prop[0] == "_") { + if (prop[0] == "_" || node.contents[prop] == null) { continue; } if (node.contents.hasOwnProperty(prop)) { @@ -435,7 +432,7 @@ class TreeNode { if ((type === "string") || (type === "number")) { res[prop] = node.contents[prop]; } - else if (node.contents[prop].value !== undefined) { + else if (node.contents[prop].value != null) { res[prop] = node.contents[prop].value; } else { diff --git a/EmberServer/ElementHandlers.js b/EmberServer/ElementHandlers.js index c8d7079..a318500 100755 --- a/EmberServer/ElementHandlers.js +++ b/EmberServer/ElementHandlers.js @@ -1,6 +1,9 @@ "use strict"; const QualifiedHandlers = require("./QualifiedHandlers"); const EmberLib = require('../EmberLib'); +const ServerEvents = require("./ServerEvents"); +const Errors = require("../errors"); +const winston = require("winston"); class ElementHandlers extends QualifiedHandlers{ /** @@ -16,31 +19,34 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} root * @param {Command} cmd */ - handleCommand(client, element, cmd) { + handleCommand(client, element, cmd) { + let identifier = "root" + if (!element.isRoot()) { + const node = this.server.tree.getElementByPath(element.getPath()); + identifier = node == null || node.contents == null || node.contents.identifier == null ? "unknown" : node.contents.identifier; + } + const src = client == null ? "local" : `${client.socket.remoteAddress}:${client.socket.remotePort}`; switch(cmd.number) { case EmberLib.COMMAND_GETDIRECTORY: + this.server.emit("event", ServerEvents.GETDIRECTORY(identifier, element.getPath(), src)); this.handleGetDirectory(client, element); break; case EmberLib.COMMAND_SUBSCRIBE: + this.server.emit("event", ServerEvents.SUBSCRIBE(identifier, element.getPath(), src)); this.handleSubscribe(client, element); break; case EmberLib.COMMAND_UNSUBSCRIBE: + this.server.emit("event", ServerEvents.UNSUBSCRIBE(identifier, element.getPath(), src)); this.handleUnSubscribe(client, element); break; case EmberLib.COMMAND_INVOKE: + this.server.emit("event", ServerEvents.INVOKE(identifier, element.getPath(), src)); this.handleInvoke(client, cmd.invocation, element); break; default: - this.server.emit("error", new Error(`invalid command ${cmd.number}`)); + this.server.emit("error", new Errors.InvalidCommand(cmd.number)); return; - } - let identifier = "root" - if (!element.isRoot()) { - const node = this.server.tree.getElementByPath(element.getPath()); - identifier = node == null || node.contents == null || node.contents.identifier == null ? "unknown" : node.contents.identifier; - } - const src = client == null ? "local" : `${client.socket.remoteAddress}:${client.socket.remotePort}`; - this.server.emit("event", `${EmberLib.COMMAND_STRINGS[cmd.number]} to ${identifier}(path: ${element.getPath()}) from ${src}`); + } } /** @@ -49,7 +55,7 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} root */ handleGetDirectory(client, element) { - if (client !== undefined) { + if (client != null) { if ((element.isMatrix() || element.isParameter()) && (!element.isStream())) { // ember spec: parameter without streamIdentifier should @@ -70,7 +76,7 @@ class ElementHandlers extends QualifiedHandlers{ } const res = this.server.getQualifiedResponse(element); - this.server.log.debug("getDirectory response", res); + winston.debug("getDirectory response", res); client.sendBERNode(res); } } @@ -111,9 +117,9 @@ class ElementHandlers extends QualifiedHandlers{ // traverse the tree let element = node; let path = []; - while(element !== undefined) { + while(element != null) { if (element.number == null) { - this.server.emit("error", "invalid request"); + this.server.emit("error", new Errors.MissingElementNumber()); return; } if (element.isCommand()) { @@ -130,34 +136,34 @@ class ElementHandlers extends QualifiedHandlers{ let cmd = element; if (cmd == null) { - this.server.emit("error", "invalid request"); + this.server.emit("error", new Errors.InvalidRequest()); return this.server.handleError(client); } element = this.server.tree.getElementByPath(path.join(".")); if (element == null) { - this.server.emit("error", new Error(`unknown element at path ${path}`)); + this.server.emit("error", new Errors.UnknownElement(path.join("."))); return this.server.handleError(client); } if (cmd.isCommand()) { this.handleCommand(client, element, cmd); } - else if ((cmd.isCommand()) && (cmd.connections !== undefined)) { + else if ((cmd.isCommand()) && (cmd.connections != null)) { this.handleMatrixConnections(client, element, cmd.connections); } else if ((cmd.isParameter()) && - (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { - this.server.log.debug(`setValue for element at path ${path} with value ${cmd.contents.value}`); + (cmd.contents != null) && (cmd.contents.value != null)) { + winston.debug(`setValue for element at path ${path} with value ${cmd.contents.value}`); this.server.setValue(element, cmd.contents.value, client); const res = this.server.getResponse(element); client.sendBERNode(res) this.server.updateSubscribers(element.getPath(), res, client); } else { - this.server.emit("error", new Error("invalid request format")); - this.server.log.debug("invalid request format"); + this.server.emit("error", new Errors.InvalidRequesrFormat(path.join("."))); + winston.debug("invalid request format"); return this.server.handleError(client, element.getTreeBranch()); } return path; @@ -169,7 +175,7 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} root */ handleSubscribe(client, element) { - this.server.log.debug("subscribe"); + winston.debug("subscribe"); this.server.subscribe(client, element); } @@ -179,7 +185,7 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} root */ handleUnSubscribe(client, element) { - this.server.log.debug("unsubscribe"); + winston.debug("unsubscribe"); this.server.unsubscribe(client, element); } } diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 516e142..9fe7c73 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -3,7 +3,9 @@ const S101Server = require('../EmberSocket').S101Server; const ember = require('../EmberLib'); const JSONParser = require("./JSONParser"); const ElementHandlers = require("./ElementHandlers"); -const {Logger, LogLevel} = require("../Logger"); +const ServerEvents = require("./ServerEvents"); +const winston = require("winston"); +const Errors = require("../errors"); class TreeServer extends EventEmitter{ /** @@ -22,37 +24,29 @@ class TreeServer extends EventEmitter{ this.clients = new Set(); this.subscribers = {}; this._handlers = new ElementHandlers(this); - this.logger = new Logger(); - this.logLevel = LogLevel.INFO; - this._loggers = { - debug: (...args) => this._log(LogLevel.DEBUG, ...args), - error: (...args) => this._log(LogLevel.ERROR, ...args), - info: (...args) => this._log(LogLevel.INFO, ...args), - warn: (...args) => this._log(LogLevel.WARN, ...args) - }; this.server.on('listening', () => { - this.log.debug("listening"); + winston.debug("listening"); this.emit('listening'); - if (this.callback !== undefined) { + if (this.callback != null) { this.callback(); this.callback = undefined; } }); this.server.on('connection', client => { - this.log.debug("ember new connection from", client.remoteAddress()); + winston.debug("ember new connection from", client.remoteAddress()); this.clients.add(client); client.on("emberTree", (root) => { - this.log.debug("ember new request from", client.remoteAddress(), root); + winston.debug("ember new request from", client.remoteAddress(), root); // Queue the action to make sure responses are sent in order. client.addRequest(() => { try { - let path = this.handleRoot(client, root); + const path = this.handleRoot(client, root); this.emit("request", {client: client.remoteAddress(), root: root, path: path}); } catch(e) { - this.log.debug(e.stack) + winston.debug(e.stack) this.emit("error", e); } }); @@ -68,37 +62,18 @@ class TreeServer extends EventEmitter{ }); this.server.on('disconnected', () => { + this.clients.clear(); this.emit('disconnected'); }); this.server.on("error", (e) => { this.emit("error", e); - if (this.callback !== undefined) { + if (this.callback != null) { this.callback(e); } }); } - /** - * - * @param {Array} params - * @private - */ - _log(...params) { - if ((params.length > 1) && (Number(params[0]) <= this.logLevel)) { - const msg = params.slice(1); - this.logger[Logger.LogLevel[params[0]]](`[${Logger.LogLevel[params[0]]}]:`, ...msg); - } - } - - /** - * - * @returns {{debug: (function(...[*]): void), error: (function(...[*]): void), info: (function(...[*]): void), warn: (function(...[*]): void)}|*} - */ - get log() { - return this._loggers; - } - /** * @returns {Promise} */ @@ -111,6 +86,7 @@ class TreeServer extends EventEmitter{ return reject(e); }; this.server.server.close(); + this.clients.clear(); }); } @@ -121,14 +97,14 @@ class TreeServer extends EventEmitter{ getResponse(element) { return element.getTreeBranch(undefined, node => { node.update(element); - let children = element.getChildren(); + const children = element.getChildren(); if (children != null) { for (let i = 0; i < children.length; i++) { node.addChild(children[i].getDuplicate()); } } else { - this.log.debug("getResponse","no children"); + winston.debug("getResponse","no children"); } }); } @@ -143,7 +119,7 @@ class TreeServer extends EventEmitter{ if (element.isRoot() === false) { dup = element.toQualified(); } - let children = element.getChildren(); + const children = element.getChildren(); if (children != null) { for (let i = 0; i < children.length; i++) { res.addChild(children[i].toQualified().getMinimalContent()); @@ -161,7 +137,7 @@ class TreeServer extends EventEmitter{ * @param {TreeNode} root */ handleError(client, node) { - if (client !== undefined) { + if (client != null) { const res = node == null ? this.tree.getMinimal() : node; client.sendBERNode(res); } @@ -181,7 +157,7 @@ class TreeServer extends EventEmitter{ const node = root.getChildren()[0]; client.request = node; - if (node.path !== undefined) { + if (node.path != null) { return this._handlers.handleQualifiedNode(client, node); } else if (node.isCommand()) { @@ -247,7 +223,7 @@ class TreeServer extends EventEmitter{ let path = element.getPath(); let parent = this.tree.getElementByPath(path); if ((parent == null)||(parent._parent == null)) { - throw new Error(`Could not find element at path ${path}`); + throw new Errors.UnknownElement(path); } parent = parent._parent; const children = parent.getChildren(); @@ -277,20 +253,20 @@ class TreeServer extends EventEmitter{ } if (element.isParameter() || element.isMatrix()) { if (element.isParameter() && - (element.contents.access !== undefined) && + (element.contents.access != null) && (element.contents.access.value > 1)) { element.contents.value = value; const res = this.getResponse(element); this.updateSubscribers(element.getPath(),res, origin); } - else if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { + else if ((key != null) && (element.contents.hasOwnProperty(key))) { element.contents[key] = value; const res = this.getResponse(element); this.updateSubscribers(element.getPath(),res, origin); } const src = origin == null ? "local" : `${origin.socket.remoteAddress}:${origin.socket.remotePort}`; this.emit("value-change", element); - this.emit("event", `set value for ${element.contents.identifier}(path: ${element.getPath()}) from ${src}` ); + this.emit("event", ServerEvents.SETVALUE(element.contents.identifier,element.getPath(),src)); } return resolve(); }); @@ -374,19 +350,19 @@ class TreeServer extends EventEmitter{ const validateMatrixOperation = function(matrix, target, sources) { if (matrix == null) { - throw new Error(`matrix not found`); + throw new Errors.UnknownElement(`matrix not found`); } if (matrix.contents == null) { - throw new Error(`invalid matrix at ${matrix.getPath()} : no contents`); + throw new Errors.MissingElementContents(matrix.getPath()); } if (matrix.contents.targetCount == null) { - throw new Error(`invalid matrix at ${matrix.getPath()} : no targetCount`); + throw new Errors.InvalidEmberNode(matrix.getPath(), "no targetCount"); } if ((target < 0) || (target >= matrix.contents.targetCount)) { - throw new Error(`target id ${target} out of range 0 - ${matrix.contents.targetCount}`); + throw new Errors.InvalidEmberNode(matrix.getPath(), `target id ${target} out of range 0 - ${matrix.contents.targetCount}`); } if (sources.length == null) { - throw new Error("invalid sources format"); + throw new Errors.InvalidSourcesFormat(); } } diff --git a/EmberServer/JSONParser.js b/EmberServer/JSONParser.js index 1908b11..9fcd617 100755 --- a/EmberServer/JSONParser.js +++ b/EmberServer/JSONParser.js @@ -1,5 +1,6 @@ "use strict"; const ember = require('../EmberLib'); +const Errors = require("../errors"); class JSONParser { /** @@ -43,7 +44,7 @@ class JSONParser { Number(content.sourceCount) : Number(content.maximumConnectsPerTarget); } else { - throw new Error(`Invalid matrix type ${content.type}`); + throw new Errors.InvalidEmberNode("", `Invalid matrix type ${content.type}`); } delete content.type; } @@ -55,7 +56,7 @@ class JSONParser { matrixContent.mode = ember.MatrixMode.nonLinear; } else { - throw new Error(`Invalid matrix mode ${content.mode}`); + throw new Errors.InvalidEmberNode("",`Invalid matrix mode ${content.mode}`); } delete content.mode; } @@ -70,7 +71,7 @@ class JSONParser { for(let i = 0; i < obj.length; i++) { let emberElement; let content = obj[i]; - let number = content.number !== undefined ? content.number : i; + let number = content.number != null ? content.number : i; delete content.number; if (content.value != null) { emberElement = new ember.Parameter(number); diff --git a/EmberServer/MatrixHandlers.js b/EmberServer/MatrixHandlers.js index d96e254..1e47d8e 100755 --- a/EmberServer/MatrixHandlers.js +++ b/EmberServer/MatrixHandlers.js @@ -1,5 +1,7 @@ "use strict"; const ember = require('../EmberLib'); +const ServerEvents = require("./ServerEvents"); +const winston = require("winston"); class MatrixHandlers { /** @@ -46,7 +48,7 @@ class MatrixHandlers { handleMatrixConnections(client, matrix, connections, response = true) { let res,conResult; let root; // ember message root - this.server.log.debug("Handling Matrix Connection"); + winston.debug("Handling Matrix Connection"); if (client != null && client.request.isQualified()) { root = new ember.Root(); res = new ember.QualifiedMatrix(matrix.getPath()); @@ -64,7 +66,9 @@ class MatrixHandlers { } let connection = connections[id]; const src = client == null ? "local" : `${client.socket.remoteAddress}:${client.socket.remotePort}`; - this.server.emit("event", `Matrix connection to ${matrix.contents.identifier}(path: ${matrix.getPath()}) target ${id} connections: ${connection.sources.toString()} from ${src}`); + this.server.emit("event", ServerEvents.MATRIX_CONNECTION( + matrix.contents.identifier,matrix.getPath(),src,id,connection.sources + )); conResult = new ember.MatrixConnection(connection.target); let emitType; res.connections[connection.target] = conResult; @@ -189,7 +193,7 @@ class MatrixHandlers { } } else if (conResult.disposition !== ember.MatrixDisposition.locked){ - this.server.log.debug(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); + winston.debug(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); conResult.disposition = ember.MatrixDisposition.tally; } @@ -209,7 +213,7 @@ class MatrixHandlers { } if (conResult != null && conResult.disposition !== ember.MatrixDisposition.tally) { - this.server.log.debug("Updating subscribers for matrix change"); + winston.debug("Updating subscribers for matrix change"); this.server.updateSubscribers(matrix.getPath(), root, client); } } diff --git a/EmberServer/QualifiedHandlers.js b/EmberServer/QualifiedHandlers.js index 03915ff..1589087 100755 --- a/EmberServer/QualifiedHandlers.js +++ b/EmberServer/QualifiedHandlers.js @@ -1,5 +1,6 @@ "use strict"; const MatrixHandlers = require("./MatrixHandlers"); +const Errors = require("../errors"); class QualifiedHandlers extends MatrixHandlers { /** @@ -32,7 +33,7 @@ class QualifiedHandlers extends MatrixHandlers { const element = this.server.tree.getElementByPath(path); if (element == null) { - this.server.emit("error", new Error(`unknown element at path ${path}`)); + this.server.emit("error", new Errors.UnknownElement(path)); return this.server.handleError(client); } @@ -63,7 +64,7 @@ class QualifiedHandlers extends MatrixHandlers { */ handleQualifiedParameter(client, element, parameter) { - if (parameter.contents.value !== undefined) { + if (parameter.contents.value != null) { this.server.setValue(element, parameter.contents.value, client); let res = this.server.getQualifiedResponse(element); client.sendBERNode(res) diff --git a/EmberServer/ServerEvents.js b/EmberServer/ServerEvents.js new file mode 100755 index 0000000..6e95910 --- /dev/null +++ b/EmberServer/ServerEvents.js @@ -0,0 +1,123 @@ +"use strict"; + +const Enum = require('enum'); + +const Types = new Enum({ + UNKNOWN: 0, + SETVALUE: 1, + GETDIRECTORY: 2, + SUBSCRIBE: 3, + UNSUBSCRIBE: 4, + INVOKE: 5, + MATRIX_CONNECTION: 6 +}); + +class ServerEvents { + /** + * + * @param {string} txt + * @param {number} type=0 + */ + constructor(txt, type=Types.UNKNOWN) { + /** @type {string} */ + this._txt = txt; + /** @type {number} */ + this._type = type; + this._timestamp = Date.now(); + } + + /** + * @returns {number} + */ + get type() { + return this._type; + } + + /** + * @returns {number} + */ + get timestamp() { + return this._timestamp; + } + + /** + * @returns {string} + */ + toString() { + return this._txt; + } + + /** + * @returns {Enum} + */ + static get Types() { + return Types; + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static SETVALUE(identifier,path, src) { + return new ServerEvents(`set value for ${identifier}(path: ${path}) from ${src}`, Types.SETVALUE); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static GETDIRECTORY(identifier,path, src) { + return new ServerEvents(`getdirectory to ${identifier}(path: ${path}) from ${src}`, Types.GETDIRECTORY); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static SUBSCRIBE(identifier,path, src) { + return new ServerEvents(`subscribe to ${identifier}(path: ${path}) from ${src}`, Types.SUBSCRIBE); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static UNSUBSCRIBE(identifier,path, src) { + return new ServerEvents(`unsubscribe to ${identifier}(path: ${path}) from ${src}`, Types.UNSUBSCRIBE); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static INVOKE(identifier,path, src) { + return new ServerEvents(`invoke to ${identifier}(path: ${path}) from ${src}`, Types.INVOKE); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + * @param {number} target + * @param {number[]} sources + */ + static MATRIX_CONNECTION(identifier, path, src, target, sources) { + return new ServerEvents( + `Matrix connection to ${identifier}(path: ${path}) target ${target} connections: ${sources.toString()} from ${src}`, + Types.MATRIX_CONNECTION + ); + } +} + +module.exports = ServerEvents; \ No newline at end of file diff --git a/EmberServer/index.js b/EmberServer/index.js index 17080bd..2a6ba5c 100755 --- a/EmberServer/index.js +++ b/EmberServer/index.js @@ -1 +1,4 @@ -module.exports = require("./EmberServer"); \ No newline at end of file +module.exports = { + EmberServer: require("./EmberServer"), + ServerEvents: require("./ServerEvents") +}; \ No newline at end of file diff --git a/EmberSocket/S101Client.js b/EmberSocket/S101Client.js index 5e69b25..d87ca87 100755 --- a/EmberSocket/S101Client.js +++ b/EmberSocket/S101Client.js @@ -6,10 +6,17 @@ const BER = require('../ber.js'); const ember = require("../EmberLib"); class S101Client extends S101Socket { + /** + * + * @param {Socket} socket + * @param {S101Server} server + */ constructor(socket, server) { super() this.request = null; + /** @type {S101Server} */ this.server = server; + /** @type {Socket} */ this.socket = socket; this.pendingRequests = []; @@ -27,7 +34,7 @@ class S101Client extends S101Socket { const ber = new BER.Reader(packet); try { const root = ember.rootDecode(ber); - if (root !== undefined) { + if (root != null) { this.emit('emberTree', root); } } catch (e) { @@ -52,6 +59,10 @@ class S101Client extends S101Socket { } } + /** + * + * @param {function} cb + */ addRequest(cb) { this.pendingRequests.push(cb); this._makeRequest(); diff --git a/EmberSocket/S101Server.js b/EmberSocket/S101Server.js index 67dcb84..c712292 100755 --- a/EmberSocket/S101Server.js +++ b/EmberSocket/S101Server.js @@ -21,7 +21,7 @@ class S101Server extends EventEmitter { * @param {Socket} socket */ addClient(socket) { - var client = new S101Client(socket, this); + const client = new S101Client(socket, this); this.emit("connection", client); } /** diff --git a/EmberSocket/S101Socket.js b/EmberSocket/S101Socket.js index 7a2da2c..dfab372 100755 --- a/EmberSocket/S101Socket.js +++ b/EmberSocket/S101Socket.js @@ -64,7 +64,7 @@ class S101Socket extends EventEmitter{ const ber = new BER.Reader(packet); try { const root = ember.rootDecode(ber); - if (root !== undefined) { + if (root != null) { this.emit('emberTree', root); } } catch (e) { @@ -122,7 +122,7 @@ class S101Socket extends EventEmitter{ * @returns {boolean} */ isConnected() { - return ((this.socket !== null) && (this.socket !== undefined)); + return ((this.socket !== null) && (this.socket != null)); } /** diff --git a/ber.js b/ber.js index 8518c39..3a64b08 100755 --- a/ber.js +++ b/ber.js @@ -108,7 +108,7 @@ ExtendedReader.prototype.readValue = function() { ExtendedReader.prototype.readReal = function(tag) { - if(tag !== undefined) { + if(tag != null) { tag = UNIVERSAL(9); } @@ -313,7 +313,7 @@ ExtendedWriter.prototype.writeValue = function(value, tag) { } ExtendedWriter.prototype.writeIfDefined = function(property, writer, outer, inner) { - if(property !== undefined) { + if(property != null) { this.startSequence(CONTEXT(outer)); writer.call(this, property, inner); this.endSequence(); @@ -321,9 +321,9 @@ ExtendedWriter.prototype.writeIfDefined = function(property, writer, outer, inne } ExtendedWriter.prototype.writeIfDefinedEnum = function(property, type, writer, outer, inner) { - if(property !== undefined) { + if(property != null) { this.startSequence(CONTEXT(outer)); - if(property.value !== undefined) { + if(property.value != null) { writer.call(this, property.value, inner); } else { writer.call(this, type.get(property), inner); diff --git a/errors.js b/errors.js index 9aad982..dd37c02 100755 --- a/errors.js +++ b/errors.js @@ -1,59 +1,178 @@ -const util = require('util'); /**************************************************************************** * UnimplementedEmberType error ***************************************************************************/ -function UnimplementedEmberTypeError(tag) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - var identifier = (tag & 0xC0) >> 6; - var value = (tag & 0x1F).toString(); - var tagStr = tag.toString(); - if(identifier == 0) { - tagStr = "[UNIVERSAL " + value + "]"; - } else if(identifier == 1) { - tagStr = "[APPLICATION " + value + "]"; - } else if(identifier == 2) { - tagStr = "[CONTEXT " + value + "]"; - } else { - tagStr = "[PRIVATE " + value + "]"; - } - this.message = "Unimplemented EmBER type " + tagStr; -} - -util.inherits(UnimplementedEmberTypeError, Error); +class UnimplementedEmberTypeError extends Error { + constructor(tag) { + super(); + this.name = this.constructor.name; + var identifier = (tag & 0xC0) >> 6; + var value = (tag & 0x1F).toString(); + var tagStr = tag.toString(); + if(identifier == 0) { + tagStr = "[UNIVERSAL " + value + "]"; + } else if(identifier == 1) { + tagStr = "[APPLICATION " + value + "]"; + } else if(identifier == 2) { + tagStr = "[CONTEXT " + value + "]"; + } else { + tagStr = "[PRIVATE " + value + "]"; + } + this.message = "Unimplemented EmBER type " + tagStr; + } +} + module.exports.UnimplementedEmberTypeError = UnimplementedEmberTypeError; -function ASN1Error(message) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.message = message; +class ASN1Error extends Error { + constructor(message) { + super(message); + } } -util.inherits(ASN1Error, Error); module.exports.ASN1Error = ASN1Error; -function EmberAccessError(message) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - if(this.message !== undefined) { - this.message = message; - } else { - this.message("Parameter access error"); +class EmberAccessError extends Error { + constructor(message) { + super(message); } } -util.inherits(EmberAccessError, Error); module.exports.EmberAccessError = EmberAccessError; -function EmberTimeoutError(message) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.message = message; +class EmberTimeoutError extends Error { + constructor(message) { + super(message); + } } -util.inherits(EmberTimeoutError, Error); module.exports.EmberTimeoutError = EmberTimeoutError; +class InvalidCommand extends Error { + /** + * + * @param {number} number + */ + constructor(number) { + super(`Invalid command ${number}`); + } +} + +module.exports.InvalidCommand = InvalidCommand; + +class MissingElementNumber extends Error { + constructor() { + super("Missing element number"); + } +} + +module.exports.MissingElementNumber = MissingElementNumber; + +class MissingElementContents extends Error { + /** + * + * @param {string} path + */ + constructor(path) { + super(`Missing element contents at ${path}`); + } +} + +module.exports.MissingElementContents = MissingElementContents; + +class UnknownElement extends Error { + /** + * + * @param {string} path + */ + constructor(path) { + super(`No element at path ${path}`); + } +} + +module.exports.UnknownElement = UnknownElement; + +class InvalidRequest extends Error { + constructor() { + super("Invalid Request"); + } +} + +module.exports.InvalidRequest = InvalidRequest; + +class InvalidRequestFormat extends Error { + /** + * + * @param {string} path + */ + constructor(path) { + super(`Invalid Request Format with path ${path}`); + } +} + +module.exports.InvalidRequestFormat = InvalidRequestFormat; + +class InvalidEmberNode extends Error { + /** + * + * @param {string} path + * @param {string} info + */ + constructor(path="unknown", info="") { + super(`Invalid Ember Node at ${path}: ${info}`); + } +} +module.exports.InvalidEmberNode = InvalidEmberNode; + +class InvalidEmberResponse extends Error { + /** + * + * @param {string} req + */ + constructor(req) { + super(`Invalid Ember Response to ${req}`); + } +} +module.exports.InvalidEmberResponse = InvalidEmberResponse; + +class PathDiscoveryFailure extends Error { + /** + * + * @param {string} path + */ + constructor(path) { + super(`Failed path discovery at ${path}`); + } +} +module.exports.PathDiscoveryFailure = PathDiscoveryFailure; + +class InvalidSourcesFormat extends Error { + constructor() { + super("Sources should be an array"); + } +} +module.exports.InvalidSourcesFormat = InvalidSourcesFormat; + +class InvalidBERFormat extends Error { + /** + * + * @param {string} info + */ + constructor(info="") { + super(`Invalid BER format: ${info}`); + } +} +module.exports.InvalidBERFormat = InvalidBERFormat; + +class InvalidResultFormat extends Error { + /** + * + * @param {string} info + */ + constructor(info="") { + super(`Invalid Result format: ${info}`); + } +} +module.exports.InvalidResultFormat = InvalidResultFormat; \ No newline at end of file diff --git a/index.js b/index.js index 7b60331..1de9a28 100755 --- a/index.js +++ b/index.js @@ -2,6 +2,6 @@ const EmberClient = require('./EmberClient'); const EmberLib = require("./EmberLib"); const Decoder = EmberLib.DecodeBuffer; const S101 = require("./s101"); -const EmberServer = require("./EmberServer"); +const {EmberServer,ServerEvents} = require("./EmberServer"); const {S101Client} = require("./EmberSocket"); -module.exports = {EmberClient, Decoder, EmberLib, EmberServer, S101, S101Client}; +module.exports = {EmberClient, Decoder, EmberLib, EmberServer,ServerEvents, S101, S101Client}; diff --git a/package.json b/package.json index 5a931cb..bb05bd1 100755 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "node-emberplus", - "version": "2.2.1", + "version": "2.3.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { - "test": "jest test", + "test": "jest test --coverage", "eslint": "eslint ./", "start": "node server.js" }, diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index c072418..4f9e879 100755 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -2,7 +2,7 @@ const fs = require("fs"); const sinon = require("sinon"); const Decoder = require('../EmberLib').DecodeBuffer; const EmberClient = require("../EmberClient"); -const EmberServer = require("../EmberServer"); +const {EmberServer} = require("../EmberServer"); const LOCALHOST = "127.0.0.1"; const UNKNOWN_HOST = "192.168.99.99"; diff --git a/test/Ember.test.js b/test/Ember.test.js index f9ea636..a333af1 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -5,29 +5,122 @@ const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a const ember = require("../EmberLib"); const BER = require('../ber.js'); const errors = require('../errors.js'); +const EmberLib = require("../EmberLib"); +const identifier = "node_identifier"; +const description = "node_description"; describe("Ember", () => { - let client; + describe("generic", () => { + let client; - beforeAll(() => { - client = new S101Client(); - }); + beforeAll(() => { + client = new S101Client(); + }); + + it("should parse S101 message without error", (done) => { + client.on("emberPacket", () => { + done(); + }); + client.on("error", e => { + // eslint-disable-next-line no-console + console.log(e); + expect(e).toBeUndefined(); + done(); + }); + client.codec.dataIn(s101Buffer); + }); - it("should parse S101 message without error", (done) => { - client.on("emberPacket", () => { - done(); + it("should handle errors in message", () => { + var ber = new BER.Reader(errorBuffer); + expect(() => ember.Root.decode(ber)).toThrow(errors.UnimplementedEmberTypeError); + }); + }); + describe("Command", () => { + it("should throw error if unknown context found", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.APPLICATION(2)); + writer.startSequence(BER.CONTEXT(0)); + writer.writeInt(EmberLib.COMMAND_GETDIRECTORY); + writer.endSequence(); // BER.CONTEXT(0) + writer.startSequence(BER.CONTEXT(1)); + writer.writeInt(0); + writer.endSequence(); + writer.startSequence(BER.CONTEXT(3)); + writer.writeInt(0); + writer.endSequence(); + writer.endSequence(); // BER.APPLICATION(2) + try { + EmberLib.Command.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e).not.toBe("Should not succeed"); + } }); - client.on("error", e => { - console.log(e); - expect(e).toBeUndefined(); - done(); + it("should have a toJSON", () => { + const command = new EmberLib.Command(EmberLib.COMMAND_GETDIRECTORY); + const jsonCommand = command.toJSON(); + expect(jsonCommand.number).toBe(EmberLib.COMMAND_GETDIRECTORY); }); - client.codec.dataIn(s101Buffer); }); + describe("Node", () => { + it("should have an encoder", () => { + const node = new EmberLib.Node(0); + const identifier = "node_identifier"; + const description = "node_description"; + node.contents = new EmberLib.NodeContents(identifier, description); + const root = new EmberLib.Node(0); + root.addChild(node); + const writer = new BER.Writer(); + root.encode(writer); + expect(writer.buffer.size).not.toBe(0); - it("should handle errors in message", () => { - var ber = new BER.Reader(errorBuffer); - expect(() => ember.Root.decode(ber)).toThrow(errors.UnimplementedEmberTypeError); + }); + it("should have a decoder", () => { + const node = new EmberLib.Node(0); + node.contents = new EmberLib.NodeContents(identifier, description); + const writer = new BER.Writer(); + node.encode(writer); + const n = EmberLib.Node.decode(new BER.Reader(writer.buffer)); + expect(n.number).toBe(node.number); + expect(n.contents.identifier).toBe(identifier); + expect(n.contents.description).toBe(description); + }); + }); + describe("Function", () => { + let func; + beforeAll(() => { + func = new EmberLib.Function(0, args => { + const res = new EmberLib.FunctionArgument(); + res.type = EmberLib.ParameterType.integer; + res.value = args[0].value + args[1].value; + return [res]; + }); + }) + it("should return true when calling isFunction", () => { + expect(func.isFunction()).toBeTruthy(); + }); + it("should have an invoke function", () => { + const invoke = func.invoke(); + const children = invoke.getChildren(); + expect(children.length).toBe(1); + expect(children[0].isCommand()).toBeTruthy(); + }); + it("should have a decoder", () => { + func.contents = new EmberLib.FunctionContent(identifier, description); + func.contents.arguments = [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer,null, "arg1"), + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer,null, "arg2") + ]; + func.contents.result = [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer,null, "result") + ]; + const writer = new BER.Writer(); + func.encode(writer); + const f = EmberLib.Function.decode(new BER.Reader(writer.buffer)); + expect(f.number).toBe(func.number); + expect(f.contents.identifier).toBe(identifier); + expect(f.contents.description).toBe(description); + }); }); - }); diff --git a/test/Server.test.js b/test/Server.test.js index d11a682..85a898a 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -1,5 +1,5 @@ const expect = require("expect"); -const EmberServer = require("../EmberServer"); +const {EmberServer, ServerEvents} = require("../EmberServer"); const EmberClient = require("../EmberClient"); const ember = require("../EmberLib"); const {jsonRoot} = require("./utils"); @@ -8,12 +8,6 @@ const MatrixHandlers = require("../EmberServer/MatrixHandlers"); const LOCALHOST = "127.0.0.1"; const PORT = 9009; -const wait = function(t) { - return new Promise(resolve => { - setTimeout(resolve, t); - }); -} - describe("server", function() { describe("JSONtoTree", function() { let jsonTree; @@ -182,7 +176,6 @@ describe("server", function() { throw new Error("Should not succeed"); }) .catch(e => { - console.log(e); expect(e.message).toMatch(/Failed path discovery/); return client.disconnect(); }); @@ -224,7 +217,7 @@ describe("server", function() { }) .then(() => { expect(count).toBe(1); - expect(receivedEvent).toMatch(/getdirectory to root/); + expect(receivedEvent.type).toBe(ServerEvents.Types.GETDIRECTORY); return client.getElementByPath("0.1.0"); }) .then(matrix => { @@ -233,7 +226,7 @@ describe("server", function() { }) .then(() => { expect(count).toBe(1); - expect(receivedEvent).toMatch(/Matrix connection to matrix/); + expect(receivedEvent.type).toBe(ServerEvents.Types.MATRIX_CONNECTION); }) .then(() => { count = 0; @@ -245,7 +238,7 @@ describe("server", function() { }) .then(() => { expect(count).toBe(1); - expect(receivedEvent).toMatch(/invoke to /); + expect(receivedEvent.type).toBe(ServerEvents.Types.INVOKE); }) .then(() => client.getNodeByPathnum("0.0.2")) .then(parameter => { @@ -263,7 +256,7 @@ describe("server", function() { }) .then(() => { expect(count).toBe(1); - expect(receivedEvent).toMatch(/subscribe to version/); + expect(receivedEvent.type).toBe(ServerEvents.Types.SUBSCRIBE); }) .then(() => { server.off("event", eventHandler); @@ -281,7 +274,7 @@ describe("server", function() { }); it("should verify if connection allowed in 1-to-N", function() { let disconnectCount = 0; - const handleDisconnect = info => { + const handleDisconnect = () => { disconnectCount++; } server.on("matrix-disconnect", handleDisconnect.bind(this)); From d05a8bd146a39dad92d3afb155ddbc71e6f4b6a1 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 11 Jan 2020 21:46:50 +0100 Subject: [PATCH 133/162] Added code coverage for Parameter --- EmberLib/ParameterContents.js | 5 ++++ test/Ember.test.js | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/EmberLib/ParameterContents.js b/EmberLib/ParameterContents.js index 93a5345..54548c6 100755 --- a/EmberLib/ParameterContents.js +++ b/EmberLib/ParameterContents.js @@ -8,6 +8,11 @@ const BER = require('../ber.js'); const errors = require("../errors"); class ParameterContents { + /** + * + * @param {string|number} value + * @param {string} type + */ constructor(value, type) { this._subscribers = new Set(); if(value != null) { diff --git a/test/Ember.test.js b/test/Ember.test.js index a333af1..4bb9f81 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -69,6 +69,9 @@ describe("Ember", () => { const identifier = "node_identifier"; const description = "node_description"; node.contents = new EmberLib.NodeContents(identifier, description); + node.contents.isRoot = true; + node.contents.isOnline = true; + node.contents.schemaIdentifiers = "schema1"; const root = new EmberLib.Node(0); root.addChild(node); const writer = new BER.Writer(); @@ -79,6 +82,9 @@ describe("Ember", () => { it("should have a decoder", () => { const node = new EmberLib.Node(0); node.contents = new EmberLib.NodeContents(identifier, description); + node.contents.isRoot = true; + node.contents.isOnline = true; + node.contents.schemaIdentifiers = "schema1"; const writer = new BER.Writer(); node.encode(writer); const n = EmberLib.Node.decode(new BER.Reader(writer.buffer)); @@ -123,4 +129,48 @@ describe("Ember", () => { expect(f.contents.description).toBe(description); }); }); + describe("Parameter", () => { + it("should have an update function", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = 1; + let count = 0; + parameter.contents = new EmberLib.ParameterContents(VALUE, "integer"); + parameter.contents._subscribers.add(() => {count++;}); + const newParameter = new EmberLib.Parameter(0); + const NEW_VALUE = VALUE + 1; + newParameter.contents = new EmberLib.ParameterContents(NEW_VALUE, "integer"); + parameter.update(newParameter); + expect(count).toBe(1); + expect(parameter.contents.value).toBe(NEW_VALUE); + }); + it("should have setValue function", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = 1; + parameter.contents = new EmberLib.ParameterContents(VALUE, "integer"); + const NEW_VALUE = VALUE + 1; + const setVal = parameter.setValue(NEW_VALUE); + expect(setVal.contents.value).toBe(NEW_VALUE); + }); + it("should have decoder function", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = 1; + parameter.contents = new EmberLib.ParameterContents(VALUE, "integer"); + parameter.contents.minimum = 0; + parameter.contents.maximum = 100; + parameter.contents.access = EmberLib.ParameterAccess.readWrite; + parameter.contents.format = "db"; + parameter.contents.factor = 10; + parameter.contents.isOnline = true; + parameter.contents.formula = "x10"; + parameter.contents.step = 2; + parameter.contents.default = 0; + parameter.contents.type = EmberLib.ParameterType.integer; + const node = new EmberLib.Node(0); + parameter.addChild(node); + const writer = new BER.Writer(); + parameter.encode(writer); + const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + expect(newParameter.getChildren().length).toBe(1); + }); + }); }); From b129794ad539c22fe35b004a1a1b607f2dc51688 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 12 Jan 2020 10:51:10 +0100 Subject: [PATCH 134/162] Matrix code coverage. Bug fixing --- EmberLib/Matrix.js | 29 +++-- EmberLib/index.js | 2 + ber.js | 6 - errors.js | 14 ++- test/Ember.test.js | 265 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 296 insertions(+), 20 deletions(-) diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js index a8e3cdf..df2c2ea 100755 --- a/EmberLib/Matrix.js +++ b/EmberLib/Matrix.js @@ -13,6 +13,9 @@ class Matrix extends TreeNode super(); this._connectedSources = {}; this._numConnections = 0; + this.targets = null; + this.sources = null; + this.connections = {}; } isMatrix() { @@ -175,17 +178,23 @@ class Matrix extends TreeNode * @returns {boolean} */ static canConnect(matrixNode, targetID, sources, operation) { + if (matrixNode.connections == null) { + matrixNode.connections = {}; + } + if (matrixNode.connections[targetID] == null) { + matrixNode.connections[targetID] = new MatrixConnection(targetID); + } const type = matrixNode.contents.type == null ? MatrixType.oneToN : matrixNode.contents.type; const connection = matrixNode.connections[targetID]; const oldSources = connection == null || connection.sources == null ? [] : connection.sources.slice(); const newSources = operation === MatrixOperation.absolute ? sources : oldSources.concat(sources); const sMap = new Set(newSources.map(i => Number(i))); - + if (matrixNode.connections[targetID].isLocked()) { return false; } if (type === MatrixType.oneToN && - matrixNode.contents.maximumConnectsPerTarget == null && + matrixNode.contents.maximumTotalConnects == null && matrixNode.contents.maximumConnectsPerTarget == null) { return sMap.size < 2; } @@ -389,20 +398,20 @@ class Matrix extends TreeNode */ static validateConnection(matrixNode, targetID, sources) { if (targetID < 0) { - throw new Errors.InvalidEmberNode(matrixNode.getPath(), `Invalid negative target index ${targetID}`); + throw new Errors.InvalidMatrixSignal(targetID, "target"); } for(let i = 0; i < sources.length; i++) { if (sources[i] < 0) { - throw new Errors.InvalidEmberNode(matrixNode.getPath(),`Invalid negative source at index ${i}`); + throw new Errors.InvalidMatrixSignal(sources[i], `Source at index ${i}`); } } if (matrixNode.contents.mode === MatrixMode.linear) { if (targetID >= matrixNode.contents.targetCount) { - throw Errors.InvalidEmberNode(matrixNode.getPath(),`targetID ${targetID} higher than max value ${matrixNode.contents.targetCount}`); + throw new Errors.InvalidMatrixSignal(targetID, `Target higher than max value ${matrixNode.contents.targetCount}`); } for(let i = 0; i < sources.length; i++) { if (sources[i] >= matrixNode.contents.sourceCount) { - throw new Errors.InvalidEmberNode(matrixNode.getPath(),`Invalid source at index ${i}`); + throw new Errors.InvalidMatrixSignal(sources[i],`Source at index ${i} higher than max ${matrixNode.contents.sourceCount}`); } } } @@ -411,25 +420,25 @@ class Matrix extends TreeNode } else { let found = false; - for(let i = 0; i < matrixNode.targets; i++) { + for(let i = 0; i < matrixNode.targets.length; i++) { if (matrixNode.targets[i] === targetID) { found = true; break; } } if (!found) { - throw new Errors.InvalidEmberNode(matrixNode.getPath(),`Unknown targetid ${targetID}`); + throw new Errors.InvalidMatrixSignal(targetID, "Not part of existing targets"); } found = false; for(let i = 0; i < sources.length; i++) { - for(let j = 0; i < matrixNode.sources; j++) { + for(let j = 0; j < matrixNode.sources.length; j++) { if (matrixNode.sources[j] === sources[i]) { found = true; break; } } if (!found) { - throw new Errors.InvalidEmberNode(matrixNode.getPath(),`Unknown source at index ${i}`); + throw new Errors.InvalidMatrixSignal(sources[i],`Unknown source at index ${i}`); } } } diff --git a/EmberLib/index.js b/EmberLib/index.js index 92bf00c..6da638d 100755 --- a/EmberLib/index.js +++ b/EmberLib/index.js @@ -10,6 +10,7 @@ const FunctionContent = require("./FunctionContent"); const Invocation = require("./Invocation"); const InvocationResult = require("./InvocationResult"); const Label = require("./Label"); +const Matrix = require("./Matrix"); const MatrixNode = require("./MatrixNode"); const MatrixMode = require("./MatrixMode"); const MatrixType = require("./MatrixType"); @@ -128,6 +129,7 @@ module.exports = { Invocation, InvocationResult, Label, + Matrix, MatrixNode, MatrixMode, MatrixType, diff --git a/ber.js b/ber.js index 3a64b08..03d22d9 100755 --- a/ber.js +++ b/ber.js @@ -74,12 +74,6 @@ function ExtendedReader(data) { util.inherits(ExtendedReader, BER.Reader); module.exports.Reader = ExtendedReader; - -readBlock = function(ber) { - -} - - ExtendedReader.prototype.getSequence = function(tag) { var buf = this.readString(tag, true); return new ExtendedReader(buf); diff --git a/errors.js b/errors.js index dd37c02..e89d463 100755 --- a/errors.js +++ b/errors.js @@ -175,4 +175,16 @@ class InvalidResultFormat extends Error { super(`Invalid Result format: ${info}`); } } -module.exports.InvalidResultFormat = InvalidResultFormat; \ No newline at end of file +module.exports.InvalidResultFormat = InvalidResultFormat; + +class InvalidMatrixSignal extends Error { + /** + * + * @param {number} value + * @param {string} info + */ + constructor(value, info) { + super(`Invalid Matrix Signal ${value}: ${info}`); + } +} +module.exports.InvalidMatrixSignal = InvalidMatrixSignal; \ No newline at end of file diff --git a/test/Ember.test.js b/test/Ember.test.js index 4bb9f81..80c7789 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -4,7 +4,7 @@ const s101Buffer = Buffer.from("fe000e0001c001021f026082008d6b820089a0176a15a005 const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); const ember = require("../EmberLib"); const BER = require('../ber.js'); -const errors = require('../errors.js'); +const Errors = require('../errors.js'); const EmberLib = require("../EmberLib"); const identifier = "node_identifier"; @@ -30,9 +30,9 @@ describe("Ember", () => { client.codec.dataIn(s101Buffer); }); - it("should handle errors in message", () => { + it("should handle Errors in message", () => { var ber = new BER.Reader(errorBuffer); - expect(() => ember.Root.decode(ber)).toThrow(errors.UnimplementedEmberTypeError); + expect(() => ember.Root.decode(ber)).toThrow(Errors.UnimplementedEmberTypeError); }); }); describe("Command", () => { @@ -173,4 +173,263 @@ describe("Ember", () => { expect(newParameter.getChildren().length).toBe(1); }); }); + describe("Matrix", () => { + describe("validateConnection", () => { + let matrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeAll(() => { + matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + }); + it("should through an error if target is negative", () => { + try { + EmberLib.Matrix.validateConnection(matrixNode, -1, []); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should through an error if source is negative", () => { + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [-1]); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should through an error if target higher than max target", () => { + try { + EmberLib.Matrix.validateConnection(matrixNode, TARGETCOUNT, [0]); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should through an error if target higher than max target", () => { + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [SOURCECOUNT]); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should through an error if non-Linear Matrix without targets", () => { + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [0]); + throw new Error("Should not succeed"); + } + catch(e) { + matrixNode.contents.mode = EmberLib.MatrixMode.linear; + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + it("should through an error if non-Linear Matrix without sources", () => { + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.targets = [0, 3]; + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [0]); + throw new Error("Should not succeed"); + } + catch(e) { + matrixNode.contents.mode = EmberLib.MatrixMode.linear; + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + it("should through an error if non-Linear Matrix and not valid target", () => { + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.targets = [0, 3]; + matrixNode.sources = [0, 3]; + try { + EmberLib.Matrix.validateConnection(matrixNode, 1, [0]); + throw new Error("Should not succeed"); + } + catch(e) { + matrixNode.contents.mode = EmberLib.MatrixMode.linear; + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should through an error if non-Linear Matrix and not valid source", () => { + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.targets = [0, 3]; + matrixNode.sources = [0, 3]; + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [1]); + throw new Error("Should not succeed"); + } + catch(e) { + matrixNode.contents.mode = EmberLib.MatrixMode.linear; + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should not through an error on valid non-linear connect", () => { + let error = null; + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.targets = [0, 3]; + matrixNode.sources = [0, 3]; + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [0]); + } + catch(e) { + error = e; + } + expect(error == null).toBeTruthy(); + }); + }); + describe("MatrixUpdate", () => { + let matrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeAll(() => { + matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + }); + it("should not through an error on valid non-linear connect", () => { + let error = null; + const newMatrixNode = new EmberLib.MatrixNode(0); + newMatrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + newMatrixNode.targets = [0, 3]; + newMatrixNode.sources = [0, 3]; + newMatrixNode.contents.identifier = "matrix"; + newMatrixNode.contents.description = "matrix"; + + + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.connections = null; + try { + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + } + catch(e) { + error = e; + } + expect(error == null).toBeTruthy(); + expect(matrixNode.targets).toBeDefined(); + expect(matrixNode.targets.length).toBe(newMatrixNode.targets.length); + expect(matrixNode.sources.length).toBe(newMatrixNode.sources.length); + }); + }); + describe("disconnectSources", () => { + let matrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeAll(() => { + matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + }); + it("should generate the connection structure if not existent", () => { + EmberLib.Matrix.disconnectSources(matrixNode, 0, [1]); + }); + }); + describe("decodeConnections", () => { + let matrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeAll(() => { + matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + }); + it("should generate the connection structure if not existent", () => { + const SOURCEID = 0; + EmberLib.Matrix.connectSources(matrixNode, 0, [SOURCEID]); + const writer = new BER.Writer(); + matrixNode.encodeConnections(writer); + const ber = new BER.Reader(writer.buffer); + const seq = ber.getSequence(BER.CONTEXT(5)); + const connections = EmberLib.Matrix.decodeConnections(seq); + expect(connections[0].sources).toBeDefined(); + expect(connections[0].sources.length).toBe(1); + expect(connections[0].sources[0]).toBe(SOURCEID); + }); + }); + describe("canConnect", () => { + let matrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeAll(() => { + matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + }); + it("should return false if more than 1 source in 1toN", () => { + matrixNode.connections = null; + matrixNode.contents.maximumTotalConnects = 1; + const res = EmberLib.Matrix.canConnect(matrixNode, 0, [0,3]); + expect(res).toBeFalsy(); + }); + }); + describe("Matrix Non-Linear", () => { + it("should have encoder / decoder", () => { + const matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.targets = [0,3]; + matrixNode.sources = [1,2]; + const writer = new BER.Writer(); + matrixNode.encode(writer); + const newMatrixNode = EmberLib.Matrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.targets).toBeDefined(); + }); + it("should have connect function", () => { + const root = new EmberLib.Root(); + const matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.targets = [0,3]; + matrixNode.sources = [1,2]; + root.addChild(matrixNode); + const connect = matrixNode.connect({0: new EmberLib.MatrixConnection(0)}); + expect(connect).toBeDefined(); + }); + }); + }); }); From 3ee90868f12e4819563f8396b3876f1d508bfa89 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 13 Jan 2020 18:36:40 +0100 Subject: [PATCH 135/162] Adding test code coverage. EmberLib 100% upto Matrix --- EmberClient/EmberClient.js | 6 +- EmberLib/Command.js | 10 +- EmberLib/Function.js | 15 +- EmberLib/FunctionArgument.js | 35 ++- EmberLib/FunctionContent.js | 20 +- EmberLib/Invocation.js | 56 +++- EmberLib/InvocationResult.js | 16 +- EmberLib/Label.js | 22 +- EmberLib/Matrix.js | 5 +- EmberLib/MatrixContents.js | 4 +- EmberLib/MatrixNode.js | 9 +- EmberLib/Node.js | 25 +- EmberLib/Parameter.js | 39 +-- EmberLib/QualifiedElement.js | 24 -- EmberLib/QualifiedFunction.js | 13 +- EmberLib/QualifiedMatrix.js | 11 +- EmberLib/QualifiedNode.js | 11 +- EmberLib/QualifiedParameter.js | 11 +- EmberLib/StreamDescription.js | 32 ++- EmberLib/StringIntegerCollection.js | 77 +++--- EmberLib/TreeNode.js | 78 ++++-- EmberLib/index.js | 2 + ber.js | 10 + embrionix.ember | Bin 41743 -> 0 bytes test/DeviceTree.test.js | 2 +- test/Ember.test.js | 409 +++++++++++++++++++++++++++- test/Server.test.js | 67 ++--- 27 files changed, 771 insertions(+), 238 deletions(-) delete mode 100755 embrionix.ember diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index 8ac03f5..f2d2d43 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -683,10 +683,6 @@ class EmberClient extends EventEmitter { */ unsubscribe(qnode, callback) { if (qnode.isParameter() && qnode.isStream()) { - if (qnode == null) { - this.root.clear(); - qnode = this.root; - } return new Promise((resolve, reject) => { this.addRequest({node: qnode, func: (error) => { if (error != null) { @@ -698,7 +694,7 @@ class EmberClient extends EventEmitter { resolve(); }}); }); - } + } } } diff --git a/EmberLib/Command.js b/EmberLib/Command.js index 3ff9d6a..f91b78b 100755 --- a/EmberLib/Command.js +++ b/EmberLib/Command.js @@ -40,7 +40,7 @@ class Command { * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.APPLICATION(2)); + ber.startSequence(Command.BERID); ber.startSequence(BER.CONTEXT(0)); ber.writeInt(this.number); @@ -87,7 +87,7 @@ class Command { */ static decode(ber) { const c = new Command(); - ber = ber.getSequence(BER.APPLICATION(2)); + ber = ber.getSequence(Command.BERID); while(ber.remain > 0) { let tag = ber.peek(); @@ -110,6 +110,12 @@ class Command { return c; } + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(2); + } } module.exports = Command; \ No newline at end of file diff --git a/EmberLib/Function.js b/EmberLib/Function.js index 0c54eab..4d3913c 100755 --- a/EmberLib/Function.js +++ b/EmberLib/Function.js @@ -5,14 +5,14 @@ const BER = require('../ber.js'); const Command = require("./Command"); const {COMMAND_INVOKE} = require("./constants"); const FunctionContent = require("./FunctionContent"); -const errors = require("../errors"); +const Errors = require("../errors"); class Function extends Element { constructor(number, func) { super(); this.number = number; this.func = func; - this._seqID = BER.APPLICATION(19); + this._seqID = Function.BERID; } /** @@ -48,7 +48,7 @@ class Function extends Element { */ static decode(ber) { const f = new Function(); - ber = ber.getSequence(BER.APPLICATION(19)); + ber = ber.getSequence(Function.BERID); while(ber.remain > 0) { let tag = ber.peek(); @@ -60,11 +60,18 @@ class Function extends Element { } else if(tag == BER.CONTEXT(2)) { f.decodeChildren(seq); } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return f; } + + /** + * + */ + static get BERID() { + return BER.APPLICATION(19); + } } module.exports = Function; \ No newline at end of file diff --git a/EmberLib/FunctionArgument.js b/EmberLib/FunctionArgument.js index 216e402..38a7d3d 100755 --- a/EmberLib/FunctionArgument.js +++ b/EmberLib/FunctionArgument.js @@ -1,6 +1,7 @@ "use strict"; const BER = require('../ber.js'); const {ParameterType} = require("./ParameterType"); +const Errors = require("../errors"); /* TupleDescription ::= @@ -40,12 +41,13 @@ class FunctionArgument { * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.APPLICATION(21)); - if (this.type != null) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.type.value); - ber.endSequence(); + ber.startSequence(FunctionArgument.BERID); + if (this.type == null) { + throw new Errors.InvalidEmberNode("", "FunctionArgument requires a type") } + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.type.value); + ber.endSequence(); if (this.name != null) { ber.startSequence(BER.CONTEXT(1)); ber.writeString(this.name, BER.EMBER_STRING); @@ -54,6 +56,17 @@ class FunctionArgument { ber.endSequence(); } + /** + * + */ + toJSON() { + return { + type: this.type, + name: this.name, + value: this.value + }; + } + /** * * @param {BER} ber @@ -61,7 +74,7 @@ class FunctionArgument { */ static decode(ber) { const tuple = new FunctionArgument(); - ber = ber.getSequence(BER.APPLICATION(21)); + ber = ber.getSequence(FunctionArgument.BERID); while(ber.remain > 0) { let tag = ber.peek(); let seq = ber.getSequence(tag); @@ -71,9 +84,19 @@ class FunctionArgument { else if (tag === BER.CONTEXT(1)) { tuple.name = seq.readString(BER.EMBER_STRING); } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } } return tuple; } + + /** + * + */ + static get BERID() { + return BER.APPLICATION(21); + } } module.exports = FunctionArgument; \ No newline at end of file diff --git a/EmberLib/FunctionContent.js b/EmberLib/FunctionContent.js index dacab9e..37d434e 100755 --- a/EmberLib/FunctionContent.js +++ b/EmberLib/FunctionContent.js @@ -42,15 +42,20 @@ class FunctionContent { ber.endSequence(); // BER.CONTEXT(2) } - if(this.result != null) { + if(this.result != null && this.result.length > 0) { ber.startSequence(BER.CONTEXT(3)); - ber.startSequence(BER.EMBER_SEQUENCE); - for(let i = 0; i < this.result.length; i++) { + for(let i = 0; i < this.result.length; i++) { ber.startSequence(BER.CONTEXT(0)); + /** @type {FunctionArgument} */ this.result[i].encode(ber); ber.endSequence(); - } - ber.endSequence(); + } + ber.endSequence(); // BER.CONTEXT(3) + } + + if(this.templateReference != null) { + ber.startSequence(BER.CONTEXT(4)); + ber.writeRelativeOID(this.templateReference, BER.EMBER_RELATIVE_OID); ber.endSequence(); // BER.CONTEXT(3) } @@ -83,10 +88,13 @@ class FunctionContent { fc.result = []; while(seq.remain > 0) { tag = seq.peek(); - let dataSeq = seq.getSequence(tag); + const dataSeq = seq.getSequence(tag); if (tag === BER.CONTEXT(0)) { fc.result.push(FunctionArgument.decode(dataSeq)); } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } } } else if(tag == BER.CONTEXT(4)) { fc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); diff --git a/EmberLib/Invocation.js b/EmberLib/Invocation.js index b114e0a..580a68c 100755 --- a/EmberLib/Invocation.js +++ b/EmberLib/Invocation.js @@ -6,9 +6,14 @@ const errors = require("../errors"); let _id = 1; class Invocation { - constructor(id = null) { - this.id = id == null ? _id++ : id; - this.arguments = []; + /** + * + * @param {number} id + * @param {FunctionArgument[]} args + */ + constructor(id = null, args = []) { + this.id = id; + this.arguments = args; } /** @@ -16,13 +21,12 @@ class Invocation { * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.APPLICATION(22)); - // ber.startSequence(BER.EMBER_SEQUENCE); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.id) - ber.endSequence(); - + ber.startSequence(Invocation.BERID); + if (this.id != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.id) + ber.endSequence(); + } ber.startSequence(BER.CONTEXT(1)); ber.startSequence(BER.EMBER_SEQUENCE); for(var i = 0; i < this.arguments.length; i++) { @@ -39,21 +43,29 @@ class Invocation { ber.endSequence(); // BER.APPLICATION(22) } + /** + * + */ + toJSON() { + return { + id: this.id, + arguments: this.arguments == null ? null : this.arguments.map(a => a.toJSON()), + } + } + /** * * @param {BER} ber * @returns {Invocation} */ static decode(ber) { - let invocation = null; - ber = ber.getSequence(BER.APPLICATION(22)); + const invocation = new Invocation(); + ber = ber.getSequence(Invocation.BERID); while(ber.remain > 0) { let tag = ber.peek(); let seq = ber.getSequence(tag); if(tag == BER.CONTEXT(0)) { - // Create the invocation with the id received otherwise we will - // increment the internal id counter. - invocation = new Invocation(seq.readInt()); + invocation.id = seq.readInt(); } else if(tag == BER.CONTEXT(1)) { invocation.arguments = []; @@ -74,6 +86,20 @@ class Invocation { } return invocation; } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(22); + } + + /** + * @returns {number} + */ + static newInvocationID() { + return _id++; + } } module.exports = Invocation; \ No newline at end of file diff --git a/EmberLib/InvocationResult.js b/EmberLib/InvocationResult.js index bfd9f00..f26ea41 100755 --- a/EmberLib/InvocationResult.js +++ b/EmberLib/InvocationResult.js @@ -20,7 +20,7 @@ class InvocationResult { * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.APPLICATION(23)); + ber.startSequence(InvocationResult.BERID); if (this.invocationId != null) { ber.startSequence(BER.CONTEXT(0)); ber.writeInt(this.invocationId); @@ -81,7 +81,7 @@ class InvocationResult { */ static decode(ber) { const invocationResult = new InvocationResult(); - ber = ber.getSequence(BER.APPLICATION(23)); + ber = ber.getSequence(InvocationResult.BERID); while(ber.remain > 0) { let tag = ber.peek(); let seq = ber.getSequence(tag); @@ -103,16 +103,26 @@ class InvocationResult { resTag.readValue() )); } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } } continue } else { - // TODO: options throw new Errors.UnimplementedEmberTypeError(tag); } } return invocationResult; } + + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(23); + } } module.exports = InvocationResult; \ No newline at end of file diff --git a/EmberLib/Label.js b/EmberLib/Label.js index 3c6d648..280203e 100755 --- a/EmberLib/Label.js +++ b/EmberLib/Label.js @@ -1,6 +1,6 @@ "use strict"; const BER = require('../ber.js'); -const errors = require("../errors"); +const Errors = require("../errors"); class Label { constructor(path, description) { @@ -18,16 +18,18 @@ class Label { */ encode(ber) { ber.startSequence(BER.APPLICATION(18)); - if (this.basePath != null) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.basePath, BER.EMBER_RELATIVE_OID); - ber.endSequence(); + if (this.basePath == null) { + throw new Errors.InvalidEmberNode("", "Missing label base path"); } - if (this.description != null) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); + ber.startSequence(BER.CONTEXT(0)); + ber.writeRelativeOID(this.basePath, BER.EMBER_RELATIVE_OID); + ber.endSequence(); + if (this.description == null) { + throw new Errors.InvalidEmberNode("", "Missing label description"); } + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); ber.endSequence(); } @@ -50,7 +52,7 @@ class Label { l.description = seq.readString(BER.EMBER_STRING); } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return l; diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js index df2c2ea..c8e4d95 100755 --- a/EmberLib/Matrix.js +++ b/EmberLib/Matrix.js @@ -224,9 +224,8 @@ class Matrix extends TreeNode } return count <= matrixNode.contents.maximumTotalConnects; } - - } - return true; + return true; + } } /** diff --git a/EmberLib/MatrixContents.js b/EmberLib/MatrixContents.js index e75643d..b7f75af 100755 --- a/EmberLib/MatrixContents.js +++ b/EmberLib/MatrixContents.js @@ -87,7 +87,7 @@ class MatrixContents { } if (this.schemaIdentifiers != null) { ber.startSequence(BER.CONTEXT(11)); - ber.writeInt(this.schemaIdentifiers, BER.EMBER_STRING); + ber.writeString(this.schemaIdentifiers, BER.EMBER_STRING); ber.endSequence(); } if (this.templateReference != null) { @@ -139,7 +139,7 @@ class MatrixContents { mc.labels.push(Label.decode(lSeq)); } } else if(tag == BER.CONTEXT(11)) { - mc.schemaIdentifiers = seq.readInt(); + mc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(12)) { mc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); } diff --git a/EmberLib/MatrixNode.js b/EmberLib/MatrixNode.js index 0c86a76..b368f15 100755 --- a/EmberLib/MatrixNode.js +++ b/EmberLib/MatrixNode.js @@ -80,7 +80,7 @@ class MatrixNode extends Matrix { */ static decode(ber) { const m = new MatrixNode(); - ber = ber.getSequence(BER.APPLICATION(13)); + ber = ber.getSequence(MatrixNode.BERID); while (ber.remain > 0) { let tag = ber.peek(); let seq = ber.getSequence(tag); @@ -105,6 +105,13 @@ class MatrixNode extends Matrix { } return m; } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(13); + } } module.exports = MatrixNode; \ No newline at end of file diff --git a/EmberLib/Node.js b/EmberLib/Node.js index 6af1c6a..a42f001 100755 --- a/EmberLib/Node.js +++ b/EmberLib/Node.js @@ -4,7 +4,7 @@ const Element = require("./Element"); const QualifiedNode = require("./QualifiedNode"); const NodeContents = require("./NodeContents"); const BER = require('../ber.js'); -const errors = require("../errors"); +const Errors = require("../errors"); class Node extends Element { /** @@ -13,7 +13,7 @@ class Node extends Element { */ constructor(number) { super(number); - this._seqID = BER.APPLICATION(3); + this._seqID = Node.BERID; /** @type {NodeContents} */ this.contents = null; } @@ -25,16 +25,6 @@ class Node extends Element { return true; } - /** - * - * @param {function} callback - */ - subscribe(callback) { - if (callback != null && this.isStream()) { - this.contents._subscribers.add(callback); - } - } - /** * @returns {QualifiedNode} */ @@ -51,7 +41,7 @@ class Node extends Element { */ static decode(ber) { const n = new Node(); - ber = ber.getSequence(BER.APPLICATION(3)); + ber = ber.getSequence(Node.BERID); while(ber.remain > 0) { let tag = ber.peek(); @@ -63,11 +53,18 @@ class Node extends Element { } else if(tag == BER.CONTEXT(2)) { n.decodeChildren(seq); } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return n; } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(3); + } } module.exports = Node; diff --git a/EmberLib/Parameter.js b/EmberLib/Parameter.js index bad3eaf..c0ba322 100755 --- a/EmberLib/Parameter.js +++ b/EmberLib/Parameter.js @@ -4,7 +4,8 @@ const Element = require("./Element"); const QualifiedParameter = require("./QualifiedParameter"); const BER = require('../ber.js'); const ParameterContents = require("./ParameterContents"); -const errors = require("../errors"); +const Errors = require("../errors"); +const {COMMAND_SUBSCRIBE} = require("./Command"); class Parameter extends Element { /** @@ -14,7 +15,7 @@ class Parameter extends Element { constructor(number) { super(); this.number = number; - this._seqID = BER.APPLICATION(1); + this._seqID = Parameter.BERID; } /** @@ -43,30 +44,6 @@ class Parameter extends Element { qp.update(this); return qp; } - - /** - * - * @param {Parameter} other - */ - update(other) { - if ((other != null) && (other.contents != null)) { - if (this.contents == null) { - this.contents = other.contents; - } - else { - for (var key in other.contents) { - if (key[0] === "_") { continue; } - if (other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; - } - } - for(let cb of this.contents._subscribers) { - cb(this); - } - } - } - return; - } /** * @@ -75,7 +52,7 @@ class Parameter extends Element { */ static decode(ber) { const p = new Parameter(); - ber = ber.getSequence(BER.APPLICATION(1)); + ber = ber.getSequence(Parameter.BERID); while(ber.remain > 0) { let tag = ber.peek(); @@ -88,12 +65,18 @@ class Parameter extends Element { } else if(tag == BER.CONTEXT(2)) { p.decodeChildren(seq); } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return p; } + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(1); + } } module.exports = Parameter; \ No newline at end of file diff --git a/EmberLib/QualifiedElement.js b/EmberLib/QualifiedElement.js index 59ab929..f7a78c3 100755 --- a/EmberLib/QualifiedElement.js +++ b/EmberLib/QualifiedElement.js @@ -75,30 +75,6 @@ class QualifiedElement extends TreeNode { } return this.getCommand(COMMAND_GETDIRECTORY); } - - /** - * - * @param {function} callback - * @returns {TreeNode} - */ - subscribe(callback) { - if (callback != null && this.isStream()) { - this.contents._subscribers.add(callback); - } - return this.getCommand(COMMAND_SUBSCRIBE); - } - - /** - * - * @param {function} callback - * @returns {TreeNode} - */ - unsubscribe(callback) { - if (callback != null && this.isStream()) { - this.contents._subscribers.delete(callback); - } - return this.getCommand(COMMAND_UNSUBSCRIBE); - } } module.exports = QualifiedElement; \ No newline at end of file diff --git a/EmberLib/QualifiedFunction.js b/EmberLib/QualifiedFunction.js index 96966ab..b34ca15 100755 --- a/EmberLib/QualifiedFunction.js +++ b/EmberLib/QualifiedFunction.js @@ -16,7 +16,7 @@ class QualifiedFunction extends QualifiedElement { constructor(path, func) { super(path); this.func = func; - this._seqID = BER.APPLICATION(20); + this._seqID = QualifiedFunction.BERID; } /** @@ -39,7 +39,7 @@ class QualifiedFunction extends QualifiedElement { * @param {*} params */ invoke(params) { - const invocation = new Invocation() + const invocation = new Invocation(Invocation.newInvocationID()); invocation.arguments = params; const qualifiedFunctionNode = this.getCommand(COMMAND_INVOKE, "invocation", invocation); //qualifiedFunctionNode.getElementByNumber(this.getNumber()).getElementByNumber(COMMAND_INVOKE).invocation = invocation @@ -54,7 +54,7 @@ class QualifiedFunction extends QualifiedElement { */ static decode(ber) { const qf = new QualifiedFunction(); - ber = ber.getSequence(BER.APPLICATION(20)); + ber = ber.getSequence(QualifiedFunction.BERID); while(ber.remain > 0) { let tag = ber.peek(); let seq = ber.getSequence(tag); @@ -72,6 +72,13 @@ class QualifiedFunction extends QualifiedElement { } return qf; } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(20); + } } module.exports = QualifiedFunction; \ No newline at end of file diff --git a/EmberLib/QualifiedMatrix.js b/EmberLib/QualifiedMatrix.js index a746846..98efd23 100755 --- a/EmberLib/QualifiedMatrix.js +++ b/EmberLib/QualifiedMatrix.js @@ -40,7 +40,7 @@ class QualifiedMatrix extends Matrix { * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.APPLICATION(17)); + ber.startSequence(QualifiedMatrix.BERID); ber.startSequence(BER.CONTEXT(0)); ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); @@ -117,7 +117,7 @@ class QualifiedMatrix extends Matrix { */ static decode(ber) { const qm = new QualifiedMatrix(); - ber = ber.getSequence(BER.APPLICATION(17)); + ber = ber.getSequence(QualifiedMatrix.BERID); while(ber.remain > 0) { let tag = ber.peek(); let seq = ber.getSequence(tag); @@ -149,6 +149,13 @@ class QualifiedMatrix extends Matrix { } return qm; } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(17); + } } module.exports = QualifiedMatrix; \ No newline at end of file diff --git a/EmberLib/QualifiedNode.js b/EmberLib/QualifiedNode.js index 7dce7a5..d0ec097 100755 --- a/EmberLib/QualifiedNode.js +++ b/EmberLib/QualifiedNode.js @@ -8,7 +8,7 @@ const errors = require("../errors"); class QualifiedNode extends QualifiedElement { constructor (path) { super(path); - this._seqID = BER.APPLICATION(10); + this._seqID = QualifiedNode.BERID; } /** @@ -39,7 +39,7 @@ class QualifiedNode extends QualifiedElement { */ static decode(ber) { const qn = new QualifiedNode(); - ber = ber.getSequence(BER.APPLICATION(10)); + ber = ber.getSequence(QualifiedNode.BERID); while(ber.remain > 0) { let tag = ber.peek(); let seq = ber.getSequence(tag); @@ -56,6 +56,13 @@ class QualifiedNode extends QualifiedElement { } return qn; } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(10); + } } module.exports = QualifiedNode; \ No newline at end of file diff --git a/EmberLib/QualifiedParameter.js b/EmberLib/QualifiedParameter.js index aec7990..a079c03 100755 --- a/EmberLib/QualifiedParameter.js +++ b/EmberLib/QualifiedParameter.js @@ -12,7 +12,7 @@ class QualifiedParameter extends QualifiedElement { */ constructor(path) { super(path); - this._seqID = BER.APPLICATION(9); + this._seqID = QualifiedParameter.BERID; } /** @@ -84,7 +84,7 @@ class QualifiedParameter extends QualifiedElement { */ static decode(ber) { var qp = new QualifiedParameter(); - ber = ber.getSequence(BER.APPLICATION(9)); + ber = ber.getSequence(QualifiedParameter.BERID); while(ber.remain > 0) { var tag = ber.peek(); var seq = ber.getSequence(tag); @@ -101,6 +101,13 @@ class QualifiedParameter extends QualifiedElement { } return qp; } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(9); + } } module.exports = QualifiedParameter; \ No newline at end of file diff --git a/EmberLib/StreamDescription.js b/EmberLib/StreamDescription.js index 08087b3..81ac7e6 100755 --- a/EmberLib/StreamDescription.js +++ b/EmberLib/StreamDescription.js @@ -2,7 +2,7 @@ const Element = require("./Element"); const BER = require('../ber.js'); const StreamFormat = require("./StreamFormat"); -const errors = require("../errors"); +const Errors = require("../errors"); class StreamDescription extends Element{ /** @@ -17,17 +17,32 @@ class StreamDescription extends Element{ * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.APPLICATION(12)); + ber.startSequence(StreamDescription.BERID); ber.writeIfDefinedEnum(this.format, StreamFormat, ber.writeInt, 0); ber.writeIfDefined(this.offset, ber.writeInt, 1); ber.endSequence(); } - + + /** + * + */ + toJSON() { + return { + format: this.format == null ? null : this.format.key, + offset: this.offset + }; + } + + /** + * + * @param {BER} ber + * @returns {StreamDescription} + */ static decode(ber) { const sd = new StreamDescription(); - ber = ber.getSequence(BER.APPLICATION(12)); + ber = ber.getSequence(StreamDescription.BERID); while(ber.remain > 0) { var tag = ber.peek(); @@ -37,11 +52,18 @@ class StreamDescription extends Element{ } else if(tag == BER.CONTEXT(1)) { sd.offset = seq.readInt(); } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return sd; } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(12); + } } module.exports = StreamDescription; \ No newline at end of file diff --git a/EmberLib/StringIntegerCollection.js b/EmberLib/StringIntegerCollection.js index a341145..21baa99 100755 --- a/EmberLib/StringIntegerCollection.js +++ b/EmberLib/StringIntegerCollection.js @@ -1,19 +1,27 @@ "use strict"; -const Element = require("./Element"); const BER = require('../ber.js'); -const errors = require("../errors"); +const StringIntegerPair = require("./StringIntegerPair"); +const Errors = require("../errors"); -class StringIntegerCollection extends Element { +class StringIntegerCollection { constructor() { - super(); - this._seqID = BER.APPLICATION(8); this._collection = new Map(); } + /** + * + * @param {string} key + * @param {StringIntegerPair} value + */ addEntry(key, value) { this._collection.set(key, value); } + /** + * + * @param {string} key + * @returns {StringIntegerPair} + */ get(key) { return this._collection.get(key); } @@ -23,51 +31,52 @@ class StringIntegerCollection extends Element { * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.CONTEXT(15)); - ber.startSequence(BER.APPLICATION(8)); - ber.startSequence(BER.CONTEXT(0)); - for(let [key,value] of this._collection) { - ber.startSequence(BER.APPLICATION(7)); + ber.startSequence(StringIntegerCollection.BERID); + for(let [key,sp] of this._collection) { ber.startSequence(BER.CONTEXT(0)); - ber.writeString(key, BER.EMBER_STRING); - ber.endSequence(); - ber.startSequence(BER.CONTEXT(1)); - ber.writeInt(value); - ber.endSequence(); + sp.encode(ber); ber.endSequence(); - } - ber.endSequence(); + } ber.endSequence(); ber.endSequence(); } + /** + * + */ + toJSON() { + const collection = []; + for(let [key,sp] of this._collection) { + collection.push(sp.toJSON()); + } + } + /** * * @param {BER} ber + * @returns {StringIntegerCollection} */ static decode(ber) { const sc = new StringIntegerCollection(); - ber = ber.getSequence(BER.APPLICATION(8)); - while(ber.remain > 0) { - var seq = ber.getSequence(BER.CONTEXT(0)); - seq = seq.getSequence(BER.APPLICATION(7)); - var entryString, entryInteger; - while(seq.remain > 0) { - var tag = seq.peek(); - var dataSeq = seq.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - entryString = dataSeq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - entryInteger = dataSeq.readInt(); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } + const seq = ber.getSequence(StringIntegerCollection.BERID); + while(seq.remain > 0) { + const tag = seq.peek(); + if (tag != BER.CONTEXT(0)) { + throw new Errors.UnimplementedEmberTypeError(tag); } - - sc.addEntry(entryString,entryInteger); + const data = seq.getSequence(BER.CONTEXT(0)); + const sp = StringIntegerPair.decode(data) + sc.addEntry(sp.key, sp); } return sc; } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(8); + } } module.exports = StringIntegerCollection; \ No newline at end of file diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index 508da87..166b751 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -2,6 +2,7 @@ const BER = require('../ber.js'); const Command = require("./Command"); const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); +const Errors = require("../errors"); class TreeNode { constructor() { @@ -9,6 +10,25 @@ class TreeNode { this._parent = null; } + _isSubscribable(callback) { + return (callback != null && this.isParameter() && this.isStream()); + } + + _subscribe(callback) { + if (this.contents == null) { + throw new Errors.InvalidEmberNode(this.getPath(), "No content to subscribe"); + } + this.contents._subscribers.add(callback); + } + + _unsubscribe(callback) { + this.contents._subscribers.delete(callback); + } + + /** + * + * @param {TreeNode} child + */ addChild(child) { TreeNode.addElement(this, child); } @@ -222,26 +242,21 @@ class TreeNode { } } + getCommand(cmd) { + return this.getTreeBranch(new Command(cmd)); + } + + /** + * + * @param {function} callback + */ getDirectory(callback) { - if (callback != null && this.contents != null && !this.isStream()) { - this.contents._subscribers.add(callback); + if (this._isSubscribable(callback)) { + this._subscribe(callback); } - return this.getTreeBranch(new Command(COMMAND_GETDIRECTORY)); + return this.getCommand(COMMAND_GETDIRECTORY); } - subscribe(callback) { - if (callback != null && this.isParameter() && this.isStream()) { - this.contents._subscribers.add(callback); - } - return this.getTreeBranch(new Command(COMMAND_SUBSCRIBE)); - } - - unsubscribe(callback) { - if (callback != null && this.isParameter() && this.isStream()) { - this.contents._subscribers.delete(callback); - } - return this.getTreeBranch(new Command(COMMAND_UNSUBSCRIBE)); - } /** * @returns {TreeNode[]} @@ -473,6 +488,26 @@ class TreeNode { /** * + * @param {function} callback + */ + subscribe(callback) { + if (this._isSubscribable(callback)) { + this._subscribe(callback); + } + return this.getCommand(COMMAND_SUBSCRIBE); + } + + /** + * + * @param {*} callback + */ + unsubscribe(callback) { + this._unsubscribe(callback); + return this.getCommand(COMMAND_UNSUBSCRIBE); + } + + /** + * * @param {TreeNode} other */ update(other) { @@ -481,9 +516,18 @@ class TreeNode { this.contents = other.contents; } else { + let modified = false; for (var key in other.contents) { - if (other.contents.hasOwnProperty(key)) { + if (key[0] === "_") { continue; } + if (other.contents.hasOwnProperty(key) && + this.contents[key] != other.contents[key]) { this.contents[key] = other.contents[key]; + modified = true; + } + } + if (modified && this.contents._subscribers != null) { + for(let cb of this.contents._subscribers) { + cb(this); } } } diff --git a/EmberLib/index.js b/EmberLib/index.js index 6da638d..1bdf67f 100755 --- a/EmberLib/index.js +++ b/EmberLib/index.js @@ -28,6 +28,7 @@ const QualifiedFunction = require("./QualifiedFunction"); const QualifiedMatrix = require("./QualifiedMatrix"); const QualifiedNode = require("./QualifiedNode"); const QualifiedParameter = require("./QualifiedParameter"); +const StringIntegerPair = require("./StringIntegerPair"); const StringIntegerCollection = require("./StringIntegerCollection"); const StreamFormat = require("./StreamFormat"); const StreamDescription = require("./StreamDescription"); @@ -149,6 +150,7 @@ module.exports = { QualifiedParameter, StreamFormat, StreamDescription, + StringIntegerPair, StringIntegerCollection, Subscribe,COMMAND_SUBSCRIBE, Unsubscribe,COMMAND_UNSUBSCRIBE, diff --git a/ber.js b/ber.js index 03d22d9..08bfbe9 100755 --- a/ber.js +++ b/ber.js @@ -79,6 +79,16 @@ ExtendedReader.prototype.getSequence = function(tag) { return new ExtendedReader(buf); } +/** +Value ::= + CHOICE { + integer Integer64, + real REAL, + string EmberString, + boolean BOOLEAN, + octets OCTET STRING, + null NULL + } */ ExtendedReader.prototype.readValue = function() { var tag = this.peek(); diff --git a/embrionix.ember b/embrionix.ember deleted file mode 100755 index f2bacc41f99bb2fbb230da1009756e52616e14f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41743 zcmeHQX;<7v7ByhJ;Kob#J&Ln0jU}~uC7r|u$0jkPW3XdqBcfYC$Gt@@>^Lju%!m0K z4#|9(bLPz7ntw6#R8>6bm7pXTV`y7_jyb++T}f5-?t8Cm(|7B)*6*#?*Y6!39zJyF zk9A}H-m%P)Y3Fvc?i_;8#(D$3x5M9`%8XyO-Nqf;b+kFV?Q9&)j9#4mZ0_Pe{A&#T z%l|v#bNvr;huF0r%bcF?E_;l7-EsTPZpWva6Z(X%qz^MX+l?#6KD zkdijSXycg^yhQwYPiIa#?f!BvYwKA(pS5gui4pn1MWq~Om&jzsX6=r>;V;R&39?W8d94 znaPA6#!x=WQFak%w!UY3&HDCbiP(my^JDA+P=0X%Wl^@f$Z_c+s*-GXjWKqOEzpQ18-t)}(d zX4`h}YJI2P?KI$j<74gW?PkO2YNie!?dn49-1+M@ty~x%t5?OHOwfZqVNICl3YLGp zes|5SJK5!C3rb&UcIqunJ`-E}puTm~(u6^fR*}s>6c)9JdgMGW*Yc)G7 z)tggGQ*PF(m=$wUyVGoVt5qncKB=ub&6QQJYUm{Z&X0E8TXw;_xS*G+HmNn7o_{KQ zw=gwdou9rssnxr!E)>`C+`H8a3q`#=snuqyehpo!%}#3b^K;d+^9w%CntH|5jpA8g z*Vq{gwYf>H*@5Dv*KRf{qVjiC<+;RFo=dAdTIC@-kClPF)@Vj#P~1WWR^m1ZL@LRks9iCwb5;pt zaBgek0-b3Lwu6?W<_O)hcq@`;NFUP%PZeJb9bG;bks`3p(P51tw1FZ+)D zYu;kw;uZJ1cze>krNreg?RWV@0VA<`%ZZCu-tXe=N%I@*K?EE(hDsLWdlBsGEGd z(GJp0c_HnlOuH!~ULbkGx^2GtDEAD9^q6*2ChUbxom#b z`Qgl+lc&xDnK>sD>^`itn=)wPZPUKNZsN3?GHBvN zX;#`zxtBGttDflOnO53OnRZjMDJy$v!#nx0w1XbIw3|{rVQ;$}Y!JFB`J7>xsw`$x z-f4RERc!%gOeL5=$|n%R#Ar^wbZusOum~fkRtNvdT zZbP9Y!7ztVqCdcqDlwgh%_rG!_EDrtd>~5XksMb41YtN|a`L+EhvCMK!mwCr!NWVO zZJB4QBD9+8^kKmdOAJOr0etA+>#MLtY-Rl(+z$_l|s4aaWqW9+f; z80U`i0zzf_x46M>aU^`~0zY;Xk3m&U&&*G;GmrU4xBB1;KVEQXlJjiNeuCU;*o53_ zIQxKetHj;G$@`HK&34<%!K~f%F%0Hd-Oh4`)WTr7S-NN?ei;}>i}5j~g+*hbk;X!? zF#FMsg_t(*rNgk$dTQ0~bevY7r8Gm~ltzxB7(Y1|_ipKA2+?D3 zkS0SYy&K<1A@rL&wXpq+pUDS-2FAjo@zYG>XBt1(MaRI_@pCv){Ny`sVEoJteEdvj zn~C^&kY<}bRPi&lutBr1X#C8j@iUE|_alDt4W}@ES_$H3!SBB{4r>2xI-U1`PUqcW z3{9=f-!yFBdT|GJZwsZBMdPTI#?ds6-j6uS_gus{njic)ny#4$Ce$CKH4{n?7wyYE z;BhpyvH`TRXdKO_aWsvi_alztzQcSzQDN}oXz~d~>Hb*{a!Jd>6-QGm3*zWOTK=fC zvS=JFq;WKjqxU0@^3C}1_h@nO<7o1UMd>DmX&j~RRY*Mh+eob}h@%5Mzo?kT(KL?U zk2uO-PlIu^H285e`Sg!;i-k0f?lz95Ru;t3ftkq`{XJSr<7gU3??)UR-+ay6wC&m3 zzJ@Kl%;j5un$gV%Jk2QG#zwq#4mO+FYK!qlYPaq1l9<#Q)4kvC_lEboUw&Fn!}22^ zmi=F%;SVk;<#Ryb#i)D;b=LOW=8yiFd|hcf{OV=tEWSzCpKQO4zea~)lZkaUfN}bt z>pE=7;ljI|$B!Hu9s2t!zW&v}@Sp74j<~RYTUZ5mY1SbAqlCG*8e1 zLDvYnPS6(w-5}^DL0=N|6+vGU^bJAZ60}IrcLdo4EfG{Fs6miJ&@w?Q1g#R(BXw?DzN-4(|R zU4gu#9jn-dK^#8jIMpEnUOjSy39G<>8eV45MO+H82=SisDM zm}_XS|O&|bWTake2A$wp3@Q&t;dW};~9yF=3{2nHT#%EMEfzb>WY0_B9=D| z&&;ap^$AKe(c_p|b+tYz5z*zCS#_;GB@xl*m|3+ZpO%Q|bj++;l+Q>+^g3o%t;%O5 zBAN~ufLfN%NzB5g(E(E}%jYF#F~n3G&kGW>6k@8a=S7K$?gyS$Z9XqiW)9sCFxB?+ zvcyF915C96X%Z6+C>JBkS0o}DP%cK6XC)#UP%cK6uS!IWTe%onz9tdT_2gn?`MN|z z*OQBpwL+4X6+!%c~L*4X6+!%Xx{2v8xax%LR#uuBQ+q%WD!5T~8rKme(aBx}HLeEWeP5 z=z0nnM2ua< z7+E$L5#Hdi*=2~nrx>G4M`oh)DaHtMS!Sa5DaI&sMP{P=DaJ^1Rc509DaL5CDKpW4 zzzo#l{9dMF00XL8owsBvMlhhN<=K*{7{Y+6)@NI$q9X#TTA&??8gxDbRjtsjOhs1& zRJBBVG8Ij!6r<4}WG0$YDMqBO%tTWv#i+C|GclHxVr1&cO!P&i7@4lgOms%27@6Ld zndps5F*3a)GtnKDVr2TG#0KoEz!KCl{Yj=`Km)2;ra#M6jA%er%k&qS ziXjcCYMK5jQ_&>>RV~xsWGcEOpsHp1yG%uw1k^2M>W@nLADp9wBaHp-np<}g40*yJ z4-9=>Y{MH6mA{B0W1ay~-3<6hJOFMsy?;k_BSc0$gFC95ATsP35Y-JZ${HXv?imob ztbQC2jtqPTMD-|3WaKj-s(+wHhCTzLdXyzH_6e!UrU9r`93A`&QPsv19sLYZ)#ei& k{tQvo1{59t3{ll46deE!QPoBi9RUqdw=^TJ;hp{cAFtSbSpWb4 diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index 4f9e879..1c91f72 100755 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -15,7 +15,7 @@ describe("EmberClient", () => { beforeAll(() => { return Promise.resolve() .then(() => new Promise((resolve, reject) => { - fs.readFile("./embrionix.ember", (e, data) => { + fs.readFile("./test/embrionix.ember", (e, data) => { if (e) { reject(e); } diff --git a/test/Ember.test.js b/test/Ember.test.js index 80c7789..be0c90f 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -58,9 +58,28 @@ describe("Ember", () => { } }); it("should have a toJSON", () => { - const command = new EmberLib.Command(EmberLib.COMMAND_GETDIRECTORY); - const jsonCommand = command.toJSON(); + const command = new EmberLib.Command(EmberLib.COMMAND_GETDIRECTORY); + let jsonCommand = command.toJSON(); expect(jsonCommand.number).toBe(EmberLib.COMMAND_GETDIRECTORY); + command.invocation = new EmberLib.Invocation(1, [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1, "arg1") + ]); + jsonCommand = command.toJSON(); + expect(jsonCommand.invocation.arguments.length).toBe(1); + }); + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Command.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Command.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } }); }); describe("Node", () => { @@ -92,6 +111,34 @@ describe("Ember", () => { expect(n.contents.identifier).toBe(identifier); expect(n.contents.description).toBe(description); }); + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Node.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Node.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should through an error if unable to decode content", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.NodeContents.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); }); describe("Function", () => { let func; @@ -102,7 +149,39 @@ describe("Ember", () => { res.value = args[0].value + args[1].value; return [res]; }); - }) + }); + it("should be able to encode FunctionArgument with no name", () => { + const res = new EmberLib.FunctionArgument(); + res.type = EmberLib.ParameterType.integer; + const writer = new BER.Writer(); + res.encode(writer); + expect(writer.buffer.length > 0).toBeTruthy(); + }); + it("should throw an Error if encoding FunctionArgument with no type", () => { + const res = new EmberLib.FunctionArgument(); + const writer = new BER.Writer(); + try { + res.encode(writer); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode); + } + }); + it("should throw an Error if unable to decode", () => { + const writer = new BER.Writer(); + try { + writer.startSequence(EmberLib.Function.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); // BER.CONTEXT(0) + writer.endSequence(); // BER.CONTEXT(0) + EmberLib.Function.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError); + } + }); it("should return true when calling isFunction", () => { expect(func.isFunction()).toBeTruthy(); }); @@ -112,8 +191,10 @@ describe("Ember", () => { expect(children.length).toBe(1); expect(children[0].isCommand()).toBeTruthy(); }); - it("should have a decoder", () => { + it("should have a encoder/decoder", () => { func.contents = new EmberLib.FunctionContent(identifier, description); + func.contents.templateReference = "1.2.3"; + func.addChild(new EmberLib.Node(1)); func.contents.arguments = [ new EmberLib.FunctionArgument(EmberLib.ParameterType.integer,null, "arg1"), new EmberLib.FunctionArgument(EmberLib.ParameterType.integer,null, "arg2") @@ -121,12 +202,68 @@ describe("Ember", () => { func.contents.result = [ new EmberLib.FunctionArgument(EmberLib.ParameterType.integer,null, "result") ]; - const writer = new BER.Writer(); + let writer = new BER.Writer(); func.encode(writer); - const f = EmberLib.Function.decode(new BER.Reader(writer.buffer)); + let f = EmberLib.Function.decode(new BER.Reader(writer.buffer)); expect(f.number).toBe(func.number); expect(f.contents.identifier).toBe(identifier); expect(f.contents.description).toBe(description); + expect(f.contents.result.length).toBe(1); + expect(f.contents.templateReference).toBe(func.contents.templateReference); + + writer = new BER.Writer(); + func.contents.identifier = null; + func.contents.arguments = null; + func.contents.result = null; + func.encode(writer); + f = EmberLib.Function.decode(new BER.Reader(writer.buffer)); + expect(f.number).toBe(func.number); + expect(f.contents.identifier == null).toBeTruthy(); + expect(f.contents.result == null || f.contents.result.length == 0).toBeTruthy(); + }); + it("should through an error if unable to decode result", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(3)); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.FunctionContent.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should through an error if unable to decode content", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.FunctionContent.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should through an error if unable to decode FunctionArgument", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.FunctionArgument.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.FunctionArgument.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } }); }); describe("Parameter", () => { @@ -172,6 +309,61 @@ describe("Ember", () => { const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); expect(newParameter.getChildren().length).toBe(1); }); + it("should support type real", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = 1.1; + parameter.contents = new EmberLib.ParameterContents(VALUE, "real"); + const writer = new BER.Writer(); + parameter.encode(writer); + const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + expect(newParameter.contents.value).toBe(VALUE); + }); + it("should support type string", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE ="string support"; + parameter.contents = new EmberLib.ParameterContents(VALUE, "string"); + const writer = new BER.Writer(); + parameter.encode(writer); + const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + expect(newParameter.contents.value).toBe(VALUE); + }); + it("should support type boolean", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = true; + parameter.contents = new EmberLib.ParameterContents(VALUE, "boolean"); + const writer = new BER.Writer(); + parameter.encode(writer); + const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + expect(newParameter.contents.value).toBe(VALUE); + }); + it("should through an error if fails to decode StringIntegerPair", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.StringIntegerPair.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.StringIntegerPair.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should through an error if fails to decode StringIntegerCollection", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.StringIntegerCollection.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.StringIntegerCollection.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); }); describe("Matrix", () => { describe("validateConnection", () => { @@ -252,6 +444,11 @@ describe("Ember", () => { matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; matrixNode.targets = [0, 3]; matrixNode.sources = [0, 3]; + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + } + const min = matrixNode.getMinimal(true); + expect(min.sources).toBeDefined(); try { EmberLib.Matrix.validateConnection(matrixNode, 1, [0]); throw new Error("Should not succeed"); @@ -398,6 +595,17 @@ describe("Ember", () => { const res = EmberLib.Matrix.canConnect(matrixNode, 0, [0,3]); expect(res).toBeFalsy(); }); + it("should always return true if NtoN and no limits", () => { + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.nToN, + EmberLib.MatrixMode.linear + ); + matrixNode.connections = null; + matrixNode.contents.maximumConnectsPerTarget = null; + matrixNode.contents.maximumTotalConnects = null; + const res = EmberLib.Matrix.canConnect(matrixNode, 0, [0,3]); + expect(res).toBeTruthy(); + }); }); describe("Matrix Non-Linear", () => { it("should have encoder / decoder", () => { @@ -406,8 +614,14 @@ describe("Ember", () => { EmberLib.MatrixType.onetoN, EmberLib.MatrixMode.nonLinear ); + matrixNode.contents.gainParameterNumber = 4; matrixNode.contents.identifier = "matrix"; matrixNode.contents.description = "matrix"; + matrixNode.contents.maximumTotalConnects = 5; + matrixNode.contents.maximumConnectsPerTarget = 1; + matrixNode.contents.parametersLocation = "1.2.3"; + matrixNode.contents.schemaIdentifiers = "de.l-s-b.emberplus.schema1"; + matrixNode.contents.templateReference = "0.1.2.3"; matrixNode.targets = [0,3]; matrixNode.sources = [1,2]; const writer = new BER.Writer(); @@ -430,6 +644,189 @@ describe("Ember", () => { const connect = matrixNode.connect({0: new EmberLib.MatrixConnection(0)}); expect(connect).toBeDefined(); }); + it("should through an error if can't decode", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.APPLICATION(13)); + writer.startSequence(BER.CONTEXT(0)); + writer.writeInt(1); + writer.endSequence(); // BER.CONTEXT(0) + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.MatrixNode.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + }); + describe("Label", () => { + it ("should throw an error if it fails to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.APPLICATION(18)); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Label.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it ("should throw an error if no basePath", () => { + const label = new EmberLib.Label(null, "test"); + const writer = new BER.Writer(); + try { + label.encode(writer); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + it ("should throw an error if no description", () => { + const label = new EmberLib.Label("1.2.3", null); + const writer = new BER.Writer(); + try { + label.encode(writer); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + it ("should be able to encode/decode a valid label", () => { + const label = new EmberLib.Label("1.2.3", "primary"); + const writer = new BER.Writer(); + label.encode(writer); + const reader = new BER.Reader(writer.buffer); + const newLabel = EmberLib.Label.decode(reader); + expect(newLabel.description).toBe(label.description); + expect(newLabel.basePath).toBe(label.basePath); + }); + }) + }); + describe("Invocation", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Invocation.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Invocation.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("Should be able to encode even if no id", () => { + const invocation = new EmberLib.Invocation(); + const writer = new BER.Writer(); + invocation.encode(writer); + const i = EmberLib.Invocation.decode(new BER.Reader(writer.buffer)); + expect(i.id == null).toBeTruthy(); + }); + it("Should have a toJSON", () => { + const invocation = new EmberLib.Invocation(1, [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 10, "arg1" ) + ]); + let js = invocation.toJSON(); + expect(js.id).toBe(invocation.id); + expect(js.arguments.length).toBe(invocation.arguments.length); + invocation.arguments = null; + js = invocation.toJSON(); + expect(js.id).toBe(invocation.id); + expect(js.arguments).toBe(null); + }); + }); + describe("InvocationResult", () => { + it("should support all types of result", () => { + const invocationResult = new EmberLib.InvocationResult(); + invocationResult.invocationId = 100; + const valBuf = [0xa, 0x1, 0x2]; + invocationResult.setFailure(); + expect(invocationResult.success).toBeFalsy(); + invocationResult.setSuccess(); + expect(invocationResult.success).toBeTruthy(); + try { + invocationResult.setResult(new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1)); + throw new Error("should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidResultFormat).toBeTruthy(); + } + expect(invocationResult.toQualified()).toBe(invocationResult); + invocationResult.setResult([ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1), + new EmberLib.FunctionArgument(EmberLib.ParameterType.real, 1.1), + new EmberLib.FunctionArgument(EmberLib.ParameterType.string, "one"), + new EmberLib.FunctionArgument(EmberLib.ParameterType.boolean, false), + new EmberLib.FunctionArgument(EmberLib.ParameterType.octets, Buffer.from(valBuf)) + ]); + const writer = new BER.Writer(); + invocationResult.encode(writer); + const newInvocationRes = EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + expect(newInvocationRes.success).toBe(invocationResult.success); + expect(newInvocationRes.invocationId).toBe(invocationResult.invocationId); + expect(newInvocationRes.result.length).toBe(invocationResult.result.length); + expect(newInvocationRes.result[4].value.length).toBe(valBuf.length); + expect(newInvocationRes.result[4].value[0]).toBe(valBuf[0]); + }); + it("should be able to encode with not result, no success", () => { + const invocationResult = new EmberLib.InvocationResult(); + invocationResult.invocationId = 100; + invocationResult.result = null; + invocationResult.sucess = null; + const writer = new BER.Writer(); + invocationResult.encode(writer); + const newInvocationRes = EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + expect(newInvocationRes.result).not.toBeDefined(); + }); + it("should be able to encode with no invocationId", () => { + const invocationResult = new EmberLib.InvocationResult(); + invocationResult.invocationId = null; + invocationResult.result = null; + invocationResult.sucess = null; + const writer = new BER.Writer(); + invocationResult.encode(writer); + const newInvocationRes = EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + expect(newInvocationRes.invocationId == null).toBeTruthy(); + }); + it("should through an error if can't decode", () => { + let writer = new BER.Writer(); + writer.startSequence(EmberLib.InvocationResult.BERID); + writer.startSequence(BER.CONTEXT(3)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + writer = new BER.Writer(); + writer.startSequence(EmberLib.InvocationResult.BERID); + writer.startSequence(BER.CONTEXT(2)); + writer.startSequence(BER.EMBER_SEQUENCE); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } }); }); }); diff --git a/test/Server.test.js b/test/Server.test.js index 85a898a..428a341 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -19,28 +19,27 @@ describe("server", function() { expect(root).toBeDefined(); expect(root.elements).toBeDefined(); expect(root.elements.size).toBe(1); - console.log("root", root.getElementByNumber(0).contents); expect(root.getElementByNumber(0).contents.identifier).toBe("scoreMaster"); expect(root.getElementByNumber(0).elements.size).toBe(jsonTree[0].children.length); }); }); describe("Server - Client communication", function() { - let server,client; + let server,client,jsonTree; beforeAll(function() { jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); server = new EmberServer(LOCALHOST, PORT, root); server.on("error", e => { + // eslint-disable-next-line no-console console.log(e); }); server.on("clientError", e => { + // eslint-disable-next-line no-console console.log(e); }); //server._debug = true; - return server.listen().then(() => { - console.log("server listening"); - }); + return server.listen(); }); afterAll(function() { return server.close(); @@ -51,7 +50,6 @@ describe("server", function() { return Promise.resolve() .then(() => client.connect()) .then(() => { - console.log("client connected"); return client.getDirectory(); }) .then(() => { @@ -71,7 +69,7 @@ describe("server", function() { // Issue #33 EmberServer.handleGetDirectory does not subscribe to child parameters expect(server.subscribers["0.0.0"]).toBeDefined(); // Keepalive - return client.disconnect(); + return client.disconnect(); }); }); it("should be able to modify a parameter", () => { @@ -110,7 +108,6 @@ describe("server", function() { ]); }) .then(result => { - console.log(result); expect(result).toBeDefined(); expect(result.result).toBeDefined(); expect(result.result.length).toBe(1); @@ -119,7 +116,7 @@ describe("server", function() { }); }); - it("should be able to get child with tree.getNodeByPath", function() { + it("should be able to get child with tree.getNodeByPath", function() { //server._debug = true; client = new EmberClient(LOCALHOST, PORT); //client._debug = true; @@ -127,17 +124,14 @@ describe("server", function() { return Promise.resolve() .then(() => client.connect()) .then(() => { - console.log("client connected"); return client.getDirectory(); }) .then(() => client.getNodeByPath("scoreMaster/identity/product")) - .then(child => { - console.log(child); + .then(() => { return client.getNodeByPath("scoreMaster/router/labels/group 1"); }) - .then(child => { - console.log("router/labels", child); - return client.disconnect(); + .then(() => { + return client.disconnect(); }); }); it("should be able to get child with getElementByPath", function() { @@ -148,17 +142,14 @@ describe("server", function() { return Promise.resolve() .then(() => client.connect()) .then(() => { - console.log("client connected"); return client.getDirectory(); }) .then(() => client.getElementByPath("scoreMaster/identity/product")) - .then(child => { - console.log(child); + .then(() => { return client.getElementByPath("scoreMaster/router/labels/group 1"); }) - .then(child => { - console.log("router/labels", child); - return client.disconnect(); + .then(() => { + return client.disconnect(); }); }); it("should throw an error if getNodeByPath for unknown path", function() { @@ -167,12 +158,10 @@ describe("server", function() { return Promise.resolve() .then(() => client.connect()) .then(() => { - console.log("client connected"); return client.getDirectory(); }) .then(() => client.getNodeByPath("scoreMaster/router/labels/group")) - .then(child => { - console.log("router/labels", child); + .then(() => { throw new Error("Should not succeed"); }) .catch(e => { @@ -193,7 +182,6 @@ describe("server", function() { return client.getElementByPath(matrix.getPath()); }) .then(matrix => { - console.log(matrix); expect(matrix.connections['0'].sources).toBeDefined(); expect(matrix.connections['0'].sources.length).toBe(1); expect(matrix.connections['0'].sources[0]).toBe(1); @@ -244,7 +232,7 @@ describe("server", function() { .then(parameter => { server._subscribe = server.subscribe; let _resolve; - const p = new Promise((resolve, reject) => { + const p = new Promise((resolve) => { _resolve = resolve; }); server.subscribe = (c,e) => { @@ -312,7 +300,7 @@ describe("server", function() { it("should verify if connection allowed in 1-to-1", function() { const matrix = server.tree.getElementByPath("0.1.0"); let disconnectCount = 0; - const handleDisconnect = info => { + const handleDisconnect = () => { disconnectCount++; } server.on("matrix-disconnect", handleDisconnect.bind(this)); @@ -344,7 +332,7 @@ describe("server", function() { it("should disconnect if trying to connect same source and target in 1-to-1", function() { const matrix = server.tree.getElementByPath("0.1.0"); let disconnectCount = 0; - const handleDisconnect = info => { + const handleDisconnect = () => { disconnectCount++; } server.on("matrix-disconnect", handleDisconnect.bind(this)); @@ -360,7 +348,7 @@ describe("server", function() { it("should be able to lock a connection", function() { const matrix = server.tree.getElementByPath("0.1.0"); let disconnectCount = 0; - const handleDisconnect = info => { + const handleDisconnect = () => { disconnectCount++; } server.on("matrix-disconnect", handleDisconnect.bind(this)); @@ -429,9 +417,11 @@ describe("server", function() { it("should return modified answer on absolute connect", function() { let client; server.on("error", e => { + // eslint-disable-next-line no-console console.log(e); }); server.on("clientError", e => { + // eslint-disable-next-line no-console console.log(e); }); //server._debug = true; @@ -443,12 +433,8 @@ describe("server", function() { .then(() => client.connect()) .then(() => client.getDirectory()) .then(() => client.getNodeByPathnum("0.1.0")) - .then(matrix => { - console.log(matrix); - return client.matrixSetConnection(matrix, 0, [1]); - }) + .then(matrix => client.matrixSetConnection(matrix, 0, [1])) .then(result => { - console.log(result); expect(result).toBeDefined(); expect(result.connections).toBeDefined(); expect(result.connections[0]).toBeDefined(); @@ -456,7 +442,6 @@ describe("server", function() { return client.disconnect(); }) .then(() => { - console.log("closing server"); server.close(); }); }); @@ -469,9 +454,11 @@ describe("server", function() { const root = EmberServer.JSONtoTree(jsonTree); server = new EmberServer(LOCALHOST, PORT, root); server.on("error", e => { + // eslint-disable-next-line no-console console.log(e); }); server.on("clientError", e => { + // eslint-disable-next-line no-console console.log(e); }); return server.listen(); @@ -481,7 +468,6 @@ describe("server", function() { }); it("should not auto subscribe stream parameter", function() { const parameter = server.tree.getElementByPath("0.0.2"); - console.log(parameter); expect(parameter.isStream()).toBeTruthy(); expect(server.subscribers["0.0.2"]).not.toBeDefined(); }); @@ -498,13 +484,12 @@ describe("server", function() { }) .then(() => client.getNodeByPathnum("0.0.2")) .then(parameter => { - console.log(parameter); expect(server.subscribers["0.0.2"]).not.toBeDefined(); expect(parameter.contents._subscribers).toBeDefined(); expect(parameter.contents._subscribers.size).toBe(0); server._subscribe = server.subscribe; let _resolve; - const p = new Promise((resolve, reject) => { + const p = new Promise(resolve => { _resolve = resolve; }); server.subscribe = (c,e) => { @@ -523,24 +508,20 @@ describe("server", function() { expect(parameter.contents._subscribers.size).toBe(1); server._unsubscribe = server.unsubscribe; let _resolve; - const p = new Promise((resolve, reject) => { + const p = new Promise(resolve => { _resolve = resolve; }); server.unsubscribe = (c,e) => { - console.log("unsubscribe"); server._unsubscribe(c,e); _resolve(); }; - console.log(parameter); return client.unsubscribe(parameter, cb).then(() => (p)) }) .then(() => { - console.log(server.subscribers); expect(server.subscribers["0.0.2"]).toBeDefined(); return client.getNodeByPathnum("0.0.2"); }) .then(parameter => { - console.log(parameter); expect(server.subscribers["0.0.2"]).toBeDefined(); expect(server.subscribers["0.0.2"].size).toBe(0); expect(parameter.contents._subscribers).toBeDefined(); From 4ed624c294b3fcf597aeced0f548047d89418423 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 13 Jan 2020 18:37:15 +0100 Subject: [PATCH 136/162] Adding test code coverage. EmberLib 100% upto Matrix --- EmberLib/StringIntegerPair.js | 68 ++++++++++++++++++++++++++++++++++ test/embrionix.ember | Bin 0 -> 41743 bytes 2 files changed, 68 insertions(+) create mode 100755 EmberLib/StringIntegerPair.js create mode 100755 test/embrionix.ember diff --git a/EmberLib/StringIntegerPair.js b/EmberLib/StringIntegerPair.js new file mode 100755 index 0000000..187a6fb --- /dev/null +++ b/EmberLib/StringIntegerPair.js @@ -0,0 +1,68 @@ +"use strict"; +const BER = require('../ber.js'); +const errors = require("../errors"); + +class StringIntegerPair { + constructor(key,value) { + this.key = key; + this.value = value; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + if (this.key == null || this.value == null) { + throw new errors.InvalidEmberNode("", "Invalid key/value missing"); + } + ber.startSequence(StringIntegerPair.BERID); + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.key, BER.EMBER_STRING); + ber.endSequence(); + ber.startSequence(BER.CONTEXT(1)); + ber.writeInt(this.value); + ber.endSequence(); + ber.endSequence(); + } + + /** + * + */ + toJSON() { + return { + key: this.key, + value: this.value + } + } + /** + * + * @param {BER} ber + * @returns {StringIntegerPair} + */ + static decode(ber) { + const sp = new StringIntegerPair(); + let seq = ber.getSequence(StringIntegerPair.BERID); + while(seq.remain > 0) { + const tag = seq.peek(); + const dataSeq = seq.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + sp.key = dataSeq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + sp.value = dataSeq.readInt(); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return sp; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(7); + } +} + +module.exports = StringIntegerPair; \ No newline at end of file diff --git a/test/embrionix.ember b/test/embrionix.ember new file mode 100755 index 0000000000000000000000000000000000000000..f2bacc41f99bb2fbb230da1009756e52616e14f4 GIT binary patch literal 41743 zcmeHQX;<7v7ByhJ;Kob#J&Ln0jU}~uC7r|u$0jkPW3XdqBcfYC$Gt@@>^Lju%!m0K z4#|9(bLPz7ntw6#R8>6bm7pXTV`y7_jyb++T}f5-?t8Cm(|7B)*6*#?*Y6!39zJyF zk9A}H-m%P)Y3Fvc?i_;8#(D$3x5M9`%8XyO-Nqf;b+kFV?Q9&)j9#4mZ0_Pe{A&#T z%l|v#bNvr;huF0r%bcF?E_;l7-EsTPZpWva6Z(X%qz^MX+l?#6KD zkdijSXycg^yhQwYPiIa#?f!BvYwKA(pS5gui4pn1MWq~Om&jzsX6=r>;V;R&39?W8d94 znaPA6#!x=WQFak%w!UY3&HDCbiP(my^JDA+P=0X%Wl^@f$Z_c+s*-GXjWKqOEzpQ18-t)}(d zX4`h}YJI2P?KI$j<74gW?PkO2YNie!?dn49-1+M@ty~x%t5?OHOwfZqVNICl3YLGp zes|5SJK5!C3rb&UcIqunJ`-E}puTm~(u6^fR*}s>6c)9JdgMGW*Yc)G7 z)tggGQ*PF(m=$wUyVGoVt5qncKB=ub&6QQJYUm{Z&X0E8TXw;_xS*G+HmNn7o_{KQ zw=gwdou9rssnxr!E)>`C+`H8a3q`#=snuqyehpo!%}#3b^K;d+^9w%CntH|5jpA8g z*Vq{gwYf>H*@5Dv*KRf{qVjiC<+;RFo=dAdTIC@-kClPF)@Vj#P~1WWR^m1ZL@LRks9iCwb5;pt zaBgek0-b3Lwu6?W<_O)hcq@`;NFUP%PZeJb9bG;bks`3p(P51tw1FZ+)D zYu;kw;uZJ1cze>krNreg?RWV@0VA<`%ZZCu-tXe=N%I@*K?EE(hDsLWdlBsGEGd z(GJp0c_HnlOuH!~ULbkGx^2GtDEAD9^q6*2ChUbxom#b z`Qgl+lc&xDnK>sD>^`itn=)wPZPUKNZsN3?GHBvN zX;#`zxtBGttDflOnO53OnRZjMDJy$v!#nx0w1XbIw3|{rVQ;$}Y!JFB`J7>xsw`$x z-f4RERc!%gOeL5=$|n%R#Ar^wbZusOum~fkRtNvdT zZbP9Y!7ztVqCdcqDlwgh%_rG!_EDrtd>~5XksMb41YtN|a`L+EhvCMK!mwCr!NWVO zZJB4QBD9+8^kKmdOAJOr0etA+>#MLtY-Rl(+z$_l|s4aaWqW9+f; z80U`i0zzf_x46M>aU^`~0zY;Xk3m&U&&*G;GmrU4xBB1;KVEQXlJjiNeuCU;*o53_ zIQxKetHj;G$@`HK&34<%!K~f%F%0Hd-Oh4`)WTr7S-NN?ei;}>i}5j~g+*hbk;X!? zF#FMsg_t(*rNgk$dTQ0~bevY7r8Gm~ltzxB7(Y1|_ipKA2+?D3 zkS0SYy&K<1A@rL&wXpq+pUDS-2FAjo@zYG>XBt1(MaRI_@pCv){Ny`sVEoJteEdvj zn~C^&kY<}bRPi&lutBr1X#C8j@iUE|_alDt4W}@ES_$H3!SBB{4r>2xI-U1`PUqcW z3{9=f-!yFBdT|GJZwsZBMdPTI#?ds6-j6uS_gus{njic)ny#4$Ce$CKH4{n?7wyYE z;BhpyvH`TRXdKO_aWsvi_alztzQcSzQDN}oXz~d~>Hb*{a!Jd>6-QGm3*zWOTK=fC zvS=JFq;WKjqxU0@^3C}1_h@nO<7o1UMd>DmX&j~RRY*Mh+eob}h@%5Mzo?kT(KL?U zk2uO-PlIu^H285e`Sg!;i-k0f?lz95Ru;t3ftkq`{XJSr<7gU3??)UR-+ay6wC&m3 zzJ@Kl%;j5un$gV%Jk2QG#zwq#4mO+FYK!qlYPaq1l9<#Q)4kvC_lEboUw&Fn!}22^ zmi=F%;SVk;<#Ryb#i)D;b=LOW=8yiFd|hcf{OV=tEWSzCpKQO4zea~)lZkaUfN}bt z>pE=7;ljI|$B!Hu9s2t!zW&v}@Sp74j<~RYTUZ5mY1SbAqlCG*8e1 zLDvYnPS6(w-5}^DL0=N|6+vGU^bJAZ60}IrcLdo4EfG{Fs6miJ&@w?Q1g#R(BXw?DzN-4(|R zU4gu#9jn-dK^#8jIMpEnUOjSy39G<>8eV45MO+H82=SisDM zm}_XS|O&|bWTake2A$wp3@Q&t;dW};~9yF=3{2nHT#%EMEfzb>WY0_B9=D| z&&;ap^$AKe(c_p|b+tYz5z*zCS#_;GB@xl*m|3+ZpO%Q|bj++;l+Q>+^g3o%t;%O5 zBAN~ufLfN%NzB5g(E(E}%jYF#F~n3G&kGW>6k@8a=S7K$?gyS$Z9XqiW)9sCFxB?+ zvcyF915C96X%Z6+C>JBkS0o}DP%cK6XC)#UP%cK6uS!IWTe%onz9tdT_2gn?`MN|z z*OQBpwL+4X6+!%c~L*4X6+!%Xx{2v8xax%LR#uuBQ+q%WD!5T~8rKme(aBx}HLeEWeP5 z=z0nnM2ua< z7+E$L5#Hdi*=2~nrx>G4M`oh)DaHtMS!Sa5DaI&sMP{P=DaJ^1Rc509DaL5CDKpW4 zzzo#l{9dMF00XL8owsBvMlhhN<=K*{7{Y+6)@NI$q9X#TTA&??8gxDbRjtsjOhs1& zRJBBVG8Ij!6r<4}WG0$YDMqBO%tTWv#i+C|GclHxVr1&cO!P&i7@4lgOms%27@6Ld zndps5F*3a)GtnKDVr2TG#0KoEz!KCl{Yj=`Km)2;ra#M6jA%er%k&qS ziXjcCYMK5jQ_&>>RV~xsWGcEOpsHp1yG%uw1k^2M>W@nLADp9wBaHp-np<}g40*yJ z4-9=>Y{MH6mA{B0W1ay~-3<6hJOFMsy?;k_BSc0$gFC95ATsP35Y-JZ${HXv?imob ztbQC2jtqPTMD-|3WaKj-s(+wHhCTzLdXyzH_6e!UrU9r`93A`&QPsv19sLYZ)#ei& k{tQvo1{59t3{ll46deE!QPoBi9RUqdw=^TJ;hp{cAFtSbSpWb4 literal 0 HcmV?d00001 From 9af68b57a5915a77275d7077c3dee5057480275f Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 15 Jan 2020 20:25:06 +0100 Subject: [PATCH 137/162] Added support for Template and QualifiedTemplate. Improved code coverage. --- EmberClient/EmberClient.js | 2 +- EmberLib/Command.js | 2 +- EmberLib/Element.js | 14 +- EmberLib/Function.js | 2 +- EmberLib/FunctionArgument.js | 2 +- EmberLib/FunctionContent.js | 2 +- EmberLib/Invocation.js | 2 +- EmberLib/InvocationResult.js | 2 +- EmberLib/Label.js | 13 +- EmberLib/Matrix.js | 58 ++- EmberLib/MatrixConnection.js | 70 ++- EmberLib/MatrixContents.js | 2 +- EmberLib/MatrixNode.js | 4 +- EmberLib/Node.js | 2 +- EmberLib/NodeContents.js | 3 +- EmberLib/Parameter.js | 5 +- EmberLib/ParameterContents.js | 17 +- EmberLib/ParameterType.js | 4 +- EmberLib/QualifiedElement.js | 5 +- EmberLib/QualifiedFunction.js | 4 +- EmberLib/QualifiedMatrix.js | 42 +- EmberLib/QualifiedNode.js | 19 +- EmberLib/QualifiedParameter.js | 23 +- EmberLib/QualifiedTemplate.js | 78 ++++ EmberLib/StreamDescription.js | 2 +- EmberLib/StringIntegerCollection.js | 13 +- EmberLib/StringIntegerPair.js | 18 +- EmberLib/Template.js | 85 ++++ EmberLib/TemplateElement.js | 75 +++ EmberLib/TreeNode.js | 53 ++- EmberLib/index.js | 42 +- EmberServer/ElementHandlers.js | 2 +- EmberServer/EmberServer.js | 2 +- EmberServer/JSONParser.js | 2 +- EmberServer/QualifiedHandlers.js | 2 +- errors.js => Errors.js | 12 +- ber.js | 2 +- package.json | 4 +- test/Ember.test.js | 693 ++++++++++++++++++++++++++-- test/Server.test.js | 12 +- 40 files changed, 1145 insertions(+), 251 deletions(-) create mode 100755 EmberLib/QualifiedTemplate.js create mode 100755 EmberLib/Template.js create mode 100755 EmberLib/TemplateElement.js rename errors.js => Errors.js (94%) diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index f2d2d43..1d148fd 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -2,7 +2,7 @@ const EventEmitter = require('events').EventEmitter; const S101Socket = require('../EmberSocket').S101Socket; const ember = require('../EmberLib'); const BER = require('../ber.js'); -const errors = require('../errors.js'); +const errors = require('../Errors.js'); const winston = require("winston"); const DEFAULT_PORT = 9000; diff --git a/EmberLib/Command.js b/EmberLib/Command.js index f91b78b..a3fab00 100755 --- a/EmberLib/Command.js +++ b/EmberLib/Command.js @@ -3,7 +3,7 @@ const Enum = require('enum'); const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); const BER = require('../ber.js'); const Invocation = require("./Invocation"); -const errors = require("../errors"); +const errors = require("../Errors"); const FieldFlags = new Enum({ sparse: -2, diff --git a/EmberLib/Element.js b/EmberLib/Element.js index 26bfa9d..00aede0 100755 --- a/EmberLib/Element.js +++ b/EmberLib/Element.js @@ -19,9 +19,7 @@ class Element extends TreeNode { encode(ber) { ber.startSequence(this._seqID); - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) + this.encodeNumber(ber); if(this.contents != null) { ber.startSequence(BER.CONTEXT(1)); @@ -33,6 +31,16 @@ class Element extends TreeNode { ber.endSequence(); // BER.APPLICATION(3) } + + /** + * + * @param {BER} ber + */ + encodeNumber(ber) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + } } module.exports = Element; \ No newline at end of file diff --git a/EmberLib/Function.js b/EmberLib/Function.js index 4d3913c..8611c31 100755 --- a/EmberLib/Function.js +++ b/EmberLib/Function.js @@ -5,7 +5,7 @@ const BER = require('../ber.js'); const Command = require("./Command"); const {COMMAND_INVOKE} = require("./constants"); const FunctionContent = require("./FunctionContent"); -const Errors = require("../errors"); +const Errors = require("../Errors"); class Function extends Element { constructor(number, func) { diff --git a/EmberLib/FunctionArgument.js b/EmberLib/FunctionArgument.js index 38a7d3d..de1a792 100755 --- a/EmberLib/FunctionArgument.js +++ b/EmberLib/FunctionArgument.js @@ -1,7 +1,7 @@ "use strict"; const BER = require('../ber.js'); const {ParameterType} = require("./ParameterType"); -const Errors = require("../errors"); +const Errors = require("../Errors"); /* TupleDescription ::= diff --git a/EmberLib/FunctionContent.js b/EmberLib/FunctionContent.js index 37d434e..cd368b8 100755 --- a/EmberLib/FunctionContent.js +++ b/EmberLib/FunctionContent.js @@ -1,7 +1,7 @@ "use strict"; const BER = require('../ber.js'); const FunctionArgument = require("./FunctionArgument"); -const errors = require("../errors"); +const errors = require("../Errors"); class FunctionContent { constructor(identifier=null, description=null) { diff --git a/EmberLib/Invocation.js b/EmberLib/Invocation.js index 580a68c..945b828 100755 --- a/EmberLib/Invocation.js +++ b/EmberLib/Invocation.js @@ -2,7 +2,7 @@ const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); const BER = require('../ber.js'); const FunctionArgument = require("./FunctionArgument"); -const errors = require("../errors"); +const errors = require("../Errors"); let _id = 1; class Invocation { diff --git a/EmberLib/InvocationResult.js b/EmberLib/InvocationResult.js index f26ea41..b036b32 100755 --- a/EmberLib/InvocationResult.js +++ b/EmberLib/InvocationResult.js @@ -3,7 +3,7 @@ const BER = require('../ber.js'); const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); const FunctionArgument = require("./FunctionArgument"); -const Errors = require("../errors"); +const Errors = require("../Errors"); class InvocationResult { diff --git a/EmberLib/Label.js b/EmberLib/Label.js index 280203e..8a41235 100755 --- a/EmberLib/Label.js +++ b/EmberLib/Label.js @@ -1,6 +1,6 @@ "use strict"; const BER = require('../ber.js'); -const Errors = require("../errors"); +const Errors = require("../Errors"); class Label { constructor(path, description) { @@ -17,7 +17,7 @@ class Label { * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.APPLICATION(18)); + ber.startSequence(Label.BERID); if (this.basePath == null) { throw new Errors.InvalidEmberNode("", "Missing label base path"); } @@ -41,7 +41,7 @@ class Label { static decode(ber) { var l = new Label(); - ber = ber.getSequence(BER.APPLICATION(18)); + ber = ber.getSequence(Label.BERID); while (ber.remain > 0) { var tag = ber.peek(); @@ -57,6 +57,13 @@ class Label { } return l; } + + /** + * + */ + static get BERID() { + return BER.APPLICATION(18); + } } module.exports = Label; \ No newline at end of file diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js index c8e4d95..9add4f8 100755 --- a/EmberLib/Matrix.js +++ b/EmberLib/Matrix.js @@ -5,7 +5,7 @@ const BER = require('../ber.js'); const MatrixMode = require("./MatrixMode"); const MatrixOperation = require("./MatrixOperation"); const MatrixType = require("./MatrixType"); -const Errors = require("../errors"); +const Errors = require("../Errors"); class Matrix extends TreeNode { @@ -72,7 +72,7 @@ class Matrix extends TreeNode ber.startSequence(BER.CONTEXT(5)); ber.startSequence(BER.EMBER_SEQUENCE); - for(var id in this.connections) { + for(let id in this.connections) { if (this.connections.hasOwnProperty(id)) { ber.startSequence(BER.CONTEXT(0)); this.connections[id].encode(ber); @@ -93,7 +93,7 @@ class Matrix extends TreeNode ber.startSequence(BER.CONTEXT(4)); ber.startSequence(BER.EMBER_SEQUENCE); - for(var i=0; i 0) { - var seq = ber.getSequence(BER.CONTEXT(0)); + let seq = ber.getSequence(BER.CONTEXT(0)); seq = seq.getSequence(BER.APPLICATION(14)); seq = seq.getSequence(BER.CONTEXT(0)); targets.push(seq.readInt()); @@ -279,7 +278,7 @@ class Matrix extends TreeNode const sources = []; ber = ber.getSequence(BER.EMBER_SEQUENCE); while(ber.remain > 0) { - var seq = ber.getSequence(BER.CONTEXT(0)); + let seq = ber.getSequence(BER.CONTEXT(0)); seq = seq.getSequence(BER.APPLICATION(15)); seq = seq.getSequence(BER.CONTEXT(0)); sources.push(seq.readInt()); @@ -293,14 +292,12 @@ class Matrix extends TreeNode * @returns {Object} */ static decodeConnections(ber) { - let connections = {}; - let seq = ber.getSequence(BER.EMBER_SEQUENCE); + const connections = {}; + const seq = ber.getSequence(BER.EMBER_SEQUENCE); while(seq.remain > 0) { - var conSeq = seq.getSequence(BER.CONTEXT(0)); - var con = MatrixConnection.decode(conSeq); - if (con.target != null) { - connections[con.target] = (con); - } + const conSeq = seq.getSequence(BER.CONTEXT(0)); + const con = MatrixConnection.decode(conSeq); + connections[con.target] = (con); } return connections; } @@ -347,31 +344,44 @@ class Matrix extends TreeNode * * @param {QualifiedMatrix|MatrixNode} matrix * @param {QualifiedMatrix|MatrixNode} newMatrix + * @returns {boolean} - True if something changed */ - static MatrixUpdate(matrix, newMatrix) { + static MatrixUpdate(matrix, newMatrix) { + let modified = false; if (newMatrix.targets != null) { matrix.targets = newMatrix.targets; + modified = true; } if (newMatrix.sources != null) { matrix.sources = newMatrix.sources; + modified = true; } if (newMatrix.connections != null) { if (matrix.connections == null) { matrix.connections = {}; + modified = true; } for(let id in newMatrix.connections) { if (newMatrix.connections.hasOwnProperty(id)) { - let connection = newMatrix.connections[id]; + const connection = newMatrix.connections[id]; if ((connection.target < matrix.contents.targetCount) && (connection.target >= 0)) { if (matrix.connections[connection.target] == null) { matrix.connections[connection.target] = new MatrixConnection(connection.target); + modified = true; + } + if (matrix.connections[connection.target].isDifferent(connection.sources)) { + matrix.connections[connection.target].setSources(connection.sources); + modified = true; } - matrix.connections[connection.target].setSources(connection.sources); + } + else { + throw new Errors.InvalidMatrixSignal(connection.target, "Invalid target") } } } - } + } + return modified; } /** diff --git a/EmberLib/MatrixConnection.js b/EmberLib/MatrixConnection.js index 693eee2..45ec689 100755 --- a/EmberLib/MatrixConnection.js +++ b/EmberLib/MatrixConnection.js @@ -2,7 +2,7 @@ const BER = require('../ber.js'); const MatrixOperation = require("./MatrixOperation"); const MatrixDisposition = require("./MatrixDisposition"); -const errors = require("../errors"); +const Errors = require("../Errors"); class MatrixConnection { /** @@ -12,7 +12,9 @@ class MatrixConnection { constructor(target) { if (target) { let _target = Number(target); - if (isNaN(_target)) { _target = 0; } + if (isNaN(_target)) { + throw new Errors.InvalidMatrixSignal(target, "Can't create connection with invalid target.") + } this.target = _target; } else { @@ -26,14 +28,7 @@ class MatrixConnection { * @param {number[]} sources */ connectSources(sources) { - if (sources == null) { - return; - } - let s = new Set(this.sources); - for(let item of sources) { - s.add(item); - } - this.sources = [...s].sort(); + this.sources = this.validateSources(sources); } /** @@ -56,13 +51,13 @@ class MatrixConnection { * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.APPLICATION(16)); + ber.startSequence(MatrixConnection.BERID); ber.startSequence(BER.CONTEXT(0)); ber.writeInt(this.target); ber.endSequence(); - if ((this.sources != null)&& (this.sources.length > 0)) { + if (this.sources != null) { ber.startSequence(BER.CONTEXT(1)); ber.writeRelativeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); ber.endSequence(); @@ -79,6 +74,31 @@ class MatrixConnection { } ber.endSequence(); } + + /** + * + * @param {number[]|null} sources + */ + isDifferent(sources) { + const newSources = this.validateSources(sources); + + if (this.sources == null && newSources == null) { + return false; + } + + if ((this.sources == null && newSources != null)|| + (this.sources != null && newSources == null) || + (this.sources.length != newSources.length)) { + return true; + } + // list are ordered, so we can simply parse 1 by 1. + for(let i = 0; i < this.sources.length; i++) { + if (this.sources[i] !== newSources[i]) { + return true; + } + } + return false; + } /** * @returns {boolean} @@ -103,8 +123,20 @@ class MatrixConnection { delete this.sources; return; } - let s = new Set(sources.map(i => Number(i))); - this.sources = [...s].sort(); // sources should be an array + this.sources = this.validateSources(sources); + } + + /** + * + * @param {number[]} sources + * @returns {number[]} - uniq and sorted + */ + validateSources(sources) { + if (sources == null) { + return null; + } + const s = new Set(sources.map(i => Number(i))); + return [...s].sort(); } /** @@ -121,7 +153,7 @@ class MatrixConnection { */ static decode(ber) { const c = new MatrixConnection(); - ber = ber.getSequence(BER.APPLICATION(16)); + ber = ber.getSequence(MatrixConnection.BERID); while (ber.remain > 0) { let tag = ber.peek(); let seq = ber.getSequence(tag); @@ -143,12 +175,18 @@ class MatrixConnection { c.disposition = MatrixDisposition.get(seq.readInt()); } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return c; } + /** + * + */ + static get BERID() { + return BER.APPLICATION(16); + } } module.exports = MatrixConnection; \ No newline at end of file diff --git a/EmberLib/MatrixContents.js b/EmberLib/MatrixContents.js index b7f75af..1bbcda8 100755 --- a/EmberLib/MatrixContents.js +++ b/EmberLib/MatrixContents.js @@ -4,7 +4,7 @@ const MatrixType = require("./MatrixType"); const MatrixMode = require("./MatrixMode"); const BER = require('../ber.js'); const Label = require("./Label"); -const errors = require("../errors"); +const errors = require("../Errors"); class MatrixContents { constructor(type = MatrixType.oneToN, mode = MatrixMode.linear) { diff --git a/EmberLib/MatrixNode.js b/EmberLib/MatrixNode.js index b368f15..7cf53d4 100755 --- a/EmberLib/MatrixNode.js +++ b/EmberLib/MatrixNode.js @@ -4,7 +4,7 @@ const Matrix = require("./Matrix"); const MatrixContents = require("./MatrixContents"); const QualifiedMatrix = require("./QualifiedMatrix"); const BER = require('../ber.js'); -const errors = require("../errors"); +const errors = require("../Errors"); class MatrixNode extends Matrix { constructor(number = undefined) { @@ -17,7 +17,7 @@ class MatrixNode extends Matrix { * @param {BER} ber */ encode(ber) { - ber.startSequence(BER.APPLICATION(13)); + ber.startSequence(MatrixNode.BERID); ber.startSequence(BER.CONTEXT(0)); ber.writeInt(this.number); diff --git a/EmberLib/Node.js b/EmberLib/Node.js index a42f001..e24c2eb 100755 --- a/EmberLib/Node.js +++ b/EmberLib/Node.js @@ -4,7 +4,7 @@ const Element = require("./Element"); const QualifiedNode = require("./QualifiedNode"); const NodeContents = require("./NodeContents"); const BER = require('../ber.js'); -const Errors = require("../errors"); +const Errors = require("../Errors"); class Node extends Element { /** diff --git a/EmberLib/NodeContents.js b/EmberLib/NodeContents.js index 68aeede..cb3c479 100755 --- a/EmberLib/NodeContents.js +++ b/EmberLib/NodeContents.js @@ -1,6 +1,6 @@ "use strict"; const BER = require('../ber.js'); -const errors = require("../errors"); +const errors = require("../Errors"); class NodeContents{ /** @@ -12,7 +12,6 @@ class NodeContents{ this.isOnline = true; this.identifier = identifier; this.description = description; - this._subscribers = new Set(); } /** diff --git a/EmberLib/Parameter.js b/EmberLib/Parameter.js index c0ba322..5e0dfd9 100755 --- a/EmberLib/Parameter.js +++ b/EmberLib/Parameter.js @@ -4,8 +4,7 @@ const Element = require("./Element"); const QualifiedParameter = require("./QualifiedParameter"); const BER = require('../ber.js'); const ParameterContents = require("./ParameterContents"); -const Errors = require("../errors"); -const {COMMAND_SUBSCRIBE} = require("./Command"); +const Errors = require("../Errors"); class Parameter extends Element { /** @@ -40,7 +39,7 @@ class Parameter extends Element { * @returns {QualifiedParameter} */ toQualified() { - let qp = new QualifiedParameter(this.getPath()); + const qp = new QualifiedParameter(this.getPath()); qp.update(this); return qp; } diff --git a/EmberLib/ParameterContents.js b/EmberLib/ParameterContents.js index 54548c6..4846b99 100755 --- a/EmberLib/ParameterContents.js +++ b/EmberLib/ParameterContents.js @@ -5,7 +5,7 @@ const ParameterAccess = require("./ParameterAccess"); const StringIntegerCollection = require("./StringIntegerCollection"); const StreamDescription = require("./StreamDescription"); const BER = require('../ber.js'); -const errors = require("../errors"); +const Errors = require("../Errors"); class ParameterContents { /** @@ -14,7 +14,6 @@ class ParameterContents { * @param {string} type */ constructor(value, type) { - this._subscribers = new Set(); if(value != null) { this.value = value; } @@ -48,8 +47,10 @@ class ParameterContents { ber.writeIfDefinedEnum(this.type, ParameterType, ber.writeInt, 13); ber.writeIfDefined(this.streamIdentifier, ber.writeInt, 14); - if(this.stringIntegerCollection != null) { - this.stringIntegerCollection.encode(ber); + if(this.enumMap != null) { + ber.startSequence(BER.CONTEXT(15)); + this.enumMap.encode(ber); + ber.endSequence(); } if(this.streamDescriptor != null) { @@ -106,16 +107,14 @@ class ParameterContents { } else if(tag == BER.CONTEXT(14)) { pc.streamIdentifier = seq.readInt(); } else if(tag == BER.CONTEXT(15)) { - pc.stringIntegerCollection = StringIntegerCollection.decode(seq); + pc.enumMap = StringIntegerCollection.decode(seq); } else if(tag == BER.CONTEXT(16)) { pc.streamDescriptor = StreamDescription.decode(seq); } else if(tag == BER.CONTEXT(17)) { pc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); - } else if (tag == null) { - break; - } + } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return pc; diff --git a/EmberLib/ParameterType.js b/EmberLib/ParameterType.js index 6b9fb04..421a1c9 100755 --- a/EmberLib/ParameterType.js +++ b/EmberLib/ParameterType.js @@ -1,6 +1,6 @@ const Enum = require('enum'); const BER = require('../ber.js'); -const Errors = require("../errors"); +const Errors = require("../Errors"); function ParameterTypetoBERTAG(type) { switch (type.value) { @@ -10,7 +10,7 @@ function ParameterTypetoBERTAG(type) { case 4: return BER.EMBER_BOOLEAN; case 7: return BER.EMBER_OCTETSTRING; default: - throw new Errors.InvalidBERFormat(`Unhandled ParameterType ${type}`); + throw new Errors.InvalidEmberNode("", `Unhandled ParameterType ${type}`); } } diff --git a/EmberLib/QualifiedElement.js b/EmberLib/QualifiedElement.js index f7a78c3..b233375 100755 --- a/EmberLib/QualifiedElement.js +++ b/EmberLib/QualifiedElement.js @@ -28,9 +28,7 @@ class QualifiedElement extends TreeNode { encode(ber) { ber.startSequence(this._seqID); - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) + this.encodePath(ber); if(this.contents != null) { ber.startSequence(BER.CONTEXT(1)); @@ -43,7 +41,6 @@ class QualifiedElement extends TreeNode { ber.endSequence(); // BER.APPLICATION(3) } - /** * * @param {number} cmd diff --git a/EmberLib/QualifiedFunction.js b/EmberLib/QualifiedFunction.js index b34ca15..7a4d017 100755 --- a/EmberLib/QualifiedFunction.js +++ b/EmberLib/QualifiedFunction.js @@ -5,7 +5,7 @@ const FunctionContent = require("./FunctionContent"); const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); const BER = require('../ber.js'); const Invocation = require("./Invocation"); -const errors = require("../errors"); +const Errors = require("../Errors"); class QualifiedFunction extends QualifiedElement { /** @@ -67,7 +67,7 @@ class QualifiedFunction extends QualifiedElement { qf.decodeChildren(seq); } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return qf; diff --git a/EmberLib/QualifiedMatrix.js b/EmberLib/QualifiedMatrix.js index 98efd23..ab4e8e4 100755 --- a/EmberLib/QualifiedMatrix.js +++ b/EmberLib/QualifiedMatrix.js @@ -1,12 +1,11 @@ "use strict"; const Matrix = require("./Matrix"); -const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); const BER = require('../ber.js'); const Command = require("./Command"); const MatrixContents = require("./MatrixContents"); const MatrixConnection = require("./MatrixConnection"); -const errors = require("../errors"); +const errors = require("../Errors"); class QualifiedMatrix extends Matrix { /** @@ -42,9 +41,7 @@ class QualifiedMatrix extends Matrix { encode(ber) { ber.startSequence(QualifiedMatrix.BERID); - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) + this.encodePath(ber); if(this.contents != null) { ber.startSequence(BER.CONTEXT(1)); @@ -74,41 +71,6 @@ class QualifiedMatrix extends Matrix { return r; } - /** - * - * @param {function} callback - * @returns {TreeNode} - */ - getDirectory(callback) { - if (callback != null && !this.isStream()) { - this.contents._subscribers.add(callback); - } - return this.getCommand(COMMAND_GETDIRECTORY); - } - - /** - * - * @param {function} callback - * @returns {TreeNode} - */ - subscribe(callback) { - if (callback != null && this.isStream()) { - this.contents._subscribers.add(callback); - } - return this.getCommand(COMMAND_SUBSCRIBE); - } - - /** - * - * @param {function} callback - * @returns {TreeNode} - */ - unsubscribe(callback) { - if (callback != null && this.isStream()) { - this.contents._subscribers.delete(callback); - } - return this.getCommand(COMMAND_UNSUBSCRIBE); - } /** * diff --git a/EmberLib/QualifiedNode.js b/EmberLib/QualifiedNode.js index d0ec097..155e23c 100755 --- a/EmberLib/QualifiedNode.js +++ b/EmberLib/QualifiedNode.js @@ -2,8 +2,7 @@ const QualifiedElement = require("./QualifiedElement"); const BER = require('../ber.js'); const NodeContents = require("./NodeContents"); -const Node = require("./Node"); -const errors = require("../errors"); +const Errors = require("../Errors"); class QualifiedNode extends QualifiedElement { constructor (path) { @@ -18,20 +17,6 @@ class QualifiedNode extends QualifiedElement { return true; } - /** - * - * @param {boolean} complete - * @returns {QualifiedNode} - */ - getMinimal(complete = false) { - const number = this.getNumber(); - const n = new Node(number); - if (complete && (this.contents != null)) { - n.contents = this.contents; - } - return n; - } - /** * * @param {BER} ber @@ -51,7 +36,7 @@ class QualifiedNode extends QualifiedElement { } else if(tag == BER.CONTEXT(2)) { qn.decodeChildren(seq); } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return qn; diff --git a/EmberLib/QualifiedParameter.js b/EmberLib/QualifiedParameter.js index a079c03..9a14095 100755 --- a/EmberLib/QualifiedParameter.js +++ b/EmberLib/QualifiedParameter.js @@ -3,7 +3,7 @@ const QualifiedElement = require("./QualifiedElement"); const ParameterContents = require("./ParameterContents"); const BER = require('../ber.js'); -const Parameter = require("./Parameter"); +const Errors = require("../Errors"); class QualifiedParameter extends QualifiedElement { /** @@ -22,22 +22,6 @@ class QualifiedParameter extends QualifiedElement { return true; } - /** - * - * @param {boolean} complete - * @returns {Parameter} - */ - getMinimal(complete = false) { - const number = this.getNumber(); - const p = new Parameter(number); - if (complete) { - if (this.contents != null) { - p.contents = this.contents; - } - } - return p; - } - /** * * @param {number|string} value @@ -69,9 +53,6 @@ class QualifiedParameter extends QualifiedElement { this.contents[key] = other.contents[key]; } } - for(let cb of this.contents._subscribers) { - cb(this); - } } } return; @@ -96,7 +77,7 @@ class QualifiedParameter extends QualifiedElement { } else if(tag == BER.CONTEXT(2)) { qp.decodeChildren(seq); } else { - return qp; + throw new Errors.UnimplementedEmberTypeError(tag); } } return qp; diff --git a/EmberLib/QualifiedTemplate.js b/EmberLib/QualifiedTemplate.js new file mode 100755 index 0000000..a5a2d51 --- /dev/null +++ b/EmberLib/QualifiedTemplate.js @@ -0,0 +1,78 @@ +"use strict"; +const TemplateElement = require("./TemplateElement"); +const QualifiedElement = require("./QualifiedElement"); +const Template = require("./Template"); +const BER = require('../ber.js'); +const Errors = require("../Errors"); + +class QualifiedTemplate extends QualifiedElement { + /** + * + * @param {string} path + * @param {Node|Function|MatrixNode|Parameter} element + */ + constructor(path, element) { + super(path); + this.element = element; + this._seqID = QualifiedTemplate.BERID; + } + + /** + * @returns {boolean} + */ + isTemplate() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(QualifiedTemplate.BERID); + + this.encodePath(ber); + + TemplateElement.encodeContent(this, ber); + + ber.endSequence(); + } + + /** + * + * @param {Template} other + */ + update(other) { + this.element = other.element; + } + + /** + * + * @param {BER} ber + * @returns {QualifiedTemplate} + */ + static decode(ber) { + const qt = new QualifiedTemplate(); + ber = ber.getSequence(QualifiedTemplate.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qt.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else { + TemplateElement.decodeContent(qt, tag, seq); + } + } + return qt; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(25); + } +} + +module.exports = QualifiedTemplate; \ No newline at end of file diff --git a/EmberLib/StreamDescription.js b/EmberLib/StreamDescription.js index 81ac7e6..53edb69 100755 --- a/EmberLib/StreamDescription.js +++ b/EmberLib/StreamDescription.js @@ -2,7 +2,7 @@ const Element = require("./Element"); const BER = require('../ber.js'); const StreamFormat = require("./StreamFormat"); -const Errors = require("../errors"); +const Errors = require("../Errors"); class StreamDescription extends Element{ /** diff --git a/EmberLib/StringIntegerCollection.js b/EmberLib/StringIntegerCollection.js index 21baa99..3fd3eb1 100755 --- a/EmberLib/StringIntegerCollection.js +++ b/EmberLib/StringIntegerCollection.js @@ -1,7 +1,7 @@ "use strict"; const BER = require('../ber.js'); const StringIntegerPair = require("./StringIntegerPair"); -const Errors = require("../errors"); +const Errors = require("../Errors"); class StringIntegerCollection { constructor() { @@ -14,6 +14,9 @@ class StringIntegerCollection { * @param {StringIntegerPair} value */ addEntry(key, value) { + if (!(value instanceof StringIntegerPair)) { + throw new Errors.InvalidStringPair(); + } this._collection.set(key, value); } @@ -32,23 +35,23 @@ class StringIntegerCollection { */ encode(ber) { ber.startSequence(StringIntegerCollection.BERID); - for(let [key,sp] of this._collection) { + for(let [,sp] of this._collection) { ber.startSequence(BER.CONTEXT(0)); sp.encode(ber); ber.endSequence(); } ber.endSequence(); - ber.endSequence(); } /** - * + * @returns {JSON_StringPair[]} */ toJSON() { const collection = []; - for(let [key,sp] of this._collection) { + for(let [,sp] of this._collection) { collection.push(sp.toJSON()); } + return collection; } /** diff --git a/EmberLib/StringIntegerPair.js b/EmberLib/StringIntegerPair.js index 187a6fb..15bbe1c 100755 --- a/EmberLib/StringIntegerPair.js +++ b/EmberLib/StringIntegerPair.js @@ -1,6 +1,6 @@ "use strict"; const BER = require('../ber.js'); -const errors = require("../errors"); +const Errors = require("../Errors"); class StringIntegerPair { constructor(key,value) { @@ -14,7 +14,7 @@ class StringIntegerPair { */ encode(ber) { if (this.key == null || this.value == null) { - throw new errors.InvalidEmberNode("", "Invalid key/value missing"); + throw new Errors.InvalidEmberNode("", "Invalid key/value missing"); } ber.startSequence(StringIntegerPair.BERID); ber.startSequence(BER.CONTEXT(0)); @@ -26,8 +26,18 @@ class StringIntegerPair { ber.endSequence(); } + /** + * @typedef {{ + * key: string + * value: number + * }} JSON_StringPair + */ + /** - * + * @returns {{ + * key: string + * value: number + * }} */ toJSON() { return { @@ -51,7 +61,7 @@ class StringIntegerPair { } else if(tag == BER.CONTEXT(1)) { sp.value = dataSeq.readInt(); } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return sp; diff --git a/EmberLib/Template.js b/EmberLib/Template.js new file mode 100755 index 0000000..44a3950 --- /dev/null +++ b/EmberLib/Template.js @@ -0,0 +1,85 @@ +"use strict"; + +const Element = require("./Element"); +const QualifiedTemplate = require("./QualifiedTemplate"); +const BER = require('../ber.js'); +const TemplateElement = require("./TemplateElement"); +const Errors = require("../Errors"); + +class Template extends Element { + /** + * + * @param {number} number + * @param {Node|Function|MatrixNode|Parameter} element + */ + constructor(number, element) { + super(); + this.number = number; + this.element = element; + this._seqID = Template.BERID; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(Template.BERID); + this.encodeNumber(ber); + TemplateElement.encodeContent(this, ber); + ber.endSequence(); + } + + /** + * @returns {boolean} + */ + isTemplate() { + return true; + } + + /** + * @returns {QualifiedParameter} + */ + toQualified() { + const qp = new QualifiedTemplate(this.getPath()); + qp.update(this); + return qp; + } + + /** + * + * @param {Template} other + */ + update(other) { + this.element = other.element; + } + + /** + * + * @param {BER} ber + * @returns {Template} + */ + static decode(ber) { + const template = new Template(); + ber = ber.getSequence(Template.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + template.number = seq.readInt(); + } else { + TemplateElement.decodeContent(template, tag, seq); + } + } + return template; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(24); + } +} + +module.exports = Template; \ No newline at end of file diff --git a/EmberLib/TemplateElement.js b/EmberLib/TemplateElement.js new file mode 100755 index 0000000..1642ac0 --- /dev/null +++ b/EmberLib/TemplateElement.js @@ -0,0 +1,75 @@ +"use strict"; + +const BER = require('../ber.js'); +const Parameter = require("./Parameter"); +const Node = require("./Node"); +const MatrixNode = require("./MatrixNode"); +const Function = require("./Function"); +const Errors = require("../Errors"); + +/* +TemplateElement ::= + CHOICE { + parameter Parameter, + node Node, + matrix Matrix, + function Function + } +*/ +class TemplateElement { + /** + * + * @param {Node|Function|Parameter|MatrixNode} ber + */ + static decode(ber) { + const tag = ber.peek(); + if (tag == BER.APPLICATION(1)) { + return Parameter.decode(ber); + } else if(tag == BER.APPLICATION(3)) { + return Node.decode(ber); + } else if(tag == BER.APPLICATION(19)) { + return Function.decode(ber); + } else if(tag == BER.APPLICATION(13)) { + return MatrixNode.decode(ber); + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + + /** + * + * @param {Template|QualifiedTemplate} template + * @param {number} tag + * @param {BER} ber + */ + static decodeContent(template, tag, ber) { + if(tag == BER.CONTEXT(1)) { + template.element = TemplateElement.decode(ber); + } else if(tag == BER.CONTEXT(2)) { + template.description = ber.readString(BER.EMBER_STRING); + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + /** + * + * @param {Template|QualifiedTemplate} template + * @param {BER} ber + */ + static encodeContent(template, ber) { + if(template.element != null) { + ber.startSequence(BER.CONTEXT(1)); + template.element.encode(ber); + ber.endSequence(); + } + + if (template.description != null) { + ber.startSequence(BER.CONTEXT(2)); + ber.writeString(template.description, BER.EMBER_STRING); + ber.endSequence(); + } + } +} + +module.exports = TemplateElement; \ No newline at end of file diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index 166b751..a7344b1 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -2,27 +2,26 @@ const BER = require('../ber.js'); const Command = require("./Command"); const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); -const Errors = require("../errors"); class TreeNode { constructor() { /** @type {TreeNode} */ - this._parent = null; + this._parent = null; + this._subscribers = new Set(); } _isSubscribable(callback) { - return (callback != null && this.isParameter() && this.isStream()); + return (callback != null && + ((this.isParameter() && this.isStream()) || + this.isMatrix())); } _subscribe(callback) { - if (this.contents == null) { - throw new Errors.InvalidEmberNode(this.getPath(), "No content to subscribe"); - } - this.contents._subscribers.add(callback); + this._subscribers.add(callback); } _unsubscribe(callback) { - this.contents._subscribers.delete(callback); + this._subscribers.delete(callback); } /** @@ -119,6 +118,18 @@ class TreeNode { } } + /** + * + * @param {BER} ber + */ + encodePath(ber) { + if (this.isQualified()) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.endSequence(); // BER.CONTEXT(0) + } + } + /** * @returns {TreeNode} */ @@ -182,6 +193,14 @@ class TreeNode { return this.contents != null && this.contents.streamIdentifier != null; } + + /** + * @returns {boolean} + */ + isTemplate() { + return false; + } + /** * @returns {TreeNode} */ @@ -511,12 +530,13 @@ class TreeNode { * @param {TreeNode} other */ update(other) { + let modified = false; if ((other != null) && (other.contents != null)) { if (this.contents == null) { this.contents = other.contents; + modified = true; } else { - let modified = false; for (var key in other.contents) { if (key[0] === "_") { continue; } if (other.contents.hasOwnProperty(key) && @@ -525,14 +545,17 @@ class TreeNode { modified = true; } } - if (modified && this.contents._subscribers != null) { - for(let cb of this.contents._subscribers) { - cb(this); - } - } } } - return; + return modified; + } + + updateSubscribers() { + if (this._subscribers != null) { + for(let cb of this._subscribers) { + cb(this); + } + } } /** diff --git a/EmberLib/index.js b/EmberLib/index.js index 1bdf67f..f890109 100755 --- a/EmberLib/index.js +++ b/EmberLib/index.js @@ -1,7 +1,7 @@ const {Subscribe,COMMAND_SUBSCRIBE,Unsubscribe,COMMAND_UNSUBSCRIBE, GetDirectory,COMMAND_GETDIRECTORY,Invoke,COMMAND_INVOKE, COMMAND_STRINGS} = require("./constants"); const BER = require('../ber.js'); -const errors = require("../errors"); +const Errors = require("../Errors"); const TreeNode = require("./TreeNode"); const Command = require("./Command"); const Function = require("./Function"); @@ -32,6 +32,9 @@ const StringIntegerPair = require("./StringIntegerPair"); const StringIntegerCollection = require("./StringIntegerCollection"); const StreamFormat = require("./StreamFormat"); const StreamDescription = require("./StreamDescription"); +const Template = require("./Template"); +const TemplateElement = require("./TemplateElement"); +const QualifiedTemplate = require("./QualifiedTemplate"); const rootDecode = function(ber) { const r = new TreeNode(); @@ -62,7 +65,7 @@ const rootDecode = function(ber) { else { // StreamCollection BER.APPLICATION(6) // InvocationResult BER.APPLICATION(23) - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } else if (tag === BER.CONTEXT(0)) { @@ -76,7 +79,7 @@ const rootDecode = function(ber) { } } else { - throw new errors.UnimplementedEmberTypeError(tag); + throw new Errors.UnimplementedEmberTypeError(tag); } } return r; @@ -84,29 +87,31 @@ const rootDecode = function(ber) { const childDecode = function(ber) { const tag = ber.peek(); - if (tag == BER.APPLICATION(1)) { + if (tag == Parameter.BERID) { return Parameter.decode(ber); - } else if(tag == BER.APPLICATION(3)) { + } else if(tag == Node.BERID) { return Node.decode(ber); - } else if(tag == BER.APPLICATION(2)) { + } else if(tag == Command.BERID) { return Command.decode(ber); - } else if(tag == BER.APPLICATION(9)) { + } else if(tag == QualifiedParameter.BERID) { return QualifiedParameter.decode(ber); - } else if(tag == BER.APPLICATION(10)) { + } else if(tag == QualifiedNode.BERID) { return QualifiedNode.decode(ber); - } else if(tag == BER.APPLICATION(13)) { + } else if(tag == MatrixNode.BERID) { return MatrixNode.decode(ber); - } else if(tag == BER.APPLICATION(17)) { + } else if(tag == QualifiedMatrix.BERID) { return QualifiedMatrix.decode(ber); - } else if(tag == BER.APPLICATION(19)) { + } else if(tag == Function.BERID) { return Function.decode(ber); - } else if (tag == BER.APPLICATION(20)) { + } else if (tag == QualifiedFunction.BERID) { return QualifiedFunction.decode(ber); - } else if(tag == BER.APPLICATION(24)) { - // Template - throw new errors.UnimplementedEmberTypeError(tag); - } else { - throw new errors.UnimplementedEmberTypeError(tag); + } else if(tag == Template.BERID) { + return Template.decode(ber); + } else if (tag == QualifiedTemplate.BERID) { + return QualifiedTemplate.decode(ber) + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); } } @@ -148,10 +153,13 @@ module.exports = { QualifiedMatrix, QualifiedNode, QualifiedParameter, + QualifiedTemplate, StreamFormat, StreamDescription, StringIntegerPair, StringIntegerCollection, + Template, + TemplateElement, Subscribe,COMMAND_SUBSCRIBE, Unsubscribe,COMMAND_UNSUBSCRIBE, GetDirectory,COMMAND_GETDIRECTORY, diff --git a/EmberServer/ElementHandlers.js b/EmberServer/ElementHandlers.js index a318500..ece5ce0 100755 --- a/EmberServer/ElementHandlers.js +++ b/EmberServer/ElementHandlers.js @@ -2,7 +2,7 @@ const QualifiedHandlers = require("./QualifiedHandlers"); const EmberLib = require('../EmberLib'); const ServerEvents = require("./ServerEvents"); -const Errors = require("../errors"); +const Errors = require("../Errors"); const winston = require("winston"); class ElementHandlers extends QualifiedHandlers{ diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 9fe7c73..99f74bf 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -5,7 +5,7 @@ const JSONParser = require("./JSONParser"); const ElementHandlers = require("./ElementHandlers"); const ServerEvents = require("./ServerEvents"); const winston = require("winston"); -const Errors = require("../errors"); +const Errors = require("../Errors"); class TreeServer extends EventEmitter{ /** diff --git a/EmberServer/JSONParser.js b/EmberServer/JSONParser.js index 9fcd617..126164a 100755 --- a/EmberServer/JSONParser.js +++ b/EmberServer/JSONParser.js @@ -1,6 +1,6 @@ "use strict"; const ember = require('../EmberLib'); -const Errors = require("../errors"); +const Errors = require("../Errors"); class JSONParser { /** diff --git a/EmberServer/QualifiedHandlers.js b/EmberServer/QualifiedHandlers.js index 1589087..21ec5c2 100755 --- a/EmberServer/QualifiedHandlers.js +++ b/EmberServer/QualifiedHandlers.js @@ -1,6 +1,6 @@ "use strict"; const MatrixHandlers = require("./MatrixHandlers"); -const Errors = require("../errors"); +const Errors = require("../Errors"); class QualifiedHandlers extends MatrixHandlers { /** diff --git a/errors.js b/Errors.js similarity index 94% rename from errors.js rename to Errors.js index e89d463..89c85ad 100755 --- a/errors.js +++ b/Errors.js @@ -187,4 +187,14 @@ class InvalidMatrixSignal extends Error { super(`Invalid Matrix Signal ${value}: ${info}`); } } -module.exports.InvalidMatrixSignal = InvalidMatrixSignal; \ No newline at end of file +module.exports.InvalidMatrixSignal = InvalidMatrixSignal; + +class InvalidStringPair extends Error { + /** + * + */ + constructor() { + super("Invalid StringPair Value"); + } +} +module.exports.InvalidStringPair = InvalidStringPair; \ No newline at end of file diff --git a/ber.js b/ber.js index 08bfbe9..72ae011 100755 --- a/ber.js +++ b/ber.js @@ -22,7 +22,7 @@ ***************************************************************************/ const BER = require('asn1').Ber; -const errors = require('./errors.js'); +const errors = require('./Errors.js'); const util = require('util'); const Long = require('long'); diff --git a/package.json b/package.json index bb05bd1..5832de4 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.3.0", + "version": "2.4.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { @@ -19,7 +19,7 @@ }, "license": "MIT", "dependencies": { - "asn1": "github:evs-broadcast/node-asn1#date_2018_01_02", + "asn1": "github:evs-broadcast/node-asn1#date_20190114", "enum": "^2.4.0", "long": "^3.2.0", "smart-buffer": "^3.0.3", diff --git a/test/Ember.test.js b/test/Ember.test.js index be0c90f..4195c7e 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -4,16 +4,16 @@ const s101Buffer = Buffer.from("fe000e0001c001021f026082008d6b820089a0176a15a005 const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); const ember = require("../EmberLib"); const BER = require('../ber.js'); -const Errors = require('../errors.js'); +const Errors = require('../Errors.js'); const EmberLib = require("../EmberLib"); - +const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("../EmberLib/ParameterType"); const identifier = "node_identifier"; const description = "node_description"; describe("Ember", () => { describe("generic", () => { let client; - beforeAll(() => { + beforeEach(() => { client = new S101Client(); }); @@ -93,10 +93,14 @@ describe("Ember", () => { node.contents.schemaIdentifiers = "schema1"; const root = new EmberLib.Node(0); root.addChild(node); - const writer = new BER.Writer(); + let writer = new BER.Writer(); + root.encode(writer); + expect(writer.buffer.size).not.toBe(0); + node.contents.isOnline = null; + node.contents.identifier = null; + writer = new BER.Writer(); root.encode(writer); expect(writer.buffer.size).not.toBe(0); - }); it("should have a decoder", () => { const node = new EmberLib.Node(0); @@ -125,7 +129,7 @@ describe("Ember", () => { expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); } }); - it("should through an error if unable to decode content", () => { + it("should throw an error if unable to decode content", () => { const writer = new BER.Writer(); writer.startSequence(BER.EMBER_SET); writer.startSequence(BER.CONTEXT(99)); @@ -142,7 +146,7 @@ describe("Ember", () => { }); describe("Function", () => { let func; - beforeAll(() => { + beforeEach(() => { func = new EmberLib.Function(0, args => { const res = new EmberLib.FunctionArgument(); res.type = EmberLib.ParameterType.integer; @@ -221,7 +225,7 @@ describe("Ember", () => { expect(f.contents.identifier == null).toBeTruthy(); expect(f.contents.result == null || f.contents.result.length == 0).toBeTruthy(); }); - it("should through an error if unable to decode result", () => { + it("should throw an error if unable to decode result", () => { const writer = new BER.Writer(); writer.startSequence(BER.EMBER_SET); writer.startSequence(BER.CONTEXT(3)); @@ -237,7 +241,7 @@ describe("Ember", () => { expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); } }); - it("should through an error if unable to decode content", () => { + it("should throw an error if unable to decode content", () => { const writer = new BER.Writer(); writer.startSequence(BER.EMBER_SET); writer.startSequence(BER.CONTEXT(99)); @@ -251,7 +255,7 @@ describe("Ember", () => { expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); } }); - it("should through an error if unable to decode FunctionArgument", () => { + it("should throw an error if unable to decode FunctionArgument", () => { const writer = new BER.Writer(); writer.startSequence(EmberLib.FunctionArgument.BERID); writer.startSequence(BER.CONTEXT(99)); @@ -267,25 +271,39 @@ describe("Ember", () => { }); }); describe("Parameter", () => { + it("should through an error if decoding unknown parameter type", () => { + try { + ParameterTypefromBERTAG(99); + } + catch(e) { + expect(e instanceof Errors.InvalidBERFormat).toBeTruthy(); + } + try { + ParameterTypetoBERTAG(99); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); it("should have an update function", () => { const parameter = new EmberLib.Parameter(0); const VALUE = 1; - let count = 0; parameter.contents = new EmberLib.ParameterContents(VALUE, "integer"); - parameter.contents._subscribers.add(() => {count++;}); const newParameter = new EmberLib.Parameter(0); const NEW_VALUE = VALUE + 1; newParameter.contents = new EmberLib.ParameterContents(NEW_VALUE, "integer"); parameter.update(newParameter); - expect(count).toBe(1); expect(parameter.contents.value).toBe(NEW_VALUE); }); it("should have setValue function", () => { const parameter = new EmberLib.Parameter(0); const VALUE = 1; parameter.contents = new EmberLib.ParameterContents(VALUE, "integer"); - const NEW_VALUE = VALUE + 1; - const setVal = parameter.setValue(NEW_VALUE); + let NEW_VALUE = VALUE + 1; + let setVal = parameter.setValue(NEW_VALUE); + expect(setVal.contents.value).toBe(NEW_VALUE); + NEW_VALUE = NEW_VALUE + 1; + setVal = parameter.setValue(new EmberLib.ParameterContents(NEW_VALUE)); expect(setVal.contents.value).toBe(NEW_VALUE); }); it("should have decoder function", () => { @@ -299,15 +317,35 @@ describe("Ember", () => { parameter.contents.factor = 10; parameter.contents.isOnline = true; parameter.contents.formula = "x10"; - parameter.contents.step = 2; - parameter.contents.default = 0; + const STEP = 2; + parameter.contents.step = STEP; + const DEFAULT = 0; + parameter.contents.default = DEFAULT; parameter.contents.type = EmberLib.ParameterType.integer; + parameter.contents.enumeration = "enumeration"; + parameter.contents.description = "description"; + parameter.contents.enumMap = new EmberLib.StringIntegerCollection(); + const KEY = "one"; + const KEY_VAL = 1; + parameter.contents.enumMap.addEntry(KEY, new EmberLib.StringIntegerPair(KEY, KEY_VAL)); + parameter.contents.streamDescriptor = new EmberLib.StreamDescription(); + parameter.contents.streamDescriptor.format = EmberLib.StreamFormat.signedInt8; + const OFFSET = 4; + parameter.contents.streamDescriptor.offset = OFFSET; + + const SCHEMA = "schema"; + parameter.contents.schemaIdentifiers = SCHEMA; const node = new EmberLib.Node(0); parameter.addChild(node); const writer = new BER.Writer(); parameter.encode(writer); const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); expect(newParameter.getChildren().length).toBe(1); + expect(newParameter.contents.streamDescriptor.offset).toBe(OFFSET); + expect(newParameter.contents.step).toBe(STEP); + expect(newParameter.contents.default).toBe(DEFAULT); + expect(newParameter.contents.enumMap.get(KEY).value).toBe(KEY_VAL); + expect(newParameter.contents.schemaIdentifiers).toBe(SCHEMA); }); it("should support type real", () => { const parameter = new EmberLib.Parameter(0); @@ -336,7 +374,21 @@ describe("Ember", () => { const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); expect(newParameter.contents.value).toBe(VALUE); }); - it("should through an error if fails to decode StringIntegerPair", () => { + it("should throw an error if fails to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Parameter.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should throw an error if fails to decode StringIntegerPair", () => { const writer = new BER.Writer(); writer.startSequence(EmberLib.StringIntegerPair.BERID); writer.startSequence(BER.CONTEXT(99)); @@ -350,7 +402,7 @@ describe("Ember", () => { expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); } }); - it("should through an error if fails to decode StringIntegerCollection", () => { + it("should throw an error if fails to decode StringIntegerCollection", () => { const writer = new BER.Writer(); writer.startSequence(EmberLib.StringIntegerCollection.BERID); writer.startSequence(BER.CONTEXT(99)); @@ -364,14 +416,31 @@ describe("Ember", () => { expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); } }); + it("should throw an error if fails to decode ParameterContents", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.ParameterContents.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); }); describe("Matrix", () => { describe("validateConnection", () => { + const PATH = "0.0.0"; let matrixNode; + let qMatrixNode; const TARGETCOUNT = 5; const SOURCECOUNT = 5; - beforeAll(() => { + beforeEach(() => { matrixNode = new EmberLib.MatrixNode(0); + qMatrixNode = new EmberLib.QualifiedMatrix(PATH); matrixNode.contents = new EmberLib.MatrixContents( EmberLib.MatrixType.onetoN, EmberLib.MatrixMode.linear @@ -380,8 +449,46 @@ describe("Ember", () => { matrixNode.contents.description = "matrix"; matrixNode.contents.targetCount = TARGETCOUNT; matrixNode.contents.sourceCount = SOURCECOUNT; + qMatrixNode.contents = matrixNode.contents; + }); + it("should have encoder/decoder", () => { + matrixNode.addChild(new EmberLib.Node(0)); + let writer = new BER.Writer(); + matrixNode.encode(writer); + let newMatrixNode = EmberLib.MatrixNode.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.getChildren().length).toBe(1); + + writer = new BER.Writer(); + qMatrixNode.encode(writer); + newMatrixNode = EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.path).toBe(PATH); + + matrixNode.contents.identifier = null; + matrixNode.contents.type = null; + matrixNode.contents.mode = null; + writer = new BER.Writer(); + matrixNode.encode(writer); + newMatrixNode = EmberLib.MatrixNode.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.contents.identifier == null).toBeTruthy(); + + writer = new BER.Writer(); + qMatrixNode.encode(writer); + newMatrixNode = EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.contents.identifier == null).toBeTruthy(); + + matrixNode.contents = null; + writer = new BER.Writer(); + matrixNode.encode(writer); + newMatrixNode = EmberLib.MatrixNode.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.contents == null).toBeTruthy(); + + qMatrixNode.contents = null; + writer = new BER.Writer(); + qMatrixNode.encode(writer); + newMatrixNode = EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.contents == null).toBeTruthy(); }); - it("should through an error if target is negative", () => { + it("should throw an error if target is negative", () => { try { EmberLib.Matrix.validateConnection(matrixNode, -1, []); throw new Error("Should not succeed"); @@ -390,7 +497,7 @@ describe("Ember", () => { expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); } }); - it("should through an error if source is negative", () => { + it("should throw an error if source is negative", () => { try { EmberLib.Matrix.validateConnection(matrixNode, 0, [-1]); throw new Error("Should not succeed"); @@ -399,7 +506,7 @@ describe("Ember", () => { expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); } }); - it("should through an error if target higher than max target", () => { + it("should throw an error if target higher than max target", () => { try { EmberLib.Matrix.validateConnection(matrixNode, TARGETCOUNT, [0]); throw new Error("Should not succeed"); @@ -408,7 +515,7 @@ describe("Ember", () => { expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); } }); - it("should through an error if target higher than max target", () => { + it("should throw an error if target higher than max target", () => { try { EmberLib.Matrix.validateConnection(matrixNode, 0, [SOURCECOUNT]); throw new Error("Should not succeed"); @@ -417,7 +524,7 @@ describe("Ember", () => { expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); } }); - it("should through an error if non-Linear Matrix without targets", () => { + it("should throw an error if non-Linear Matrix without targets", () => { matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; try { EmberLib.Matrix.validateConnection(matrixNode, 0, [0]); @@ -428,7 +535,7 @@ describe("Ember", () => { expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); } }); - it("should through an error if non-Linear Matrix without sources", () => { + it("should throw an error if non-Linear Matrix without sources", () => { matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; matrixNode.targets = [0, 3]; try { @@ -440,7 +547,7 @@ describe("Ember", () => { expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); } }); - it("should through an error if non-Linear Matrix and not valid target", () => { + it("should throw an error if non-Linear Matrix and not valid target", () => { matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; matrixNode.targets = [0, 3]; matrixNode.sources = [0, 3]; @@ -458,7 +565,13 @@ describe("Ember", () => { expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); } }); - it("should through an error if non-Linear Matrix and not valid source", () => { + it("should have getMinimal function", () => { + matrixNode.contents = null; + matrixNode.connections = null; + const min = matrixNode.getMinimal(true); + expect(min.number).toBe(matrixNode.getNumber()); + }); + it("should throw an error if non-Linear Matrix and not valid source", () => { matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; matrixNode.targets = [0, 3]; matrixNode.sources = [0, 3]; @@ -471,7 +584,7 @@ describe("Ember", () => { expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); } }); - it("should not through an error on valid non-linear connect", () => { + it("should not throw an error on valid non-linear connect", () => { let error = null; matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; matrixNode.targets = [0, 3]; @@ -484,12 +597,26 @@ describe("Ember", () => { } expect(error == null).toBeTruthy(); }); + it("should not throw an error if can't decode MatrixContent", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.MatrixContents.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); }); describe("MatrixUpdate", () => { let matrixNode; const TARGETCOUNT = 5; const SOURCECOUNT = 5; - beforeAll(() => { + beforeEach(() => { matrixNode = new EmberLib.MatrixNode(0); matrixNode.contents = new EmberLib.MatrixContents( EmberLib.MatrixType.onetoN, @@ -500,7 +627,69 @@ describe("Ember", () => { matrixNode.contents.targetCount = TARGETCOUNT; matrixNode.contents.sourceCount = SOURCECOUNT; }); - it("should not through an error on valid non-linear connect", () => { + it("should update connections", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + + const newMatrixNode = new EmberLib.MatrixNode(0); + newMatrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + newMatrixNode.contents.identifier = "matrix"; + newMatrixNode.contents.description = "matrix"; + matrixNode.connections[0].sources = [1]; + newMatrixNode.connections = { + 0: matrixNode.connections[0], + 1: new EmberLib.MatrixConnection(1) + }; + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + expect(matrixNode.connections[1]).toBeDefined(); + matrixNode.connections = null; + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + expect(matrixNode.connections[1]).toBeDefined(); + }); + it("should ignore empty connections request", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + + const newMatrixNode = new EmberLib.MatrixNode(0); + newMatrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + newMatrixNode.contents.identifier = "matrix"; + newMatrixNode.contents.description = "matrix"; + newMatrixNode.connections = null; + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + expect(matrixNode.connections[0]).toBeDefined(); + }); + it("should throw error if invalid target inside new connections", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + + const newMatrixNode = new EmberLib.MatrixNode(0); + newMatrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + newMatrixNode.contents.identifier = "matrix"; + newMatrixNode.contents.description = "matrix"; + newMatrixNode.connections = { + 7: new EmberLib.MatrixConnection(7) + }; + try { + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should not throw an error on valid non-linear connect", () => { let error = null; const newMatrixNode = new EmberLib.MatrixNode(0); newMatrixNode.contents = new EmberLib.MatrixContents( @@ -531,7 +720,7 @@ describe("Ember", () => { let matrixNode; const TARGETCOUNT = 5; const SOURCECOUNT = 5; - beforeAll(() => { + beforeEach(() => { matrixNode = new EmberLib.MatrixNode(0); matrixNode.contents = new EmberLib.MatrixContents( EmberLib.MatrixType.onetoN, @@ -545,12 +734,46 @@ describe("Ember", () => { it("should generate the connection structure if not existent", () => { EmberLib.Matrix.disconnectSources(matrixNode, 0, [1]); }); + it("should disconnect existing connection", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + EmberLib.Matrix.connectSources(matrixNode, 0, [1]); + EmberLib.Matrix.connectSources(matrixNode, 1, [1]); + expect(matrixNode._numConnections).toBe(2); + EmberLib.Matrix.disconnectSources(matrixNode, 0, [1]); + expect(matrixNode.connections[0]).toBeDefined(); + expect(matrixNode.connections[0].sources.length).toBe(0); + expect(matrixNode._numConnections).toBe(1); + }); + it("should ignore disconnect with no source", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + EmberLib.Matrix.connectSources(matrixNode, 0, [1]); + expect(matrixNode._numConnections).toBe(1); + EmberLib.Matrix.disconnectSources(matrixNode, 0, null); + expect(matrixNode.connections[0]).toBeDefined(); + expect(matrixNode.connections[0].sources.length).toBe(1); + }); + it("should ignore disconnect with not connected source", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + EmberLib.Matrix.connectSources(matrixNode, 0, [1]); + EmberLib.Matrix.connectSources(matrixNode, 1, [0]); + expect(matrixNode._numConnections).toBe(2); + EmberLib.Matrix.disconnectSources(matrixNode, 0, [0]); + expect(matrixNode.connections[0]).toBeDefined(); + expect(matrixNode.connections[0].sources.length).toBe(1); + expect(matrixNode._numConnections).toBe(2); + }); }); describe("decodeConnections", () => { let matrixNode; const TARGETCOUNT = 5; const SOURCECOUNT = 5; - beforeAll(() => { + beforeEach(() => { matrixNode = new EmberLib.MatrixNode(0); matrixNode.contents = new EmberLib.MatrixContents( EmberLib.MatrixType.onetoN, @@ -574,11 +797,20 @@ describe("Ember", () => { expect(connections[0].sources[0]).toBe(SOURCEID); }); }); + describe("encodeConnections", () => { + it ("should ignore empty/null connections", () => { + const matrixNode = new EmberLib.MatrixNode(0); + matrixNode.connections = null; + const writer = new BER.Writer(); + matrixNode.encodeConnections(writer); + expect(writer.buffer.length).toBe(0); + }); + }); describe("canConnect", () => { let matrixNode; const TARGETCOUNT = 5; const SOURCECOUNT = 5; - beforeAll(() => { + beforeEach(() => { matrixNode = new EmberLib.MatrixNode(0); matrixNode.contents = new EmberLib.MatrixContents( EmberLib.MatrixType.onetoN, @@ -589,6 +821,13 @@ describe("Ember", () => { matrixNode.contents.targetCount = TARGETCOUNT; matrixNode.contents.sourceCount = SOURCECOUNT; }); + it("should consider default type as 1toN", () => { + matrixNode.connections = null; + matrixNode.contents.type = null; + matrixNode.contents.maximumTotalConnects = 1; + const res = EmberLib.Matrix.canConnect(matrixNode, 0, [0,3]); + expect(res).toBeFalsy(); + }); it("should return false if more than 1 source in 1toN", () => { matrixNode.connections = null; matrixNode.contents.maximumTotalConnects = 1; @@ -606,10 +845,50 @@ describe("Ember", () => { const res = EmberLib.Matrix.canConnect(matrixNode, 0, [0,3]); expect(res).toBeTruthy(); }); + it("should check maximumTotalConnects in NtoN and reject on limit pass", () => { + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.nToN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.maximumConnectsPerTarget = null; + matrixNode.contents.maximumTotalConnects = 2; + EmberLib.Matrix.connectSources(matrixNode, 0, [1,2]); + const res = EmberLib.Matrix.canConnect(matrixNode, 1, [3]); + expect(res).toBeFalsy(); + }); + it("should check maximumTotalConnects in NtoN and accept if below limit", () => { + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.nToN, + EmberLib.MatrixMode.linear + ); + matrixNode.connections = null; + matrixNode.contents.maximumConnectsPerTarget = null; + matrixNode.contents.maximumTotalConnects = 2; + const res = EmberLib.Matrix.canConnect(matrixNode, 1, [3]); + expect(res).toBeTruthy(); + }); + it("should check locked connection", () => { + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.nToN, + EmberLib.MatrixMode.linear + ); + matrixNode.connections = null; + matrixNode.contents.maximumConnectsPerTarget = null; + matrixNode.contents.maximumTotalConnects = 2; + EmberLib.Matrix.connectSources(matrixNode, 0, [1]); + matrixNode.connections[0].lock(); + let res = EmberLib.Matrix.canConnect(matrixNode, 0, [3]); + expect(res).toBeFalsy(); + matrixNode.connections[0].unlock(); + res = EmberLib.Matrix.canConnect(matrixNode, 0, [3]); + expect(res).toBeTruthy(); + }); }); describe("Matrix Non-Linear", () => { it("should have encoder / decoder", () => { + const PATH = "0.1.2"; const matrixNode = new EmberLib.MatrixNode(0); + const qMatrixNode = new EmberLib.QualifiedMatrix(PATH); matrixNode.contents = new EmberLib.MatrixContents( EmberLib.MatrixType.onetoN, EmberLib.MatrixMode.nonLinear @@ -622,12 +901,29 @@ describe("Ember", () => { matrixNode.contents.parametersLocation = "1.2.3"; matrixNode.contents.schemaIdentifiers = "de.l-s-b.emberplus.schema1"; matrixNode.contents.templateReference = "0.1.2.3"; + qMatrixNode.contents = matrixNode.contents; matrixNode.targets = [0,3]; + qMatrixNode.targets = matrixNode.targets; matrixNode.sources = [1,2]; - const writer = new BER.Writer(); + qMatrixNode.sources = matrixNode.sources; + let writer = new BER.Writer(); + matrixNode.encode(writer); + let newMatrixNode = EmberLib.Matrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.targets).toBeDefined(); + + writer = new BER.Writer(); + qMatrixNode.encode(writer); + newMatrixNode = EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.targets).toBeDefined(); + + + // Should support int + matrixNode.contents.parametersLocation = 123; + writer = new BER.Writer(); matrixNode.encode(writer); - const newMatrixNode = EmberLib.Matrix.decode(new BER.Reader(writer.buffer)); + newMatrixNode = EmberLib.Matrix.decode(new BER.Reader(writer.buffer)); expect(newMatrixNode.targets).toBeDefined(); + expect(newMatrixNode.contents.parametersLocation).toBe(matrixNode.contents.parametersLocation); }); it("should have connect function", () => { const root = new EmberLib.Root(); @@ -644,7 +940,7 @@ describe("Ember", () => { const connect = matrixNode.connect({0: new EmberLib.MatrixConnection(0)}); expect(connect).toBeDefined(); }); - it("should through an error if can't decode", () => { + it("should throw an error if can't decode", () => { const writer = new BER.Writer(); writer.startSequence(BER.APPLICATION(13)); writer.startSequence(BER.CONTEXT(0)); @@ -798,7 +1094,7 @@ describe("Ember", () => { const newInvocationRes = EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); expect(newInvocationRes.invocationId == null).toBeTruthy(); }); - it("should through an error if can't decode", () => { + it("should throw an error if can't decode", () => { let writer = new BER.Writer(); writer.startSequence(EmberLib.InvocationResult.BERID); writer.startSequence(BER.CONTEXT(3)); @@ -829,4 +1125,325 @@ describe("Ember", () => { } }); }); + describe("MatrixConnection", () => { + it("should have a decoder and throw error if can't decode", () => { + let writer = new BER.Writer(); + writer.startSequence(EmberLib.MatrixConnection.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.MatrixConnection.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should decode connection with no source", () => { + const matrixConnection = new EmberLib.MatrixConnection(0); + matrixConnection.sources = []; + const writer = new BER.Writer(); + matrixConnection.encode(writer); + const newMC = EmberLib.MatrixConnection.decode(new BER.Reader(writer.buffer)); + expect(newMC.sources).toBeDefined(); + expect(newMC.sources.length).toBe(0); + }); + it("should throw an error if invalid target", () => { + try { + new EmberLib.MatrixConnection("zero"); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + }); + describe("QualifiedFunction", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedFunction.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedFunction.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + }); + describe("QualifiedMatrix", () => { + const PATH = "1.2.3"; + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedMatrix.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("Should have a subscribe/unsubscribe function", () => { + const qMatrixNode = new EmberLib.QualifiedMatrix(PATH); + qMatrixNode.contents = new EmberLib.MatrixContents(); + const cb = function() {}; + let cmd = qMatrixNode.subscribe(cb); + expect(cmd).toBeDefined(); + expect(cmd instanceof EmberLib.Root).toBeTruthy(); + }); + }); + describe("QualifiedNode", () => { + const PATH = "0.1.2"; + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedNode.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedNode.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("Should return true to isNode() call", () => { + const qNode = new EmberLib.QualifiedNode(PATH); + expect(qNode.isNode()).toBeTruthy(); + }); + }); + describe("QualifiedParameter", () => { + const PATH = "0.1.2"; + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedParameter.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedParameter.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should update and ignore key starting with _", () => { + const NEW_VAL = 15; + const qp = new EmberLib.QualifiedParameter(PATH); + qp.contents = new EmberLib.ParameterContents(5, "integer"); + const dup = new EmberLib.QualifiedParameter(PATH); + dup.contents = new EmberLib.ParameterContents(NEW_VAL, "integer"); + dup.contents["_ignore"] = "test"; + qp.update(dup); + expect(qp.contents._ignore).not.toBeDefined(); + expect(qp.contents.value).toBe(NEW_VAL); + }); + + it("Should return true to isParameter() call", () => { + const qNode = new EmberLib.QualifiedParameter(PATH); + expect(qNode.isParameter()).toBeTruthy(); + }); + }); + describe("StreamDescription", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.StreamDescription.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.StreamDescription.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("Should have a toJSON", () => { + const streamDescriptor = new EmberLib.StreamDescription(); + streamDescriptor.format = EmberLib.StreamFormat.signedInt8; + const OFFSET = 4; + streamDescriptor.offset = OFFSET; + + let js = streamDescriptor.toJSON(); + expect(js).toBeDefined(); + expect(js.format).toBeDefined(); + expect(js.offset).toBe(OFFSET); + + streamDescriptor.format = null; + js = streamDescriptor.toJSON(); + expect(js).toBeDefined(); + expect(js.format).toBe(null); + expect(js.offset).toBe(OFFSET); + }); + }); + describe("StringIntegerCollection", () => { + it("should reject invalid value", () => { + const sic = new EmberLib.StringIntegerCollection(); + try { + sic.addEntry("test", 4); + } + catch(e) { + expect(e instanceof Errors.InvalidStringPair).toBeTruthy(); + } + }); + it("should have a toJSON", () => { + const KEY = "test"; + const VAL = 4; + const sic = new EmberLib.StringIntegerCollection(); + sic.addEntry("test", new EmberLib.StringIntegerPair(KEY, VAL)); + const js = sic.toJSON(); + expect(js).toBeDefined(); + expect(js.length).toBe(1); + }); + }); + describe("StringIntegerPair", () => { + it("should throw an error if trying to encode invalid key/value", () => { + const sp = new EmberLib.StringIntegerPair(); + const writer = new BER.Writer(); + try { + sp.encode(writer); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + }); + describe("rootDecode", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + try { + EmberLib.rootDecode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + }); + describe("QualifiedTemplate", () => { + const PATH = "0.1.2"; + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedTemplate.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedTemplate.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should have encoder/decoder", () => { + const qp = new EmberLib.QualifiedTemplate(PATH, new EmberLib.Node(0)); + let writer = new BER.Writer(); + qp.encode(writer); + let dup = EmberLib.QualifiedTemplate.decode(new BER.Reader(writer.buffer)); + expect(dup).toBeDefined(); + expect(dup.getPath()).toBe(PATH); + expect(dup.element instanceof EmberLib.Node).toBeTruthy(); + + const DESCRIPTION = "description"; + qp.description = DESCRIPTION; + writer = new BER.Writer(); + qp.encode(writer); + dup = EmberLib.QualifiedTemplate.decode(new BER.Reader(writer.buffer)); + expect(dup).toBeDefined(); + expect(dup.getPath()).toBe(PATH); + expect(dup.element instanceof EmberLib.Node).toBeTruthy(); + expect(dup.description).toBe(DESCRIPTION); + }); + + it("Should return true to isTemplate() call", () => { + const qp = new EmberLib.QualifiedTemplate(PATH, new EmberLib.Node(0)); + expect(qp.isTemplate()).toBeTruthy(); + }); + }); + describe("Template", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Template.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Template.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should have encoder/decoder", () => { + const qp = new EmberLib.Template(10, new EmberLib.Node(0)); + let writer = new BER.Writer(); + qp.encode(writer); + let dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup).toBeDefined(); + expect(dup.getNumber()).toBe(10); + + const DESCRIPTION = "description"; + qp.description = DESCRIPTION; + writer = new BER.Writer(); + qp.encode(writer); + dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup).toBeDefined(); + expect(dup.element instanceof EmberLib.Node).toBeTruthy(); + expect(dup.description).toBe(DESCRIPTION); + + writer = new BER.Writer(); + qp.element = new EmberLib.Function(0, null); + qp.encode(writer); + dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup.element instanceof EmberLib.Function).toBeTruthy(); + + writer = new BER.Writer(); + qp.element = new EmberLib.Parameter(0); + qp.encode(writer); + dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup.element instanceof EmberLib.Parameter).toBeTruthy(); + + writer = new BER.Writer(); + qp.element = new EmberLib.MatrixNode(0); + qp.encode(writer); + dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup.element instanceof EmberLib.MatrixNode).toBeTruthy(); + + }); + + it("Should return true to isTemplate() call", () => { + const qp = new EmberLib.Template(10, new EmberLib.Node(0)); + expect(qp.isTemplate()).toBeTruthy(); + }); + + it("Should have toQualified function", () => { + const template = new EmberLib.Template(10, new EmberLib.Node(0)); + const qp = template.toQualified(); + expect(qp.isTemplate()).toBeTruthy(); + }); + + it("Should have update function", () => { + const template = new EmberLib.Template(10, new EmberLib.Node(0)); + const DUP_NUM = 5; + const dup = new EmberLib.Template(10, new EmberLib.Node(DUP_NUM)); + template.update(dup); + expect(template.element.getNumber()).toBe(DUP_NUM); + }); + }); }); diff --git a/test/Server.test.js b/test/Server.test.js index 428a341..05f5dc5 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -485,8 +485,8 @@ describe("server", function() { .then(() => client.getNodeByPathnum("0.0.2")) .then(parameter => { expect(server.subscribers["0.0.2"]).not.toBeDefined(); - expect(parameter.contents._subscribers).toBeDefined(); - expect(parameter.contents._subscribers.size).toBe(0); + expect(parameter._subscribers).toBeDefined(); + expect(parameter._subscribers.size).toBe(0); server._subscribe = server.subscribe; let _resolve; const p = new Promise(resolve => { @@ -504,8 +504,8 @@ describe("server", function() { return client.getNodeByPathnum("0.0.2"); }) .then(parameter => { - expect(parameter.contents._subscribers).toBeDefined(); - expect(parameter.contents._subscribers.size).toBe(1); + expect(parameter._subscribers).toBeDefined(); + expect(parameter._subscribers.size).toBe(1); server._unsubscribe = server.unsubscribe; let _resolve; const p = new Promise(resolve => { @@ -524,8 +524,8 @@ describe("server", function() { .then(parameter => { expect(server.subscribers["0.0.2"]).toBeDefined(); expect(server.subscribers["0.0.2"].size).toBe(0); - expect(parameter.contents._subscribers).toBeDefined(); - expect(parameter.contents._subscribers.size).toBe(0); + expect(parameter._subscribers).toBeDefined(); + expect(parameter._subscribers.size).toBe(0); }) .then(() => client.disconnect()); }); From 53646255b7e6d7bc68e975148af4a8d703cbcb86 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 20 Jan 2020 11:34:01 +0100 Subject: [PATCH 138/162] 92% code coverage --- EmberClient/EmberClient.js | 188 ++++---- EmberLib/Command.js | 26 +- EmberLib/Function.js | 9 - EmberLib/Matrix.js | 3 + EmberLib/Parameter.js | 3 +- EmberLib/ParameterContents.js | 96 +++-- EmberLib/QualifiedElement.js | 26 +- EmberLib/QualifiedFunction.js | 25 +- EmberLib/QualifiedMatrix.js | 15 - EmberLib/QualifiedParameter.js | 5 +- EmberLib/TreeNode.js | 136 +++--- EmberLib/index.js | 41 +- EmberServer/ElementHandlers.js | 30 +- EmberServer/EmberServer.js | 71 ++-- EmberServer/ServerEvents.js | 3 +- EmberSocket/S101Socket.js | 31 +- Errors.js | 26 +- package.json | 2 +- test/DeviceTree.test.js | 20 +- test/Ember.test.js | 181 +++++++- test/Server.test.js | 753 +++++++++++++++++++++++++++------ test/utils.js | 4 +- 22 files changed, 1166 insertions(+), 528 deletions(-) diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index 1d148fd..e1426f5 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -2,7 +2,7 @@ const EventEmitter = require('events').EventEmitter; const S101Socket = require('../EmberSocket').S101Socket; const ember = require('../EmberLib'); const BER = require('../ber.js'); -const errors = require('../Errors.js'); +const Errors = require('../Errors.js'); const winston = require("winston"); const DEFAULT_PORT = 9000; @@ -79,6 +79,7 @@ class EmberClient extends EventEmitter { _finishRequest() { this._clearTimeout(); + this._callback = undefined; this._activeRequest = null; try { this._makeRequest(); @@ -95,7 +96,7 @@ class EmberClient extends EventEmitter { if (this._activeRequest == null && this._pendingRequests.length > 0) { this._activeRequest = this._pendingRequests.shift(); const req = `${ this._requestID++} - ${this._activeRequest.node.getPath()}`; - this._activeRequest.timeoutError = new errors.EmberTimeoutError(`Request ${req} timed out`) + this._activeRequest.timeoutError = new Errors.EmberTimeoutError(`Request ${req} timed out`) winston.debug(`Making request ${req}`, Date.now()); this._timeout = setTimeout(() => { @@ -239,12 +240,13 @@ class EmberClient extends EventEmitter { } /** - * + * @returns {Promise} */ disconnect() { if (this._client != null) { return this._client.disconnect(); } + return Promise.resolve(); } /** @@ -255,33 +257,32 @@ class EmberClient extends EventEmitter { */ expand(node, callback = null) { if (node == null) { - return Promise.reject(new errors.InvalidEmberNode("Invalid null node")); + return Promise.reject(new Errors.InvalidEmberNode("Invalid null node")); } if (node.isParameter() || node.isMatrix() || node.isFunction()) { return this.getDirectory(node); } - return this.getDirectory(node, callback).then((res) => { - let children = node.getChildren(); - if ((res === undefined) || (children === undefined) || (children === null)) { + return this.getDirectory(node, callback).then(res => { + const children = node.getChildren(); + if ((res == null) || (children == null)) { winston.debug("No more children for ", node); return; } - let p = Promise.resolve(); + let directChildren = Promise.resolve(); for (let child of children) { if (child.isParameter()) { // Parameter can only have a single child of type Command. continue; } - winston.debug("Expanding child", child); - p = p.then(() => { - return this.expand(child).catch((e) => { + directChildren = directChildren.then(() => { + return this.expand(child, callback).catch((e) => { // We had an error on some expansion // let's save it on the child itthis child.error = e; }); }); } - return p; + return directChildren }); } @@ -291,7 +292,7 @@ class EmberClient extends EventEmitter { * @param {function} callback=null * @returns {Promise} */ - getDirectory(qnode, callback = null) { + getDirectory(qnode, callback = null) { if (qnode == null) { this.root.clear(); qnode = this.root; @@ -300,28 +301,26 @@ class EmberClient extends EventEmitter { this.addRequest({node: qnode, func: error => { if (error) { this._finishRequest(); - reject(error); - return; + return reject(error); } this._callback = (error, node) => { const requestedPath = qnode.getPath(); if (node == null) { winston.debug(`received null response for ${requestedPath}`); + // ignore return; } if (error) { winston.debug("Received getDirectory error", error); - this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); - reject(error); - return; + return reject(error); } if (qnode.isRoot()) { const elements = qnode.getChildren(); if (elements == null || elements.length === 0) { winston.debug("getDirectory response", node); - return this._callback(new errors.InvalidEmberNode()); + return reject(new Errors.InvalidEmberNode()); } const nodeElements = node == null ? null : node.getChildren(); @@ -329,16 +328,14 @@ class EmberClient extends EventEmitter { if (nodeElements != null && nodeElements.every(el => el._parent instanceof ember.Root)) { winston.debug("Received getDirectory response", node); - this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); - resolve(node); // make sure the info is treated before going to next request. + return resolve(node); // make sure the info is treated before going to next request. } else { - return this._callback(new errors.InvalidEmberResponse(`getDirectory ${requestedPath}`)); + return this._callback(new Errors.InvalidEmberResponse(`getDirectory ${requestedPath}`)); } } else if (node.getElementByPath(requestedPath) != null) { - this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); return resolve(node); // make sure the info is treated before going to next request. } @@ -348,7 +345,6 @@ class EmberClient extends EventEmitter { ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { winston.debug("Received getDirectory response", node); - this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); return resolve(node); // make sure the info is treated before going to next request. } @@ -359,106 +355,79 @@ class EmberClient extends EventEmitter { } }; winston.debug("Sending getDirectory", qnode); - this._client.sendBERNode(qnode.getDirectory(callback)); + try { + this._client.sendBERNode(qnode.getDirectory(callback)); + } + catch(e) { + reject(e); + } }}); }); } /** - * @deprecated + * * @param {string} path ie: "path/to/destination" * @param {function} callback=null * @returns {Promise} */ getElementByPath(path, callback=null) { + const pathError = new Errors.PathDiscoveryFailure(path); + const TYPE_NUM = 1; + const TYPE_ID = 2; + let type = TYPE_NUM; + let pathArray = []; if (path.indexOf("/") >= 0) { - return this.getNodeByPath(path, callback); + type = TYPE_ID; + pathArray = path.split("/"); } else { - return this.getNodeByPathnum(path, callback); - } - } - - /** - * @deprecated - * @param {string} path ie: "path/to/destination" - * @param {function} callback=null - * @returns {Promise} - */ - getNodeByPath(path, callback = null) { - if (typeof path === 'string') { - path = path.split('/'); + pathArray = path.split("."); + if (pathArray.length === 1) { + if (isNaN(Number(pathArray[0]))) { + type = TYPE_ID; + } + } } - var pathError = new errors.PathDiscoveryFailure(path.slice(0, pos + 1).join("/")); - var pos = 0; - var lastMissingPos = -1; - var currentNode = this.root; + let pos = 0; + let lastMissingPos = -1; + let currentNode = this.root; const getNext = () => { return Promise.resolve() - .then(() => { - const children = currentNode.getChildren(); - const identifier = path[pos]; - if (children != null) { - for (let i = 0; i < children.length; i++) { - var node = children[i]; - if (node.contents != null && node.contents.identifier === identifier) { - // We have this part already. - pos++; - if (pos >= path.length) { - return node; + .then(() => { + let node; + if (type === TYPE_NUM) { + const number = Number(pathArray[pos]); + node = currentNode.getElementByNumber(number); + } + else { + const children = currentNode.getChildren(); + const identifier = pathArray[pos]; + if (children != null) { + let i = 0; + for (i = 0; i < children.length; i++) { + node = children[i]; + if (node.contents != null && node.contents.identifier === identifier) { + break; } - currentNode = node; - return getNext(); + } + if (i >= children.length) { + node = null; } } } - // We do not have that node yet. - if (lastMissingPos === pos) { - throw pathError; - } - lastMissingPos = pos; - return this.getDirectory(currentNode, callback).then(() => getNext()); - }); - } - return getNext(); - } - - /** - * @deprecated - * @param {string|number[]} path ie: 1.0.2 - * @param {function} callback=null - * @returns {Promise} - */ - getNodeByPathnum(path, callback = null) { - if (typeof path === 'string') { - path = path.split('.'); - } - var pathnumError = new errors.PathDiscoveryFailure(path.slice(0, pos).join("/")); - var pos = 0; - var lastMissingPos = -1; - var currentNode = this.root; - const getNext = () => { - return Promise.resolve() - .then(() => { - const children = currentNode.getChildren(); - const number = Number(path[pos]); - if (children != null) { - for (let i = 0; i < children.length; i++) { - var node = children[i]; - if (node.getNumber() === number) { - // We have this part already. - pos++; - if (pos >= path.length) { - return node; - } - currentNode = node; - return getNext(); - } + if (node != null) { + // We have this part already. + pos++; + if (pos >= pathArray.length) { + return node; } + currentNode = node; + return getNext(); } // We do not have that node yet. if (lastMissingPos === pos) { - throw pathnumError; + throw pathError; } lastMissingPos = pos; return this.getDirectory(currentNode, callback).then(() => getNext()); @@ -466,7 +435,7 @@ class EmberClient extends EventEmitter { } return getNext(); } - + /** * * @param {TreeNode} fnNode @@ -516,7 +485,7 @@ class EmberClient extends EventEmitter { matrixOPeration(matrixNode, targetID, sources, operation = ember.MatrixOperation.connect) { return new Promise((resolve, reject) => { if (!Array.isArray(sources)) { - return reject(new errors.InvalidSourcesFormat()); + return reject(new Errors.InvalidSourcesFormat()); } try { matrixNode.validateConnection(targetID, sources); @@ -617,12 +586,10 @@ class EmberClient extends EventEmitter { */ setValue(node, value) { return new Promise((resolve, reject) => { - if ((!(node instanceof ember.Parameter)) && - (!(node instanceof ember.QualifiedParameter))) { - reject(new errors.EmberAccessError('not a property')); + if (!node.isParameter()) { + reject(new Errors.EmberAccessError('not a Parameter')); } else { - // if (this._debug) { console.log('setValue', node.getPath(), value); } this.addRequest({node: node, func: error => { if (error) { this._finishRequest(); @@ -630,19 +597,18 @@ class EmberClient extends EventEmitter { return; } - let cb = (error, node) => { - this._clearTimeout(); + this._callback = (error, node) => { this._finishRequest(); + this._callback = null; if (error) { reject(error); } else { + resolve(node); } }; - - this._callback = cb; - winston.debug('setValue sending ...', node.getPath(), value); + winston.debug('setValue sending ...', node.getPath(), value); this._client.sendBERNode(node.setValue(value)); }}); } diff --git a/EmberLib/Command.js b/EmberLib/Command.js index a3fab00..73bf653 100755 --- a/EmberLib/Command.js +++ b/EmberLib/Command.js @@ -4,6 +4,7 @@ const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); const BER = require('../ber.js'); const Invocation = require("./Invocation"); const errors = require("../Errors"); +const ElementInterface = require("./ElementInterface"); const FieldFlags = new Enum({ sparse: -2, @@ -16,12 +17,13 @@ const FieldFlags = new Enum({ connections: 5 }); -class Command { +class Command extends ElementInterface{ /** * * @param {number} number */ constructor(number) { + super(); this.number = number; if(number == COMMAND_GETDIRECTORY) { this.fieldFlags = FieldFlags.all; @@ -109,6 +111,28 @@ class Command { return c; } + + /** + * + * @param {number} cmd + * @param {string} key + * @param {string|value|object} value + */ + static getCommand(cmd, key, value) { + const command = new Command(cmd); + if (key != null) { + command[key] = value; + } + return command; + } + + /** + * + * @param {Invocation} invocation + */ + static getInvocationCommand(invocation) { + return this.getCommand(COMMAND_INVOKE, "invocation", invocation); + } /** * @returns {number} diff --git a/EmberLib/Function.js b/EmberLib/Function.js index 8611c31..54c777b 100755 --- a/EmberLib/Function.js +++ b/EmberLib/Function.js @@ -22,15 +22,6 @@ class Function extends Element { return true; } - /** - * @returns {Root} - */ - invoke() { - return this.getTreeBranch(undefined, (m) => { - m.addChild(new Command(COMMAND_INVOKE)) - }); - } - /** * @returns {QualifiedFunction} */ diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js index 9add4f8..9f8ac2f 100755 --- a/EmberLib/Matrix.js +++ b/EmberLib/Matrix.js @@ -409,6 +409,9 @@ class Matrix extends TreeNode if (targetID < 0) { throw new Errors.InvalidMatrixSignal(targetID, "target"); } + if (sources == null) { + throw new Errors.InvalidSourcesFormat(); + } for(let i = 0; i < sources.length; i++) { if (sources[i] < 0) { throw new Errors.InvalidMatrixSignal(sources[i], `Source at index ${i}`); diff --git a/EmberLib/Parameter.js b/EmberLib/Parameter.js index 5e0dfd9..a7a07c8 100755 --- a/EmberLib/Parameter.js +++ b/EmberLib/Parameter.js @@ -25,7 +25,8 @@ class Parameter extends Element { } /** - * + * Generate a Root of a partial tree containing the Parameter and its new value. + * Should be sent to the Provider to update the value. * @param {string|number} value * @returns {Root} */ diff --git a/EmberLib/ParameterContents.js b/EmberLib/ParameterContents.js index 4846b99..07ad48a 100755 --- a/EmberLib/ParameterContents.js +++ b/EmberLib/ParameterContents.js @@ -76,45 +76,63 @@ class ParameterContents { while(ber.remain > 0) { let tag = ber.peek(); let seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - pc.identifier = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - pc.description = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(2)) { - pc.value = seq.readValue(); - } else if(tag == BER.CONTEXT(3)) { - pc.minimum = seq.readValue(); - } else if(tag == BER.CONTEXT(4)) { - pc.maximum = seq.readValue(); - } else if(tag == BER.CONTEXT(5)) { - pc.access = ParameterAccess.get(seq.readInt()); - } else if(tag == BER.CONTEXT(6)) { - pc.format = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(7)) { - pc.enumeration = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(8)) { - pc.factor = seq.readInt(); - } else if(tag == BER.CONTEXT(9)) { - pc.isOnline = seq.readBoolean(); - } else if(tag == BER.CONTEXT(10)) { - pc.formula = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(11)) { - pc.step = seq.readInt(); - } else if(tag == BER.CONTEXT(12)) { - pc.default = seq.readValue(); - } else if(tag == BER.CONTEXT(13)) { - pc.type = ParameterType.get(seq.readInt()); - } else if(tag == BER.CONTEXT(14)) { - pc.streamIdentifier = seq.readInt(); - } else if(tag == BER.CONTEXT(15)) { - pc.enumMap = StringIntegerCollection.decode(seq); - } else if(tag == BER.CONTEXT(16)) { - pc.streamDescriptor = StreamDescription.decode(seq); - } else if(tag == BER.CONTEXT(17)) { - pc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); - } - else { - throw new Errors.UnimplementedEmberTypeError(tag); + switch(tag) { + case BER.CONTEXT(0): + pc.identifier = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(1): + pc.description = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(2): + pc.value = seq.readValue(); + break; + case BER.CONTEXT(3): + pc.minimum = seq.readValue(); + break; + case BER.CONTEXT(4): + pc.maximum = seq.readValue(); + break; + case BER.CONTEXT(5): + pc.access = ParameterAccess.get(seq.readInt()); + break; + case BER.CONTEXT(6): + pc.format = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(7): + pc.enumeration = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(8): + pc.factor = seq.readInt(); + break; + case BER.CONTEXT(9): + pc.isOnline = seq.readBoolean(); + break; + case BER.CONTEXT(10): + pc.formula = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(11): + pc.step = seq.readInt(); + break; + case BER.CONTEXT(12): + pc.default = seq.readValue(); + break; + case BER.CONTEXT(13): + pc.type = ParameterType.get(seq.readInt()); + break; + case BER.CONTEXT(14): + pc.streamIdentifier = seq.readInt(); + break; + case BER.CONTEXT(15): + pc.enumMap = StringIntegerCollection.decode(seq); + break; + case BER.CONTEXT(16): + pc.streamDescriptor = StreamDescription.decode(seq); + break; + case BER.CONTEXT(17): + pc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); + break; + default: + throw new Errors.UnimplementedEmberTypeError(tag); } } return pc; diff --git a/EmberLib/QualifiedElement.js b/EmberLib/QualifiedElement.js index b233375..98e45f4 100755 --- a/EmberLib/QualifiedElement.js +++ b/EmberLib/QualifiedElement.js @@ -2,7 +2,7 @@ const TreeNode = require("./TreeNode"); const BER = require('../ber.js'); const Command = require("./Command"); -const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); +const {COMMAND_GETDIRECTORY} = require("./constants"); class QualifiedElement extends TreeNode { /** @@ -43,35 +43,17 @@ class QualifiedElement extends TreeNode { /** * - * @param {number} cmd - * @param {string} key - * @param {string} value + * @param {Command} cmd * @returns {TreeNode} */ - getCommand(cmd, key, value) { + getCommand(cmd) { const r = this.getNewTree(); const qn = new this.constructor(); qn.path = this.getPath(); r.addElement(qn); - const command = new Command(cmd); - if (key != null) { - command[key] = value; - } - qn.addChild(command); + qn.addChild(cmd); return r; } - - /** - * - * @param {function} callback - * @returns {TreeNode} - */ - getDirectory(callback) { - if (callback != null && !this.isStream()) { - this.contents._subscribers.add(callback); - } - return this.getCommand(COMMAND_GETDIRECTORY); - } } module.exports = QualifiedElement; \ No newline at end of file diff --git a/EmberLib/QualifiedFunction.js b/EmberLib/QualifiedFunction.js index 7a4d017..8e4ac84 100755 --- a/EmberLib/QualifiedFunction.js +++ b/EmberLib/QualifiedFunction.js @@ -2,9 +2,9 @@ const QualifiedElement = require("./QualifiedElement"); const FunctionContent = require("./FunctionContent"); -const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); +const {COMMAND_GETDIRECTORY} = require("./constants"); const BER = require('../ber.js'); -const Invocation = require("./Invocation"); +const Command = require("./Command"); const Errors = require("../Errors"); class QualifiedFunction extends QualifiedElement { @@ -19,14 +19,6 @@ class QualifiedFunction extends QualifiedElement { this._seqID = QualifiedFunction.BERID; } - /** - * - * @returns {TreeNode} - */ - getDirectory() { - return this.getCommand(COMMAND_GETDIRECTORY); - } - /** * @returns {boolean} */ @@ -34,19 +26,6 @@ class QualifiedFunction extends QualifiedElement { return true; } - /** - * - * @param {*} params - */ - invoke(params) { - const invocation = new Invocation(Invocation.newInvocationID()); - invocation.arguments = params; - const qualifiedFunctionNode = this.getCommand(COMMAND_INVOKE, "invocation", invocation); - //qualifiedFunctionNode.getElementByNumber(this.getNumber()).getElementByNumber(COMMAND_INVOKE).invocation = invocation - return qualifiedFunctionNode; - } - - /** * * @param {BER} ber diff --git a/EmberLib/QualifiedMatrix.js b/EmberLib/QualifiedMatrix.js index ab4e8e4..3d4f890 100755 --- a/EmberLib/QualifiedMatrix.js +++ b/EmberLib/QualifiedMatrix.js @@ -57,21 +57,6 @@ class QualifiedMatrix extends Matrix { ber.endSequence(); // BER.APPLICATION(3) } - /** - * - * @param {number} cmd - * @returns {TreeNode} - */ - getCommand(cmd) { - const r = this.getNewTree(); - const qn = new QualifiedMatrix(); - qn.path = this.getPath(); - r.addElement(qn); - qn.addChild(new Command(cmd)); - return r; - } - - /** * * @param {BER} ber diff --git a/EmberLib/QualifiedParameter.js b/EmberLib/QualifiedParameter.js index 9a14095..3871823 100755 --- a/EmberLib/QualifiedParameter.js +++ b/EmberLib/QualifiedParameter.js @@ -23,10 +23,11 @@ class QualifiedParameter extends QualifiedElement { } /** - * + * Generate a Root containing a minimal QualifiedParameter and its new value. + * Should be sent to the Provider to update the value. * @param {number|string} value * @returns {TreeNode} - */ + */ setValue(value) { let r = this.getNewTree(); let qp = new QualifiedParameter(this.path); diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index a7344b1..2cb7919 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -1,10 +1,14 @@ "use strict"; const BER = require('../ber.js'); +const ElementInterface = require("./ElementInterface"); +const Invocation = require("./Invocation"); const Command = require("./Command"); -const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); +const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE, COMMAND_INVOKE} = require("./constants"); +const Errors = require("../Errors"); -class TreeNode { +class TreeNode extends ElementInterface { constructor() { + super(); /** @type {TreeNode} */ this._parent = null; this._subscribers = new Set(); @@ -144,48 +148,13 @@ class TreeNode { return this.elements != null && this.elements.size > 0; } - /** - * @returns {boolean} - */ - isCommand() { - return false; - } - /** - * @returns {boolean} - */ - isNode() { - return false; - } - /** - * @returns {boolean} - */ - isMatrix() { - return false; - } - /** - * @returns {boolean} - */ - isParameter() { - return false; - } - /** - * @returns {boolean} - */ - isFunction() { - return false; - } /** * @returns {boolean} */ isRoot() { return this._parent == null; } - /** - * @returns {boolean} - */ - isQualified() { - return false; - } + /** * @returns {boolean} */ @@ -194,13 +163,6 @@ class TreeNode { this.contents.streamIdentifier != null; } - /** - * @returns {boolean} - */ - isTemplate() { - return false; - } - /** * @returns {TreeNode} */ @@ -261,8 +223,12 @@ class TreeNode { } } + /** + * + * @param {Command} cmd + */ getCommand(cmd) { - return this.getTreeBranch(new Command(cmd)); + return this.getTreeBranch(cmd); } /** @@ -273,7 +239,7 @@ class TreeNode { if (this._isSubscribable(callback)) { this._subscribe(callback); } - return this.getCommand(COMMAND_GETDIRECTORY); + return this.getCommand(new Command(COMMAND_GETDIRECTORY)); } @@ -314,7 +280,14 @@ class TreeNode { getElementByPath(path) { if (this.elements == null || this.elements.size === 0) { return null; - } + } + if (this.isRoot()) { + // check if we have QualifiedElement + const node = this.elements.get(path); + if (node != null) { + return node; + } + } const myPath = this.getPath(); if (path == myPath) { return this; @@ -322,7 +295,7 @@ class TreeNode { const myPathArray = this.isRoot() ? [] : myPath.split("."); let pathArray = path.split("."); - if (pathArray.length <= myPathArray.length) { + if (pathArray.length < myPathArray.length) { // We are lower in the tree than the requested path return null; } @@ -369,7 +342,7 @@ class TreeNode { getElementByIdentifier(identifier) { const children = this.getChildren(); if (children == null) return null; - for(var i = 0; i < children.length; i++) { + for(let i = 0; i < children.length; i++) { if(children[i].contents != null && children[i].contents.identifier == identifier) { return children[i]; @@ -390,34 +363,7 @@ class TreeNode { return this.getElementByIdentifier(id); } } - - getNodeByPath(client, path, callback) { - if(path.length === 0) { - callback(null, this); - return; - } - - let child = this.getElement(path[0]); - if(child !== null) { - child.getNodeByPath(client, path.slice(1), callback); - } else { - const cmd = this.getDirectory((error, node) => { - if(error) { - callback(error); - } - child = node.getElement(path[0]); - if(child === null) { - //DO NOT REJECT !!!! We could still be updating the tree. - return; - } else { - child.getNodeByPath(client, path.slice(1), callback); - } - }); - if(cmd !== null) { - client.sendBERNode(cmd); - } - } - } + /** * @returns {string} @@ -442,6 +388,21 @@ class TreeNode { } } + /** + * + * @param {FunctionArgument[]} params + * @returns {TreeNode} + */ + invoke(params) { + if (!this.isFunction()) { + throw new Errors.InvalidEmberNode(this.getPath(), "Invoke only for Ember Function"); + } + const invocation = new Invocation(Invocation.newInvocationID()); + invocation.arguments = params; + const req = this.getCommand(Command.getInvocationCommand(invocation)); + return req; + } + /** * */ @@ -513,7 +474,7 @@ class TreeNode { if (this._isSubscribable(callback)) { this._subscribe(callback); } - return this.getCommand(COMMAND_SUBSCRIBE); + return this.getCommand(new Command(COMMAND_SUBSCRIBE)); } /** @@ -522,7 +483,7 @@ class TreeNode { */ unsubscribe(callback) { this._unsubscribe(callback); - return this.getCommand(COMMAND_UNSUBSCRIBE); + return this.getCommand(new Command(COMMAND_UNSUBSCRIBE)); } /** @@ -564,12 +525,25 @@ class TreeNode { * @param {TreeNode} element */ static addElement(parent, element) { + /* + Store element hashed by number direct to the parent. + But if QualifiedElement, it could be directly attached to the root. + In this case, use the path instead of number. + However, if the path is a single number, it is equivalent to number. + */ element._parent = parent; if(parent.elements == null) { parent.elements = new Map(); } + if (parent.isRoot() && element.isQualified()) { + const path = element.getPath().split("."); + if (path.length > 1) { + parent.elements.set(element.getPath(), element); + return; + } + } parent.elements.set(element.getNumber(), element); - } + } static path2number(path) { try { diff --git a/EmberLib/index.js b/EmberLib/index.js index f890109..d3f10f8 100755 --- a/EmberLib/index.js +++ b/EmberLib/index.js @@ -85,33 +85,28 @@ const rootDecode = function(ber) { return r; } +const TreeNodeDecoders = { + [Parameter.BERID]: Parameter.decode, + [Node.BERID]: Node.decode, + [Command.BERID]: Command.decode, + [QualifiedParameter.BERID]: QualifiedParameter.decode, + [QualifiedNode.BERID]: QualifiedNode.decode, + [MatrixNode.BERID]: MatrixNode.decode, + [QualifiedMatrix.BERID]: QualifiedMatrix.decode, + [Function.BERID]: Function.decode, + [QualifiedFunction.BERID]: QualifiedFunction.decode, + [Template.BERID]: Template.decode, + [QualifiedTemplate.BERID]: QualifiedTemplate +}; + const childDecode = function(ber) { const tag = ber.peek(); - if (tag == Parameter.BERID) { - return Parameter.decode(ber); - } else if(tag == Node.BERID) { - return Node.decode(ber); - } else if(tag == Command.BERID) { - return Command.decode(ber); - } else if(tag == QualifiedParameter.BERID) { - return QualifiedParameter.decode(ber); - } else if(tag == QualifiedNode.BERID) { - return QualifiedNode.decode(ber); - } else if(tag == MatrixNode.BERID) { - return MatrixNode.decode(ber); - } else if(tag == QualifiedMatrix.BERID) { - return QualifiedMatrix.decode(ber); - } else if(tag == Function.BERID) { - return Function.decode(ber); - } else if (tag == QualifiedFunction.BERID) { - return QualifiedFunction.decode(ber); - } else if(tag == Template.BERID) { - return Template.decode(ber); - } else if (tag == QualifiedTemplate.BERID) { - return QualifiedTemplate.decode(ber) + const decode = TreeNodeDecoders[tag]; + if (decode == null) { + throw new Errors.UnimplementedEmberTypeError(tag); } else { - throw new Errors.UnimplementedEmberTypeError(tag); + return decode(ber); } } diff --git a/EmberServer/ElementHandlers.js b/EmberServer/ElementHandlers.js index ece5ce0..e48d60f 100755 --- a/EmberServer/ElementHandlers.js +++ b/EmberServer/ElementHandlers.js @@ -39,7 +39,7 @@ class ElementHandlers extends QualifiedHandlers{ this.server.emit("event", ServerEvents.UNSUBSCRIBE(identifier, element.getPath(), src)); this.handleUnSubscribe(client, element); break; - case EmberLib.COMMAND_INVOKE: + case EmberLib.COMMAND_INVOKE: this.server.emit("event", ServerEvents.INVOKE(identifier, element.getPath(), src)); this.handleInvoke(client, cmd.invocation, element); break; @@ -118,39 +118,40 @@ class ElementHandlers extends QualifiedHandlers{ let element = node; let path = []; while(element != null) { + if (element.isCommand()) { + break; + } if (element.number == null) { this.server.emit("error", new Errors.MissingElementNumber()); return; } - if (element.isCommand()) { - break; - } + path.push(element.number); - let children = element.getChildren(); + const children = element.getChildren(); if ((! children) || (children.length === 0)) { break; } - element = element.children[0]; + element = children[0]; } let cmd = element; if (cmd == null) { this.server.emit("error", new Errors.InvalidRequest()); - return this.server.handleError(client); - } + this.server.handleError(client); + return path; + } element = this.server.tree.getElementByPath(path.join(".")); - + if (element == null) { this.server.emit("error", new Errors.UnknownElement(path.join("."))); return this.server.handleError(client); } - if (cmd.isCommand()) { this.handleCommand(client, element, cmd); - } - else if ((cmd.isCommand()) && (cmd.connections != null)) { + return path; + } else if ((cmd.isMatrix()) && (cmd.connections != null)) { this.handleMatrixConnections(client, element, cmd.connections); } else if ((cmd.isParameter()) && @@ -166,6 +167,7 @@ class ElementHandlers extends QualifiedHandlers{ winston.debug("invalid request format"); return this.server.handleError(client, element.getTreeBranch()); } + // for logging purpose, return the path. return path; } @@ -175,7 +177,7 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} root */ handleSubscribe(client, element) { - winston.debug("subscribe"); + winston.debug("subscribe", element); this.server.subscribe(client, element); } @@ -185,7 +187,7 @@ class ElementHandlers extends QualifiedHandlers{ * @param {TreeNode} root */ handleUnSubscribe(client, element) { - winston.debug("unsubscribe"); + winston.debug("unsubscribe", element); this.server.unsubscribe(client, element); } } diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 99f74bf..8f1997f 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -1,6 +1,6 @@ const EventEmitter = require('events').EventEmitter; const S101Server = require('../EmberSocket').S101Server; -const ember = require('../EmberLib'); +const EmberLib = require('../EmberLib'); const JSONParser = require("./JSONParser"); const ElementHandlers = require("./ElementHandlers"); const ServerEvents = require("./ServerEvents"); @@ -79,20 +79,26 @@ class TreeServer extends EventEmitter{ */ close() { return new Promise((resolve, reject) => { - this.callback = (e) => { + const cb = e => { if (e == null) { return resolve(); } return reject(e); }; - this.server.server.close(); + if (this.server.server != null) { + this.server.server.close(cb); + } + else { + cb(); + } this.clients.clear(); }); } /** * - * @param {TreeNode} element + * @param {TreeNode} element + * @returns {TreeNode} */ getResponse(element) { return element.getTreeBranch(undefined, node => { @@ -103,9 +109,6 @@ class TreeServer extends EventEmitter{ node.addChild(children[i].getDuplicate()); } } - else { - winston.debug("getResponse","no children"); - } }); } @@ -114,7 +117,7 @@ class TreeServer extends EventEmitter{ * @param {TreeNode} element */ getQualifiedResponse(element) { - const res = new ember.Root(); + const res = new EmberLib.Root(); let dup; if (element.isRoot() === false) { dup = element.toQualified(); @@ -175,7 +178,8 @@ class TreeServer extends EventEmitter{ */ listen() { return new Promise((resolve, reject) => { - this.callback = (e) => { + this.callback = e => { + this.callback = null; if (e == null) { return resolve(); } @@ -192,7 +196,7 @@ class TreeServer extends EventEmitter{ * @param {number[]} sources */ matrixConnect(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.connect); + doMatrixOperation(this, path, target, sources, EmberLib.MatrixOperation.connect); } /** @@ -202,7 +206,7 @@ class TreeServer extends EventEmitter{ * @param {number[]} sources */ matrixDisconnect(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.disconnect); + doMatrixOperation(this, path, target, sources, EmberLib.MatrixOperation.disconnect); } /** @@ -212,7 +216,7 @@ class TreeServer extends EventEmitter{ * @param {number[]} sources */ matrixSet(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.absolute); + doMatrixOperation(this, path, target, sources, EmberLib.MatrixOperation.absolute); } /** @@ -220,22 +224,21 @@ class TreeServer extends EventEmitter{ * @param {TreeNode} element */ replaceElement(element) { - let path = element.getPath(); - let parent = this.tree.getElementByPath(path); - if ((parent == null)||(parent._parent == null)) { + const path = element.getPath(); + const existingElement = this.tree.getElementByPath(path); + if (existingElement == null) { throw new Errors.UnknownElement(path); } - parent = parent._parent; - const children = parent.getChildren(); - for(let i = 0; i <= children.length; i++) { - if (children[i] && children[i].getPath() == path) { - element._parent = parent; // move it to new tree. - children[i] = element; - let res = this.getResponse(element); - this.updateSubscribers(path,res); - return; - } + const parent = existingElement._parent; + if (parent == null) { + throw new Errors.InvalidEmberNode(path, "No parent. Can't execute replaceElement"); } + // Replace the element at the parent + parent.elements.set(existingElement.getNumber(), element); + // point the new element to parent + element._parent = parent; + const res = this.getResponse(element); + this.updateSubscribers(path,res); } /** @@ -341,7 +344,7 @@ class TreeServer extends EventEmitter{ * @returns {TreeNode} */ static JSONtoTree(obj) { - const tree = new ember.Root(); + const tree = new EmberLib.Root(); JSONParser.parseObj(tree, obj); return tree; } @@ -355,26 +358,18 @@ const validateMatrixOperation = function(matrix, target, sources) { if (matrix.contents == null) { throw new Errors.MissingElementContents(matrix.getPath()); } - if (matrix.contents.targetCount == null) { - throw new Errors.InvalidEmberNode(matrix.getPath(), "no targetCount"); - } - if ((target < 0) || (target >= matrix.contents.targetCount)) { - throw new Errors.InvalidEmberNode(matrix.getPath(), `target id ${target} out of range 0 - ${matrix.contents.targetCount}`); - } - if (sources.length == null) { - throw new Errors.InvalidSourcesFormat(); - } + matrix.validateConnection(target, sources); } const doMatrixOperation = function(server, path, target, sources, operation) { - let matrix = server.tree.getElementByPath(path); + const matrix = server.tree.getElementByPath(path); validateMatrixOperation(matrix, target, sources); - let connection = new ember.MatrixConnection(target); + const connection = new EmberLib.MatrixConnection(target); connection.sources = sources; connection.operation = operation; - server.handleMatrixConnections(undefined, matrix, [connection], false); + server._handlers.handleMatrixConnections(undefined, matrix, [connection], false); } module.exports = TreeServer; diff --git a/EmberServer/ServerEvents.js b/EmberServer/ServerEvents.js index 6e95910..62b19a5 100755 --- a/EmberServer/ServerEvents.js +++ b/EmberServer/ServerEvents.js @@ -113,8 +113,9 @@ class ServerEvents { * @param {number[]} sources */ static MATRIX_CONNECTION(identifier, path, src, target, sources) { + const sourcesInfo = sources == null || sources.length === 0 ? "empty" : sources.toString(); return new ServerEvents( - `Matrix connection to ${identifier}(path: ${path}) target ${target} connections: ${sources.toString()} from ${src}`, + `Matrix connection to ${identifier}(path: ${path}) target ${target} connections: ${sourcesInfo} from ${src}`, Types.MATRIX_CONNECTION ); } diff --git a/EmberSocket/S101Socket.js b/EmberSocket/S101Socket.js index dfab372..22307f7 100755 --- a/EmberSocket/S101Socket.js +++ b/EmberSocket/S101Socket.js @@ -88,21 +88,34 @@ class S101Socket extends EventEmitter{ } /** - * + * @param {number} timeout=2 */ - disconnect() { + disconnect(timeout = 2) { if (!this.isConnected()) { return Promise.resolve(); } return new Promise((resolve, reject) => { - this.socket.once('close', () => { - this.codec = null; - this.socket = null; - resolve(); - }); - this.socket.once('error', reject); clearInterval(this.keepaliveIntervalTimer); - this.socket.end(); + let done = false; + const cb = (data, error) => { + if (done) { return; } + done = true; + if (timer != null) { + clearTimeout(timer); + timer = null; + } + if (error == null) { + resolve(); + } + else { + reject(error); + } + }; + let timer; + if (timeout != null && (!isNaN(timeout)) && timeout > 0) { + timer = setTimeout(cb, 100 * timeout); + } + this.socket.end(cb); this.status = "disconnected"; } ); diff --git a/Errors.js b/Errors.js index 89c85ad..01273d6 100755 --- a/Errors.js +++ b/Errors.js @@ -143,7 +143,19 @@ class PathDiscoveryFailure extends Error { * @param {string} path */ constructor(path) { - super(`Failed path discovery at ${path}`); + super(PathDiscoveryFailure.getMessage(path)); + } + + /** + * + * @param {string} path + */ + setPath(path) { + this.message = PathDiscoveryFailure.getMessage(path); + } + + static getMessage(path) { + return `Failed path discovery at ${path}`; } } module.exports.PathDiscoveryFailure = PathDiscoveryFailure; @@ -197,4 +209,14 @@ class InvalidStringPair extends Error { super("Invalid StringPair Value"); } } -module.exports.InvalidStringPair = InvalidStringPair; \ No newline at end of file +module.exports.InvalidStringPair = InvalidStringPair; + +class InvalidRequesrFormat extends Error { + /** + * @param {string} path + */ + constructor(path) { + super(`Can't process request for node ${path}`); + } +} +module.exports.InvalidRequesrFormat = InvalidRequesrFormat; \ No newline at end of file diff --git a/package.json b/package.json index 5832de4..8439bf6 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.4.0", + "version": "2.4.1", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index 1c91f72..a1c645e 100755 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -63,25 +63,23 @@ describe("EmberClient", () => { tree.on("error", () => { }); const expectedTimeoutInSec = 2; - const initialTime = performance.now(); + const initialTime = Date.now(); return tree.connect(expectedTimeoutInSec) .then(() => { throw new Error("Should have thrown"); }, error => { - const durationMs = performance.now() - initialTime; + const durationMs = Date.now() - initialTime; const deltaMs = Math.abs(expectedTimeoutInSec * 1000 - durationMs); expect(deltaMs).toBeLessThan(10); expect(error.message).toBe(`Could not connect to ${UNKNOWN_HOST}:${PORT} after a timeout of ${expectedTimeoutInSec} seconds`) }); - }); - - + }); it("should gracefully connect and getDirectory", () => { let tree = new EmberClient(LOCALHOST, PORT); - tree.on("error", e => { - console.log(e); - }) + tree.on("error", () => { + // ignore + }); let stub = sinon.stub(tree._client, "sendBER"); tree._debug = true; server._debug = true; @@ -95,10 +93,10 @@ describe("EmberClient", () => { .then(() => { stub.restore(); tree.disconnect(); - }, error => { + }, () => { stub.restore(); - tree.disconnect(); - console.log(error); + tree.disconnect(); + // do nothinf }); }, 10000); }); diff --git a/test/Ember.test.js b/test/Ember.test.js index 4195c7e..f96b136 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -34,6 +34,146 @@ describe("Ember", () => { var ber = new BER.Reader(errorBuffer); expect(() => ember.Root.decode(ber)).toThrow(Errors.UnimplementedEmberTypeError); }); + it("Should have a toJSON()", () => { + const node = new EmberLib.Node(); + node.addChild(new EmberLib.Node(0)); + node.getElementByNumber(0).addChild(new EmberLib.Parameter(1)); + const matrix = new EmberLib.MatrixNode(2); + matrix.targets = [0,3,6,7]; + matrix.sources = [2,6,8]; + matrix.contents = new EmberLib.MatrixContents(EmberLib.MatrixType.oneToN, EmberLib.MatrixMode.nonLinear); + node.getElementByNumber(0).addChild(matrix); + const js = node.toJSON(); + expect(js).toBeDefined(); + expect(js.elements.length).toBe(1); + expect(js.elements[0].number).toBe(0); + expect(js.elements[0].children[0].number).toBe(1); + expect(js.elements[0].children[1].number).toBe(2); + expect(js.elements[0].children[1].targets.length).toBe(matrix.targets.length); + }); + it("should have a getElement()", () => { + const node = new EmberLib.Node(); + node.addChild(new EmberLib.Node(0)); + let res = node.getElement(0); + expect(res).toBeDefined(); + }); + it("should have a isCommand(), isRoot() ... functions", () => { + const root = new EmberLib.Root(); + const node = new EmberLib.Node(); + root.addElement(node); + expect(node.isCommand()).toBeFalsy(); + expect(node.isRoot()).toBeFalsy(); + expect(node.isStream()).toBeFalsy(); + expect(node.isTemplate()).toBeFalsy(); + }); + it("should have function getElement", () => { + const node = new EmberLib.Node(0); + const identifier = "node_identifier"; + const description = "node_description"; + node.contents = new EmberLib.NodeContents(identifier, description); + const root = new EmberLib.Root(); + root.addElement(node); + let res = root.getElement(identifier); + expect(res).toBeDefined(); + expect(res.contents.identifier).toBe(identifier); + }); + + it("should throw error if function getElement called from a node with longer parh", () => { + const root = new EmberLib.Root(); + root.addChild(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(1)); + root.getElementByPath("0.1").addChild(new EmberLib.Node(1)); + const node = new EmberLib.Node(0); + root.getElementByPath("0.1.1").addChild(node); + const identifier = "node_identifier"; + const description = "node_description"; + node.contents = new EmberLib.NodeContents(identifier, description); + let res = root.getElementByPath("0.1").getElementByPath("0"); + expect(res).toBe(null); + + res = root.getElementByPath("0.1").getElementByPath("0.2.0"); + expect(res).toBe(null); + + res = root.getElementByPath("0.1").getElementByPath("0.1"); + expect(res).toBeDefined(); + }); + it("should have a getRoot function", () => { + const root = new EmberLib.Root(); + root.addChild(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(1)); + root.getElementByPath("0.1").addChild(new EmberLib.Node(1)); + const node = new EmberLib.Node(0); + root.getElementByPath("0.1.1").addChild(node); + let res = node.getRoot(); + expect(res).toBe(root); + }); + it("should have a getDirectory() and accept a callback for subscribers", () => { + const parameter = new EmberLib.Parameter(0); + parameter.contents = new EmberLib.ParameterContents(7, "integer"); + parameter.contents.streamIdentifier = 12345; + let res = parameter.getDirectory(0, () => {}); + expect(res).toBeDefined(); + expect(parameter._subscribers.size).toBe(1); + }); + it("should have a getDuplicate function", () => { + const parameter = new EmberLib.Parameter(0); + parameter.contents = new EmberLib.ParameterContents("test", "string"); + let res = parameter.getDuplicate(); + expect(res).toBeDefined(); + + const qp = new EmberLib.QualifiedParameter("0.1"); + qp.contents = parameter.contents; + res = qp.getDuplicate(); + expect(res).toBeDefined(); + }); + it("should decode continuation messages", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.CONTEXT(0)); + const qp = new EmberLib.QualifiedParameter("0.1"); + qp.encode(writer); + writer.endSequence(); + const res = EmberLib.rootDecode(new BER.Reader(writer.buffer)); + expect(res).toBeDefined(); + expect(res.getElementByPath("0.1")).toBeDefined(); + }); + it("should throw an error if not able to decode root", () => { + let writer = new BER.Writer(); + writer.startSequence(BER.CONTEXT(0)); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.rootDecode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(error){ + expect(error instanceof Errors.UnimplementedEmberTypeError); + } + + writer = new BER.Writer(); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + try { + EmberLib.rootDecode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(error){ + expect(error instanceof Errors.UnimplementedEmberTypeError); + } + + writer = new BER.Writer(); + writer.startSequence(BER.APPLICATION(0)); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.rootDecode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(error){ + expect(error instanceof Errors.UnimplementedEmberTypeError); + } + }); }); describe("Command", () => { it("should throw error if unknown context found", () => { @@ -81,6 +221,20 @@ describe("Ember", () => { expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); } }); + it("should have a getElementByIdentifier", () => { + const node = new EmberLib.Node(0); + const identifier = "node_identifier"; + const description = "node_description"; + node.contents = new EmberLib.NodeContents(identifier, description); + const root = new EmberLib.Root(); + root.addElement(node); + let res = root.getElementByIdentifier(identifier); + expect(res).toBeDefined(); + expect(res.contents.identifier).toBe(identifier); + + res = root.getElementByIdentifier("unknown"); + expect(res).toBe(null); + }); }); describe("Node", () => { it("should have an encoder", () => { @@ -1248,11 +1402,36 @@ describe("Ember", () => { expect(qp.contents._ignore).not.toBeDefined(); expect(qp.contents.value).toBe(NEW_VAL); }); - it("Should return true to isParameter() call", () => { const qNode = new EmberLib.QualifiedParameter(PATH); expect(qNode.isParameter()).toBeTruthy(); }); + it("should have setValue function", () => { + const qp = new EmberLib.QualifiedParameter(PATH); + const VALUE = 1; + qp.contents = new EmberLib.ParameterContents(VALUE, "integer"); + let NEW_VALUE = VALUE + 1; + let setVal = qp.setValue(NEW_VALUE); + let dup = setVal.getElementByPath(PATH); + expect(dup).toBeDefined(); + expect(dup.contents.value).toBe(NEW_VALUE); + NEW_VALUE = NEW_VALUE + 1; + setVal = qp.setValue(new EmberLib.ParameterContents(NEW_VALUE)); + expect(setVal.getElementByPath(PATH).contents.value).toBe(NEW_VALUE); + }); + it("should accept subscribers and have a function to update them", () => { + const qp = new EmberLib.QualifiedParameter(PATH); + const VALUE = 1; + qp.contents = new EmberLib.ParameterContents(VALUE, "integer"); + qp.contents.streamIdentifier = 12345; + let updatedValue = null; + const handleUpdate = function(param) { + updatedValue = param.contents.value; + } + qp.subscribe(handleUpdate); + qp.updateSubscribers(); + expect(updatedValue).toBe(VALUE); + }); }); describe("StreamDescription", () => { it("Should throw an error if unable to decode", () => { diff --git a/test/Server.test.js b/test/Server.test.js index 05f5dc5..5dac151 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -1,17 +1,18 @@ const expect = require("expect"); const {EmberServer, ServerEvents} = require("../EmberServer"); const EmberClient = require("../EmberClient"); -const ember = require("../EmberLib"); +const EmberLib = require("../EmberLib"); const {jsonRoot} = require("./utils"); const MatrixHandlers = require("../EmberServer/MatrixHandlers"); +const Errors = require("../Errors"); const LOCALHOST = "127.0.0.1"; -const PORT = 9009; +let PORT = 9009; describe("server", function() { describe("JSONtoTree", function() { let jsonTree; - beforeAll(function() { + beforeEach(function() { jsonTree = jsonRoot(); }); it("should generate an ember tree from json", function() { @@ -22,36 +23,74 @@ describe("server", function() { expect(root.getElementByNumber(0).contents.identifier).toBe("scoreMaster"); expect(root.getElementByNumber(0).elements.size).toBe(jsonTree[0].children.length); }); + it("should throw an error if invalid matrix mode", function() { + jsonTree[0].children[1].children[0].mode = "invalid"; + let error; + try { + const root = EmberServer.JSONtoTree(jsonTree); + } + catch(e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof Errors.InvalidEmberNode).toBeDefined(); + }); + it("should support matrix type nToN nonLinear", function() { + jsonTree[0].children[1].children[0].type = "nToN"; + jsonTree[0].children[1].children[0].mode = "nonLinear"; + jsonTree[0].children[1].children[0].maximumConnectsPerTarget = 10; + jsonTree[0].children[1].children[0].maximumTotalConnects = 100; + const root = EmberServer.JSONtoTree(jsonTree); + const matrix = root.getElementByPath("0.1.0"); + expect(matrix).toBeDefined(); + expect(matrix.contents.maximumConnectsPerTarget).toBe(jsonTree[0].children[1].children[0].maximumConnectsPerTarget); + expect(matrix.contents.maximumTotalConnects).toBe(jsonTree[0].children[1].children[0].maximumTotalConnects); + }); + it("should support matrix type oneToOne", function() { + jsonTree[0].children[1].children[0].type = "oneToOne"; + const root = EmberServer.JSONtoTree(jsonTree); + const matrix = root.getElementByPath("0.1.0"); + expect(matrix).toBeDefined(); + expect(matrix.contents.type).toBe(EmberLib.MatrixType.oneToOne); + }); + it("should throw an error if invalid matrix type", function() { + jsonTree[0].children[1].children[0].type = "invalid"; + let error; + try { + const root = EmberServer.JSONtoTree(jsonTree); + } + catch(e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof Errors.InvalidEmberNode).toBeDefined(); + }); }); describe("Server - Client communication", function() { let server,client,jsonTree; - beforeAll(function() { + beforeEach(() => { jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); server = new EmberServer(LOCALHOST, PORT, root); server.on("error", e => { - // eslint-disable-next-line no-console - console.log(e); + // ignore }); server.on("clientError", e => { - // eslint-disable-next-line no-console - console.log(e); + // ignore }); //server._debug = true; return server.listen(); }); - afterAll(function() { + afterEach(() => { return server.close(); }); - it("should receive and decode the full tree", function () { + it("should receive and decode the full tree", () => { client = new EmberClient(LOCALHOST, PORT); //client._debug = true; return Promise.resolve() .then(() => client.connect()) - .then(() => { - return client.getDirectory(); - }) + .then(() => client.getDirectory()) .then(() => { expect(client.root).toBeDefined(); expect(client.root.elements).toBeDefined(); @@ -68,43 +107,32 @@ describe("server", function() { expect(client.root.getElementByPath("0.0.3").contents.identifier).toBe("author"); // Issue #33 EmberServer.handleGetDirectory does not subscribe to child parameters expect(server.subscribers["0.0.0"]).toBeDefined(); - // Keepalive return client.disconnect(); }); }); - it("should be able to modify a parameter", () => { + it("should be able to modify a parameter", async () => { client = new EmberClient(LOCALHOST, PORT); - //client._debug = true; - return Promise.resolve() - .then(() => client.connect()) - .then(() => { - return client.getDirectory(); - }) - .then(() => client.expand(client.root.getElementByNumber(0))) - .then(() => { - expect(server.tree.getElementByPath("0.0.1").contents.value).not.toBe("gdnet"); - return client.setValue(client.root.getElementByPath("0.0.1"), "gdnet"); - }) - .then(() => { - expect(server.tree.getElementByPath("0.0.1").contents.value).toBe("gdnet"); - return client.disconnect(); - }); + await client.connect() + await client.getDirectory(); + await client.getElementByPath("0.0.1"); + expect(server.tree.getElementByPath("0.0.1").contents.value).not.toBe("gdnet"); + await client.setValue(client.root.getElementByPath("0.0.1"), "gdnet"); + expect(server.tree.getElementByPath("0.0.1").contents.value).toBe("gdnet"); + console.log("result", server.tree.getElementByPath("0.0.1").contents.value) + return client.disconnect().then(() => { console.log("disconnected")}); }); it("should be able to call a function with parameters", () => { client = new EmberClient(LOCALHOST, PORT); //client._debug = true; - return Promise.resolve() - .then(() => client.connect()) - .then(() => { - return client.getDirectory(); - }) - .then(() => client.expand(client.root.getElementByNumber(0))) + return client.connect() + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("0.2")) .then(() => { const func = client.root.getElementByPath("0.2"); return client.invokeFunction(func, [ - new ember.FunctionArgument(ember.ParameterType.integer, 1), - new ember.FunctionArgument(ember.ParameterType.integer, 7) + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1), + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 7) ]); }) .then(result => { @@ -116,51 +144,30 @@ describe("server", function() { }); }); - it("should be able to get child with tree.getNodeByPath", function() { - //server._debug = true; + it("should be able to get child with client.getElement", function() { client = new EmberClient(LOCALHOST, PORT); - //client._debug = true; - //client._debug = true; return Promise.resolve() .then(() => client.connect()) - .then(() => { - return client.getDirectory(); - }) - .then(() => client.getNodeByPath("scoreMaster/identity/product")) - .then(() => { - return client.getNodeByPath("scoreMaster/router/labels/group 1"); - }) - .then(() => { - return client.disconnect(); - }); + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("scoreMaster/identity/product")) + .then(() => client.getElementByPath("scoreMaster/router/labels/group 1")) + .then(() => client.disconnect()); }); it("should be able to get child with getElementByPath", function() { - //server._debug = true; client = new EmberClient(LOCALHOST, PORT); - //client._debug = true; - //client._debug = true; return Promise.resolve() .then(() => client.connect()) - .then(() => { - return client.getDirectory(); - }) + .then(() => client.getDirectory()) .then(() => client.getElementByPath("scoreMaster/identity/product")) - .then(() => { - return client.getElementByPath("scoreMaster/router/labels/group 1"); - }) - .then(() => { - return client.disconnect(); - }); + .then(() => client.getElementByPath("scoreMaster/router/labels/group 1")) + .then(() => client.disconnect()); }); - it("should throw an error if getNodeByPath for unknown path", function() { - //server._debug = true; + it("should throw an error if getElementByPath for unknown path", function() { client = new EmberClient(LOCALHOST, PORT); return Promise.resolve() .then(() => client.connect()) - .then(() => { - return client.getDirectory(); - }) - .then(() => client.getNodeByPath("scoreMaster/router/labels/group")) + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("scoreMaster/router/labels/group")) .then(() => { throw new Error("Should not succeed"); }) @@ -173,14 +180,10 @@ describe("server", function() { client = new EmberClient(LOCALHOST, PORT); return Promise.resolve() .then(() => client.connect()) - .then(() => { - return client.getDirectory(); - }) + .then(() => client.getDirectory()) .then(() => client.getElementByPath("0.1.0")) .then(matrix => client.matrixConnect(matrix, 0, [1])) - .then(matrix => { - return client.getElementByPath(matrix.getPath()); - }) + .then(matrix => client.getElementByPath(matrix.getPath())) .then(matrix => { expect(matrix.connections['0'].sources).toBeDefined(); expect(matrix.connections['0'].sources.length).toBe(1); @@ -220,15 +223,15 @@ describe("server", function() { count = 0; const func = client.root.getElementByPath("0.2"); return client.invokeFunction(func, [ - new ember.FunctionArgument(ember.ParameterType.integer, 1), - new ember.FunctionArgument(ember.ParameterType.integer, 7) + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1), + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 7) ]); }) .then(() => { expect(count).toBe(1); expect(receivedEvent.type).toBe(ServerEvents.Types.INVOKE); }) - .then(() => client.getNodeByPathnum("0.0.2")) + .then(() => client.getElementByPath("0.0.2")) .then(parameter => { server._subscribe = server.subscribe; let _resolve; @@ -260,6 +263,9 @@ describe("server", function() { const root = EmberServer.JSONtoTree(jsonTree); server = new EmberServer(LOCALHOST, PORT, root); }); + afterEach(() => { + return server.close(); + }); it("should verify if connection allowed in 1-to-N", function() { let disconnectCount = 0; const handleDisconnect = () => { @@ -267,15 +273,15 @@ describe("server", function() { } server.on("matrix-disconnect", handleDisconnect.bind(this)); const matrix = server.tree.getElementByPath("0.1.0"); - let connection = new ember.MatrixConnection(0); + let connection = new EmberLib.MatrixConnection(0); connection.setSources([1]); - connection.operation = ember.MatrixOperation.connect; + connection.operation = EmberLib.MatrixOperation.connect; let res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); matrix.setSources(0, [0]); res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); - connection.operation = ember.MatrixOperation.absolute; + connection.operation = EmberLib.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); // We can't connect. But server will disconnect existing source and connect new one. @@ -291,8 +297,8 @@ describe("server", function() { expect(disconnectCount).toBe(2); expect(matrix.connections[0].sources[0]).toBe(222); matrix.setSources(0, [0]); - connection = new ember.MatrixConnection(1); - connection.operation = ember.MatrixOperation.absolute; + connection = new EmberLib.MatrixConnection(1); + connection.operation = EmberLib.MatrixOperation.absolute; connection.setSources([1]); res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); @@ -304,10 +310,10 @@ describe("server", function() { disconnectCount++; } server.on("matrix-disconnect", handleDisconnect.bind(this)); - matrix.contents.type = ember.MatrixType.oneToOne; - const connection = new ember.MatrixConnection(0); + matrix.contents.type = EmberLib.MatrixType.oneToOne; + const connection = new EmberLib.MatrixConnection(0); connection.setSources([1]); - connection.operation = ember.MatrixOperation.connect; + connection.operation = EmberLib.MatrixOperation.connect; let res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); matrix.setSources(0, [0]); @@ -320,7 +326,7 @@ describe("server", function() { // But if connecting same source and dest. just disconnect and do not reconnect. server._handlers.handleMatrixConnections(null, matrix, {0: connection}); expect(disconnectCount).toBe(2); - connection.operation = ember.MatrixOperation.absolute; + connection.operation = EmberLib.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); matrix.setSources(0, []); @@ -336,11 +342,11 @@ describe("server", function() { disconnectCount++; } server.on("matrix-disconnect", handleDisconnect.bind(this)); - matrix.contents.type = ember.MatrixType.oneToOne; + matrix.contents.type = EmberLib.MatrixType.oneToOne; matrix.setSources(0, [1]); - const connection = new ember.MatrixConnection(0); + const connection = new EmberLib.MatrixConnection(0); connection.setSources([1]); - connection.operation = ember.MatrixOperation.connect; + connection.operation = EmberLib.MatrixOperation.connect; server._handlers.handleMatrixConnections(null, matrix, {0: connection}); expect(matrix.connections[0].sources.length).toBe(0); expect(disconnectCount).toBe(1); @@ -352,12 +358,12 @@ describe("server", function() { disconnectCount++; } server.on("matrix-disconnect", handleDisconnect.bind(this)); - matrix.contents.type = ember.MatrixType.oneToOne; + matrix.contents.type = EmberLib.MatrixType.oneToOne; matrix.setSources(0, [1]); matrix.connections[0].lock(); - const connection = new ember.MatrixConnection(0); + const connection = new EmberLib.MatrixConnection(0); connection.setSources([0]); - connection.operation = ember.MatrixOperation.connect; + connection.operation = EmberLib.MatrixOperation.connect; server._handlers.handleMatrixConnections(null, matrix, {0: connection}); expect(matrix.connections[0].sources.length).toBe(1); expect(matrix.connections[0].sources[0]).toBe(1); @@ -365,13 +371,13 @@ describe("server", function() { }); it("should verify if connection allowed in N-to-N", function() { const matrix = server.tree.getElementByPath("0.1.0"); - matrix.contents.type = ember.MatrixType.nToN; + matrix.contents.type = EmberLib.MatrixType.nToN; matrix.contents.maximumTotalConnects = 2; matrix.setSources(0, [0,1]); - const connection = new ember.MatrixConnection(0); + const connection = new EmberLib.MatrixConnection(0); connection.setSources([2]); - connection.operation = ember.MatrixOperation.connect; + connection.operation = EmberLib.MatrixOperation.connect; let res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); @@ -387,7 +393,7 @@ describe("server", function() { matrix.setSources(0, [1,2]); matrix.setSources(1, []); - connection.operation = ember.MatrixOperation.absolute; + connection.operation = EmberLib.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); @@ -399,7 +405,7 @@ describe("server", function() { matrix.setSources(1, [1]); matrix.setSources(0, [0]); connection.setSources([2]); - connection.operation = ember.MatrixOperation.connect; + connection.operation = EmberLib.MatrixOperation.connect; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeFalsy(); @@ -409,20 +415,18 @@ describe("server", function() { expect(res).toBeTruthy(); matrix.setSources(0, [0]); - connection.operation = ember.MatrixOperation.absolute; + connection.operation = EmberLib.MatrixOperation.absolute; res = matrix.canConnect(connection.target,connection.sources,connection.operation); expect(res).toBeTruthy(); }); it("should return modified answer on absolute connect", function() { let client; - server.on("error", e => { - // eslint-disable-next-line no-console - console.log(e); + server.on("error", () => { + // ignore }); - server.on("clientError", e => { - // eslint-disable-next-line no-console - console.log(e); + server.on("clientError", () => { + // ignore }); //server._debug = true; return server.listen() @@ -432,38 +436,33 @@ describe("server", function() { }) .then(() => client.connect()) .then(() => client.getDirectory()) - .then(() => client.getNodeByPathnum("0.1.0")) + .then(() => client.getElementByPath("0.1.0")) .then(matrix => client.matrixSetConnection(matrix, 0, [1])) .then(result => { expect(result).toBeDefined(); expect(result.connections).toBeDefined(); expect(result.connections[0]).toBeDefined(); - expect(result.connections[0].disposition).toBe(ember.MatrixDisposition.modified); + expect(result.connections[0].disposition).toBe(EmberLib.MatrixDisposition.modified); return client.disconnect(); - }) - .then(() => { - server.close(); }); }); }); describe("Parameters subscribe/unsubscribe", function( ){ let jsonTree; let server; - beforeAll(function() { + beforeEach(function() { jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); server = new EmberServer(LOCALHOST, PORT, root); - server.on("error", e => { - // eslint-disable-next-line no-console - console.log(e); + server.on("error", () => { + // ignore }); - server.on("clientError", e => { - // eslint-disable-next-line no-console - console.log(e); + server.on("clientError", () => { + // ignore }); return server.listen(); }); - afterAll(function() { + afterEach(function() { return server.close(); }); it("should not auto subscribe stream parameter", function() { @@ -482,7 +481,7 @@ describe("server", function() { .then(() => { return client.getDirectory(); }) - .then(() => client.getNodeByPathnum("0.0.2")) + .then(() => client.getElementByPath("0.0.2")) .then(parameter => { expect(server.subscribers["0.0.2"]).not.toBeDefined(); expect(parameter._subscribers).toBeDefined(); @@ -501,7 +500,7 @@ describe("server", function() { .then(() => { expect(server.subscribers["0.0.2"]).toBeDefined(); expect(server.subscribers["0.0.2"].size).toBe(1); - return client.getNodeByPathnum("0.0.2"); + return client.getElementByPath("0.0.2"); }) .then(parameter => { expect(parameter._subscribers).toBeDefined(); @@ -519,7 +518,7 @@ describe("server", function() { }) .then(() => { expect(server.subscribers["0.0.2"]).toBeDefined(); - return client.getNodeByPathnum("0.0.2"); + return client.getElementByPath("0.0.2"); }) .then(parameter => { expect(server.subscribers["0.0.2"]).toBeDefined(); @@ -530,4 +529,514 @@ describe("server", function() { .then(() => client.disconnect()); }); }); + describe("Handlers", () => { + let jsonTree; + let server; + beforeEach(function() { + jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.on("error", () => { + //ignore + }); + server.on("clientError", () => { + //ignore + }); + return server.listen(); + }); + afterEach(function() { + return server.close(); + }); + it("Should through an error if can't process request", () => { + const root = new EmberLib.Root(); + root.addElement(new EmberLib.Node(0)); + let error; + const errorHandler = function(e) { + error = e; + } + server.on("error", errorHandler); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + server.handleRoot(client._client, root); + expect(error instanceof Errors.InvalidRequesrFormat); + client.disconnect(); + }); + }); + it("should ignore empty or null tree", () => { + const root = new EmberLib.Root(); + let error; + try { + server.handleRoot(null, root); + } + catch(e) { + error = e; + } + expect(error).not.toBeDefined(); + }); + it("should generate responses which include children", () => { + const node = server.tree.getElementByNumber(0); + server.getResponse(node); + expect(node.getChildren().length > 0).toBeTruthy(); + }); + it("Should update parameter value if new parameter value received", () => { + const root = new EmberLib.Root(); + const parameter = new EmberLib.Parameter(2); + const VALUE = "3.4.5"; + parameter.contents = new EmberLib.ParameterContents(VALUE, "string"); + root.addElement(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(0)); + root.getElementByPath("0.0").addChild(parameter); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + server.handleRoot(client._client, root); + const res = server.tree.getElementByPath("0.0.2"); + expect(res.contents.value).toBe(VALUE); + return client.disconnect(); + }); + }); + it("Should throw an error if element not found during request process", () => { + const root = new EmberLib.Root(); + const parameter = new EmberLib.Parameter(99); + const VALUE = "3.4.5"; + parameter.contents = new EmberLib.ParameterContents(VALUE, "string"); + root.addElement(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(0)); + root.getElementByPath("0.0").addChild(parameter); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + let count = 0; + server.handleError = () => { + count++; + } + server.handleRoot(client._client, root); + expect(count).toBe(1); + return client.disconnect(); + }); + }); + it("Should throw an error if element contains null child", () => { + const root = new EmberLib.Root(); + const node = new EmberLib.Node(0); + root.addElement(node); + node.elements = new Map(); + node.elements.set(0, null); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + let count = 0; + server.handleError = () => { + count++; + } + server.handleRoot(client._client, root); + expect(count).toBe(1); + return client.disconnect(); + }); + }); + it("should handle commands embedded in Node", () => { + const root = new EmberLib.Root(); + const node = new EmberLib.Node(0); + root.addElement(node); + node.elements = new Map(); + node.elements.set(EmberLib.COMMAND_GETDIRECTORYGETDIRECTORY, new EmberLib.Command(EmberLib.COMMAND_GETDIRECTORYGETDIRECTORY)); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + let count = 0; + server._handlers.handleCommand = () => { + count++; + } + server.handleRoot(client._client, root); + expect(count).toBe(1); + return client.disconnect(); + }); + }); + it("should catch unknown commands", () => { + const command = new EmberLib.Command(99); + let count = 0; + server.on("error", e => { + expect(e instanceof Errors.InvalidCommand); + count++; + }); + server._handlers.handleCommand(null, new EmberLib.Root(), command); + expect(count).toBe(1); + }); + it("should catch invalid node with no number", () => { + const node = new EmberLib.Node(99); + node.number = null; + let count = 0; + server.on("error", e => { + expect(e instanceof Errors.MissingElementNumber); + count++; + }); + server._handlers.handleNode(null, node); + expect(count).toBe(1); + }); + it("should handle matrix connections embedded in Node", () => { + const root = new EmberLib.Root(); + const node = new EmberLib.Node(0); + root.addElement(node); + const matrix = new EmberLib.MatrixNode(0); + matrix.connections = [ + new EmberLib.MatrixConnection(0) + ]; + node.elements = new Map(); + node.elements.set(0, matrix); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + let count = 0; + server._handlers.handleMatrixConnections = () => { + count++; + } + server.handleRoot(client._client, root); + expect(count).toBe(1); + return client.disconnect(); + }); + }); + it("should catch function invocation errors and set success to false", () => { + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + const root = new EmberLib.Root(); + const func = new EmberLib.Function(0, () => { throw Error("function error")}); + root.addElement(func); + server.tree = root; + return client.invokeFunction(func, []); + }) + .then(result => { + expect(result).toBeDefined(); + expect(result.success).toBeFalsy(); + }) + .then(() => client.disconnect()); + }); + it("should catch invoke to non function", () => { + const client = new EmberClient(LOCALHOST, PORT); + let result; + client.sendBERNode = function(res) { + result = res; + } + const root = new EmberLib.Root(); + const func = new EmberLib.Node(0); + root.addElement(func); + server.tree = root; + const command = EmberLib.Command.getInvocationCommand(new EmberLib.Invocation(1, [])); + server._handlers.handleInvoke(client, func, command); + expect(result).toBeDefined(); + expect(result.success).toBeFalsy(); + return client.disconnect(); + }); + }); + describe("Matrix", () => { + let jsonTree; + const MATRIX_PATH = "0.1.0"; + /** @type {EmberServer} */ + let server; + beforeEach(function() { + jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.on("error", () => { + //ignore + }); + server.on("clientError", () => { + //ignore + }); + return server.listen(); + }); + afterEach(function() { + return server.close(); + }); + it("should generate connections structure if none provided when calling JSONtoStree", () => { + const js = jsonRoot(); + js[0].children[1].children[0].connections = null; + const tree = EmberServer.JSONtoTree(js); + const matrix = tree.getElementByPath(MATRIX_PATH); + expect(matrix.connections).toBeDefined(); + for(let i = 0; i < matrix.contents.targetCount; i++) { + expect(matrix.connections[i]).toBeDefined(); + expect(matrix.connections[i].target).toBe(i); + } + }); + it("should have a matrixConnect function", () => { + const matrix = server.tree.getElementByPath(MATRIX_PATH); + matrix.connections[0].setSources([]); + server.matrixConnect(MATRIX_PATH, 0, [1]); + expect(matrix.connections[0].sources).toBeDefined(); + expect(matrix.connections[0].sources.length).toBe(1); + expect(matrix.connections[0].sources[0]).toBe(1); + }); + it("should throw an error if can't find matrix", () => { + try { + server.matrixConnect("0.99.0", 0, [1]); + throw new Error("Should not succeed"); + } + catch(error) { + expect(error instanceof Errors.UnknownElement); + } + }); + it("should throw an error if invalid matrix", () => { + const matrix = server.tree.getElementByPath(MATRIX_PATH); + matrix.contents = null; + try { + server.matrixConnect(MATRIX_PATH, 0, [1]); + throw new Error("Should not succeed"); + } + catch(error) { + expect(error instanceof Errors.MissingElementContents); + } + }); + it("should have a matrixSet operation on matrix", () => { + const matrix = server.tree.getElementByPath(MATRIX_PATH); + matrix.connections[0].setSources([0]); + server.matrixSet(MATRIX_PATH, 0, [1]); + expect(matrix.connections[0].sources).toBeDefined(); + expect(matrix.connections[0].sources.length).toBe(1); + expect(matrix.connections[0].sources[0]).toBe(1); + }); + it("should have a matrixDisconnect operation on matrix", () => { + const matrix = server.tree.getElementByPath(MATRIX_PATH); + matrix.contents.type = EmberLib.MatrixType.nToN; + matrix.connections[0].setSources([1]); + server.matrixDisconnect(MATRIX_PATH, 0, [1]); + expect(matrix.connections[0].sources).toBeDefined(); + expect(matrix.connections[0].sources.length).toBe(0); + }); + }); + describe("subscribers", () => { + let jsonTree; + const PARAMETER_PATH = "0.0.1"; + const MATRIX_PATH = "0.1.0"; + /** @type {EmberServer} */ + let server; + beforeEach(function() { + jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.on("error", () => { + //ignore + }); + server.on("clientError", () => { + //ignore + }); + return server.listen(); + }); + afterEach(function() { + return server.close(); + }); + it("should accept client to subscribe to parameter and update those who subscribed", () => { + const client = new EmberClient(LOCALHOST, PORT); + const VALUE = "The new Value"; + return client.connect() + .then(() => client.getDirectory()) + .then(() => client.getElementByPath(PARAMETER_PATH)) + .then(() => { + // A get directory on non stream is auto subscribe + expect(server.subscribers).toBeDefined(); + expect(server.subscribers[PARAMETER_PATH]).toBeDefined(); + expect(server.subscribers[PARAMETER_PATH].size).toBe(1); + let res; + for(let c of server.subscribers[PARAMETER_PATH]) { + c.queueMessage = message => { + res = message; + } + } + server.setValue(server.tree.getElementByPath(PARAMETER_PATH), VALUE, null, null); + expect(res).toBeDefined(); + const resParam = res.getElementByPath(PARAMETER_PATH); + expect(resParam).toBeDefined(); + expect(resParam.getPath()).toBe(PARAMETER_PATH); + expect(resParam.contents).toBeDefined(); + expect(resParam.contents.value).toBe(VALUE); + return client.disconnect(); + }); + }); + it("should accept client to subscribe to matrix and update those who subscribed", () => { + const client = new EmberClient(LOCALHOST, PORT); + const VALUE = 17; + const MatrixParamName = "maximumTotalConnects"; + server.tree.getElementByPath(MATRIX_PATH).contents[MatrixParamName] = 0; + return client.connect() + .then(() => client.getDirectory()) + .then(() => client.getElementByPath(MATRIX_PATH)) + .then(() => { + // A get directory on non stream is auto subscribe + expect(server.subscribers).toBeDefined(); + expect(server.subscribers[MATRIX_PATH]).toBeDefined(); + expect(server.subscribers[MATRIX_PATH].size).toBe(1); + let res; + for(let c of server.subscribers[MATRIX_PATH]) { + c.queueMessage = message => { + res = message; + } + } + server.setValue(server.tree.getElementByPath(MATRIX_PATH), VALUE, null, MatrixParamName); + expect(res).toBeDefined(); + const resParam = res.getElementByPath(MATRIX_PATH); + expect(resParam).toBeDefined(); + expect(resParam.getPath()).toBe(MATRIX_PATH); + expect(resParam.contents).toBeDefined(); + expect(resParam.contents[MatrixParamName]).toBe(VALUE); + return client.disconnect(); + }); + }); + it("should clean up subscribers if client not connected anymore", () => { + const client = new EmberClient(LOCALHOST, PORT); + const VALUE = "The new Value"; + server.subscribers[PARAMETER_PATH] = new Set(); + server.subscribers[PARAMETER_PATH].add(client); + expect(server.subscribers[PARAMETER_PATH].size).toBe(1); + let res; + for(let c of server.subscribers[PARAMETER_PATH]) { + c.queueMessage = message => { + res = message; + } + } + server.setValue(server.tree.getElementByPath(PARAMETER_PATH), VALUE, null, null); + expect(res).not.toBeDefined(); + expect(server.subscribers[PARAMETER_PATH].has(client)).toBeFalsy(); + }); + it("should ignore unsubscribe if no subcribers", () => { + const client = new EmberClient(LOCALHOST, PORT); + let error; + try { + server.unsubscribe(client, server.tree.getElementByPath(PARAMETER_PATH)); + } + catch(e) { + error =e ; + } + expect(error).not.toBeDefined(); + }); + it("should ignore serValue on element with no contents", () => { + const param = server.tree.getElementByPath(PARAMETER_PATH); + const VALUE = "The new Value"; + param.contents = null; + let error; + try { + server.setValue(param, VALUE, null, null); + } + catch(e) { + error =e ; + } + expect(error).not.toBeDefined(); + }); + }); + describe("EmberServer toJSON", () => { + it("should have a toJSON", () => { + const PARAMETER_PATH = "0.0.1"; + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + const js = server.toJSON(); + expect(js[0].children[0].children[1].path).toBe(PARAMETER_PATH); + }); + it("should have a toJSON and return empty array if no tree", () => { + server = new EmberServer(LOCALHOST, PORT, null); + const js = server.toJSON(); + expect(js).toBeDefined(); + expect(js.length).toBe(0); + }); + }); + describe("replaceElement", () => { + it("should replace existing element with new one", () => { + const PARAMETER_PATH = "0.0.1"; + const VALUE = "Gilles Dufour" + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + const newParam = new EmberLib.Parameter(1); + newParam.contents = new EmberLib.ParameterContents(VALUE); + newParam.path = PARAMETER_PATH; + server.replaceElement(newParam); + expect(server.tree.getElementByPath(PARAMETER_PATH).contents.value).toBe(VALUE); + }); + it("should throw an error if unknown element path", () => { + const VALUE = "Gilles Dufour" + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + const newParam = new EmberLib.Parameter(1); + newParam.contents = new EmberLib.ParameterContents(VALUE); + let error; + try { + server.replaceElement(newParam); + } + catch(e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof Errors.UnknownElement).toBeTruthy(); + }); + it("should throw an error if trying to replace root or unattached element", () => { + const PARAMETER_PATH = "0.0.1"; + const VALUE = "Gilles Dufour" + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.tree.getElementByPath(PARAMETER_PATH)._parent = null; + const newParam = new EmberLib.Parameter(1); + newParam.contents = new EmberLib.ParameterContents(VALUE); + newParam.path = PARAMETER_PATH; + let error; + try { + server.replaceElement(newParam); + } + catch(e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof Errors.InvalidEmberNode).toBeTruthy(); + }); + }); + describe("Events", () => { + it("should catch error emitted by internal tcp server", () => { + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const ERROR_MESSAGE = "gdnet internal error"; + server = new EmberServer(LOCALHOST, PORT, root); + let error; + server.on("error", e => {error = e;}); + server.server.emit("error", new Error(ERROR_MESSAGE)); + expect(error).toBeDefined(); + expect(error.message).toBe(ERROR_MESSAGE); + }); + it("should catch error and use callback if present", () => { + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const ERROR_MESSAGE = "gdnet internal error"; + server = new EmberServer(LOCALHOST, PORT, root); + let error; + server.on("error", e => {}); + server.callback = e => {error = e;} + server.server.emit("error", new Error(ERROR_MESSAGE)); + expect(error).toBeDefined(); + expect(error.message).toBe(ERROR_MESSAGE); + }); + it("should catch tcp server disconnected message, and clean up clients", () => { + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.clients.add(new EmberClient(LOCALHOST, PORT)); + let count = 0; + server.on("disconnected", () => {count++;}); + server.server.emit("disconnected"); + expect(count).toBe(1); + expect(server.clients.size).toBe(0); + }); + it("should catch error from connection to clients", () => { + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const ERROR_MESSAGE = "gdnet internal error"; + server = new EmberServer(LOCALHOST, PORT, root); + const client = new EmberClient(LOCALHOST, PORT); + client.remoteAddress = () => {return "address";} + let info; + server.on("clientError", data => {info = data;}); + server.server.emit("connection", client); + client.emit("error", new Error(ERROR_MESSAGE)); + expect(info).toBeDefined(); + expect(info.error.message).toBe(ERROR_MESSAGE); + }); + }); }); diff --git a/test/utils.js b/test/utils.js index 7fb6f65..f776d62 100755 --- a/test/utils.js +++ b/test/utils.js @@ -39,7 +39,7 @@ const init = function(_src,_tgt) { // path "0.0" identifier: "identity", children: [ - {identifier: "product", value: "S-CORE Master"}, + {identifier: "product", value: "S-CORE Master", type: "string"}, {identifier: "company", value: "EVS", access: "readWrite"}, {identifier: "version", value: "1.2.0", access: "readWrite", streamIdentifier: 1234567}, {identifier: "author", value: "g.dufour@evs.com"}, @@ -55,7 +55,7 @@ const init = function(_src,_tgt) { type: "oneToN", mode: "linear", targetCount: targets.length, - sourceCount: sources.length, + sourceCount: sources.length, connections: buildConnections(sources, targets), labels: [{basePath: "0.1.1000", description: "primary"}], }, From a06f5cdbbbec552d1a7681a1b10d48789c561cf0 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 20 Jan 2020 12:04:05 +0100 Subject: [PATCH 139/162] Add missing files. Reformat S101Socket and S101Client --- EmberClient/EmberClient.js | 28 ++--- EmberLib/ElementInterface.js | 61 +++++++++++ EmberSocket/S101Client.js | 123 ++++++++------------- EmberSocket/S101Server.js | 7 +- EmberSocket/S101Socket.js | 201 +++++++++++++++++++---------------- README.md | 25 +++-- test/EmberClient.test.js | 66 ++++++++++++ 7 files changed, 315 insertions(+), 196 deletions(-) create mode 100755 EmberLib/ElementInterface.js create mode 100755 test/EmberClient.test.js diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index e1426f5..245251b 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -1,5 +1,5 @@ const EventEmitter = require('events').EventEmitter; -const S101Socket = require('../EmberSocket').S101Socket; +const S101Client = require('../EmberSocket').S101Client; const ember = require('../EmberLib'); const BER = require('../ber.js'); const Errors = require('../Errors.js'); @@ -8,10 +8,14 @@ const winston = require("winston"); const DEFAULT_PORT = 9000; const DEFAULT_TIMEOUT = 3000; -/** @typedef {{ +/** + * @typedef {{ * node: TreeNode, * func: function - * }} REQUEST*/ + * }} REQUEST + * + */ + class EmberClient extends EventEmitter { /** * @@ -28,7 +32,7 @@ class EmberClient extends EventEmitter { this._timeout = null; this._callback = undefined; this._requestID = 0; - this._client = new S101Socket(host, port); + this._client = new S101Client(host, port); this.timeoutValue = DEFAULT_TIMEOUT; /** @type {Root} */ this.root = new ember.Root(); @@ -132,7 +136,7 @@ class EmberClient extends EventEmitter { * @param {TreeNode} node */ _handleNode(parent, node) { - var n = parent.getElementByNumber(node.getNumber()); + let n = parent.getElementByNumber(node.getNumber()); if (n === null) { parent.addChild(node); n = node; @@ -140,9 +144,9 @@ class EmberClient extends EventEmitter { n.update(node); } - var children = node.getChildren(); + const children = node.getChildren(); if (children !== null) { - for (var i = 0; i < children.length; i++) { + for (let i = 0; i < children.length; i++) { this._handleNode(n, children[i]); } } @@ -158,13 +162,13 @@ class EmberClient extends EventEmitter { * @param {TreeNode} node */ _handleQualifiedNode(parent, node) { - var element = parent.getElementByPath(node.path); + let element = parent.getElementByPath(node.path); if (element !== null) { this.emit("value-change", node); element.update(node); } else { - var path = node.path.split("."); + const path = node.path.split("."); if (path.length === 1) { this.root.addChild(node); } @@ -181,9 +185,9 @@ class EmberClient extends EventEmitter { element = node; } - var children = node.getChildren(); + const children = node.getChildren(); if (children !== null) { - for (var i = 0; i < children.length; i++) { + for (let i = 0; i < children.length; i++) { if (children[i].isQualified()) { this._handleQualifiedNode(element, children[i]); } @@ -205,7 +209,7 @@ class EmberClient extends EventEmitter { this.root.update(root); if (root.elements != null) { const elements = root.getChildren(); - for (var i = 0; i < elements.length; i++) { + for (let i = 0; i < elements.length; i++) { if (elements[i].isQualified()) { this._handleQualifiedNode(this.root, elements[i]); } diff --git a/EmberLib/ElementInterface.js b/EmberLib/ElementInterface.js new file mode 100755 index 0000000..0d93c6c --- /dev/null +++ b/EmberLib/ElementInterface.js @@ -0,0 +1,61 @@ +"use strict"; + +class ElementType { + /** + * @returns {boolean} + */ + isCommand() { + return false; + } + /** + * @returns {boolean} + */ + isNode() { + return false; + } + /** + * @returns {boolean} + */ + isMatrix() { + return false; + } + /** + * @returns {boolean} + */ + isParameter() { + return false; + } + /** + * @returns {boolean} + */ + isFunction() { + return false; + } + /** + * @returns {boolean} + */ + isRoot() { + return false; + } + /** + * @returns {boolean} + */ + isQualified() { + return false; + } + /** + * @returns {boolean} + */ + isStream() { + return false; + } + + /** + * @returns {boolean} + */ + isTemplate() { + return false; + } +} + +module.exports = ElementType; \ No newline at end of file diff --git a/EmberSocket/S101Client.js b/EmberSocket/S101Client.js index d87ca87..d34ce97 100755 --- a/EmberSocket/S101Client.js +++ b/EmberSocket/S101Client.js @@ -1,97 +1,58 @@ "use strict"; - +const net = require('net'); const S101Socket = require("./S101Socket"); -const S101Codec = require('../s101.js'); -const BER = require('../ber.js'); -const ember = require("../EmberLib"); +const DEFAULT_PORT = 9000; class S101Client extends S101Socket { /** * - * @param {Socket} socket - * @param {S101Server} server + * @param {string} address + * @param {number} port=9000 */ - constructor(socket, server) { - super() - this.request = null; - /** @type {S101Server} */ - this.server = server; - /** @type {Socket} */ - this.socket = socket; - - this.pendingRequests = []; - this.activeRequest = null; - - this.status = "connected"; - - this.codec = new S101Codec(); - this.codec.on('keepaliveReq', () => { - this.sendKeepaliveResponse(); - }); - - this.codec.on('emberPacket', (packet) => { - this.emit('emberPacket', packet); - const ber = new BER.Reader(packet); - try { - const root = ember.rootDecode(ber); - if (root != null) { - this.emit('emberTree', root); - } - } catch (e) { - this.emit("error", e); - } - }); - - if (socket != null) { - this.socket.on('data', (data) => { - this.codec.dataIn(data); - }); - - this.socket.on('close', () => { - this.emit('disconnected'); - this.status = "disconnected"; - this.socket = null; - }); - - this.socket.on('error', (e) => { - this.emit("error", e); - }); - } + constructor(address, port=DEFAULT_PORT) { + super(); + this.address = address; + this.port = port; } - /** + /** * - * @param {function} cb + * @param {number} timeout */ - addRequest(cb) { - this.pendingRequests.push(cb); - this._makeRequest(); - } - - _makeRequest() { - if (this.activeRequest === null && this.pendingRequests.length > 0) { - this.activeRequest = this.pendingRequests.shift(); - this.activeRequest(); - this.activeRequest = null; - } - } - - /** - * - * @param {TreeNode} node - */ - queueMessage(node) { - this.addRequest(() => this.sendBERNode(node)); - } - - /** - * @returns {string} - ie: "10.1.1.1:9000" - */ - remoteAddress() { - if (this.socket === undefined) { + connect(timeout = 2) { + if (this.status !== "disconnected") { return; } - return `${this.socket.remoteAddress}:${this.socket.remotePort}` + this.emit('connecting'); + const connectTimeoutListener = () => { + this.socket.destroy(); + this.emit("error", new Error(`Could not connect to ${this.address}:${this.port} after a timeout of ${timeout} seconds`)); + }; + + this.socket = net.createConnection({ + port: this.port, + host: this.address, + timeout: 1000 * timeout + }, + () => { + // Disable connect timeout to hand-over to keepalive mechanism + this.socket.removeListener("timeout", connectTimeoutListener); + this.socket.setTimeout(0); + this.startKeepAlive(); + this.emit('connected'); + }) + .on('error', (e) => { + this.emit("error", e); + }) + .once("timeout", connectTimeoutListener) + .on('data', (data) => { + if (this.isConnected()) { + this.codec.dataIn(data); + } + }) + .on('close', this.handleClose) + .on("end", this.handleClose); + this._initSocket(); } } diff --git a/EmberSocket/S101Server.js b/EmberSocket/S101Server.js index c712292..9660931 100755 --- a/EmberSocket/S101Server.js +++ b/EmberSocket/S101Server.js @@ -1,6 +1,6 @@ "use strict"; const EventEmitter = require('events').EventEmitter; -const S101Client = require("./S101Client"); +const S101Socket = require("./S101Socket"); const net = require('net'); class S101Server extends EventEmitter { @@ -18,10 +18,11 @@ class S101Server extends EventEmitter { } /** * - * @param {Socket} socket + * @param {Socket} socket - tcp socket */ addClient(socket) { - const client = new S101Client(socket, this); + // Wrap the tcp socket into an S101Socket. + const client = new S101Socket(socket); this.emit("connection", client); } /** diff --git a/EmberSocket/S101Socket.js b/EmberSocket/S101Socket.js index 22307f7..9035be7 100755 --- a/EmberSocket/S101Socket.js +++ b/EmberSocket/S101Socket.js @@ -1,90 +1,97 @@ "use strict"; const EventEmitter = require('events').EventEmitter; -const net = require('net'); const BER = require('../ber.js'); const ember = require('../EmberLib'); const S101Codec = require('../s101.js'); class S101Socket extends EventEmitter{ - constructor(address, port) { + /** + * + * @param {Socket} socket + */ + constructor(socket = null) { super(); - this.address = address; - this.port = port; - this.socket = null; + this.socket = socket; this.keepaliveInterval = 10; - this.codec = null; - this.status = "disconnected"; + this.keepaliveIntervalTimer = null; + this.pendingRequests = []; + this.activeRequest = null; + this.status = this.isConnected() ? "connected" : "disconnected"; + + this.codec = new S101Codec(); + this.codec.on('keepaliveReq', () => { + this.sendKeepaliveResponse(); + }); + + this.codec.on('emberPacket', (packet) => { + this.emit('emberPacket', packet); + const ber = new BER.Reader(packet); + try { + const root = ember.rootDecode(ber); + if (root != null) { + this.emit('emberTree', root); + } + } catch (e) { + this.emit("error", e); + } + }); + + this._initSocket(); + } + + _initSocket() { + if (this.socket != null) { + this.socket.on('data', (data) => { + this.codec.dataIn(data); + }); + + this.socket.on('close', () => { + this.emit('disconnected'); + this.status = "disconnected"; + this.socket = null; + }); + + this.socket.on('error', (e) => { + this.emit("error", e); + }); + } + } + + /** + * + * @param {function} cb + */ + addRequest(cb) { + this.pendingRequests.push(cb); + this._makeRequest(); + } + + _makeRequest() { + if (this.activeRequest === null && this.pendingRequests.length > 0) { + this.activeRequest = this.pendingRequests.shift(); + this.activeRequest(); + this.activeRequest = null; + } } /** * - * @param {number} timeout + * @param {TreeNode} node + */ + queueMessage(node) { + this.addRequest(() => this.sendBERNode(node)); + } + + /** + * @returns {string} - ie: "10.1.1.1:9000" */ - connect(timeout = 2) { - if (this.status !== "disconnected") { - return; + remoteAddress() { + if (this.socket == null) { + return "not connected"; } - - this.emit('connecting'); - - this.codec = new S101Codec(); - - const connectTimeoutListener = () => { - this.socket.destroy(); - this.emit("error", new Error(`Could not connect to ${this.address}:${this.port} after a timeout of ${timeout} seconds`)); - }; - - this.socket = net.createConnection({ - port: this.port, - host: this.address, - timeout: 1000 * timeout - }, - () => { - // Disable connect timeout to hand-over to keepalive mechanism - this.socket.removeListener("timeout", connectTimeoutListener); - this.socket.setTimeout(0); - - this.keepaliveIntervalTimer = setInterval(() => { - try { - this.sendKeepaliveRequest(); - } catch (e) { - this.emit("error", e); - } - }, 1000 * this.keepaliveInterval); - - this.codec.on('keepaliveReq', () => { - this.sendKeepaliveResponse(); - }); - - this.codec.on('emberPacket', (packet) => { - this.emit('emberPacket', packet); - - const ber = new BER.Reader(packet); - try { - const root = ember.rootDecode(ber); - if (root != null) { - this.emit('emberTree', root); - } - } catch (e) { - this.emit("error", e); - } - }); - - this.emit('connected'); - }) - .on('error', (e) => { - this.emit("error", e); - }) - .once("timeout", connectTimeoutListener) - .on('data', (data) => { - if (this.isConnected()) { - this.codec.dataIn(data); - } - }) - .on('close', this.handleClose) - .on("end", this.handleClose); + return `${this.socket.remoteAddress}:${this.socket.remotePort}` } /** @@ -95,30 +102,32 @@ class S101Socket extends EventEmitter{ return Promise.resolve(); } return new Promise((resolve, reject) => { + if (this.keepaliveIntervalTimer != null) { clearInterval(this.keepaliveIntervalTimer); - let done = false; - const cb = (data, error) => { - if (done) { return; } - done = true; - if (timer != null) { - clearTimeout(timer); - timer = null; - } - if (error == null) { - resolve(); - } - else { - reject(error); - } - }; - let timer; - if (timeout != null && (!isNaN(timeout)) && timeout > 0) { - timer = setTimeout(cb, 100 * timeout); + this.keepaliveIntervalTimer = null; + } + let done = false; + const cb = (data, error) => { + if (done) { return; } + done = true; + if (timer != null) { + clearTimeout(timer); + timer = null; + } + if (error == null) { + resolve(); } - this.socket.end(cb); - this.status = "disconnected"; + else { + reject(error); + } + }; + let timer; + if (timeout != null && (!isNaN(timeout)) && timeout > 0) { + timer = setTimeout(cb, 100 * timeout); } - ); + this.socket.end(cb); + this.status = "disconnected"; + }); } /** @@ -194,6 +203,16 @@ class S101Socket extends EventEmitter{ node.encode(writer); this.sendBER(writer.buffer); } + + startKeepAlive() { + this.keepaliveIntervalTimer = setInterval(() => { + try { + this.sendKeepaliveRequest(); + } catch (e) { + this.emit("error", e); + } + }, 1000 * this.keepaliveInterval); + } } module.exports = S101Socket; \ No newline at end of file diff --git a/README.md b/README.md index 39c937b..e8de727 100755 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ Server has been added in version 1.6.0. ## Example usage ### Client + Get Full tree: + ```javascript const EmberClient = require('node-emberplus').EmberClient; const client = new EmberClient("10.9.8.7", 9000); @@ -33,12 +35,12 @@ client.connect() // Get a Specific Node .then(() => client.getElementByPath("0.0.2")) .then(node => { - console.log(node); + console.log(node); }) // Get a node by its path identifiers .then(() => client.getElementByPath("path/to/node")) .then(node => { - console.log(node); + console.log(node); }) // Expand entire tree under node 0 .then(() => client.expand(client.root.getElementByNumber(0))) @@ -48,6 +50,7 @@ client.connect() ``` Subsribe to changes + ```javascript const {EmberClient, EmberLib} = require('node-emberplus'); @@ -59,14 +62,14 @@ client.connect()) .then(node => { // For streams, use subscribe return client.subscribe(node, update => { - console.log(udpate); - }); + console.log(udpate); + }); }) .then(() => client.getElementByPath("0.2")) .then(node => { // For non-streams a getDirectory will automatically subscribe for update return client.getDirectory(node, update => { - console.log(udpate); + console.log(udpate); }); }) // You can also provide a callback to the getElementByPath @@ -76,6 +79,7 @@ client.connect()) ``` ### Invoking Function + ```javascript const {EmberClient, EmberLib} = require('node-emberplus'); @@ -95,6 +99,7 @@ client.connect()) ``` ### Matrix Connection + ```javascript const {EmberClient, EmberLib} = require('node-emberplus'); @@ -104,7 +109,7 @@ client.connect() .then(() => client.getDirectory()) .then(() => client.getElementByPath("0.1.0")) .then(matrix => { - console.log("Connecting source 1 to target 0); + console.log("Connecting source 1 to target 0); return client.matrixConnect(matrix, 0, [1]); }) .then(() => client.matrixDisconnect(matrix, 0, [1])) @@ -115,6 +120,7 @@ client.connect() ``` ### Packet decoder + ```javascript // Simple packet decoder const Decoder = require('node-emberplus').Decoder; @@ -124,6 +130,7 @@ fs.readFile("tree.ember", (e,data) => { var root = Decoder(data); }); ``` + ### Server ```javascript @@ -152,6 +159,7 @@ server.listen().then(() => { console.log("listening"); }).catch((e) => { console ``` ### Construct Tree + ```javascript const EmberServer = require("node-emberplus").EmberServer; const {ParameterType, FunctionArgument} = require("node-emberplus").EmberLib; @@ -159,8 +167,8 @@ const {ParameterType, FunctionArgument} = require("node-emberplus").EmberLib; const targets = [ "tgt1", "tgt2", "tgt3" ]; const sources = [ "src1", "src2", "src3" ]; const defaultSources = [ - {identifier: "t-0", value: -1, access: "readWrite" }, - {identifier: "t-1", value: 0, access: "readWrite"}, + {identifier: "t-0", value: -1, access: "readWrite" }, + {identifier: "t-1", value: 0, access: "readWrite"}, {identifier: "t-2", value: 0, access: "readWrite"} ]; const labels = function(endpoints, type) { @@ -277,4 +285,3 @@ const jsonTree = [ ]; const root = EmberServer.JSONtoTree(jsonTree); ``` - diff --git a/test/EmberClient.test.js b/test/EmberClient.test.js new file mode 100755 index 0000000..c9f20a7 --- /dev/null +++ b/test/EmberClient.test.js @@ -0,0 +1,66 @@ +const fs = require("fs"); +const {EmberServer} = require("../EmberServer"); +const Decoder = require('../EmberLib').DecodeBuffer; +const EmberClient = require("../EmberClient"); + +const HOST = "127.0.0.1"; +const PORT = 9008; + +function getRoot() { + return new Promise((resolve, reject) => { + fs.readFile("test/embrionix.ember", (e, data) => { + if (e) { + reject(e); + } + try { + resolve(Decoder(data)); + } + catch(error) { + reject(error); + } + }); + }); +} + +let server; +describe("EmberClient", () => { + + beforeEach(() => { + return getRoot() + .then(root => { + server = new EmberServer(HOST, PORT, root); + //server._debug = true; + server.on("error", e => { + console.log("Server Error", e); + }); + server.on("clientError", info => { + console.log("clientError", info.error); + }); + server.on("event", event => { + console.log("Event: " + event); + }); + return server.listen() + }); + }); + + afterEach(() => { + server.close(); + }); + + it("should be able to fetch a specific node", () => { + const client = new EmberClient(HOST, PORT); + const PATH = "0.1"; + client.on("error", () => { + // ignore + }); + return Promise.resolve() + .then(() => client.connect()) + .then(() => client.getDirectory()) + .then(() => client.getElementByPath(PATH)) + .then(node => { + expect(node).toBeDefined(); + expect(node.getPath()).toBe(PATH); + return client.disconnect(); + }); + }); +}); From 662bf20b651687c6e1a1a520c34c82f988ceb481 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Mon, 20 Jan 2020 12:04:22 +0100 Subject: [PATCH 140/162] v2.4.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8439bf6..99f73e4 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.4.1", + "version": "2.4.2", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From aac8f99a536bd39c95b93d86a77c394e457abbee Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Tue, 21 Jan 2020 08:40:35 +0100 Subject: [PATCH 141/162] use latest asn1 lib --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 99f73e4..1ad608a 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.4.2", + "version": "2.4.3", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { @@ -19,7 +19,7 @@ }, "license": "MIT", "dependencies": { - "asn1": "github:evs-broadcast/node-asn1#date_20190114", + "asn1": "github:evs-broadcast/node-asn1", "enum": "^2.4.0", "long": "^3.2.0", "smart-buffer": "^3.0.3", From 8145c3b3bf5bf903406432376bb642b83fb6b308 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 23 Jan 2020 09:43:16 +0100 Subject: [PATCH 142/162] Fixed some client missing promise return. Add server preMatrixConnect to provide above app a way to decide if connection allowed or not --- EmberClient/EmberClient.js | 20 ++-- EmberServer/EmberServer.js | 156 +++++++++++++++++++++++++++ EmberServer/MatrixHandlers.js | 178 ++++++++----------------------- EmberServer/QualifiedHandlers.js | 2 +- package.json | 2 +- test/EmberClient.test.js | 4 +- 6 files changed, 218 insertions(+), 144 deletions(-) diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index 245251b..b5a61f3 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -484,7 +484,8 @@ class EmberClient extends EventEmitter { * @param {Matrix} matrixNode * @param {number} targetID * @param {number[]} sources - * @param {MatrixOperation} operation + * @param {MatrixOperation} operation + * @returns {Promise} */ matrixOPeration(matrixNode, targetID, sources, operation = ember.MatrixOperation.connect) { return new Promise((resolve, reject) => { @@ -547,6 +548,7 @@ class EmberClient extends EventEmitter { * @param {Matrix} matrixNode * @param {number} targetID * @param {number[]} sources + * @returns {Promise} */ matrixConnect(matrixNode, targetID, sources) { return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.connect) @@ -556,7 +558,8 @@ class EmberClient extends EventEmitter { * * @param {Matrix} matrixNode * @param {number} targetID - * @param {number[]} sources + * @param {number[]} sources + * @returns {Promise} */ matrixDisconnect(matrixNode, targetID, sources) { return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.disconnect) @@ -567,6 +570,7 @@ class EmberClient extends EventEmitter { * @param {Matrix} matrixNode * @param {number} targetID * @param {number[]} sources + * @returns {Promise} */ matrixSetConnection(matrixNode, targetID, sources) { return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.absolute) @@ -622,7 +626,8 @@ class EmberClient extends EventEmitter { /** * * @param {TreeNode} qnode - * @param {function} callback + * @param {function} callback + * @returns {Promise} */ subscribe(qnode, callback) { if ((qnode.isParameter() || qnode.isMatrix()) && qnode.isStream()) { @@ -641,15 +646,15 @@ class EmberClient extends EventEmitter { resolve(); }}); }); - } else { - qnode.addCallback(callback); } + return Promise.resolve(); } /** * * @param {TreeNode} qnode - * @param {function} callback + * @param {function} callback + * @returns {Promise} */ unsubscribe(qnode, callback) { if (qnode.isParameter() && qnode.isStream()) { @@ -664,7 +669,8 @@ class EmberClient extends EventEmitter { resolve(); }}); }); - } + } + return Promise.resolve(); } } diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 8f1997f..1fc82c2 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -95,6 +95,16 @@ class TreeServer extends EventEmitter{ }); } + /** + * + * @param {Matrix} matrix + * @param {number} targetID + * @returns {number} + */ + getDisconnectSource(matrix, targetID) { + return this._handlers.getDisconnectSource(matrix, targetID); + } + /** * * @param {TreeNode} element @@ -219,6 +229,152 @@ class TreeServer extends EventEmitter{ doMatrixOperation(this, path, target, sources, EmberLib.MatrixOperation.absolute); } + /** + * + * @param {Matrix} matrix + * @param {number} target + * @param {number[]} sources + * @param {S101Socket} client + * @param {boolean} response + */ + disconnectMatrixTarget(matrix, target, sources, client, response) { + const disconnect = new EmberLib.MatrixConnection(target); + disconnect.setSources([]); + disconnect.disposition = EmberLib.MatrixDisposition.modified; + matrix.setSources(target, []); + if (response) { + this.emit("matrix-disconnect", { + target: target, + sources: sources, + client: client == null ? null : client.remoteAddress() + }); + } + return disconnect; + } + + /** + * + * @param {Matrix} matrix + * @param {number} target + * @param {number[]} sources + * @param {S101Socket} client + * @param {boolean} response + */ + disconnectSources(matrix, target, sources, client, response) { + const disconnect = new EmberLib.MatrixConnection(target); + disconnect.disposition = EmberLib.MatrixDisposition.modified; + matrix.disconnectSources(target, sources); + if (response) { + this.emit("matrix-disconnect", { + target: target, + sources: sources, + client: client == null ? null : client.remoteAddress() + }); + } + return disconnect; + } + + /** + * + * @param {Matrix} matrix + * @param {MatrixConnection} connection + * @param {Matrix} res - result + * @param {S101Socket} client + * @param {boolean} response + */ + preMatrixConnect(matrix, connection, res, client, response) { + const conResult = res[connection.target]; + + if (matrix.contents.type !== EmberLib.MatrixType.nToN && + connection.operation !== EmberLib.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 1) { + if (matrix.contents.type === EmberLib.MatrixType.oneToOne) { + // if the source is being used already, disconnect it from current target. + const currentTargets = matrix.getSourceConnections(connection.sources[0]); + if (currentTargets.length === 1 && currentTargets[0] !== connection.target) { + res.connections[currentTargets[0]] = + this.disconnectMatrixTarget(matrix, currentTargets[0], connection.sources, client, response); + } + } + // if the target is connected already, disconnect it + if (matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length === 1) { + if (matrix.contents.type === EmberLib.MatrixType.oneToN) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + if (disconnectSource >= 0 && disconnectSource != connection.sources[0]) { + connection.sources = [disconnectSource]; + } + else { + // do nothing => set disposition to bypass further processing + conResult.disposition = EmberLib.MatrixDisposition.tally; + } + } + } + if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { + this.disconnectMatrixTarget(matrix, connection.target, matrix.connections[connection.target].sources, client, response) + } + else if (matrix.contents.type === EmberLib.MatrixType.oneToOne) { + // let's change the request into a disconnect + connection.operation = EmberLib.MatrixOperation.disconnect; + } + } + } + } + + applyMatrixConnect(matrix, connection, conResult, client, response) { + // Apply changes + let emitType; + if ((connection.operation == null) || + (connection.operation.value == EmberLib.MatrixOperation.absolute)) { + matrix.setSources(connection.target, connection.sources); + emitType = "matrix-change"; + } + else if (connection.operation == EmberLib.MatrixOperation.connect) { + matrix.connectSources(connection.target, connection.sources); + emitType = "matrix-connect"; + } + conResult.disposition = EmberLib.MatrixDisposition.modified; + if (response && emitType != null) { + // We got a request so emit something. + this.emit(emitType, { + target: connection.target, + sources: connection.sources, + client: client == null ? null : client.remoteAddress() + }); + } + } + + /** + * + * @param {Matrix} matrix + * @param {MatrixConnection} connection + * @param {Matrix} res - result + * @param {S101Socket} client + * @param {boolean} response + */ + applyMatrixOneToNDisconnect(matrix, connection, res, client, response) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + const conResult = res[connection.target]; + if (disconnectSource >= 0 && disconnectSource != connection.sources[0]) { + if (response) { + this.server.emit("matrix-disconnect", { + target: connection.target, + sources: matrix.connections[connection.target].sources, + client: client == null ? null : client.remoteAddress() + }); + } + matrix.setSources(connection.target, [disconnectSource]); + conResult.disposition = EmberLib.MatrixDisposition.modified; + } + else { + // do nothing + conResult.disposition = EmberLib.MatrixDisposition.tally; + } + } + } + /** * * @param {TreeNode} element diff --git a/EmberServer/MatrixHandlers.js b/EmberServer/MatrixHandlers.js index 1e47d8e..72ff80a 100755 --- a/EmberServer/MatrixHandlers.js +++ b/EmberServer/MatrixHandlers.js @@ -1,5 +1,5 @@ "use strict"; -const ember = require('../EmberLib'); +const EmberLib = require('../EmberLib'); const ServerEvents = require("./ServerEvents"); const winston = require("winston"); @@ -15,7 +15,8 @@ class MatrixHandlers { /** * * @param {Matrix} matrix - * @param {number} targetID + * @param {number} targetID + * @return {number} */ getDisconnectSource(matrix, targetID) { if (matrix.defaultSources) { @@ -35,7 +36,7 @@ class MatrixHandlers { return matrix.defaultSources[targetID].contents.value; } } - return null; + return -1; } /** @@ -46,17 +47,18 @@ class MatrixHandlers { * @param {boolean} response=true */ handleMatrixConnections(client, matrix, connections, response = true) { - let res,conResult; + let res; // response + let conResult; let root; // ember message root winston.debug("Handling Matrix Connection"); if (client != null && client.request.isQualified()) { - root = new ember.Root(); - res = new ember.QualifiedMatrix(matrix.getPath()); + root = new EmberLib.Root(); + res = new EmberLib.QualifiedMatrix(matrix.getPath()); //root.elements = [res]; // do not use addchild or the element will get removed from the tree. root.addElement(res); } else { - res = new ember.MatrixNode(matrix.number); + res = new EmberLib.MatrixNode(matrix.number); root = matrix._parent.getTreeBranch(res); } res.connections = {}; @@ -64,155 +66,65 @@ class MatrixHandlers { if (!connections.hasOwnProperty(id)) { continue; } - let connection = connections[id]; + const connection = connections[id]; const src = client == null ? "local" : `${client.socket.remoteAddress}:${client.socket.remotePort}`; this.server.emit("event", ServerEvents.MATRIX_CONNECTION( matrix.contents.identifier,matrix.getPath(),src,id,connection.sources )); - conResult = new ember.MatrixConnection(connection.target); - let emitType; + conResult = new EmberLib.MatrixConnection(connection.target); res.connections[connection.target] = conResult; if (matrix.connections[connection.target].isLocked()) { - conResult.disposition = ember.MatrixDisposition.locked; + conResult.disposition = EmberLib.MatrixDisposition.locked; } - else if (matrix.contents.type !== ember.MatrixType.nToN && - connection.operation !== ember.MatrixOperation.disconnect && - connection.sources != null && connection.sources.length === 1) { - if (matrix.contents.type === ember.MatrixType.oneToOne) { - // if the source is being used already, disconnect it. - const targets = matrix.getSourceConnections(connection.sources[0]); - if (targets.length === 1 && targets[0] !== connection.target) { - const disconnect = new ember.MatrixConnection(targets[0]); - disconnect.setSources([]); - disconnect.disposition = ember.MatrixDisposition.modified; - res.connections[targets[0]] = disconnect; - matrix.setSources(targets[0], []); - if (response) { - this.server.emit("matrix-disconnect", { - target: targets[0], - sources: connection.sources, - client: client == null ? null : client.remoteAddress() - }); - } - } - } - // if the target is connected already, disconnect it - if (matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length === 1) { - if (matrix.contents.type === ember.MatrixType.oneToN) { - const disconnectSource = this.getDisconnectSource(matrix, connection.target); - if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { - if (disconnectSource != null && disconnectSource != -1 && - disconnectSource != connection.sources[0]) { - connection.sources = [disconnectSource]; - } - else { - // do nothing - connection.operarion = ember.MatrixOperation.tally; - } - } - } - if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { - const source = matrix.connections[connection.target].sources[0]; - matrix.setSources(connection.target, []); - if (response) { - this.server.emit("matrix-disconnect", { - target: connection.target, - sources: [source], - client: client == null ? null : client.remoteAddress() - }); - } - } - else if (matrix.contents.type === ember.MatrixType.oneToOne) { - // let's change the request into a disconnect - connection.operation = ember.MatrixOperation.disconnect; - } - } + else { + // Call pre-processing function + this.server.preMatrixConnect(matrix, connection, res, client, response); } - if (connection.operation !== ember.MatrixOperation.disconnect && - connection.sources != null && connection.sources.length > 0 && - matrix.canConnect(connection.target,connection.sources,connection.operation)) { - // Apply changes - if ((connection.operation == null) || - (connection.operation.value == ember.MatrixOperation.absolute)) { - matrix.setSources(connection.target, connection.sources); - emitType = "matrix-change"; + if (conResult.disposition == null) { + // No decision made yet + if (connection.operation !== EmberLib.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length > 0 && + matrix.canConnect(connection.target,connection.sources,connection.operation)) { + this.server.applyMatrixConnect(matrix, connection, conResult, client, response); } - else if (connection.operation == ember.MatrixOperation.connect) { - matrix.connectSources(connection.target, connection.sources); - emitType = "matrix-connect"; + else if (connection.operation !== EmberLib.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 0 && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // let's disconnect + conResult = this.server.disconnectMatrixTarget( + matrix, connection.target, + matrix.connections[connection.target].sources, + client, + response); } - conResult.disposition = ember.MatrixDisposition.modified; - } - else if (connection.operation !== ember.MatrixOperation.disconnect && - connection.sources != null && connection.sources.length === 0 && - matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length > 0) { - // let's disconnect - if (response) { - this.server.emit("matrix-disconnect", { - target: connection.target, - sources: matrix.connections[connection.target].sources, - client: client == null ? null : client.remoteAddress() - }); - } - matrix.setSources(connection.target, []); - conResult.disposition = ember.MatrixDisposition.modified; - } - else if (connection.operation === ember.MatrixOperation.disconnect && - matrix.connections[connection.target].sources != null && - matrix.connections[connection.target].sources.length > 0) { - // Disconnect - if (matrix.contents.type === ember.MatrixType.oneToN) { - const disconnectSource = this.getDisconnectSource(matrix, connection.target); - if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { - if (disconnectSource != null && disconnectSource != -1 && - disconnectSource != connection.sources[0]) { - if (response) { - this.server.emit("matrix-disconnect", { - target: connection.target, - sources: matrix.connections[connection.target].sources, - client: client == null ? null : client.remoteAddress() - }); - } - matrix.setSources(connection.target, [disconnectSource]); - connection.operarion = ember.MatrixOperation.modified; - } - else { - // do nothing - connection.operarion = ember.MatrixOperation.tally; - } + else if (connection.operation === EmberLib.MatrixOperation.disconnect && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // Disconnect + if (matrix.contents.type === EmberLib.MatrixType.oneToN) { + this.server.applyMatrixOneToNDisconnect() + } + else { + conResult = this.server.disconnectSources(matrix, connection.target, connection.sources, client, response); } - } - else { - matrix.disconnectSources(connection.target, connection.sources); - conResult.disposition = ember.MatrixDisposition.modified; - emitType = "matrix-disconnect"; } } - else if (conResult.disposition !== ember.MatrixDisposition.locked){ + if (conResult.disposition == null){ winston.debug(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); - conResult.disposition = ember.MatrixDisposition.tally; + conResult.disposition = EmberLib.MatrixDisposition.tally; } // Send response or update subscribers. - conResult.sources = matrix.connections[connection.target].sources; - if (response) { - // We got a request so emit something. - this.server.emit(emitType, { - target: connection.target, - sources: connection.sources, - client: client == null ? null : client.remoteAddress() - }); - } + conResult.sources = matrix.connections[connection.target].sources; } if (client != null) { client.sendBERNode(root); } - if (conResult != null && conResult.disposition !== ember.MatrixDisposition.tally) { + if (conResult != null && conResult.disposition !== EmberLib.MatrixDisposition.tally) { winston.debug("Updating subscribers for matrix change"); this.server.updateSubscribers(matrix.getPath(), root, client); } @@ -220,4 +132,4 @@ class MatrixHandlers { } -module.exports = MatrixHandlers; \ No newline at end of file +module.exports = MatrixHandlers; diff --git a/EmberServer/QualifiedHandlers.js b/EmberServer/QualifiedHandlers.js index 21ec5c2..db73742 100755 --- a/EmberServer/QualifiedHandlers.js +++ b/EmberServer/QualifiedHandlers.js @@ -40,7 +40,7 @@ class QualifiedHandlers extends MatrixHandlers { if (node.hasChildren()) { for(let child of node.children) { if (child.isCommand()) { - this.handleCommand(client, element, child); + this.server._handlers.handleCommand(client, element, child); } break; } diff --git a/package.json b/package.json index 1ad608a..c050650 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.4.3", + "version": "2.5.0", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/test/EmberClient.test.js b/test/EmberClient.test.js index c9f20a7..66f66cd 100755 --- a/test/EmberClient.test.js +++ b/test/EmberClient.test.js @@ -4,7 +4,7 @@ const Decoder = require('../EmberLib').DecodeBuffer; const EmberClient = require("../EmberClient"); const HOST = "127.0.0.1"; -const PORT = 9008; +const PORT = 9010; function getRoot() { return new Promise((resolve, reject) => { @@ -44,7 +44,7 @@ describe("EmberClient", () => { }); afterEach(() => { - server.close(); + return server.close(); }); it("should be able to fetch a specific node", () => { From e68ecaad15d2393ff22a1b929fa330e599633739 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 23 Jan 2020 11:21:22 +0100 Subject: [PATCH 143/162] Fix matrix handling on server side --- EmberServer/EmberServer.js | 4 ++-- EmberServer/MatrixHandlers.js | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 1fc82c2..b60b50c 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -283,7 +283,7 @@ class TreeServer extends EventEmitter{ * @param {boolean} response */ preMatrixConnect(matrix, connection, res, client, response) { - const conResult = res[connection.target]; + const conResult = res.connections[connection.target]; if (matrix.contents.type !== EmberLib.MatrixType.nToN && connection.operation !== EmberLib.MatrixOperation.disconnect && @@ -356,7 +356,7 @@ class TreeServer extends EventEmitter{ applyMatrixOneToNDisconnect(matrix, connection, res, client, response) { const disconnectSource = this.getDisconnectSource(matrix, connection.target); if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { - const conResult = res[connection.target]; + const conResult = res.connections[connection.target]; if (disconnectSource >= 0 && disconnectSource != connection.sources[0]) { if (response) { this.server.emit("matrix-disconnect", { diff --git a/EmberServer/MatrixHandlers.js b/EmberServer/MatrixHandlers.js index 72ff80a..0bb9f9c 100755 --- a/EmberServer/MatrixHandlers.js +++ b/EmberServer/MatrixHandlers.js @@ -105,7 +105,7 @@ class MatrixHandlers { matrix.connections[connection.target].sources.length > 0) { // Disconnect if (matrix.contents.type === EmberLib.MatrixType.oneToN) { - this.server.applyMatrixOneToNDisconnect() + this.server.applyMatrixOneToNDisconnect(matrix, connection, res, client, response); } else { conResult = this.server.disconnectSources(matrix, connection.target, connection.sources, client, response); @@ -113,7 +113,7 @@ class MatrixHandlers { } } if (conResult.disposition == null){ - winston.debug(`Invalid Matrix operation ${connection.operarion} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); + winston.debug(`Invalid Matrix operation ${connection.operation} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); conResult.disposition = EmberLib.MatrixDisposition.tally; } diff --git a/package.json b/package.json index c050650..61328cb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.0", + "version": "2.5.1", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From ec83eb6d98b2f087cc06db27fe283025b081ef96 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 23 Jan 2020 16:11:03 +0100 Subject: [PATCH 144/162] Fixed tests. Changed server listen function to return promise --- EmberServer/EmberServer.js | 19 +------------------ EmberSocket/S101Server.js | 39 +++++++++++++++++++------------------- Errors.js | 6 ++++++ package.json | 2 +- test/Server.test.js | 26 +++++++------------------ 5 files changed, 35 insertions(+), 57 deletions(-) diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index b60b50c..23fb37d 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -17,7 +17,6 @@ class TreeServer extends EventEmitter{ constructor(host, port, tree) { super(); this._debug = false; - this.callback = undefined; this.timeoutValue = 2000; this.server = new S101Server(host, port); this.tree = tree; @@ -28,10 +27,6 @@ class TreeServer extends EventEmitter{ this.server.on('listening', () => { winston.debug("listening"); this.emit('listening'); - if (this.callback != null) { - this.callback(); - this.callback = undefined; - } }); this.server.on('connection', client => { @@ -68,9 +63,6 @@ class TreeServer extends EventEmitter{ this.server.on("error", (e) => { this.emit("error", e); - if (this.callback != null) { - this.callback(e); - } }); } @@ -187,16 +179,7 @@ class TreeServer extends EventEmitter{ * @returns {Promise} */ listen() { - return new Promise((resolve, reject) => { - this.callback = e => { - this.callback = null; - if (e == null) { - return resolve(); - } - return reject(e); - }; - this.server.listen(); - }); + return this.server.listen(); } /** diff --git a/EmberSocket/S101Server.js b/EmberSocket/S101Server.js index 9660931..d4e80ec 100755 --- a/EmberSocket/S101Server.js +++ b/EmberSocket/S101Server.js @@ -2,6 +2,7 @@ const EventEmitter = require('events').EventEmitter; const S101Socket = require("./S101Socket"); const net = require('net'); +const Errors = require("../Errors"); class S101Server extends EventEmitter { /** @@ -26,27 +27,27 @@ class S101Server extends EventEmitter { this.emit("connection", client); } /** - * + * @returns {Promise} */ - listen () { - if (this.status !== "disconnected") { - return; - } - - this.server = net.createServer((socket) => { - this.addClient(socket); - }); - - this.server.on("error", (e) => { - this.emit("error", e); + listen() { + return new Promise((resolve, reject) => { + if (this.status !== "disconnected") { + return reject(new Errors.S101SocketError("Already listening")); + } + this.server = net.createServer((socket) => { + this.addClient(socket); + }).on("error", (e) => { + this.emit("error", e); + if (this.status === "disconnected") { + return reject(e); + } + }).on("listening", () => { + this.emit("listening"); + this.status = "listening"; + resolve(); + }); + this.server.listen(this.port, this.address); }); - - this.server.on("listening", () => { - this.emit("listening"); - this.status = "listening"; - }); - - this.server.listen(this.port, this.address); } } diff --git a/Errors.js b/Errors.js index 01273d6..dcffe41 100755 --- a/Errors.js +++ b/Errors.js @@ -25,6 +25,12 @@ class UnimplementedEmberTypeError extends Error { module.exports.UnimplementedEmberTypeError = UnimplementedEmberTypeError; +class S101SocketError extends Error { + constructor(message) { + super(message); + } +} +module.exports.S101SocketError = S101SocketError; class ASN1Error extends Error { constructor(message) { diff --git a/package.json b/package.json index 61328cb..0252389 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.1", + "version": "2.5.2", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/test/Server.test.js b/test/Server.test.js index 5dac151..458b29b 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -927,12 +927,12 @@ describe("server", function() { const PARAMETER_PATH = "0.0.1"; const jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); - server = new EmberServer(LOCALHOST, PORT, root); + const server = new EmberServer(LOCALHOST, PORT, root); const js = server.toJSON(); expect(js[0].children[0].children[1].path).toBe(PARAMETER_PATH); }); it("should have a toJSON and return empty array if no tree", () => { - server = new EmberServer(LOCALHOST, PORT, null); + const server = new EmberServer(LOCALHOST, PORT, null); const js = server.toJSON(); expect(js).toBeDefined(); expect(js.length).toBe(0); @@ -944,7 +944,7 @@ describe("server", function() { const VALUE = "Gilles Dufour" const jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); - server = new EmberServer(LOCALHOST, PORT, root); + const server = new EmberServer(LOCALHOST, PORT, root); const newParam = new EmberLib.Parameter(1); newParam.contents = new EmberLib.ParameterContents(VALUE); newParam.path = PARAMETER_PATH; @@ -955,7 +955,7 @@ describe("server", function() { const VALUE = "Gilles Dufour" const jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); - server = new EmberServer(LOCALHOST, PORT, root); + const server = new EmberServer(LOCALHOST, PORT, root); const newParam = new EmberLib.Parameter(1); newParam.contents = new EmberLib.ParameterContents(VALUE); let error; @@ -973,7 +973,7 @@ describe("server", function() { const VALUE = "Gilles Dufour" const jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); - server = new EmberServer(LOCALHOST, PORT, root); + const server = new EmberServer(LOCALHOST, PORT, root); server.tree.getElementByPath(PARAMETER_PATH)._parent = null; const newParam = new EmberLib.Parameter(1); newParam.contents = new EmberLib.ParameterContents(VALUE); @@ -1001,22 +1001,10 @@ describe("server", function() { expect(error).toBeDefined(); expect(error.message).toBe(ERROR_MESSAGE); }); - it("should catch error and use callback if present", () => { - const jsonTree = jsonRoot(); - const root = EmberServer.JSONtoTree(jsonTree); - const ERROR_MESSAGE = "gdnet internal error"; - server = new EmberServer(LOCALHOST, PORT, root); - let error; - server.on("error", e => {}); - server.callback = e => {error = e;} - server.server.emit("error", new Error(ERROR_MESSAGE)); - expect(error).toBeDefined(); - expect(error.message).toBe(ERROR_MESSAGE); - }); it("should catch tcp server disconnected message, and clean up clients", () => { const jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); - server = new EmberServer(LOCALHOST, PORT, root); + const server = new EmberServer(LOCALHOST, PORT, root); server.clients.add(new EmberClient(LOCALHOST, PORT)); let count = 0; server.on("disconnected", () => {count++;}); @@ -1028,7 +1016,7 @@ describe("server", function() { const jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); const ERROR_MESSAGE = "gdnet internal error"; - server = new EmberServer(LOCALHOST, PORT, root); + const server = new EmberServer(LOCALHOST, PORT, root); const client = new EmberClient(LOCALHOST, PORT); client.remoteAddress = () => {return "address";} let info; From 5b1377d16c82d1c5d95844f836fa8f609d98546c Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 29 Jan 2020 09:16:59 +0100 Subject: [PATCH 145/162] Added example for setValue --- README.md | 18 +++++++++++++++++- package.json | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8de727..eec1086 100755 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ communication protocols and user interfaces; this module allows those to be integrated with Ember+ somewhat more easily than the reference libember C++ implementation. -This version support following ember objects : Node, Parameter, Matrix, QualifiedNode, +This version support following ember objects : Node, Parameter, Matrix, Function, QualifiedNode, QualifiedParameter, QualifiedMatrix, QualifiedFunction. It has been tested with EVS XT4k and Embrionix IP solutions. @@ -17,6 +17,10 @@ the way the lib is used so that now it uses Promise Server has been added in version 1.6.0. +Support for StreamCollection and UDP is available in a private branch. +If you want access to full version and/or would like support for new features or integration +with your projects, please contact Gilles Dufour - dufour.gilles@gmail.com + ## Example usage ### Client @@ -78,6 +82,18 @@ client.connect()) ; ``` +### Setting new value + +```javascript +client = new EmberClient(LOCALHOST, PORT); +await client.connect() +await client.getDirectory(); +await client.getElementByPath("0.0.1"); +await client.setValue(client.root.getElementByPath("0.0.1"), "gdnet"); +console.log("result", server.tree.getElementByPath("0.0.1").contents.value) +return client.disconnect().then(() => { console.log("disconnected")}); +``` + ### Invoking Function ```javascript diff --git a/package.json b/package.json index 0252389..bc95542 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.2", + "version": "2.5.3", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 767779f23b0eeb04685e11e24b3cd41f6bc7fc55 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 1 Feb 2020 15:17:23 +0100 Subject: [PATCH 146/162] Fix duplicate dataIn --- EmberSocket/S101Client.js | 12 +----------- package.json | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/EmberSocket/S101Client.js b/EmberSocket/S101Client.js index d34ce97..f4dee32 100755 --- a/EmberSocket/S101Client.js +++ b/EmberSocket/S101Client.js @@ -41,17 +41,7 @@ class S101Client extends S101Socket { this.startKeepAlive(); this.emit('connected'); }) - .on('error', (e) => { - this.emit("error", e); - }) - .once("timeout", connectTimeoutListener) - .on('data', (data) => { - if (this.isConnected()) { - this.codec.dataIn(data); - } - }) - .on('close', this.handleClose) - .on("end", this.handleClose); + once("timeout", connectTimeoutListener); this._initSocket(); } } diff --git a/package.json b/package.json index bc95542..fc1fe93 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.3", + "version": "2.5.4", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From a339f14388d59ec53f2fefa02fb79ed77034389c Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 1 Feb 2020 17:13:02 +0100 Subject: [PATCH 147/162] Fix FunctionContent result encoding --- EmberLib/FunctionContent.js | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/EmberLib/FunctionContent.js b/EmberLib/FunctionContent.js index cd368b8..ec13aaf 100755 --- a/EmberLib/FunctionContent.js +++ b/EmberLib/FunctionContent.js @@ -44,12 +44,14 @@ class FunctionContent { if(this.result != null && this.result.length > 0) { ber.startSequence(BER.CONTEXT(3)); + ber.startSequence(BER.EMBER_SEQUENCE); for(let i = 0; i < this.result.length; i++) { ber.startSequence(BER.CONTEXT(0)); /** @type {FunctionArgument} */ this.result[i].encode(ber); ber.endSequence(); - } + } + ber.endSequence(); ber.endSequence(); // BER.CONTEXT(3) } diff --git a/package.json b/package.json index fc1fe93..9557785 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.4", + "version": "2.5.5", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From ed89db5468e6dfe7227dbb863761959bd7de28f4 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 14 Feb 2020 17:16:31 +0100 Subject: [PATCH 148/162] fix expand and function content encoding/decoding --- EmberClient/EmberClient.js | 10 +++------- EmberLib/FunctionContent.js | 11 ++++++----- EmberSocket/S101Client.js | 2 +- README.md | 3 ++- package.json | 2 +- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index b5a61f3..4fa9502 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -137,7 +137,7 @@ class EmberClient extends EventEmitter { */ _handleNode(parent, node) { let n = parent.getElementByNumber(node.getNumber()); - if (n === null) { + if (n == null) { parent.addChild(node); n = node; } else { @@ -260,14 +260,11 @@ class EmberClient extends EventEmitter { * @returns {Promise} */ expand(node, callback = null) { - if (node == null) { - return Promise.reject(new Errors.InvalidEmberNode("Invalid null node")); - } - if (node.isParameter() || node.isMatrix() || node.isFunction()) { + if (node != null && (node.isParameter() || node.isMatrix() || node.isFunction())) { return this.getDirectory(node); } return this.getDirectory(node, callback).then(res => { - const children = node.getChildren(); + const children = node == null ? res == null ? null : res.getChildren() : node.getChildren(); if ((res == null) || (children == null)) { winston.debug("No more children for ", node); return; @@ -289,7 +286,6 @@ class EmberClient extends EventEmitter { return directChildren }); } - /** * * @param {TreeNode} qnode diff --git a/EmberLib/FunctionContent.js b/EmberLib/FunctionContent.js index ec13aaf..737beae 100755 --- a/EmberLib/FunctionContent.js +++ b/EmberLib/FunctionContent.js @@ -81,18 +81,19 @@ class FunctionContent { fc.description = seq.readString(BER.EMBER_STRING); } else if(tag == BER.CONTEXT(2)) { fc.arguments = []; - let dataSeq = seq.getSequence(BER.EMBER_SEQUENCE); + let dataSeq = seq.getSequence(BER.EMBER_SEQUENCE); while(dataSeq.remain > 0) { seq = dataSeq.getSequence(BER.CONTEXT(0)); fc.arguments.push(FunctionArgument.decode(seq)); } } else if(tag == BER.CONTEXT(3)) { fc.result = []; - while(seq.remain > 0) { - tag = seq.peek(); - const dataSeq = seq.getSequence(tag); + let dataSeq = seq.getSequence(BER.EMBER_SEQUENCE); + while(dataSeq.remain > 0) { + tag = dataSeq.peek(); if (tag === BER.CONTEXT(0)) { - fc.result.push(FunctionArgument.decode(dataSeq)); + const fcSeq = dataSeq.getSequence(tag); + fc.result.push(FunctionArgument.decode(fcSeq)); } else { throw new errors.UnimplementedEmberTypeError(tag); diff --git a/EmberSocket/S101Client.js b/EmberSocket/S101Client.js index f4dee32..d1d2527 100755 --- a/EmberSocket/S101Client.js +++ b/EmberSocket/S101Client.js @@ -41,7 +41,7 @@ class S101Client extends S101Socket { this.startKeepAlive(); this.emit('connected'); }) - once("timeout", connectTimeoutListener); + .once("timeout", connectTimeoutListener); this._initSocket(); } } diff --git a/README.md b/README.md index eec1086..e2c77ae 100755 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ the way the lib is used so that now it uses Promise Server has been added in version 1.6.0. -Support for StreamCollection and UDP is available in a private branch. +Support for StreamCollection, UDP, packet stats/rate, support for tree with size higher than 8M +is available in a private branch. If you want access to full version and/or would like support for new features or integration with your projects, please contact Gilles Dufour - dufour.gilles@gmail.com diff --git a/package.json b/package.json index 9557785..9e9bc19 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.5", + "version": "2.5.6", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { From 3b382b7352dfde79e295b94956f1f76a765dd5e8 Mon Sep 17 00:00:00 2001 From: olzzon Date: Wed, 26 Feb 2020 08:56:13 +0100 Subject: [PATCH 149/162] feat: hack setValue to immediately resolve - added setValueWithHacksaw() function from NRKNO fork --- EmberClient/EmberClient.js | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index 4fa9502..ed8ce53 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -619,6 +619,51 @@ class EmberClient extends EventEmitter { }); } + /** + * + * @param {TreeNode} node + * @param {string|number} value + * @returns {Promise} + */ + setValueWithHacksaw(node, value) { + return new Promise((resolve, reject) => { + if (!node.isParameter()) { + reject(new Errors.EmberAccessError('not a Parameter')); + } + else { + this.addRequest({node: node, func: error => { + if (error) { + this._finishRequest(); + reject(error); + return; + } + + this._callback = (error, node) => { + // this._finishRequest(); + // this._callback = null; + if (error) { + reject(error); + } + else { + + // resolve(node); + } + }; + winston.debug('setValue sending ...', node.getPath(), value); + this._client.sendBERNode(node.setValue(value)); + + // We now immediately finish & resolve so we can't get any timeouts ever + // This is a pretty ugly hack, but as far as I can tell it doesn't bring + // any negative consequences regarding the execution and resolving of other + // functions. We need this because if the node already has the value we are + // setting it too, it will cause a timeout. + this._finishRequest(); + this._callback = null; + }}); + } + }); + } + /** * * @param {TreeNode} qnode From 82618c3ccc13b6355c893a47bafd1d226c7e86ae Mon Sep 17 00:00:00 2001 From: olzzon Date: Wed, 26 Feb 2020 13:03:57 +0100 Subject: [PATCH 150/162] feat: setValueNoAck - rename of function and cleanup --- EmberClient/EmberClient.js | 46 ++++++++++++-------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index ed8ce53..8a93d1f 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -625,43 +625,25 @@ class EmberClient extends EventEmitter { * @param {string|number} value * @returns {Promise} */ - setValueWithHacksaw(node, value) { + setValueNoAck(node, value) { return new Promise((resolve, reject) => { if (!node.isParameter()) { reject(new Errors.EmberAccessError('not a Parameter')); + return; } - else { - this.addRequest({node: node, func: error => { - if (error) { - this._finishRequest(); - reject(error); - return; - } - - this._callback = (error, node) => { - // this._finishRequest(); - // this._callback = null; - if (error) { - reject(error); - } - else { - - // resolve(node); - } - }; - winston.debug('setValue sending ...', node.getPath(), value); - this._client.sendBERNode(node.setValue(value)); - - // We now immediately finish & resolve so we can't get any timeouts ever - // This is a pretty ugly hack, but as far as I can tell it doesn't bring - // any negative consequences regarding the execution and resolving of other - // functions. We need this because if the node already has the value we are - // setting it too, it will cause a timeout. + this.addRequest({node: node, func: error => { + if (error) { this._finishRequest(); - this._callback = null; - }}); - } - }); + reject(error); + return; + } + winston.debug('setValue sending ...', node.getPath(), value); + this._client.sendBERNode(node.setValue(value)); + + this._finishRequest(); + this._callback = null; + }}); + }) } /** From 91100007882c08b814b84424726ea21543e4345d Mon Sep 17 00:00:00 2001 From: olzzon Date: Wed, 26 Feb 2020 13:21:36 +0100 Subject: [PATCH 151/162] =?UTF-8?q?fix:=20promise=20didn=C2=B4t=20resolve?= =?UTF-8?q?=20doc:=20Added=20comments=20to=20explain=20reason=20for=20hack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EmberClient/EmberClient.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index 8a93d1f..541e91b 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -626,6 +626,11 @@ class EmberClient extends EventEmitter { * @returns {Promise} */ setValueNoAck(node, value) { + // This function immediately finish & resolve so we can't get any timeouts ever + // This is a pretty ugly hack, but it doesn't look to bring + // any negative consequences regarding the execution and resolving of other + // functions. It´s needed this because if the node already has the value we are + // setting it too, it will cause a timeout. return new Promise((resolve, reject) => { if (!node.isParameter()) { reject(new Errors.EmberAccessError('not a Parameter')); @@ -642,6 +647,7 @@ class EmberClient extends EventEmitter { this._finishRequest(); this._callback = null; + return resolve(node) }}); }) } From fe9153e750bb08e308f56bbde8e0ddbbc4717777 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sun, 1 Mar 2020 08:42:32 +0100 Subject: [PATCH 152/162] Fix client parameter subscribe --- EmberClient/EmberClient.js | 24 +++++--- EmberLib/QualifiedParameter.js | 22 ------- EmberLib/StreamCollection.js | 102 +++++++++++++++++++++++++++++++ EmberLib/StreamEntry.js | 70 +++++++++++++++++++++ EmberLib/TreeNode.js | 7 +-- EmberServer/EmberServer.js | 9 ++- EmberServer/QualifiedHandlers.js | 1 - package.json | 5 +- 8 files changed, 199 insertions(+), 41 deletions(-) create mode 100755 EmberLib/StreamCollection.js create mode 100755 EmberLib/StreamEntry.js diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js index 4fa9502..c1b33e9 100755 --- a/EmberClient/EmberClient.js +++ b/EmberClient/EmberClient.js @@ -73,7 +73,7 @@ class EmberClient extends EventEmitter { } } catch(e) { - winston.debug(e, root); + winston.error(e, root); if (this._callback) { this._callback(e); } @@ -88,7 +88,7 @@ class EmberClient extends EventEmitter { try { this._makeRequest(); } catch(e) { - winston.debug(e); + winston.error(e); if (this._callback != null) { this._callback(e); } @@ -140,8 +140,8 @@ class EmberClient extends EventEmitter { if (n == null) { parent.addChild(node); n = node; - } else { - n.update(node); + } else if (n.update(node)) { + n.updateSubscribers(); } const children = node.getChildren(); @@ -165,7 +165,9 @@ class EmberClient extends EventEmitter { let element = parent.getElementByPath(node.path); if (element !== null) { this.emit("value-change", node); - element.update(node); + if (element.update(node)) { + element.updateSubscribers(); + } } else { const path = node.path.split("."); @@ -344,17 +346,16 @@ class EmberClient extends EventEmitter { if (nodeElements != null && ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { - winston.debug("Received getDirectory response", node); + winston.debug("Received getDirectory response", node); this._finishRequest(); return resolve(node); // make sure the info is treated before going to next request. } else { winston.debug(node); - winston.debug(new Error(requestedPath)); + winston.error(new Error(requestedPath)); } } }; - winston.debug("Sending getDirectory", qnode); try { this._client.sendBERNode(qnode.getDirectory(callback)); } @@ -420,6 +421,9 @@ class EmberClient extends EventEmitter { // We have this part already. pos++; if (pos >= pathArray.length) { + if (callback) { + node.getDirectory(callback); + } return node; } currentNode = node; @@ -514,7 +518,7 @@ class EmberClient extends EventEmitter { return; } if (error) { - winston.debug("Received getDirectory error", error); + winston.error("Received getDirectory error", error); this._clearTimeout(); // clear the timeout now. The resolve below may take a while. this._finishRequest(); reject(error); @@ -612,7 +616,7 @@ class EmberClient extends EventEmitter { resolve(node); } }; - winston.debug('setValue sending ...', node.getPath(), value); + winston.debug('setValue sending ...', node.getPath(), value); this._client.sendBERNode(node.setValue(value)); }}); } diff --git a/EmberLib/QualifiedParameter.js b/EmberLib/QualifiedParameter.js index 3871823..2ff6d8c 100755 --- a/EmberLib/QualifiedParameter.js +++ b/EmberLib/QualifiedParameter.js @@ -36,28 +36,6 @@ class QualifiedParameter extends QualifiedElement { return r; } - /** - * - * @param {QualifiedParameter} other - */ - update(other) { - if ((other != null) && (other.contents != null)) { - if (this.contents == null) { - this.contents = other.contents; - } - else { - for (var key in other.contents) { - if (key[0] === "_") { - continue; - } - if (other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; - } - } - } - } - return; - } /** * diff --git a/EmberLib/StreamCollection.js b/EmberLib/StreamCollection.js new file mode 100755 index 0000000..4576809 --- /dev/null +++ b/EmberLib/StreamCollection.js @@ -0,0 +1,102 @@ +const BER = require("../ber"); +const StreamEntry = require("./StreamEntry"); + +class StreamCollection { + /** + * + */ + constructor() { + /** @type {Map} */ + this.elements = new Map(); + } + /** + * + * @param {StreamEntry} entry + */ + addEntry(entry) { + this.elements.set(entry.identifier, entry); + } + /** + * + * @param {StreamEntry} entry + */ + removeEntry(entry) { + this.elements.delete(entry.identifier); + } + /** + * + * @param {number} identifier + * @returns {StreamEntry} + */ + getEntry(identifier) { + return this.elements.get(identifier); + } + + /** + * @returns {StreamEntry} + */ + [Symbol.iterator]() { + return this.elements.values(); + } + + /** + * @retuns {number} + */ + size() { + return this.elements.size; + } + + /** + * + * @param {BER.Writer} ber + */ + encode(ber) { + ber.startSequence(StreamCollection.BERID); + for(let [, entry] of this.elements) { + ber.startSequence(BER.CONTEXT(0)); + entry.encode(ber); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * @returns { + * {identifier: number, value: string|number|boolean|Buffer}[] + * } + */ + toJSON() { + const js = []; + for(let [, entry] of this.elements) { + js.push(entry.toJSON()); + } + return js; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(5); + } + + /** + * + * @param {BER.ExtendedReader} ber + * @returns {StreamCollection} + */ + static decode(ber) { + const streamCollection = new StreamCollection(); + const seq = ber.getSequence(this.BERID); + while (seq.remain > 0) { + const rootReader = seq.getSequence(BER.CONTEXT(0)); + while (rootReader.remain > 0) { + const entry = StreamEntry.decode(rootReader); + streamCollection.addEntry(entry); + } + } + return streamCollection; + } +} + +module.exports = StreamCollection; \ No newline at end of file diff --git a/EmberLib/StreamEntry.js b/EmberLib/StreamEntry.js new file mode 100755 index 0000000..2e6b10e --- /dev/null +++ b/EmberLib/StreamEntry.js @@ -0,0 +1,70 @@ +const BER = require("../ber"); +const Errors = require("../Errors"); + +class StreamEntry { + /** + * + * @param {number} identifier + * @param {string|number|boolean|Buffer} value + */ + constructor(identifier, value ) { + this.identifier = identifier; + this.value = value; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(StreamEntry.BERID); + if (this.identifier != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.identifier); + ber.endSequence(); + } + if (this.value != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeValue(this.value); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * @returns {{ + * identifier: number, + * value: string|number + * }} + */ + toJSON() { + return { + identifier: this.identifier, + value: this.value + } + } + + static get BERID() { + return BER.APPLICATION(5); + } + + static decode(ber) { + const entry = new StreamEntry(); + const seq = ber.getSequence(this.BERID); + while(seq.remain > 0) { + const tag = seq.peek(); + const data = seq.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + entry.identifier = data.readInt(); + } else if(tag == BER.CONTEXT(1)) { + entry.value = data.readValue(); + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return entry; + } +} + +module.exports = StreamEntry; \ No newline at end of file diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index 2cb7919..69b6ebe 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -16,8 +16,7 @@ class TreeNode extends ElementInterface { _isSubscribable(callback) { return (callback != null && - ((this.isParameter() && this.isStream()) || - this.isMatrix())); + (this.isParameter() || this.isMatrix())); } _subscribe(callback) { @@ -236,7 +235,7 @@ class TreeNode extends ElementInterface { * @param {function} callback */ getDirectory(callback) { - if (this._isSubscribable(callback)) { + if (this._isSubscribable(callback) && !this.isStream()) { this._subscribe(callback); } return this.getCommand(new Command(COMMAND_GETDIRECTORY)); @@ -471,7 +470,7 @@ class TreeNode extends ElementInterface { * @param {function} callback */ subscribe(callback) { - if (this._isSubscribable(callback)) { + if (this._isSubscribable(callback) && this.isStream()) { this._subscribe(callback); } return this.getCommand(new Command(COMMAND_SUBSCRIBE)); diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 23fb37d..741f045 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -4,8 +4,8 @@ const EmberLib = require('../EmberLib'); const JSONParser = require("./JSONParser"); const ElementHandlers = require("./ElementHandlers"); const ServerEvents = require("./ServerEvents"); -const winston = require("winston"); const Errors = require("../Errors"); +const winston = require("winston"); class TreeServer extends EventEmitter{ /** @@ -16,7 +16,7 @@ class TreeServer extends EventEmitter{ */ constructor(host, port, tree) { super(); - this._debug = false; + this._debug = true; this.timeoutValue = 2000; this.server = new S101Server(host, port); this.tree = tree; @@ -390,6 +390,7 @@ class TreeServer extends EventEmitter{ setValue(element, value, origin, key) { return new Promise(resolve => { // Change the element value if write access permitted. + winston.debug("New Setvalue request"); if (element.contents == null) { return resolve(); } @@ -398,6 +399,7 @@ class TreeServer extends EventEmitter{ (element.contents.access != null) && (element.contents.access.value > 1)) { element.contents.value = value; + winston.debug("New value ", value, "path", element.getPath()); const res = this.getResponse(element); this.updateSubscribers(element.getPath(),res, origin); } @@ -460,6 +462,7 @@ class TreeServer extends EventEmitter{ */ updateSubscribers(path, response, origin) { if (this.subscribers[path] == null) { + winston.debug("No subscribers for", path); return; } @@ -468,10 +471,12 @@ class TreeServer extends EventEmitter{ continue; // already sent the response to origin } if (this.clients.has(client)) { + winston.debug("Sending new value to", client.remoteAddress()); client.queueMessage(response); } else { // clean up subscribers - client is gone + winston.debug("deleting client"); this.subscribers[path].delete(client); } } diff --git a/EmberServer/QualifiedHandlers.js b/EmberServer/QualifiedHandlers.js index db73742..43755ff 100755 --- a/EmberServer/QualifiedHandlers.js +++ b/EmberServer/QualifiedHandlers.js @@ -68,7 +68,6 @@ class QualifiedHandlers extends MatrixHandlers { this.server.setValue(element, parameter.contents.value, client); let res = this.server.getQualifiedResponse(element); client.sendBERNode(res) - this.server.updateSubscribers(element.getPath(), res, client); } } } diff --git a/package.json b/package.json index 9e9bc19..3c39074 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.6", + "version": "2.5.7", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { @@ -24,7 +24,8 @@ "long": "^3.2.0", "smart-buffer": "^3.0.3", "winston": "^2.1.1", - "winston-color": "^1.0.0" + "winston-color": "^1.0.0", + "yargs": "^15.1.0" }, "devDependencies": { "eslint": "^5.5.0", From 48037abe55b34072c8c7a1cb35d288af4c2b24e3 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Thu, 5 Mar 2020 15:43:59 +0100 Subject: [PATCH 153/162] Add support for streamDescriptor parsing from json --- .gitignore | 3 +- EmberLib/StreamDescription.js | 10 ++++--- EmberLib/index.js | 2 ++ EmberServer/JSONParser.js | 9 ++++++ package.json | 2 +- serve.js | 55 +++++++++++++++++++++++++++++++++++ test/Server.test.js | 4 ++- test/utils.js | 13 +++++++++ 8 files changed, 91 insertions(+), 7 deletions(-) create mode 100755 serve.js diff --git a/.gitignore b/.gitignore index c882358..e77824c 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # intellij, pycharm, webstorm... /.idea/* - +.vscode node_modules .*.swp +coverage diff --git a/EmberLib/StreamDescription.js b/EmberLib/StreamDescription.js index 53edb69..4915551 100755 --- a/EmberLib/StreamDescription.js +++ b/EmberLib/StreamDescription.js @@ -1,15 +1,17 @@ "use strict"; -const Element = require("./Element"); const BER = require('../ber.js'); const StreamFormat = require("./StreamFormat"); const Errors = require("../Errors"); -class StreamDescription extends Element{ +class StreamDescription { /** * + * @param {number} offset + * @param {StreamFormat} format */ - constructor() { - super(); + constructor(offset, format) { + this.offset = offset; + this.format = format; } /** diff --git a/EmberLib/index.js b/EmberLib/index.js index d3f10f8..9dc3813 100755 --- a/EmberLib/index.js +++ b/EmberLib/index.js @@ -32,6 +32,7 @@ const StringIntegerPair = require("./StringIntegerPair"); const StringIntegerCollection = require("./StringIntegerCollection"); const StreamFormat = require("./StreamFormat"); const StreamDescription = require("./StreamDescription"); +const StreamCollection = require("./StreamCollection"); const Template = require("./Template"); const TemplateElement = require("./TemplateElement"); const QualifiedTemplate = require("./QualifiedTemplate"); @@ -151,6 +152,7 @@ module.exports = { QualifiedTemplate, StreamFormat, StreamDescription, + StreamCollection, StringIntegerPair, StringIntegerCollection, Template, diff --git a/EmberServer/JSONParser.js b/EmberServer/JSONParser.js index 126164a..dfa5015 100755 --- a/EmberServer/JSONParser.js +++ b/EmberServer/JSONParser.js @@ -90,6 +90,15 @@ class JSONParser { else { emberElement.contents.access = ember.ParameterAccess.read; } + if (content.streamDescriptor != null) { + if (content.streamDescriptor.offset == null || content.streamDescriptor.format == null) { + throw new Error("Missing offset or format for streamDescriptor"); + } + emberElement.contents.streamDescriptor = new ember.StreamDescription(); + emberElement.contents.streamDescriptor.offset = content.streamDescriptor.offset; + emberElement.contents.streamDescriptor.format = ember.StreamFormat.get(content.streamDescriptor.format); + delete content.streamDescriptor; + } } else if (content.func != null) { emberElement = new ember.Function(number, content.func); diff --git a/package.json b/package.json index 3c39074..b46f60d 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.7", + "version": "2.5.8", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/serve.js b/serve.js new file mode 100755 index 0000000..9447250 --- /dev/null +++ b/serve.js @@ -0,0 +1,55 @@ +const yargs = require('yargs'); +const { EmberServer, Decoder } = require('./index'); +const { readFileSync } = require('fs'); + +const argv = yargs.options({ + host: { + alias: 'h', + description: 'host name|ip', + default: '0.0.0.0' + }, + + port: { + alias: 'p', + default: 9000, + type: 'number', + description: 'port', + demandOption: true + }, + + file: { + alias: 'f', + description: 'file containing the ber (default) or json tree', + demandOption: true + }, + + json: { + alias: 'j', + type: 'boolean', + description: 'file format is json' + }, + debug: { + alias: 'd', + type: 'boolean', + description: 'debug' + } + +}).help().argv; + +const main = async () => { + const data = readFileSync(argv.file); + const tree = argv.json ? EmberServer.JSONtoTree(JSON.parse(data.toString())) : Decoder(data); + const server = new EmberServer(argv.host, argv.port, tree); + server._debug = true; + console.log(Date.now(), 'starting server'); + if (argv.debug) { + server._debug = true; + } + try { + server.listen(); + } catch (e) { + console.log(e); + } +}; + +main(); diff --git a/test/Server.test.js b/test/Server.test.js index 458b29b..46b0107 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -19,9 +19,11 @@ describe("server", function() { const root = EmberServer.JSONtoTree(jsonTree); expect(root).toBeDefined(); expect(root.elements).toBeDefined(); - expect(root.elements.size).toBe(1); + expect(root.elements.size).toBe(jsonTree.length); expect(root.getElementByNumber(0).contents.identifier).toBe("scoreMaster"); expect(root.getElementByNumber(0).elements.size).toBe(jsonTree[0].children.length); + expect(root.getElementByNumber(1).contents.streamDescriptor instanceof EmberLib.StreamDescription).toBeTruthy(); + expect(root.getElementByNumber(1).contents.streamDescriptor.offset).toBe(jsonTree[1].streamDescriptor.offset); }); it("should throw an error if invalid matrix mode", function() { jsonTree[0].children[1].children[0].mode = "invalid"; diff --git a/test/utils.js b/test/utils.js index f776d62..9cca4a4 100755 --- a/test/utils.js +++ b/test/utils.js @@ -119,6 +119,19 @@ const init = function(_src,_tgt) { ] } ] + }, + { + identifier: "PeakValue_2", + type: 2, + streamIdentifier: 4, + streamDescriptor: { + format: "ieeeFloat32LittleEndian", + offset: 4 + }, + access: 1, + maximum: 20, + minimum: -200, + value: -200 } ]; } From f4624aee4b99af83fac00b007036ba96804c5797 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Wed, 11 Mar 2020 11:35:01 +0100 Subject: [PATCH 154/162] Fixed tests --- package.json | 2 +- test/Server.test.js | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b46f60d..2d5206e 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.8", + "version": "2.5.9", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/test/Server.test.js b/test/Server.test.js index 46b0107..0ec6105 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -17,6 +17,8 @@ describe("server", function() { }); it("should generate an ember tree from json", function() { const root = EmberServer.JSONtoTree(jsonTree); + // JSONtoTree will modify the json object. + jsonTree = jsonRoot(); expect(root).toBeDefined(); expect(root.elements).toBeDefined(); expect(root.elements.size).toBe(jsonTree.length); @@ -47,6 +49,10 @@ describe("server", function() { expect(matrix).toBeDefined(); expect(matrix.contents.maximumConnectsPerTarget).toBe(jsonTree[0].children[1].children[0].maximumConnectsPerTarget); expect(matrix.contents.maximumTotalConnects).toBe(jsonTree[0].children[1].children[0].maximumTotalConnects); + expect(matrix.contents.type).toBe(EmberLib.MatrixType.nToN); + const jMatrix = matrix.toJSON(); + expect(jMatrix.type).toBeDefined(); + expect(jMatrix.mode).toBeDefined(); }); it("should support matrix type oneToOne", function() { jsonTree[0].children[1].children[0].type = "oneToOne"; @@ -67,6 +73,19 @@ describe("server", function() { expect(error).toBeDefined(); expect(error instanceof Errors.InvalidEmberNode).toBeDefined(); }); + it("should generate a matrix with a valid toJSON", function() { + const root = EmberServer.JSONtoTree(jsonTree); + const matrix = root.getElementByPath("0.1.0"); + matrix.connectSources(0, [0]); + matrix.connectSources(1, [1]); + const jMatrix = matrix.toJSON(); + expect(jMatrix.type).toBeDefined(); + expect(jMatrix.type).toBe(matrix.contents.type.value); + expect(jMatrix.mode).toBeDefined(); + expect(jMatrix.mode).toBe(matrix.contents.mode.value); + expect(jMatrix.connections[0].sources.length).toBe(1); + expect(jMatrix.connections[0].sources[0]).toBe(0); + }); }); describe("Server - Client communication", function() { @@ -96,7 +115,7 @@ describe("server", function() { .then(() => { expect(client.root).toBeDefined(); expect(client.root.elements).toBeDefined(); - expect(client.root.elements.size).toBe(1); + expect(client.root.elements.size).toBe(jsonTree.length); expect(client.root.getElementByNumber(0).contents.identifier).toBe("scoreMaster"); return client.getDirectory(client.root.getElementByNumber(0)); }) @@ -958,7 +977,7 @@ describe("server", function() { const jsonTree = jsonRoot(); const root = EmberServer.JSONtoTree(jsonTree); const server = new EmberServer(LOCALHOST, PORT, root); - const newParam = new EmberLib.Parameter(1); + const newParam = new EmberLib.Parameter(1000); newParam.contents = new EmberLib.ParameterContents(VALUE); let error; try { From 089121967153349c37d22298e5b8b1796881071a Mon Sep 17 00:00:00 2001 From: olzzon Date: Sat, 28 Mar 2020 19:34:30 +0100 Subject: [PATCH 155/162] feat: export BER in index.js for manual ASN.1 encoding --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 1de9a28..6727f4f 100755 --- a/index.js +++ b/index.js @@ -4,4 +4,5 @@ const Decoder = EmberLib.DecodeBuffer; const S101 = require("./s101"); const {EmberServer,ServerEvents} = require("./EmberServer"); const {S101Client} = require("./EmberSocket"); -module.exports = {EmberClient, Decoder, EmberLib, EmberServer,ServerEvents, S101, S101Client}; +const BER = require('./ber.js') +module.exports = {EmberClient, Decoder, EmberLib, EmberServer,ServerEvents, S101, S101Client, BER}; From 5baab56ee6f467b327d0750728ea4b173126acda Mon Sep 17 00:00:00 2001 From: Thomas Silvestre Date: Thu, 10 Sep 2020 16:51:23 +0200 Subject: [PATCH 156/162] merge back Gilles Dufour version (#39) (#40) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added supported for Function on server side. Fixed invokationresult decoding * new documentation * Added all matrixConnect and Disconnect functions. Fixed all tests. Modified the Doc. * Add description to label when converting json to tree * Adding validation of connection request on server * fix contributors * Fixing getDirectory when root has more than 1 elements * Added support for subscribe and unsubscribe of streams * fixing matrix absolute connect * Better error when doing getNodeByPath * handle connection for 1-to-1 matrix * add maximumTotalConnects * fix 1 to N matrix connect * Fix 1toN. Disconnect old source and connect to new one * Server should emit disconnect when connecting target with new source * Update doc * Do a disconnect if attempting to connect same src and target * fix disconnect * handle connect with empty sources * added lock/unlock of connection * Fix disconnect for 1toN * Fix function result missing * Added -1 for defaultSources to disable disconnect * fix disconnect when mismatch client server * Fix bug in command handling * fix continuation message * Ignore empty request on server * catch write error and disconnect * Removed all ref to callabcks...using different approach...Added support for subscribe * Fix toJSON for root * version 2 * Finalized version 2.0.0 * version 2 final fixes and good to go * Fixing missing EmberLib file. Added server events * Change server event format to include source ip:port * Fix setValue event to be inline with the other events * Fixed eslint errors * Adding Streams. Fixing bugs. Event/Errors using static value * Added code coverage for Parameter * Matrix code coverage. Bug fixing * Adding test code coverage. EmberLib 100% upto Matrix * Added support for Template and QualifiedTemplate. Improved code coverage. * 92% code coverage * Add missing files. Reformat S101Socket and S101Client * v2.4.2 * use latest asn1 lib * Fixed some client missing promise return. Add server preMatrixConnect to provide above app a way to decide if connection allowed or not * Fix matrix handling on server side * Fixed tests. Changed server listen function to return promise * Added example for setValue * Fix duplicate dataIn * Fix FunctionContent result encoding * fix expand and function content encoding/decoding * feat: hack setValue to immediately resolve - added setValueWithHacksaw() function from NRKNO fork * feat: setValueNoAck - rename of function and cleanup * fix: promise didn´t resolve doc: Added comments to explain reason for hack * Fix client parameter subscribe * Add support for streamDescriptor parsing from json * Fixed tests * feat: export BER in index.js for manual ASN.1 encoding Co-authored-by: Gilles Dufour Co-authored-by: Kasper Olsson Hans --- .gitignore | 3 +- EmberClient/EmberClient.js | 715 ++++++ EmberClient/index.js | 1 + EmberLib/Command.js | 145 ++ EmberLib/Element.js | 46 + EmberLib/ElementInterface.js | 61 + EmberLib/Function.js | 68 + EmberLib/FunctionArgument.js | 102 + EmberLib/FunctionContent.js | 113 + EmberLib/Invocation.js | 105 + EmberLib/InvocationResult.js | 128 ++ EmberLib/Label.js | 69 + EmberLib/Matrix.js | 460 ++++ EmberLib/MatrixConnection.js | 192 ++ EmberLib/MatrixContents.js | 154 ++ EmberLib/MatrixDisposition.js | 18 + EmberLib/MatrixMode.js | 8 + EmberLib/MatrixNode.js | 117 + EmberLib/MatrixOperation.js | 17 + EmberLib/MatrixType.js | 10 + EmberLib/Node.js | 70 + EmberLib/NodeContents.js | 87 + EmberLib/Parameter.js | 82 + EmberLib/ParameterAccess.js | 12 + EmberLib/ParameterContents.js | 142 ++ EmberLib/ParameterType.js | 53 + EmberLib/QualifiedElement.js | 59 + EmberLib/QualifiedFunction.js | 63 + EmberLib/QualifiedMatrix.js | 108 + EmberLib/QualifiedNode.js | 53 + EmberLib/QualifiedParameter.js | 73 + EmberLib/QualifiedTemplate.js | 78 + EmberLib/StreamCollection.js | 102 + EmberLib/StreamDescription.js | 71 + EmberLib/StreamEntry.js | 70 + EmberLib/StreamFormat.js | 25 + EmberLib/StringIntegerCollection.js | 85 + EmberLib/StringIntegerPair.js | 78 + EmberLib/Template.js | 85 + EmberLib/TemplateElement.js | 75 + EmberLib/TreeNode.js | 560 +++++ EmberLib/constants.js | 23 + EmberLib/index.js | 164 ++ EmberServer/ElementHandlers.js | 195 ++ EmberServer/EmberServer.js | 519 +++++ EmberServer/JSONParser.js | 170 ++ EmberServer/MatrixHandlers.js | 135 ++ EmberServer/QualifiedHandlers.js | 75 + EmberServer/ServerEvents.js | 124 ++ EmberServer/index.js | 4 + EmberSocket/S101Client.js | 49 + EmberSocket/S101Server.js | 55 + EmberSocket/S101Socket.js | 218 ++ EmberSocket/index.js | 7 + Errors.js | 228 ++ README.md | 297 ++- ber.js | 26 +- client.js | 291 --- device.js | 518 ----- ember.js | 2708 ----------------------- errors.js | 59 - index.js | 13 +- package.json | 37 +- serve.js | 55 + server.js | 611 ----- test/DeviceTree.test.js | 106 +- test/Ember.test.js | 1634 +++++++++++++- test/EmberClient.test.js | 66 + test/Server.test.js | 1140 ++++++++-- test/client.js | 21 - embrionix.ember => test/embrionix.ember | Bin test/utils.js | 71 +- 72 files changed, 9690 insertions(+), 4492 deletions(-) create mode 100755 EmberClient/EmberClient.js create mode 100755 EmberClient/index.js create mode 100755 EmberLib/Command.js create mode 100755 EmberLib/Element.js create mode 100755 EmberLib/ElementInterface.js create mode 100755 EmberLib/Function.js create mode 100755 EmberLib/FunctionArgument.js create mode 100755 EmberLib/FunctionContent.js create mode 100755 EmberLib/Invocation.js create mode 100755 EmberLib/InvocationResult.js create mode 100755 EmberLib/Label.js create mode 100755 EmberLib/Matrix.js create mode 100755 EmberLib/MatrixConnection.js create mode 100755 EmberLib/MatrixContents.js create mode 100755 EmberLib/MatrixDisposition.js create mode 100755 EmberLib/MatrixMode.js create mode 100755 EmberLib/MatrixNode.js create mode 100755 EmberLib/MatrixOperation.js create mode 100755 EmberLib/MatrixType.js create mode 100755 EmberLib/Node.js create mode 100755 EmberLib/NodeContents.js create mode 100755 EmberLib/Parameter.js create mode 100755 EmberLib/ParameterAccess.js create mode 100755 EmberLib/ParameterContents.js create mode 100755 EmberLib/ParameterType.js create mode 100755 EmberLib/QualifiedElement.js create mode 100755 EmberLib/QualifiedFunction.js create mode 100755 EmberLib/QualifiedMatrix.js create mode 100755 EmberLib/QualifiedNode.js create mode 100755 EmberLib/QualifiedParameter.js create mode 100755 EmberLib/QualifiedTemplate.js create mode 100755 EmberLib/StreamCollection.js create mode 100755 EmberLib/StreamDescription.js create mode 100755 EmberLib/StreamEntry.js create mode 100755 EmberLib/StreamFormat.js create mode 100755 EmberLib/StringIntegerCollection.js create mode 100755 EmberLib/StringIntegerPair.js create mode 100755 EmberLib/Template.js create mode 100755 EmberLib/TemplateElement.js create mode 100755 EmberLib/TreeNode.js create mode 100755 EmberLib/constants.js create mode 100755 EmberLib/index.js create mode 100755 EmberServer/ElementHandlers.js create mode 100755 EmberServer/EmberServer.js create mode 100755 EmberServer/JSONParser.js create mode 100755 EmberServer/MatrixHandlers.js create mode 100755 EmberServer/QualifiedHandlers.js create mode 100755 EmberServer/ServerEvents.js create mode 100755 EmberServer/index.js create mode 100755 EmberSocket/S101Client.js create mode 100755 EmberSocket/S101Server.js create mode 100755 EmberSocket/S101Socket.js create mode 100755 EmberSocket/index.js create mode 100755 Errors.js delete mode 100755 client.js delete mode 100755 device.js delete mode 100755 ember.js delete mode 100755 errors.js create mode 100755 serve.js delete mode 100755 server.js create mode 100755 test/EmberClient.test.js delete mode 100755 test/client.js rename embrionix.ember => test/embrionix.ember (100%) diff --git a/.gitignore b/.gitignore index c882358..e77824c 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # intellij, pycharm, webstorm... /.idea/* - +.vscode node_modules .*.swp +coverage diff --git a/EmberClient/EmberClient.js b/EmberClient/EmberClient.js new file mode 100755 index 0000000..40f8a6a --- /dev/null +++ b/EmberClient/EmberClient.js @@ -0,0 +1,715 @@ +const EventEmitter = require('events').EventEmitter; +const S101Client = require('../EmberSocket').S101Client; +const ember = require('../EmberLib'); +const BER = require('../ber.js'); +const Errors = require('../Errors.js'); +const winston = require("winston"); + +const DEFAULT_PORT = 9000; +const DEFAULT_TIMEOUT = 3000; + +/** + * @typedef {{ + * node: TreeNode, + * func: function + * }} REQUEST + * + */ + +class EmberClient extends EventEmitter { + /** + * + * @param {string} host + * @param {number} port + */ + constructor(host, port = DEFAULT_PORT) { + super(); + this._debug = false; + /** @type {REQUEST[]} */ + this._pendingRequests = []; + /** @type {REQUEST} */ + this._activeRequest = null; + this._timeout = null; + this._callback = undefined; + this._requestID = 0; + this._client = new S101Client(host, port); + this.timeoutValue = DEFAULT_TIMEOUT; + /** @type {Root} */ + this.root = new ember.Root(); + + this._client.on('connecting', () => { + this.emit('connecting'); + }); + + this._client.on('connected', () => { + this.emit('connected'); + if (this._callback != null) { + this._callback(); + } + }); + + this._client.on('disconnected', () => { + this.emit('disconnected'); + }); + + this._client.on("error", e => { + if (this._callback != null) { + this._callback(e); + } + this.emit("error", e); + }); + + this._client.on('emberTree', root => { + try { + if (root instanceof ember.InvocationResult) { + this.emit('invocationResult', root); + winston.debug("Received InvocationResult", root); + } else { + this._handleRoot(root); + winston.debug("Received root", root); + } + if (this._callback) { + this._callback(undefined, root); + } + } + catch(e) { + winston.error(e, root); + if (this._callback) { + this._callback(e); + } + } + }); + } + + _finishRequest() { + this._clearTimeout(); + this._callback = undefined; + this._activeRequest = null; + try { + this._makeRequest(); + } catch(e) { + winston.error(e); + if (this._callback != null) { + this._callback(e); + } + this.emit("error", e); + } + } + + _makeRequest() { + if (this._activeRequest == null && this._pendingRequests.length > 0) { + this._activeRequest = this._pendingRequests.shift(); + const req = `${ this._requestID++} - ${this._activeRequest.node.getPath()}`; + this._activeRequest.timeoutError = new Errors.EmberTimeoutError(`Request ${req} timed out`) + + winston.debug(`Making request ${req}`, Date.now()); + this._timeout = setTimeout(() => { + this._timeoutRequest(); + }, this.timeoutValue); + this._activeRequest.func(); + } + } + + _timeoutRequest() { + this._activeRequest.func(this._activeRequest.timeoutError); + } + + /** + * + * @param {function} req + */ + addRequest(req) { + this._pendingRequests.push(req); + this._makeRequest(); + } + + _clearTimeout() { + if (this._timeout != null) { + clearTimeout(this._timeout); + this._timeout = null; + } + } + + /** + * + * @param {TreeNode} parent + * @param {TreeNode} node + */ + _handleNode(parent, node) { + let n = parent.getElementByNumber(node.getNumber()); + if (n == null) { + parent.addChild(node); + n = node; + } else if (n.update(node)) { + n.updateSubscribers(); + } + + const children = node.getChildren(); + if (children !== null) { + for (let i = 0; i < children.length; i++) { + this._handleNode(n, children[i]); + } + } + else { + this.emit("value-change", node); + } + return; + } + + /** + * + * @param {TreeNode} parent + * @param {TreeNode} node + */ + _handleQualifiedNode(parent, node) { + let element = parent.getElementByPath(node.path); + if (element !== null) { + this.emit("value-change", node); + if (element.update(node)) { + element.updateSubscribers(); + } + } + else { + const path = node.path.split("."); + if (path.length === 1) { + this.root.addChild(node); + } + else { + // Let's try to get the parent + path.pop(); + parent = this.root.getElementByPath(path.join(".")); + if (parent === null) { + return; + } + parent.addChild(node); + parent.update(parent); + } + element = node; + } + + const children = node.getChildren(); + if (children !== null) { + for (let i = 0; i < children.length; i++) { + if (children[i].isQualified()) { + this._handleQualifiedNode(element, children[i]); + } + else { + this._handleNode(element, children[i]); + } + } + } + + return; + } + + /** + * + * @param {TreeNode} root + */ + _handleRoot (root) { + winston.debug("handling root", JSON.stringify(root)); + this.root.update(root); + if (root.elements != null) { + const elements = root.getChildren(); + for (let i = 0; i < elements.length; i++) { + if (elements[i].isQualified()) { + this._handleQualifiedNode(this.root, elements[i]); + } + else { + this._handleNode(this.root, elements[i]); + } + } + } + if (this._callback) { + this._callback(null, root); + } + } + + /** + * + * @param {number} timeout + */ + connect(timeout = 2) { + return new Promise((resolve, reject) => { + this._callback = e => { + this._callback = undefined; + if (e === undefined) { + return resolve(); + } + return reject(e); + }; + if ((this._client != null) && (this._client.isConnected())) { + this._client.disconnect(); + } + this._client.connect(timeout); + }); + } + + /** + * @returns {Promise} + */ + disconnect() { + if (this._client != null) { + return this._client.disconnect(); + } + return Promise.resolve(); + } + + /** + * + * @param {TreeNode} qnode + * @param {function} callback=null + * @returns {Promise} + */ + expand(node, callback = null) { + if (node != null && (node.isParameter() || node.isMatrix() || node.isFunction())) { + return this.getDirectory(node); + } + return this.getDirectory(node, callback).then(res => { + const children = node == null ? res == null ? null : res.getChildren() : node.getChildren(); + if ((res == null) || (children == null)) { + winston.debug("No more children for ", node); + return; + } + let directChildren = Promise.resolve(); + for (let child of children) { + if (child.isParameter()) { + // Parameter can only have a single child of type Command. + continue; + } + directChildren = directChildren.then(() => { + return this.expand(child, callback).catch((e) => { + // We had an error on some expansion + // let's save it on the child itthis + child.error = e; + }); + }); + } + return directChildren + }); + } + /** + * + * @param {TreeNode} qnode + * @param {function} callback=null + * @returns {Promise} + */ + getDirectory(qnode, callback = null) { + if (qnode == null) { + this.root.clear(); + qnode = this.root; + } + return new Promise((resolve, reject) => { + this.addRequest({node: qnode, func: error => { + if (error) { + this._finishRequest(); + return reject(error); + } + + this._callback = (error, node) => { + const requestedPath = qnode.getPath(); + if (node == null) { + winston.debug(`received null response for ${requestedPath}`); + // ignore + return; + } + if (error) { + winston.debug("Received getDirectory error", error); + this._finishRequest(); + return reject(error); + } + if (qnode.isRoot()) { + const elements = qnode.getChildren(); + if (elements == null || elements.length === 0) { + winston.debug("getDirectory response", node); + return reject(new Errors.InvalidEmberNode()); + } + + const nodeElements = node == null ? null : node.getChildren(); + + if (nodeElements != null + && nodeElements.every(el => el._parent instanceof ember.Root)) { + winston.debug("Received getDirectory response", node); + this._finishRequest(); + return resolve(node); // make sure the info is treated before going to next request. + } + else { + return this._callback(new Errors.InvalidEmberResponse(`getDirectory ${requestedPath}`)); + } + } + else if (node.getElementByPath(requestedPath) != null) { + this._finishRequest(); + return resolve(node); // make sure the info is treated before going to next request. + } + else { + const nodeElements = node == null ? null : node.getChildren(); + if (nodeElements != null && + ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || + (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { + winston.debug("Received getDirectory response", node); + this._finishRequest(); + return resolve(node); // make sure the info is treated before going to next request. + } + else { + winston.debug(node); + winston.error(new Error(requestedPath)); + } + } + }; + try { + this._client.sendBERNode(qnode.getDirectory(callback)); + } + catch(e) { + reject(e); + } + }}); + }); + } + + /** + * + * @param {string} path ie: "path/to/destination" + * @param {function} callback=null + * @returns {Promise} + */ + getElementByPath(path, callback=null) { + const pathError = new Errors.PathDiscoveryFailure(path); + const TYPE_NUM = 1; + const TYPE_ID = 2; + let type = TYPE_NUM; + let pathArray = []; + if (path.indexOf("/") >= 0) { + type = TYPE_ID; + pathArray = path.split("/"); + } + else { + pathArray = path.split("."); + if (pathArray.length === 1) { + if (isNaN(Number(pathArray[0]))) { + type = TYPE_ID; + } + } + } + let pos = 0; + let lastMissingPos = -1; + let currentNode = this.root; + const getNext = () => { + return Promise.resolve() + .then(() => { + let node; + if (type === TYPE_NUM) { + const number = Number(pathArray[pos]); + node = currentNode.getElementByNumber(number); + } + else { + const children = currentNode.getChildren(); + const identifier = pathArray[pos]; + if (children != null) { + let i = 0; + for (i = 0; i < children.length; i++) { + node = children[i]; + if (node.contents != null && node.contents.identifier === identifier) { + break; + } + } + if (i >= children.length) { + node = null; + } + } + } + if (node != null) { + // We have this part already. + pos++; + if (pos >= pathArray.length) { + if (callback) { + node.getDirectory(callback); + } + return node; + } + currentNode = node; + return getNext(); + } + // We do not have that node yet. + if (lastMissingPos === pos) { + throw pathError; + } + lastMissingPos = pos; + return this.getDirectory(currentNode, callback).then(() => getNext()); + }); + } + return getNext(); + } + + /** + * + * @param {TreeNode} fnNode + * @param {FunctionArgument[]} params + */ + invokeFunction(fnNode, params) { + return new Promise((resolve, reject) => { + this.addRequest({node: fnNode, func: (error) => { + if (error) { + reject(error); + this._finishRequest(); + return; + } + const cb = (error, result) => { + this._clearTimeout(); + if (error) { + reject(error); + } + else { + winston.debug("InvocationResult", result); + resolve(result); + } + // cleaning callback and making next request. + this._finishRequest(); + }; + winston.debug("Invocking function", fnNode); + this._callback = cb; + this._client.sendBERNode(fnNode.invoke(params)); + }}); + }) + } + + /** + * @returns {boolean} + */ + isConnected() { + return ((this._client != null) && (this._client.isConnected())); + } + + /** + * + * @param {Matrix} matrixNode + * @param {number} targetID + * @param {number[]} sources + * @param {MatrixOperation} operation + * @returns {Promise} + */ + matrixOPeration(matrixNode, targetID, sources, operation = ember.MatrixOperation.connect) { + return new Promise((resolve, reject) => { + if (!Array.isArray(sources)) { + return reject(new Errors.InvalidSourcesFormat()); + } + try { + matrixNode.validateConnection(targetID, sources); + } + catch(e) { + return reject(e); + } + const connections = {} + const targetConnection = new ember.MatrixConnection(targetID); + targetConnection.operation = operation; + targetConnection.setSources(sources); + connections[targetID] = targetConnection; + + this.addRequest({node: matrixNode, func: (error) => { + if (error) { + this._finishRequest(); + reject(error); + return; + } + + this._callback = (error, node) => { + const requestedPath = matrixNode.getPath(); + if (node == null) { + winston.debug(`received null response for ${requestedPath}`); + return; + } + if (error) { + winston.error("Received getDirectory error", error); + this._clearTimeout(); // clear the timeout now. The resolve below may take a while. + this._finishRequest(); + reject(error); + return; + } + let matrix = null; + if (node != null) { + matrix = node.getElementByPath(requestedPath); + } + if (matrix != null && matrix.isMatrix() && matrix.getPath() === requestedPath) { + this._clearTimeout(); // clear the timeout now. The resolve below may take a while. + this._finishRequest(); + resolve(matrix); + } + else { + winston.debug(`unexpected node response during matrix connect ${requestedPath}`, + matrix == null ? null : JSON.stringify(matrix.toJSON(), null, 4)); + } + } + this._client.sendBERNode(matrixNode.connect(connections)); + }}); + }); + } + + /** + * + * @param {Matrix} matrixNode + * @param {number} targetID + * @param {number[]} sources + * @returns {Promise} + */ + matrixConnect(matrixNode, targetID, sources) { + return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.connect) + } + + /** + * + * @param {Matrix} matrixNode + * @param {number} targetID + * @param {number[]} sources + * @returns {Promise} + */ + matrixDisconnect(matrixNode, targetID, sources) { + return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.disconnect) + } + + /** + * + * @param {Matrix} matrixNode + * @param {number} targetID + * @param {number[]} sources + * @returns {Promise} + */ + matrixSetConnection(matrixNode, targetID, sources) { + return this.matrixOPeration(matrixNode, targetID,sources, ember.MatrixOperation.absolute) + } + + /** + * + * @param {function} f + */ + saveTree(f) { + const writer = new BER.Writer(); + this.root.encode(writer); + f(writer.buffer); + } + + /** + * + * @param {TreeNode} node + * @param {string|number} value + * @returns {Promise} + */ + setValue(node, value) { + return new Promise((resolve, reject) => { + if (!node.isParameter()) { + reject(new Errors.EmberAccessError('not a Parameter')); + } + else { + this.addRequest({node: node, func: error => { + if (error) { + this._finishRequest(); + reject(error); + return; + } + + this._callback = (error, node) => { + this._finishRequest(); + this._callback = null; + if (error) { + reject(error); + } + else { + + resolve(node); + } + }; + winston.debug('setValue sending ...', node.getPath(), value); + this._client.sendBERNode(node.setValue(value)); + }}); + } + }); + } + + /** + * + * @param {TreeNode} node + * @param {string|number} value + * @returns {Promise} + */ + setValueNoAck(node, value) { + // This function immediately finish & resolve so we can't get any timeouts ever + // This is a pretty ugly hack, but it doesn't look to bring + // any negative consequences regarding the execution and resolving of other + // functions. It´s needed this because if the node already has the value we are + // setting it too, it will cause a timeout. + return new Promise((resolve, reject) => { + if (!node.isParameter()) { + reject(new Errors.EmberAccessError('not a Parameter')); + return; + } + this.addRequest({node: node, func: error => { + if (error) { + this._finishRequest(); + reject(error); + return; + } + winston.debug('setValue sending ...', node.getPath(), value); + this._client.sendBERNode(node.setValue(value)); + + this._finishRequest(); + this._callback = null; + return resolve(node) + }}); + }) + } + + /** + * + * @param {TreeNode} qnode + * @param {function} callback + * @returns {Promise} + */ + subscribe(qnode, callback) { + if ((qnode.isParameter() || qnode.isMatrix()) && qnode.isStream()) { + if (qnode == null) { + this.root.clear(); + qnode = this.root; + } + return new Promise((resolve, reject) => { + this.addRequest({node: qnode, func: error => { + if (error != null) { + return reject(error); + } + winston.debug("Sending subscribe", qnode); + this._client.sendBERNode(qnode.subscribe(callback)); + this._finishRequest(); + resolve(); + }}); + }); + } + return Promise.resolve(); + } + + /** + * + * @param {TreeNode} qnode + * @param {function} callback + * @returns {Promise} + */ + unsubscribe(qnode, callback) { + if (qnode.isParameter() && qnode.isStream()) { + return new Promise((resolve, reject) => { + this.addRequest({node: qnode, func: (error) => { + if (error != null) { + return reject(error); + } + winston.debug("Sending subscribe", qnode); + this._client.sendBERNode(qnode.unsubscribe(callback)); + this._finishRequest(); + resolve(); + }}); + }); + } + return Promise.resolve(); + } +} + +function isDirectSubPathOf(path, parent) { + return path === parent || (path.lastIndexOf('.') === parent.length && path.startsWith(parent)); +} + +module.exports = EmberClient; + diff --git a/EmberClient/index.js b/EmberClient/index.js new file mode 100755 index 0000000..964a37d --- /dev/null +++ b/EmberClient/index.js @@ -0,0 +1 @@ +module.exports = require("./EmberClient"); \ No newline at end of file diff --git a/EmberLib/Command.js b/EmberLib/Command.js new file mode 100755 index 0000000..73bf653 --- /dev/null +++ b/EmberLib/Command.js @@ -0,0 +1,145 @@ +"use strict"; +const Enum = require('enum'); +const {COMMAND_GETDIRECTORY, COMMAND_INVOKE} = require("./constants"); +const BER = require('../ber.js'); +const Invocation = require("./Invocation"); +const errors = require("../Errors"); +const ElementInterface = require("./ElementInterface"); + +const FieldFlags = new Enum({ + sparse: -2, + all: -1, + default: 0, + identifier: 1, + description: 2, + tree: 3, + value: 4, + connections: 5 +}); + +class Command extends ElementInterface{ + /** + * + * @param {number} number + */ + constructor(number) { + super(); + this.number = number; + if(number == COMMAND_GETDIRECTORY) { + this.fieldFlags = FieldFlags.all; + } + } + + /** + * @returns {boolean} + */ + isCommand() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(Command.BERID); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + + if (this.number === COMMAND_GETDIRECTORY && this.fieldFlags) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeInt(this.fieldFlags.value); + ber.endSequence(); + } + + if (this.number === COMMAND_INVOKE && this.invocation) { + ber.startSequence(BER.CONTEXT(2)); + this.invocation.encode(ber); + ber.endSequence(); + } + // TODO: options + + ber.endSequence(); // BER.APPLICATION(2) + } + + /** + * @returns {number} + */ + getNumber() { + return this.number; + } + + /** + * + */ + toJSON() { + return { + number: this.number, + fieldFlags: this.fieldFlags, + invocation: this.invocation == null ? null : this.invocation.toJSON() + }; + } + + /** + * + * @param {BER} ber + * @returns {Command} + */ + static decode(ber) { + const c = new Command(); + ber = ber.getSequence(Command.BERID); + + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + c.number = seq.readInt(); + } + else if(tag == BER.CONTEXT(1)) { + c.fieldFlags = FieldFlags.get(seq.readInt()); + } + else if(tag == BER.CONTEXT(2)) { + c.invocation = Invocation.decode(seq); + } + else { + // TODO: options + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return c; + } + + /** + * + * @param {number} cmd + * @param {string} key + * @param {string|value|object} value + */ + static getCommand(cmd, key, value) { + const command = new Command(cmd); + if (key != null) { + command[key] = value; + } + return command; + } + + /** + * + * @param {Invocation} invocation + */ + static getInvocationCommand(invocation) { + return this.getCommand(COMMAND_INVOKE, "invocation", invocation); + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(2); + } +} + +module.exports = Command; \ No newline at end of file diff --git a/EmberLib/Element.js b/EmberLib/Element.js new file mode 100755 index 0000000..00aede0 --- /dev/null +++ b/EmberLib/Element.js @@ -0,0 +1,46 @@ +"use strict"; +const TreeNode = require("./TreeNode"); +const BER = require('../ber.js'); + +class Element extends TreeNode { + /** + * + * @param {number} number + */ + constructor(number) { + super(); + this.number = number; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(this._seqID); + + this.encodeNumber(ber); + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + /** + * + * @param {BER} ber + */ + encodeNumber(ber) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + } +} + +module.exports = Element; \ No newline at end of file diff --git a/EmberLib/ElementInterface.js b/EmberLib/ElementInterface.js new file mode 100755 index 0000000..0d93c6c --- /dev/null +++ b/EmberLib/ElementInterface.js @@ -0,0 +1,61 @@ +"use strict"; + +class ElementType { + /** + * @returns {boolean} + */ + isCommand() { + return false; + } + /** + * @returns {boolean} + */ + isNode() { + return false; + } + /** + * @returns {boolean} + */ + isMatrix() { + return false; + } + /** + * @returns {boolean} + */ + isParameter() { + return false; + } + /** + * @returns {boolean} + */ + isFunction() { + return false; + } + /** + * @returns {boolean} + */ + isRoot() { + return false; + } + /** + * @returns {boolean} + */ + isQualified() { + return false; + } + /** + * @returns {boolean} + */ + isStream() { + return false; + } + + /** + * @returns {boolean} + */ + isTemplate() { + return false; + } +} + +module.exports = ElementType; \ No newline at end of file diff --git a/EmberLib/Function.js b/EmberLib/Function.js new file mode 100755 index 0000000..54c777b --- /dev/null +++ b/EmberLib/Function.js @@ -0,0 +1,68 @@ +"use strict"; +const Element = require("./Element"); +const QualifiedFunction = require("./QualifiedFunction"); +const BER = require('../ber.js'); +const Command = require("./Command"); +const {COMMAND_INVOKE} = require("./constants"); +const FunctionContent = require("./FunctionContent"); +const Errors = require("../Errors"); + +class Function extends Element { + constructor(number, func) { + super(); + this.number = number; + this.func = func; + this._seqID = Function.BERID; + } + + /** + * @returns {boolean} + */ + isFunction() { + return true; + } + + /** + * @returns {QualifiedFunction} + */ + toQualified() { + const qf = new QualifiedFunction(this.getPath()); + qf.update(this); + return qf; + } + + + /** + * + * @param {BER} ber + * @returns {Function} + */ + static decode(ber) { + const f = new Function(); + ber = ber.getSequence(Function.BERID); + + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + f.number = seq.readInt(); + } else if(tag == BER.CONTEXT(1)) { + f.contents = FunctionContent.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + f.decodeChildren(seq); + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return f; + } + + /** + * + */ + static get BERID() { + return BER.APPLICATION(19); + } +} + +module.exports = Function; \ No newline at end of file diff --git a/EmberLib/FunctionArgument.js b/EmberLib/FunctionArgument.js new file mode 100755 index 0000000..de1a792 --- /dev/null +++ b/EmberLib/FunctionArgument.js @@ -0,0 +1,102 @@ +"use strict"; +const BER = require('../ber.js'); +const {ParameterType} = require("./ParameterType"); +const Errors = require("../Errors"); + +/* +TupleDescription ::= + SEQUENCE OF [0] TupleItemDescription +TupleItemDescription ::= + [APPLICATION 21] IMPLICIT + SEQUENCE { + type [0] ParameterType, + name [1] EmberString OPTIONAL + } +Invocation ::= + [APPLICATION 22] IMPLICIT + SEQUENCE { + invocationId [0] Integer32 OPTIONAL, + arguments [1] Tuple OPTIONAL + } +Tuple ::= + SEQUENCE OF [0] Value +*/ + +class FunctionArgument { + /** + * + * @param {ParameterType} type + * @param {number|string|null} value + * @param {string|null} name + */ + constructor (type = null, value = null, name = null) { + /** @type {ParameterType} */ + this.type = type; + this.value = value; + this.name = name; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(FunctionArgument.BERID); + if (this.type == null) { + throw new Errors.InvalidEmberNode("", "FunctionArgument requires a type") + } + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.type.value); + ber.endSequence(); + if (this.name != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.name, BER.EMBER_STRING); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * + */ + toJSON() { + return { + type: this.type, + name: this.name, + value: this.value + }; + } + + /** + * + * @param {BER} ber + * @returns {FunctionArgument} + */ + static decode(ber) { + const tuple = new FunctionArgument(); + ber = ber.getSequence(FunctionArgument.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if (tag === BER.CONTEXT(0)) { + tuple.type = ParameterType.get(seq.readInt()); + } + else if (tag === BER.CONTEXT(1)) { + tuple.name = seq.readString(BER.EMBER_STRING); + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return tuple; + } + + /** + * + */ + static get BERID() { + return BER.APPLICATION(21); + } +} + +module.exports = FunctionArgument; \ No newline at end of file diff --git a/EmberLib/FunctionContent.js b/EmberLib/FunctionContent.js new file mode 100755 index 0000000..737beae --- /dev/null +++ b/EmberLib/FunctionContent.js @@ -0,0 +1,113 @@ +"use strict"; +const BER = require('../ber.js'); +const FunctionArgument = require("./FunctionArgument"); +const errors = require("../Errors"); + +class FunctionContent { + constructor(identifier=null, description=null) { + this.arguments = []; + this.result = []; + this.identifier = identifier; + this.description = description; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.EMBER_SET); + + if(this.identifier != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.identifier, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(0) + } + + if(this.description != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.arguments != null) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i = 0; i < this.arguments.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + this.arguments[i].encode(ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); // BER.CONTEXT(2) + } + + if(this.result != null && this.result.length > 0) { + ber.startSequence(BER.CONTEXT(3)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(let i = 0; i < this.result.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + /** @type {FunctionArgument} */ + this.result[i].encode(ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); // BER.CONTEXT(3) + } + + if(this.templateReference != null) { + ber.startSequence(BER.CONTEXT(4)); + ber.writeRelativeOID(this.templateReference, BER.EMBER_RELATIVE_OID); + ber.endSequence(); // BER.CONTEXT(3) + } + + ber.endSequence(); // BER.EMBER_SET + } + + /** + * + * @param {BER} ber + * @returns {FunctionContent} + */ + static decode(ber) { + const fc = new FunctionContent(); + ber = ber.getSequence(BER.EMBER_SET); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + fc.identifier = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + fc.description = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(2)) { + fc.arguments = []; + let dataSeq = seq.getSequence(BER.EMBER_SEQUENCE); + while(dataSeq.remain > 0) { + seq = dataSeq.getSequence(BER.CONTEXT(0)); + fc.arguments.push(FunctionArgument.decode(seq)); + } + } else if(tag == BER.CONTEXT(3)) { + fc.result = []; + let dataSeq = seq.getSequence(BER.EMBER_SEQUENCE); + while(dataSeq.remain > 0) { + tag = dataSeq.peek(); + if (tag === BER.CONTEXT(0)) { + const fcSeq = dataSeq.getSequence(tag); + fc.result.push(FunctionArgument.decode(fcSeq)); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + } else if(tag == BER.CONTEXT(4)) { + fc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return fc; + } +} + +module.exports = FunctionContent; \ No newline at end of file diff --git a/EmberLib/Invocation.js b/EmberLib/Invocation.js new file mode 100755 index 0000000..945b828 --- /dev/null +++ b/EmberLib/Invocation.js @@ -0,0 +1,105 @@ +"use strict"; +const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); +const BER = require('../ber.js'); +const FunctionArgument = require("./FunctionArgument"); +const errors = require("../Errors"); + +let _id = 1; +class Invocation { + /** + * + * @param {number} id + * @param {FunctionArgument[]} args + */ + constructor(id = null, args = []) { + this.id = id; + this.arguments = args; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(Invocation.BERID); + if (this.id != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.id) + ber.endSequence(); + } + ber.startSequence(BER.CONTEXT(1)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i = 0; i < this.arguments.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeValue( + this.arguments[i].value, + ParameterTypetoBERTAG(this.arguments[i].type + )); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + + ber.endSequence(); // BER.APPLICATION(22) + } + + /** + * + */ + toJSON() { + return { + id: this.id, + arguments: this.arguments == null ? null : this.arguments.map(a => a.toJSON()), + } + } + + /** + * + * @param {BER} ber + * @returns {Invocation} + */ + static decode(ber) { + const invocation = new Invocation(); + ber = ber.getSequence(Invocation.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + invocation.id = seq.readInt(); + } + else if(tag == BER.CONTEXT(1)) { + invocation.arguments = []; + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + const dataSeq = seq.getSequence(BER.CONTEXT(0)); + tag = dataSeq.peek(); + const val = dataSeq.readValue(); + invocation.arguments.push( + new FunctionArgument(ParameterTypefromBERTAG(tag), val) + ); + } + } + else { + // TODO: options + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return invocation; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(22); + } + + /** + * @returns {number} + */ + static newInvocationID() { + return _id++; + } +} + +module.exports = Invocation; \ No newline at end of file diff --git a/EmberLib/InvocationResult.js b/EmberLib/InvocationResult.js new file mode 100755 index 0000000..b036b32 --- /dev/null +++ b/EmberLib/InvocationResult.js @@ -0,0 +1,128 @@ +"use strict"; + +const BER = require('../ber.js'); +const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("./ParameterType"); +const FunctionArgument = require("./FunctionArgument"); +const Errors = require("../Errors"); + + +class InvocationResult { + /** + * + * @param {number|null} invocationId=null + */ + constructor(invocationId = null) { + this.invocationId = invocationId; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(InvocationResult.BERID); + if (this.invocationId != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.invocationId); + ber.endSequence(); + } + if (this.success != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeBoolean(this.success); + ber.endSequence(); + } + if (this.result != null && this.result.length) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.EMBER_SEQUENCE); + for (let i = 0; i < this.result.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeValue(this.result[i].value, ParameterTypetoBERTAG(this.result[i].type)); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + } + ber.endSequence(); // BER.APPLICATION(23)} + } + + /** + * + */ + setFailure() { + this.success = false; + } + + /** + * + */ + setSuccess() { + this.success = true; + } + + /** + * + * @param {} result + */ + setResult(result) { + if (!Array.isArray(result)) { + throw new Errors.InvalidResultFormat(); + } + this.result = result; + } + + toQualified() { + return this; + } + + /** + * + * @param {BER} ber + * @returns {InvocationResult} + */ + static decode(ber) { + const invocationResult = new InvocationResult(); + ber = ber.getSequence(InvocationResult.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { // invocationId + invocationResult.invocationId = seq.readInt(); + } else if(tag == BER.CONTEXT(1)) { // success + invocationResult.success = seq.readBoolean() + }else if(tag == BER.CONTEXT(2)) { + invocationResult.result = []; + let res = seq.getSequence(BER.EMBER_SEQUENCE); + while(res.remain > 0) { + tag = res.peek(); + if (tag === BER.CONTEXT(0)) { + let resTag = res.getSequence(BER.CONTEXT(0)); + tag = resTag.peek(); + invocationResult.result.push( + new FunctionArgument( + ParameterTypefromBERTAG(tag), + resTag.readValue() + )); + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + continue + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + + return invocationResult; + } + + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(23); + } +} + +module.exports = InvocationResult; \ No newline at end of file diff --git a/EmberLib/Label.js b/EmberLib/Label.js new file mode 100755 index 0000000..8a41235 --- /dev/null +++ b/EmberLib/Label.js @@ -0,0 +1,69 @@ +"use strict"; +const BER = require('../ber.js'); +const Errors = require("../Errors"); + +class Label { + constructor(path, description) { + if (path) { + this.basePath = path; + } + if (description) { + this.description = description; + } + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(Label.BERID); + if (this.basePath == null) { + throw new Errors.InvalidEmberNode("", "Missing label base path"); + } + ber.startSequence(BER.CONTEXT(0)); + ber.writeRelativeOID(this.basePath, BER.EMBER_RELATIVE_OID); + ber.endSequence(); + if (this.description == null) { + throw new Errors.InvalidEmberNode("", "Missing label description"); + } + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); + ber.endSequence(); + } + + /** + * + * @param {BER} ber + * @returns {Label} + */ + static decode(ber) { + var l = new Label(); + + ber = ber.getSequence(Label.BERID); + + while (ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if (tag == BER.CONTEXT(0)) { + l.basePath = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); + } else if (tag == BER.CONTEXT(1)) { + l.description = seq.readString(BER.EMBER_STRING); + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return l; + } + + /** + * + */ + static get BERID() { + return BER.APPLICATION(18); + } +} + +module.exports = Label; \ No newline at end of file diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js new file mode 100755 index 0000000..9f8ac2f --- /dev/null +++ b/EmberLib/Matrix.js @@ -0,0 +1,460 @@ +"use strict"; +const MatrixConnection = require("./MatrixConnection"); +const TreeNode = require("./TreeNode"); +const BER = require('../ber.js'); +const MatrixMode = require("./MatrixMode"); +const MatrixOperation = require("./MatrixOperation"); +const MatrixType = require("./MatrixType"); +const Errors = require("../Errors"); + +class Matrix extends TreeNode +{ + constructor() { + super(); + this._connectedSources = {}; + this._numConnections = 0; + this.targets = null; + this.sources = null; + this.connections = {}; + } + + isMatrix() { + return true; + } + + /** + * + * @param {number} targetID + * @param {number[]} sources + * @param {Operation} operation + * @returns {boolean} + */ + canConnect(targetID, sources, operation) { + return Matrix.canConnect(this, targetID, sources, operation); + } + + /** + * + * @param {Object} connections + * @returns {root} + */ + connect(connections) { + const r = this.getTreeBranch(); + const m = r.getElementByPath(this.getPath()); + m.connections = connections; + return r; + } + + /** + * + * @param {number} targetID + * @param {number[]} sources + */ + connectSources(targetID, sources) { + return Matrix.connectSources(this, targetID, sources); + } + + /** + * + * @param {number} targetID + * @param {number[]} sources + */ + disconnectSources(targetID, sources) { + return Matrix.disconnectSources(this, targetID, sources); + } + + /** + * + * @param {BER} ber + */ + encodeConnections(ber) { + if (this.connections != null) { + ber.startSequence(BER.CONTEXT(5)); + ber.startSequence(BER.EMBER_SEQUENCE); + + for(let id in this.connections) { + if (this.connections.hasOwnProperty(id)) { + ber.startSequence(BER.CONTEXT(0)); + this.connections[id].encode(ber); + ber.endSequence(); + } + } + ber.endSequence(); + ber.endSequence(); + } + } + + /** + * + * @param {BER} ber + */ + encodeSources(ber) { + if (this.sources != null) { + ber.startSequence(BER.CONTEXT(4)); + ber.startSequence(BER.EMBER_SEQUENCE); + + for(let i=0; i Number(i))); + + if (matrixNode.connections[targetID].isLocked()) { + return false; + } + if (type === MatrixType.oneToN && + matrixNode.contents.maximumTotalConnects == null && + matrixNode.contents.maximumConnectsPerTarget == null) { + return sMap.size < 2; + } + else if (type === MatrixType.oneToN && sMap.size >= 2) { + return false; + } + else if (type === MatrixType.oneToOne) { + if (sMap.size > 1) { + return false; + } + const sourceConnections = matrixNode._connectedSources[sources[0]]; + return sourceConnections == null || sourceConnections.size === 0 || sourceConnections.has(targetID); + } + else { + // N to N + if (matrixNode.contents.maximumConnectsPerTarget != null && + newSources.length > matrixNode.contents.maximumConnectsPerTarget) { + return false; + } + if (matrixNode.contents.maximumTotalConnects != null) { + let count = matrixNode._numConnections - oldSources.length; + if (newSources) { + count += newSources.length; + } + return count <= matrixNode.contents.maximumTotalConnects; + } + return true; + } + } + + /** + * + * @param {MatrixNode} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + static connectSources(matrix, targetID, sources) { + const target = Number(targetID); + if (matrix.connections == null) { + matrix.connections = {}; + } + if (matrix.connections[target] == null) { + matrix.connections[target] = new MatrixConnection(target); + } + matrix.connections[target].connectSources(sources); + if (sources != null) { + for(let source of sources) { + if (matrix._connectedSources[source] == null) { + matrix._connectedSources[source] = new Set(); + } + if (!matrix._connectedSources[source].has(target)) { + matrix._connectedSources[source].add(target); + matrix._numConnections++; + } + } + } + } + + /** + * + * @param {BER} ber + * @returns {number[]} + */ + static decodeTargets(ber) { + const targets = []; + ber = ber.getSequence(BER.EMBER_SEQUENCE); + while(ber.remain > 0) { + let seq = ber.getSequence(BER.CONTEXT(0)); + seq = seq.getSequence(BER.APPLICATION(14)); + seq = seq.getSequence(BER.CONTEXT(0)); + targets.push(seq.readInt()); + } + return targets; + } + + /** + * + * @param {BER} ber + * @returns {number[]} + */ + static decodeSources(ber) { + const sources = []; + ber = ber.getSequence(BER.EMBER_SEQUENCE); + while(ber.remain > 0) { + let seq = ber.getSequence(BER.CONTEXT(0)); + seq = seq.getSequence(BER.APPLICATION(15)); + seq = seq.getSequence(BER.CONTEXT(0)); + sources.push(seq.readInt()); + } + return sources; + } + + /** + * + * @param {BER} ber + * @returns {Object} + */ + static decodeConnections(ber) { + const connections = {}; + const seq = ber.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + const conSeq = seq.getSequence(BER.CONTEXT(0)); + const con = MatrixConnection.decode(conSeq); + connections[con.target] = (con); + } + return connections; + } + + /** + * + * @param {MatrixNode} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + static disconnectSources(matrix, targetID, sources) { + const target = Number(targetID); + if (matrix.connections[target] == null) { + matrix.connections[target] = new MatrixConnection(target); + } + matrix.connections[target].disconnectSources(sources); + if (sources != null) { + for(let source of sources) { + if (matrix._connectedSources[source] == null) { + continue; + } + if (matrix._connectedSources[source].has(target)) { + matrix._connectedSources[source].delete(target); + matrix._numConnections--; + } + } + } + } + + /** + * + * @param {MatrixNode} matrix + * @param {number} source + */ + static getSourceConnections(matrix, source) { + const targets = matrix._connectedSources[source]; + if (targets) { + return [...targets]; + } + return []; + } + + /** + * + * @param {QualifiedMatrix|MatrixNode} matrix + * @param {QualifiedMatrix|MatrixNode} newMatrix + * @returns {boolean} - True if something changed + */ + static MatrixUpdate(matrix, newMatrix) { + let modified = false; + if (newMatrix.targets != null) { + matrix.targets = newMatrix.targets; + modified = true; + } + if (newMatrix.sources != null) { + matrix.sources = newMatrix.sources; + modified = true; + } + if (newMatrix.connections != null) { + if (matrix.connections == null) { + matrix.connections = {}; + modified = true; + } + for(let id in newMatrix.connections) { + if (newMatrix.connections.hasOwnProperty(id)) { + const connection = newMatrix.connections[id]; + if ((connection.target < matrix.contents.targetCount) && + (connection.target >= 0)) { + if (matrix.connections[connection.target] == null) { + matrix.connections[connection.target] = new MatrixConnection(connection.target); + modified = true; + } + if (matrix.connections[connection.target].isDifferent(connection.sources)) { + matrix.connections[connection.target].setSources(connection.sources); + modified = true; + } + } + else { + throw new Errors.InvalidMatrixSignal(connection.target, "Invalid target") + } + } + } + } + return modified; + } + + /** + * + * @param {MatrixNode} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + static setSources(matrix, targetID, sources) { + const currentSource = matrix.connections[targetID] == null || matrix.connections[targetID].sources == null ? + [] : matrix.connections[targetID].sources; + if (currentSource.length > 0) { + this.disconnectSources(matrix, targetID, currentSource) + } + Matrix.connectSources(matrix, targetID, sources); + } + + /** + * + * @param {MatrixNode} matrixNode + * @param {number} targetID + * @param {number[]} sources + */ + static validateConnection(matrixNode, targetID, sources) { + if (targetID < 0) { + throw new Errors.InvalidMatrixSignal(targetID, "target"); + } + if (sources == null) { + throw new Errors.InvalidSourcesFormat(); + } + for(let i = 0; i < sources.length; i++) { + if (sources[i] < 0) { + throw new Errors.InvalidMatrixSignal(sources[i], `Source at index ${i}`); + } + } + if (matrixNode.contents.mode === MatrixMode.linear) { + if (targetID >= matrixNode.contents.targetCount) { + throw new Errors.InvalidMatrixSignal(targetID, `Target higher than max value ${matrixNode.contents.targetCount}`); + } + for(let i = 0; i < sources.length; i++) { + if (sources[i] >= matrixNode.contents.sourceCount) { + throw new Errors.InvalidMatrixSignal(sources[i],`Source at index ${i} higher than max ${matrixNode.contents.sourceCount}`); + } + } + } + else if ((matrixNode.targets == null) || (matrixNode.sources == null)) { + throw new Errors.InvalidEmberNode(matrixNode.getPath(),"Non-Linear matrix should have targets and sources"); + } + else { + let found = false; + for(let i = 0; i < matrixNode.targets.length; i++) { + if (matrixNode.targets[i] === targetID) { + found = true; + break; + } + } + if (!found) { + throw new Errors.InvalidMatrixSignal(targetID, "Not part of existing targets"); + } + found = false; + for(let i = 0; i < sources.length; i++) { + for(let j = 0; j < matrixNode.sources.length; j++) { + if (matrixNode.sources[j] === sources[i]) { + found = true; + break; + } + } + if (!found) { + throw new Errors.InvalidMatrixSignal(sources[i],`Unknown source at index ${i}`); + } + } + } + } +} + +module.exports = Matrix; \ No newline at end of file diff --git a/EmberLib/MatrixConnection.js b/EmberLib/MatrixConnection.js new file mode 100755 index 0000000..45ec689 --- /dev/null +++ b/EmberLib/MatrixConnection.js @@ -0,0 +1,192 @@ +"use strict"; +const BER = require('../ber.js'); +const MatrixOperation = require("./MatrixOperation"); +const MatrixDisposition = require("./MatrixDisposition"); +const Errors = require("../Errors"); + +class MatrixConnection { + /** + * + * @param {number} target + */ + constructor(target) { + if (target) { + let _target = Number(target); + if (isNaN(_target)) { + throw new Errors.InvalidMatrixSignal(target, "Can't create connection with invalid target.") + } + this.target = _target; + } + else { + this.target = 0; + } + this._locked = false; + } + + /** + * + * @param {number[]} sources + */ + connectSources(sources) { + this.sources = this.validateSources(sources); + } + + /** + * + * @param {number[]} sources + */ + disconnectSources(sources) { + if (sources == null) { + return; + } + let s = new Set(this.sources); + for(let item of sources) { + s.delete(item); + } + this.sources = [...s].sort(); + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(MatrixConnection.BERID); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.target); + ber.endSequence(); + + if (this.sources != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeRelativeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); + ber.endSequence(); + } + if (this.operation != null) { + ber.startSequence(BER.CONTEXT(2)); + ber.writeInt(this.operation.value); + ber.endSequence(); + } + if (this.disposition != null) { + ber.startSequence(BER.CONTEXT(3)); + ber.writeInt(this.disposition.value); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * + * @param {number[]|null} sources + */ + isDifferent(sources) { + const newSources = this.validateSources(sources); + + if (this.sources == null && newSources == null) { + return false; + } + + if ((this.sources == null && newSources != null)|| + (this.sources != null && newSources == null) || + (this.sources.length != newSources.length)) { + return true; + } + // list are ordered, so we can simply parse 1 by 1. + for(let i = 0; i < this.sources.length; i++) { + if (this.sources[i] !== newSources[i]) { + return true; + } + } + return false; + } + + /** + * @returns {boolean} + */ + isLocked() { + return this._locked; + } + + /** + * + */ + lock() { + this._locked = true; + } + + /** + * + * @param {number[]} sources + */ + setSources(sources) { + if (sources == null) { + delete this.sources; + return; + } + this.sources = this.validateSources(sources); + } + + /** + * + * @param {number[]} sources + * @returns {number[]} - uniq and sorted + */ + validateSources(sources) { + if (sources == null) { + return null; + } + const s = new Set(sources.map(i => Number(i))); + return [...s].sort(); + } + + /** + * + */ + unlock() { + this._locked = false; + } + + /** + * + * @param {BER} ber + * @returns {MatrixConnection} + */ + static decode(ber) { + const c = new MatrixConnection(); + ber = ber.getSequence(MatrixConnection.BERID); + while (ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if (tag == BER.CONTEXT(0)) { + c.target = seq.readInt(); + } + else if (tag == BER.CONTEXT(1)) { + const sources = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); + if (sources === "") { + c .sources = []; + } + else { + c.sources = sources.split(".").map(i => Number(i)); + } + } else if (tag == BER.CONTEXT(2)) { + c.operation = MatrixOperation.get(seq.readInt()); + + } else if (tag == BER.CONTEXT(3)) { + c.disposition = MatrixDisposition.get(seq.readInt()); + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return c; + } + + /** + * + */ + static get BERID() { + return BER.APPLICATION(16); + } +} + +module.exports = MatrixConnection; \ No newline at end of file diff --git a/EmberLib/MatrixContents.js b/EmberLib/MatrixContents.js new file mode 100755 index 0000000..1bbcda8 --- /dev/null +++ b/EmberLib/MatrixContents.js @@ -0,0 +1,154 @@ +"use strict"; + +const MatrixType = require("./MatrixType"); +const MatrixMode = require("./MatrixMode"); +const BER = require('../ber.js'); +const Label = require("./Label"); +const errors = require("../Errors"); + +class MatrixContents { + constructor(type = MatrixType.oneToN, mode = MatrixMode.linear) { + this.type = type; + this.mode = mode; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.EMBER_SET); + if (this.identifier != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.identifier, BER.EMBER_STRING); + ber.endSequence(); + } + if (this.description != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); + } + if (this.type != null) { + ber.startSequence(BER.CONTEXT(2)); + ber.writeInt(this.type.value); + ber.endSequence(); + } + if (this.mode != null) { + ber.startSequence(BER.CONTEXT(3)); + ber.writeInt(this.mode.value); + ber.endSequence(); + } + if (this.targetCount != null) { + ber.startSequence(BER.CONTEXT(4)); + ber.writeInt(this.targetCount); + ber.endSequence(); + } + if (this.sourceCount != null) { + ber.startSequence(BER.CONTEXT(5)); + ber.writeInt(this.sourceCount); + ber.endSequence(); + } + if (this.maximumTotalConnects != null) { + ber.startSequence(BER.CONTEXT(6)); + ber.writeInt(this.maximumTotalConnects); + ber.endSequence(); + } + if (this.maximumConnectsPerTarget != null) { + ber.startSequence(BER.CONTEXT(7)); + ber.writeInt(this.maximumConnectsPerTarget); + ber.endSequence(); + } + if (this.parametersLocation != null) { + ber.startSequence(BER.CONTEXT(8)); + let param = Number(this.parametersLocation) + if (isNaN(param)) { + ber.writeRelativeOID(this.parametersLocation, BER.EMBER_RELATIVE_OID); + } + else { + ber.writeInt(param); + } + ber.endSequence(); + } + if (this.gainParameterNumber != null) { + ber.startSequence(BER.CONTEXT(9)); + ber.writeInt(this.gainParameterNumber); + ber.endSequence(); + } + if (this.labels != null) { + ber.startSequence(BER.CONTEXT(10)); + ber.startSequence(BER.EMBER_SEQUENCE); + for(var i =0; i < this.labels.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + this.labels[i].encode(ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + } + if (this.schemaIdentifiers != null) { + ber.startSequence(BER.CONTEXT(11)); + ber.writeString(this.schemaIdentifiers, BER.EMBER_STRING); + ber.endSequence(); + } + if (this.templateReference != null) { + ber.startSequence(BER.CONTEXT(12)); + ber.writeRelativeOID(this.templateReference, BER.EMBER_RELATIVE_OID); + ber.endSequence(); + } + ber.endSequence(); + } + + static decode(ber) { + const mc = new MatrixContents(); + ber = ber.getSequence(BER.EMBER_SET); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + + if(tag == BER.CONTEXT(0)) { + mc.identifier = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + mc.description = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(2)) { + mc.type = MatrixType.get(seq.readInt()); + } else if(tag == BER.CONTEXT(3)) { + mc.mode = MatrixMode.get(seq.readInt()); + } else if(tag == BER.CONTEXT(4)) { + mc.targetCount = seq.readInt(); + } else if(tag == BER.CONTEXT(5)) { + mc.sourceCount = seq.readInt(); + } else if(tag == BER.CONTEXT(6)) { + mc.maximumTotalConnects = seq.readInt(); + } else if(tag == BER.CONTEXT(7)) { + mc.maximumConnectsPerTarget = seq.readInt(); + } else if(tag == BER.CONTEXT(8)) { + tag = seq.peek(); + if (tag === BER.EMBER_RELATIVE_OID) { + mc.parametersLocation = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else { + mc.parametersLocation = seq.readInt(); + } + } else if(tag == BER.CONTEXT(9)) { + mc.gainParameterNumber = seq.readInt(); + } else if(tag == BER.CONTEXT(10)) { + mc.labels = []; + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + let lSeq = seq.getSequence(BER.CONTEXT(0)); + mc.labels.push(Label.decode(lSeq)); + } + } else if(tag == BER.CONTEXT(11)) { + mc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(12)) { + mc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return mc; + } +} + +module.exports = MatrixContents; \ No newline at end of file diff --git a/EmberLib/MatrixDisposition.js b/EmberLib/MatrixDisposition.js new file mode 100755 index 0000000..1cce450 --- /dev/null +++ b/EmberLib/MatrixDisposition.js @@ -0,0 +1,18 @@ +const Enum = require('enum'); + +// ConnectionDisposition ::= +// INTEGER { +// tally (0), -- default +// modified (1), -- sources contains new current state +// pending (2), -- sources contains future state +// locked (3) -- error: target locked. sources contains current state +// -- more tbd. +// } +const MatrixDisposition = new Enum({ + tally: 0, + modified: 1, + pending: 2, + locked: 3 +}); + +module.exports = MatrixDisposition; \ No newline at end of file diff --git a/EmberLib/MatrixMode.js b/EmberLib/MatrixMode.js new file mode 100755 index 0000000..7101df0 --- /dev/null +++ b/EmberLib/MatrixMode.js @@ -0,0 +1,8 @@ +const Enum = require('enum'); + +const MatrixMode = new Enum({ + linear: 0, + nonLinear: 1 +}); + +module.exports = MatrixMode; \ No newline at end of file diff --git a/EmberLib/MatrixNode.js b/EmberLib/MatrixNode.js new file mode 100755 index 0000000..7cf53d4 --- /dev/null +++ b/EmberLib/MatrixNode.js @@ -0,0 +1,117 @@ +"use strict"; + +const Matrix = require("./Matrix"); +const MatrixContents = require("./MatrixContents"); +const QualifiedMatrix = require("./QualifiedMatrix"); +const BER = require('../ber.js'); +const errors = require("../Errors"); + +class MatrixNode extends Matrix { + constructor(number = undefined) { + super(); + this.number = number; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(MatrixNode.BERID); + + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.number); + ber.endSequence(); // BER.CONTEXT(0) + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + this.encodeTargets(ber); + this.encodeSources(ber); + this.encodeConnections(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + + + /** + * + * @param {boolean} complete + * @returns {MatrixNode} + */ + getMinimal(complete = false) { + const number = this.getNumber(); + const m = new MatrixNode(number); + if (complete) { + if (this.contents != null) { + m.contents = this.contents; + } + if (this.targets != null) { + m.targets = this.targets; + } + if (this.sources != null) { + m.sources = this.sources; + } + if (this.connections != null) { + m.connections = this.connections; + } + } + return m; + } + + /** + * @returns {QualifiedMatrix} + */ + toQualified() { + const qm = new QualifiedMatrix(this.getPath()); + qm.update(this); + return qm; + } + + /** + * + * @param {BER} ber + * @returns {MatrixNode} + */ + static decode(ber) { + const m = new MatrixNode(); + ber = ber.getSequence(MatrixNode.BERID); + while (ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if (tag == BER.CONTEXT(0)) { + m.number = seq.readInt(); + } + else if (tag == BER.CONTEXT(1)) { + m.contents = MatrixContents.decode(seq); + + } else if (tag == BER.CONTEXT(2)) { + m.decodeChildren(seq); + } else if (tag == BER.CONTEXT(3)) { + m.targets = Matrix.decodeTargets(seq); + } else if (tag == BER.CONTEXT(4)) { + m.sources = Matrix.decodeSources(seq); + } else if (tag == BER.CONTEXT(5)) { + m.connections = Matrix.decodeConnections(seq); + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return m; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(13); + } +} + +module.exports = MatrixNode; \ No newline at end of file diff --git a/EmberLib/MatrixOperation.js b/EmberLib/MatrixOperation.js new file mode 100755 index 0000000..f211a0b --- /dev/null +++ b/EmberLib/MatrixOperation.js @@ -0,0 +1,17 @@ +const Enum = require('enum'); + +// ConnectionOperation ::= +// INTEGER { +// absolute (0), -- default. sources contains absolute information +// connect (1), -- nToN only. sources contains sources to add to connection +// disconnect (2) -- nToN only. sources contains sources to remove from +// connection +// } + +const MatrixOperation = new Enum({ + absolute: 0, + connect: 1, + disconnect: 2 +}); + +module.exports = MatrixOperation; \ No newline at end of file diff --git a/EmberLib/MatrixType.js b/EmberLib/MatrixType.js new file mode 100755 index 0000000..8008bd4 --- /dev/null +++ b/EmberLib/MatrixType.js @@ -0,0 +1,10 @@ + +const Enum = require('enum'); + +const MatrixType = new Enum({ + oneToN: 0, + oneToOne: 1, + nToN: 2 +}); + +module.exports = MatrixType; \ No newline at end of file diff --git a/EmberLib/Node.js b/EmberLib/Node.js new file mode 100755 index 0000000..e24c2eb --- /dev/null +++ b/EmberLib/Node.js @@ -0,0 +1,70 @@ +"use strict"; + +const Element = require("./Element"); +const QualifiedNode = require("./QualifiedNode"); +const NodeContents = require("./NodeContents"); +const BER = require('../ber.js'); +const Errors = require("../Errors"); + +class Node extends Element { + /** + * + * @param {number} number + */ + constructor(number) { + super(number); + this._seqID = Node.BERID; + /** @type {NodeContents} */ + this.contents = null; + } + + /** + * @returns {boolean} + */ + isNode() { + return true; + } + + /** + * @returns {QualifiedNode} + */ + toQualified() { + const qn = new QualifiedNode(this.getPath()); + qn.update(this); + return qn; + } + + /** + * + * @param {BER} ber + * @returns {Node} + */ + static decode(ber) { + const n = new Node(); + ber = ber.getSequence(Node.BERID); + + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + n.number = seq.readInt(); + } else if(tag == BER.CONTEXT(1)) { + n.contents = NodeContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + n.decodeChildren(seq); + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return n; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(3); + } +} + +module.exports = Node; diff --git a/EmberLib/NodeContents.js b/EmberLib/NodeContents.js new file mode 100755 index 0000000..cb3c479 --- /dev/null +++ b/EmberLib/NodeContents.js @@ -0,0 +1,87 @@ +"use strict"; +const BER = require('../ber.js'); +const errors = require("../Errors"); + +class NodeContents{ + /** + * + * @param {string} identifier + * @param {string} description + */ + constructor(identifier=null, description=null) { + this.isOnline = true; + this.identifier = identifier; + this.description = description; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.EMBER_SET); + + if(this.identifier != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.identifier, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(0) + } + + if(this.description != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(1) + } + + if(this.isRoot != null) { + ber.startSequence(BER.CONTEXT(2)); + ber.writeBoolean(this.isRoot); + ber.endSequence(); // BER.CONTEXT(2) + } + + if(this.isOnline != null) { + ber.startSequence(BER.CONTEXT(3)); + ber.writeBoolean(this.isOnline); + ber.endSequence(); // BER.CONTEXT(3) + } + + if(this.schemaIdentifiers != null) { + ber.startSequence(BER.CONTEXT(4)); + ber.writeString(this.schemaIdentifiers, BER.EMBER_STRING); + ber.endSequence(); // BER.CONTEXT(4) + } + + ber.endSequence(); // BER.EMBER_SET + } + + /** + * + * @param {BER} ber + * @returns {NodeContents} + */ + static decode(ber) { + var nc = new NodeContents(); + ber = ber.getSequence(BER.EMBER_SET); + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + nc.identifier = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + nc.description = seq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(2)) { + nc.isRoot = seq.readBoolean(); + } else if(tag == BER.CONTEXT(3)) { + nc.isOnline = seq.readBoolean(); + } else if(tag == BER.CONTEXT(4)) { + nc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); + } else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + + return nc; + } +} + +module.exports = NodeContents; \ No newline at end of file diff --git a/EmberLib/Parameter.js b/EmberLib/Parameter.js new file mode 100755 index 0000000..a7a07c8 --- /dev/null +++ b/EmberLib/Parameter.js @@ -0,0 +1,82 @@ +"use strict"; + +const Element = require("./Element"); +const QualifiedParameter = require("./QualifiedParameter"); +const BER = require('../ber.js'); +const ParameterContents = require("./ParameterContents"); +const Errors = require("../Errors"); + +class Parameter extends Element { + /** + * + * @param {number} number + */ + constructor(number) { + super(); + this.number = number; + this._seqID = Parameter.BERID; + } + + /** + * @returns {boolean} + */ + isParameter() { + return true; + } + + /** + * Generate a Root of a partial tree containing the Parameter and its new value. + * Should be sent to the Provider to update the value. + * @param {string|number} value + * @returns {Root} + */ + setValue(value) { + return this.getTreeBranch(undefined, (m) => { + m.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); + }); + } + + /** + * @returns {QualifiedParameter} + */ + toQualified() { + const qp = new QualifiedParameter(this.getPath()); + qp.update(this); + return qp; + } + + /** + * + * @param {BER} ber + * @returns {Parameter} + */ + static decode(ber) { + const p = new Parameter(); + ber = ber.getSequence(Parameter.BERID); + + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + p.number = seq.readInt(); + + } else if(tag == BER.CONTEXT(1)) { + p.contents = ParameterContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + p.decodeChildren(seq); + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return p; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(1); + } +} + +module.exports = Parameter; \ No newline at end of file diff --git a/EmberLib/ParameterAccess.js b/EmberLib/ParameterAccess.js new file mode 100755 index 0000000..e6f7113 --- /dev/null +++ b/EmberLib/ParameterAccess.js @@ -0,0 +1,12 @@ + +const Enum = require('enum'); + +var ParameterAccess = new Enum({ + none: 0, + read: 1, + write: 2, + readWrite: 3 +}); + + +module.exports = ParameterAccess; \ No newline at end of file diff --git a/EmberLib/ParameterContents.js b/EmberLib/ParameterContents.js new file mode 100755 index 0000000..07ad48a --- /dev/null +++ b/EmberLib/ParameterContents.js @@ -0,0 +1,142 @@ +"use strict"; + +const {ParameterType} = require("./ParameterType"); +const ParameterAccess = require("./ParameterAccess"); +const StringIntegerCollection = require("./StringIntegerCollection"); +const StreamDescription = require("./StreamDescription"); +const BER = require('../ber.js'); +const Errors = require("../Errors"); + +class ParameterContents { + /** + * + * @param {string|number} value + * @param {string} type + */ + constructor(value, type) { + if(value != null) { + this.value = value; + } + if(type != null) { + if((type = ParameterType.get(type)) != null){ + this.type = type + } + } + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.EMBER_SET); + + ber.writeIfDefined(this.identifier, ber.writeString, 0, BER.EMBER_STRING); + ber.writeIfDefined(this.description, ber.writeString, 1, BER.EMBER_STRING); + ber.writeIfDefined(this.value, ber.writeValue, 2); + ber.writeIfDefined(this.minimum, ber.writeValue, 3); + ber.writeIfDefined(this.maximum, ber.writeValue, 4); + ber.writeIfDefinedEnum(this.access, ParameterAccess, ber.writeInt, 5); + ber.writeIfDefined(this.format, ber.writeString, 6, BER.EMBER_STRING); + ber.writeIfDefined(this.enumeration, ber.writeString, 7, BER.EMBER_STRING); + ber.writeIfDefined(this.factor, ber.writeInt, 8); + ber.writeIfDefined(this.isOnline, ber.writeBoolean, 9); + ber.writeIfDefined(this.formula, ber.writeString, 10, BER.EMBER_STRING); + ber.writeIfDefined(this.step, ber.writeInt, 11); + ber.writeIfDefined(this.default, ber.writeValue, 12); + ber.writeIfDefinedEnum(this.type, ParameterType, ber.writeInt, 13); + ber.writeIfDefined(this.streamIdentifier, ber.writeInt, 14); + + if(this.enumMap != null) { + ber.startSequence(BER.CONTEXT(15)); + this.enumMap.encode(ber); + ber.endSequence(); + } + + if(this.streamDescriptor != null) { + ber.startSequence(BER.CONTEXT(16)); + this.streamDescriptor.encode(ber); + ber.endSequence(); + } + + ber.writeIfDefined(this.schemaIdentifiers, ber.writeString, 17, BER.EMBER_STRING); + + ber.endSequence(); + } + + /** + * + * @param {BER} ber + * @returns {ParameterContents} + */ + static decode(ber) { + const pc = new ParameterContents(); + ber = ber.getSequence(BER.EMBER_SET); + + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + switch(tag) { + case BER.CONTEXT(0): + pc.identifier = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(1): + pc.description = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(2): + pc.value = seq.readValue(); + break; + case BER.CONTEXT(3): + pc.minimum = seq.readValue(); + break; + case BER.CONTEXT(4): + pc.maximum = seq.readValue(); + break; + case BER.CONTEXT(5): + pc.access = ParameterAccess.get(seq.readInt()); + break; + case BER.CONTEXT(6): + pc.format = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(7): + pc.enumeration = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(8): + pc.factor = seq.readInt(); + break; + case BER.CONTEXT(9): + pc.isOnline = seq.readBoolean(); + break; + case BER.CONTEXT(10): + pc.formula = seq.readString(BER.EMBER_STRING); + break; + case BER.CONTEXT(11): + pc.step = seq.readInt(); + break; + case BER.CONTEXT(12): + pc.default = seq.readValue(); + break; + case BER.CONTEXT(13): + pc.type = ParameterType.get(seq.readInt()); + break; + case BER.CONTEXT(14): + pc.streamIdentifier = seq.readInt(); + break; + case BER.CONTEXT(15): + pc.enumMap = StringIntegerCollection.decode(seq); + break; + case BER.CONTEXT(16): + pc.streamDescriptor = StreamDescription.decode(seq); + break; + case BER.CONTEXT(17): + pc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); + break; + default: + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return pc; + } +} + +module.exports = ParameterContents; diff --git a/EmberLib/ParameterType.js b/EmberLib/ParameterType.js new file mode 100755 index 0000000..421a1c9 --- /dev/null +++ b/EmberLib/ParameterType.js @@ -0,0 +1,53 @@ +const Enum = require('enum'); +const BER = require('../ber.js'); +const Errors = require("../Errors"); + +function ParameterTypetoBERTAG(type) { + switch (type.value) { + case 1: return BER.EMBER_INTEGER; + case 2: return BER.EMBER_REAL; + case 3: return BER.EMBER_STRING; + case 4: return BER.EMBER_BOOLEAN; + case 7: return BER.EMBER_OCTETSTRING; + default: + throw new Errors.InvalidEmberNode("", `Unhandled ParameterType ${type}`); + } +} + +function ParameterTypefromBERTAG(tag) { + switch (tag) { + case BER.EMBER_INTEGER: return ParameterType.integer; + case BER.EMBER_REAL: return ParameterType.real; + case BER.EMBER_STRING: return ParameterType.string; + case BER.EMBER_BOOLEAN: return ParameterType.boolean; + case BER.EMBER_OCTETSTRING: return ParameterType.octets; + default: + throw new Errors.InvalidBERFormat(`Unhandled BER TAB ${tag}`); + } +} + +/* +BER VAlue +Value ::= + CHOICE { + integer Integer64, + real REAL, + string EmberString, + boolean BOOLEAN, + octets OCTET STRING, + null NULL + }*/ + + var ParameterType = new Enum({ + integer: 1, + real: 2, + string: 3, + boolean: 4, + trigger: 5, + enum: 6, + octets: 7 +}); + +module.exports = { + ParameterType, ParameterTypetoBERTAG, ParameterTypefromBERTAG +}; \ No newline at end of file diff --git a/EmberLib/QualifiedElement.js b/EmberLib/QualifiedElement.js new file mode 100755 index 0000000..98e45f4 --- /dev/null +++ b/EmberLib/QualifiedElement.js @@ -0,0 +1,59 @@ +"use strict"; +const TreeNode = require("./TreeNode"); +const BER = require('../ber.js'); +const Command = require("./Command"); +const {COMMAND_GETDIRECTORY} = require("./constants"); + +class QualifiedElement extends TreeNode { + /** + * + * @param {string} path + */ + constructor(path) { + super(); + this.path = path; + } + + /** + * @returns {boolean} + */ + isQualified() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(this._seqID); + + this.encodePath(ber); + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + /** + * + * @param {Command} cmd + * @returns {TreeNode} + */ + getCommand(cmd) { + const r = this.getNewTree(); + const qn = new this.constructor(); + qn.path = this.getPath(); + r.addElement(qn); + qn.addChild(cmd); + return r; + } +} + +module.exports = QualifiedElement; \ No newline at end of file diff --git a/EmberLib/QualifiedFunction.js b/EmberLib/QualifiedFunction.js new file mode 100755 index 0000000..8e4ac84 --- /dev/null +++ b/EmberLib/QualifiedFunction.js @@ -0,0 +1,63 @@ +"use strict"; + +const QualifiedElement = require("./QualifiedElement"); +const FunctionContent = require("./FunctionContent"); +const {COMMAND_GETDIRECTORY} = require("./constants"); +const BER = require('../ber.js'); +const Command = require("./Command"); +const Errors = require("../Errors"); + +class QualifiedFunction extends QualifiedElement { + /** + * + * @param {string} path + * @param {function} func + */ + constructor(path, func) { + super(path); + this.func = func; + this._seqID = QualifiedFunction.BERID; + } + + /** + * @returns {boolean} + */ + isFunction() { + return true; + } + + /** + * + * @param {BER} ber + * @returns {QualifiedFunction} + */ + static decode(ber) { + const qf = new QualifiedFunction(); + ber = ber.getSequence(QualifiedFunction.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qf.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qf.contents = FunctionContent.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qf.decodeChildren(seq); + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return qf; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(20); + } +} + +module.exports = QualifiedFunction; \ No newline at end of file diff --git a/EmberLib/QualifiedMatrix.js b/EmberLib/QualifiedMatrix.js new file mode 100755 index 0000000..3d4f890 --- /dev/null +++ b/EmberLib/QualifiedMatrix.js @@ -0,0 +1,108 @@ +"use strict"; + +const Matrix = require("./Matrix"); +const BER = require('../ber.js'); +const Command = require("./Command"); +const MatrixContents = require("./MatrixContents"); +const MatrixConnection = require("./MatrixConnection"); +const errors = require("../Errors"); + +class QualifiedMatrix extends Matrix { + /** + * + * @param {string} path + */ + constructor(path) { + super(); + this.path = path; + } + + isQualified() { + return true; + } + /** + * + * @param {Object} connections + * @returns {Root} + */ + connect(connections) { + const r = this.getNewTree(); + const qn = new QualifiedMatrix(); + qn.path = this.path; + r.addElement(qn); + qn.connections = connections; + return r; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(QualifiedMatrix.BERID); + + this.encodePath(ber); + + if(this.contents != null) { + ber.startSequence(BER.CONTEXT(1)); + this.contents.encode(ber); + ber.endSequence(); // BER.CONTEXT(1) + } + + this.encodeChildren(ber); + this.encodeTargets(ber); + this.encodeSources(ber); + this.encodeConnections(ber); + + ber.endSequence(); // BER.APPLICATION(3) + } + + /** + * + * @param {BER} ber + * @returns {QualifiedMatrix} + */ + static decode(ber) { + const qm = new QualifiedMatrix(); + ber = ber.getSequence(QualifiedMatrix.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qm.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qm.contents = MatrixContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qm.decodeChildren(seq); + } else if (tag == BER.CONTEXT(3)) { + qm.targets = Matrix.decodeTargets(seq); + } else if (tag == BER.CONTEXT(4)) { + qm.sources = Matrix.decodeSources(seq); + } else if (tag == BER.CONTEXT(5)) { + qm.connections = {}; + seq = seq.getSequence(BER.EMBER_SEQUENCE); + while(seq.remain > 0) { + let conSeq = seq.getSequence(BER.CONTEXT(0)); + let con = MatrixConnection.decode(conSeq); + if (con.target != null) { + qm.connections[con.target] = con; + } + } + } + else { + throw new errors.UnimplementedEmberTypeError(tag); + } + } + return qm; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(17); + } +} + +module.exports = QualifiedMatrix; \ No newline at end of file diff --git a/EmberLib/QualifiedNode.js b/EmberLib/QualifiedNode.js new file mode 100755 index 0000000..155e23c --- /dev/null +++ b/EmberLib/QualifiedNode.js @@ -0,0 +1,53 @@ +"user strict"; +const QualifiedElement = require("./QualifiedElement"); +const BER = require('../ber.js'); +const NodeContents = require("./NodeContents"); +const Errors = require("../Errors"); + +class QualifiedNode extends QualifiedElement { + constructor (path) { + super(path); + this._seqID = QualifiedNode.BERID; + } + + /** + * @returns {boolean} + */ + isNode() { + return true; + } + + /** + * + * @param {BER} ber + * @returns {QualifiedNode} + */ + static decode(ber) { + const qn = new QualifiedNode(); + ber = ber.getSequence(QualifiedNode.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qn.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qn.contents = NodeContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qn.decodeChildren(seq); + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return qn; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(10); + } +} + +module.exports = QualifiedNode; \ No newline at end of file diff --git a/EmberLib/QualifiedParameter.js b/EmberLib/QualifiedParameter.js new file mode 100755 index 0000000..2ff6d8c --- /dev/null +++ b/EmberLib/QualifiedParameter.js @@ -0,0 +1,73 @@ +"use strict"; + +const QualifiedElement = require("./QualifiedElement"); +const ParameterContents = require("./ParameterContents"); +const BER = require('../ber.js'); +const Errors = require("../Errors"); + +class QualifiedParameter extends QualifiedElement { + /** + * + * @param {string} path + */ + constructor(path) { + super(path); + this._seqID = QualifiedParameter.BERID; + } + + /** + * @returns {boolean} + */ + isParameter() { + return true; + } + + /** + * Generate a Root containing a minimal QualifiedParameter and its new value. + * Should be sent to the Provider to update the value. + * @param {number|string} value + * @returns {TreeNode} + */ + setValue(value) { + let r = this.getNewTree(); + let qp = new QualifiedParameter(this.path); + r.addElement(qp); + qp.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); + return r; + } + + + /** + * + * @param {BER} ber + * @returns {QualifiedParameter} + */ + static decode(ber) { + var qp = new QualifiedParameter(); + ber = ber.getSequence(QualifiedParameter.BERID); + while(ber.remain > 0) { + var tag = ber.peek(); + var seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qp.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else if(tag == BER.CONTEXT(1)) { + qp.contents = ParameterContents.decode(seq); + } else if(tag == BER.CONTEXT(2)) { + qp.decodeChildren(seq); + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return qp; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(9); + } +} + +module.exports = QualifiedParameter; \ No newline at end of file diff --git a/EmberLib/QualifiedTemplate.js b/EmberLib/QualifiedTemplate.js new file mode 100755 index 0000000..a5a2d51 --- /dev/null +++ b/EmberLib/QualifiedTemplate.js @@ -0,0 +1,78 @@ +"use strict"; +const TemplateElement = require("./TemplateElement"); +const QualifiedElement = require("./QualifiedElement"); +const Template = require("./Template"); +const BER = require('../ber.js'); +const Errors = require("../Errors"); + +class QualifiedTemplate extends QualifiedElement { + /** + * + * @param {string} path + * @param {Node|Function|MatrixNode|Parameter} element + */ + constructor(path, element) { + super(path); + this.element = element; + this._seqID = QualifiedTemplate.BERID; + } + + /** + * @returns {boolean} + */ + isTemplate() { + return true; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(QualifiedTemplate.BERID); + + this.encodePath(ber); + + TemplateElement.encodeContent(this, ber); + + ber.endSequence(); + } + + /** + * + * @param {Template} other + */ + update(other) { + this.element = other.element; + } + + /** + * + * @param {BER} ber + * @returns {QualifiedTemplate} + */ + static decode(ber) { + const qt = new QualifiedTemplate(); + ber = ber.getSequence(QualifiedTemplate.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + qt.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID + } + else { + TemplateElement.decodeContent(qt, tag, seq); + } + } + return qt; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(25); + } +} + +module.exports = QualifiedTemplate; \ No newline at end of file diff --git a/EmberLib/StreamCollection.js b/EmberLib/StreamCollection.js new file mode 100755 index 0000000..4576809 --- /dev/null +++ b/EmberLib/StreamCollection.js @@ -0,0 +1,102 @@ +const BER = require("../ber"); +const StreamEntry = require("./StreamEntry"); + +class StreamCollection { + /** + * + */ + constructor() { + /** @type {Map} */ + this.elements = new Map(); + } + /** + * + * @param {StreamEntry} entry + */ + addEntry(entry) { + this.elements.set(entry.identifier, entry); + } + /** + * + * @param {StreamEntry} entry + */ + removeEntry(entry) { + this.elements.delete(entry.identifier); + } + /** + * + * @param {number} identifier + * @returns {StreamEntry} + */ + getEntry(identifier) { + return this.elements.get(identifier); + } + + /** + * @returns {StreamEntry} + */ + [Symbol.iterator]() { + return this.elements.values(); + } + + /** + * @retuns {number} + */ + size() { + return this.elements.size; + } + + /** + * + * @param {BER.Writer} ber + */ + encode(ber) { + ber.startSequence(StreamCollection.BERID); + for(let [, entry] of this.elements) { + ber.startSequence(BER.CONTEXT(0)); + entry.encode(ber); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * @returns { + * {identifier: number, value: string|number|boolean|Buffer}[] + * } + */ + toJSON() { + const js = []; + for(let [, entry] of this.elements) { + js.push(entry.toJSON()); + } + return js; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(5); + } + + /** + * + * @param {BER.ExtendedReader} ber + * @returns {StreamCollection} + */ + static decode(ber) { + const streamCollection = new StreamCollection(); + const seq = ber.getSequence(this.BERID); + while (seq.remain > 0) { + const rootReader = seq.getSequence(BER.CONTEXT(0)); + while (rootReader.remain > 0) { + const entry = StreamEntry.decode(rootReader); + streamCollection.addEntry(entry); + } + } + return streamCollection; + } +} + +module.exports = StreamCollection; \ No newline at end of file diff --git a/EmberLib/StreamDescription.js b/EmberLib/StreamDescription.js new file mode 100755 index 0000000..4915551 --- /dev/null +++ b/EmberLib/StreamDescription.js @@ -0,0 +1,71 @@ +"use strict"; +const BER = require('../ber.js'); +const StreamFormat = require("./StreamFormat"); +const Errors = require("../Errors"); + +class StreamDescription { + /** + * + * @param {number} offset + * @param {StreamFormat} format + */ + constructor(offset, format) { + this.offset = offset; + this.format = format; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(StreamDescription.BERID); + + ber.writeIfDefinedEnum(this.format, StreamFormat, ber.writeInt, 0); + ber.writeIfDefined(this.offset, ber.writeInt, 1); + + ber.endSequence(); + } + + /** + * + */ + toJSON() { + return { + format: this.format == null ? null : this.format.key, + offset: this.offset + }; + } + + /** + * + * @param {BER} ber + * @returns {StreamDescription} + */ + static decode(ber) { + const sd = new StreamDescription(); + ber = ber.getSequence(StreamDescription.BERID); + + while(ber.remain > 0) { + var tag = ber.peek(); + var seq =ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + sd.format = StreamFormat.get(seq.readInt()); + } else if(tag == BER.CONTEXT(1)) { + sd.offset = seq.readInt(); + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return sd; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(12); + } +} + +module.exports = StreamDescription; \ No newline at end of file diff --git a/EmberLib/StreamEntry.js b/EmberLib/StreamEntry.js new file mode 100755 index 0000000..2e6b10e --- /dev/null +++ b/EmberLib/StreamEntry.js @@ -0,0 +1,70 @@ +const BER = require("../ber"); +const Errors = require("../Errors"); + +class StreamEntry { + /** + * + * @param {number} identifier + * @param {string|number|boolean|Buffer} value + */ + constructor(identifier, value ) { + this.identifier = identifier; + this.value = value; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(StreamEntry.BERID); + if (this.identifier != null) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeInt(this.identifier); + ber.endSequence(); + } + if (this.value != null) { + ber.startSequence(BER.CONTEXT(1)); + ber.writeValue(this.value); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * @returns {{ + * identifier: number, + * value: string|number + * }} + */ + toJSON() { + return { + identifier: this.identifier, + value: this.value + } + } + + static get BERID() { + return BER.APPLICATION(5); + } + + static decode(ber) { + const entry = new StreamEntry(); + const seq = ber.getSequence(this.BERID); + while(seq.remain > 0) { + const tag = seq.peek(); + const data = seq.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + entry.identifier = data.readInt(); + } else if(tag == BER.CONTEXT(1)) { + entry.value = data.readValue(); + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return entry; + } +} + +module.exports = StreamEntry; \ No newline at end of file diff --git a/EmberLib/StreamFormat.js b/EmberLib/StreamFormat.js new file mode 100755 index 0000000..ed3435d --- /dev/null +++ b/EmberLib/StreamFormat.js @@ -0,0 +1,25 @@ +"use strict"; +const Enum = require('enum'); + +const StreamFormat = new Enum({ + unsignedInt8: 0, + unsignedInt16BigEndian: 2, + unsignedInt16LittleEndian: 3, + unsignedInt32BigEndian: 4, + unsignedInt32LittleEndian: 5, + unsignedInt64BigEndian: 6, + unsignedInt64LittleENdian: 7, + signedInt8: 8, + signedInt16BigEndian: 10, + signedInt16LittleEndian: 11, + signedInt32BigEndian: 12, + signedInt32LittleEndian: 13, + signedInt64BigEndian: 14, + signedInt64LittleEndian: 15, + ieeeFloat32BigEndian: 20, + ieeeFloat32LittleEndian: 21, + ieeeFloat64BigEndian: 22, + ieeeFloat64LittleEndian: 23 +}); + +module.exports = StreamFormat; \ No newline at end of file diff --git a/EmberLib/StringIntegerCollection.js b/EmberLib/StringIntegerCollection.js new file mode 100755 index 0000000..3fd3eb1 --- /dev/null +++ b/EmberLib/StringIntegerCollection.js @@ -0,0 +1,85 @@ +"use strict"; +const BER = require('../ber.js'); +const StringIntegerPair = require("./StringIntegerPair"); +const Errors = require("../Errors"); + +class StringIntegerCollection { + constructor() { + this._collection = new Map(); + } + + /** + * + * @param {string} key + * @param {StringIntegerPair} value + */ + addEntry(key, value) { + if (!(value instanceof StringIntegerPair)) { + throw new Errors.InvalidStringPair(); + } + this._collection.set(key, value); + } + + /** + * + * @param {string} key + * @returns {StringIntegerPair} + */ + get(key) { + return this._collection.get(key); + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(StringIntegerCollection.BERID); + for(let [,sp] of this._collection) { + ber.startSequence(BER.CONTEXT(0)); + sp.encode(ber); + ber.endSequence(); + } + ber.endSequence(); + } + + /** + * @returns {JSON_StringPair[]} + */ + toJSON() { + const collection = []; + for(let [,sp] of this._collection) { + collection.push(sp.toJSON()); + } + return collection; + } + + /** + * + * @param {BER} ber + * @returns {StringIntegerCollection} + */ + static decode(ber) { + const sc = new StringIntegerCollection(); + const seq = ber.getSequence(StringIntegerCollection.BERID); + while(seq.remain > 0) { + const tag = seq.peek(); + if (tag != BER.CONTEXT(0)) { + throw new Errors.UnimplementedEmberTypeError(tag); + } + const data = seq.getSequence(BER.CONTEXT(0)); + const sp = StringIntegerPair.decode(data) + sc.addEntry(sp.key, sp); + } + return sc; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(8); + } +} + +module.exports = StringIntegerCollection; \ No newline at end of file diff --git a/EmberLib/StringIntegerPair.js b/EmberLib/StringIntegerPair.js new file mode 100755 index 0000000..15bbe1c --- /dev/null +++ b/EmberLib/StringIntegerPair.js @@ -0,0 +1,78 @@ +"use strict"; +const BER = require('../ber.js'); +const Errors = require("../Errors"); + +class StringIntegerPair { + constructor(key,value) { + this.key = key; + this.value = value; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + if (this.key == null || this.value == null) { + throw new Errors.InvalidEmberNode("", "Invalid key/value missing"); + } + ber.startSequence(StringIntegerPair.BERID); + ber.startSequence(BER.CONTEXT(0)); + ber.writeString(this.key, BER.EMBER_STRING); + ber.endSequence(); + ber.startSequence(BER.CONTEXT(1)); + ber.writeInt(this.value); + ber.endSequence(); + ber.endSequence(); + } + + /** + * @typedef {{ + * key: string + * value: number + * }} JSON_StringPair + */ + + /** + * @returns {{ + * key: string + * value: number + * }} + */ + toJSON() { + return { + key: this.key, + value: this.value + } + } + /** + * + * @param {BER} ber + * @returns {StringIntegerPair} + */ + static decode(ber) { + const sp = new StringIntegerPair(); + let seq = ber.getSequence(StringIntegerPair.BERID); + while(seq.remain > 0) { + const tag = seq.peek(); + const dataSeq = seq.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + sp.key = dataSeq.readString(BER.EMBER_STRING); + } else if(tag == BER.CONTEXT(1)) { + sp.value = dataSeq.readInt(); + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return sp; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(7); + } +} + +module.exports = StringIntegerPair; \ No newline at end of file diff --git a/EmberLib/Template.js b/EmberLib/Template.js new file mode 100755 index 0000000..44a3950 --- /dev/null +++ b/EmberLib/Template.js @@ -0,0 +1,85 @@ +"use strict"; + +const Element = require("./Element"); +const QualifiedTemplate = require("./QualifiedTemplate"); +const BER = require('../ber.js'); +const TemplateElement = require("./TemplateElement"); +const Errors = require("../Errors"); + +class Template extends Element { + /** + * + * @param {number} number + * @param {Node|Function|MatrixNode|Parameter} element + */ + constructor(number, element) { + super(); + this.number = number; + this.element = element; + this._seqID = Template.BERID; + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(Template.BERID); + this.encodeNumber(ber); + TemplateElement.encodeContent(this, ber); + ber.endSequence(); + } + + /** + * @returns {boolean} + */ + isTemplate() { + return true; + } + + /** + * @returns {QualifiedParameter} + */ + toQualified() { + const qp = new QualifiedTemplate(this.getPath()); + qp.update(this); + return qp; + } + + /** + * + * @param {Template} other + */ + update(other) { + this.element = other.element; + } + + /** + * + * @param {BER} ber + * @returns {Template} + */ + static decode(ber) { + const template = new Template(); + ber = ber.getSequence(Template.BERID); + while(ber.remain > 0) { + let tag = ber.peek(); + let seq = ber.getSequence(tag); + if(tag == BER.CONTEXT(0)) { + template.number = seq.readInt(); + } else { + TemplateElement.decodeContent(template, tag, seq); + } + } + return template; + } + + /** + * @returns {number} + */ + static get BERID() { + return BER.APPLICATION(24); + } +} + +module.exports = Template; \ No newline at end of file diff --git a/EmberLib/TemplateElement.js b/EmberLib/TemplateElement.js new file mode 100755 index 0000000..1642ac0 --- /dev/null +++ b/EmberLib/TemplateElement.js @@ -0,0 +1,75 @@ +"use strict"; + +const BER = require('../ber.js'); +const Parameter = require("./Parameter"); +const Node = require("./Node"); +const MatrixNode = require("./MatrixNode"); +const Function = require("./Function"); +const Errors = require("../Errors"); + +/* +TemplateElement ::= + CHOICE { + parameter Parameter, + node Node, + matrix Matrix, + function Function + } +*/ +class TemplateElement { + /** + * + * @param {Node|Function|Parameter|MatrixNode} ber + */ + static decode(ber) { + const tag = ber.peek(); + if (tag == BER.APPLICATION(1)) { + return Parameter.decode(ber); + } else if(tag == BER.APPLICATION(3)) { + return Node.decode(ber); + } else if(tag == BER.APPLICATION(19)) { + return Function.decode(ber); + } else if(tag == BER.APPLICATION(13)) { + return MatrixNode.decode(ber); + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + + /** + * + * @param {Template|QualifiedTemplate} template + * @param {number} tag + * @param {BER} ber + */ + static decodeContent(template, tag, ber) { + if(tag == BER.CONTEXT(1)) { + template.element = TemplateElement.decode(ber); + } else if(tag == BER.CONTEXT(2)) { + template.description = ber.readString(BER.EMBER_STRING); + } else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + /** + * + * @param {Template|QualifiedTemplate} template + * @param {BER} ber + */ + static encodeContent(template, ber) { + if(template.element != null) { + ber.startSequence(BER.CONTEXT(1)); + template.element.encode(ber); + ber.endSequence(); + } + + if (template.description != null) { + ber.startSequence(BER.CONTEXT(2)); + ber.writeString(template.description, BER.EMBER_STRING); + ber.endSequence(); + } + } +} + +module.exports = TemplateElement; \ No newline at end of file diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js new file mode 100755 index 0000000..69b6ebe --- /dev/null +++ b/EmberLib/TreeNode.js @@ -0,0 +1,560 @@ +"use strict"; +const BER = require('../ber.js'); +const ElementInterface = require("./ElementInterface"); +const Invocation = require("./Invocation"); +const Command = require("./Command"); +const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE, COMMAND_INVOKE} = require("./constants"); +const Errors = require("../Errors"); + +class TreeNode extends ElementInterface { + constructor() { + super(); + /** @type {TreeNode} */ + this._parent = null; + this._subscribers = new Set(); + } + + _isSubscribable(callback) { + return (callback != null && + (this.isParameter() || this.isMatrix())); + } + + _subscribe(callback) { + this._subscribers.add(callback); + } + + _unsubscribe(callback) { + this._subscribers.delete(callback); + } + + /** + * + * @param {TreeNode} child + */ + addChild(child) { + TreeNode.addElement(this, child); + } + + /** + * + * @param {TreeNode} element + */ + addElement(element) { + TreeNode.addElement(this, element); + } + /** + * + */ + addResult(result) { + this.result = result; + } + + /** + * + */ + clear() { + this.elements = undefined; + } + + get children() { + let it = {}; + const self = this; + it[Symbol.iterator] = function*() { + if (self.elements == null) { return null;} + for(let child of self.elements.entries()) { + yield child[1]; + } + } + return it; + } + + /** + * + * @param {BER} ber + */ + decodeChildren(ber) { + const seq = ber.getSequence(BER.APPLICATION(4)); + while(seq.remain > 0) { + const nodeSeq = seq.getSequence(BER.CONTEXT(0)); + this.addChild(TreeNode.decode(nodeSeq)); + } + } + + /** + * + * @param {BER} ber + */ + encode(ber) { + ber.startSequence(BER.APPLICATION(0)); + if(this.elements != null) { + const elements = this.getChildren(); + ber.startSequence(BER.APPLICATION(11)); + for(var i=0; i < elements.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + elements[i].encode(ber); + ber.endSequence(); // BER.CONTEXT(0) + } + ber.endSequence(); + } + if (this.result != null) { + this.result.encode(ber); + } + ber.endSequence(); // BER.APPLICATION(0) + } + + /** + * + * @param {BER} ber + */ + encodeChildren(ber) { + const children = this.getChildren(); + if(children != null) { + ber.startSequence(BER.CONTEXT(2)); + ber.startSequence(BER.APPLICATION(4)); + for(var i = 0; i < children.length; i++) { + ber.startSequence(BER.CONTEXT(0)); + children[i].encode(ber); + ber.endSequence(); + } + ber.endSequence(); + ber.endSequence(); + } + } + + /** + * + * @param {BER} ber + */ + encodePath(ber) { + if (this.isQualified()) { + ber.startSequence(BER.CONTEXT(0)); + ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); + ber.endSequence(); // BER.CONTEXT(0) + } + } + + /** + * @returns {TreeNode} + */ + getNewTree() { + return new TreeNode(); + } + + /** + * @returns {boolean} + */ + hasChildren() { + return this.elements != null && this.elements.size > 0; + } + + /** + * @returns {boolean} + */ + isRoot() { + return this._parent == null; + } + + /** + * @returns {boolean} + */ + isStream() { + return this.contents != null && + this.contents.streamIdentifier != null; + } + + /** + * @returns {TreeNode} + */ + getMinimalContent() { + let obj; + if (this.isQualified()) { + obj = new this.constructor(this.path); + } + else { + obj = new this.constructor(this.number); + } + if (this.contents != null) { + obj.contents= this.contents; + } + return obj; + } + /** + * @returns {TreeNode} + */ + getDuplicate() { + const obj = this.getMinimal(); + obj.update(this); + return obj; + } + + getMinimal() { + if (this.isQualified()) { + return new this.constructor(this.path); + } + else { + return new this.constructor(this.number); + } + } + + getTreeBranch(child, modifier) { + const m = this.getMinimal(); + if(child != null) { + m.addChild(child); + } + + if(modifier != null) { + modifier(m); + } + + if(this._parent === null) { + return m; + } + else { + return this._parent.getTreeBranch(m); + } + } + + getRoot() { + if(this._parent == null) { + return this; + } else { + return this._parent.getRoot(); + } + } + + /** + * + * @param {Command} cmd + */ + getCommand(cmd) { + return this.getTreeBranch(cmd); + } + + /** + * + * @param {function} callback + */ + getDirectory(callback) { + if (this._isSubscribable(callback) && !this.isStream()) { + this._subscribe(callback); + } + return this.getCommand(new Command(COMMAND_GETDIRECTORY)); + } + + + /** + * @returns {TreeNode[]} + */ + getChildren() { + if(this.elements != null) { + return [...this.elements.values()]; + } + return null; + } + + /** + * @returns {number} + */ + getNumber() { + if (this.isQualified()) { + return TreeNode.path2number(this.getPath()); + } + else { + return this.number; + } + } + + /** + * @returns {TreeNode} + */ + getParent() { + return this._parent; + } + + /** + * + * @param {string} path + * @returns {TreeNode} + */ + getElementByPath(path) { + if (this.elements == null || this.elements.size === 0) { + return null; + } + if (this.isRoot()) { + // check if we have QualifiedElement + const node = this.elements.get(path); + if (node != null) { + return node; + } + } + const myPath = this.getPath(); + if (path == myPath) { + return this; + } + const myPathArray = this.isRoot() ? [] : myPath.split("."); + let pathArray = path.split("."); + + if (pathArray.length < myPathArray.length) { + // We are lower in the tree than the requested path + return null; + } + + // Verify that our path matches the beginning of the requested path + for(var i = 0; i < myPathArray.length; i++) { + if (pathArray[i] != myPathArray[i]) { + return null; + } + } + //Now add child by child to get the requested path + let node = this; + while(myPathArray.length != pathArray.length) { + const number = pathArray[myPathArray.length]; + node = node.getElementByNumber(number); + if (node == null) { + return null; + } + if (node.isQualified() && node.path == path) { + return node; + } + myPathArray.push(number); + } + return node; + } + + /** + * + * @param {number} number + * @returns {TreeNode} + */ + getElementByNumber(number) { + const n = Number(number); + if (this.elements != null) { + return this.elements.get(n); + } + return null; + } + /** + * + * @param {string} identifier + * @returns {TreeNode} + */ + getElementByIdentifier(identifier) { + const children = this.getChildren(); + if (children == null) return null; + for(let i = 0; i < children.length; i++) { + if(children[i].contents != null && + children[i].contents.identifier == identifier) { + return children[i]; + } + } + return null; + } + + /** + * + * @param {number|string} id + * @returns {TreeNode} + */ + getElement(id) { + if(Number.isInteger(id)) { + return this.getElementByNumber(id); + } else { + return this.getElementByIdentifier(id); + } + } + + + /** + * @returns {string} + */ + getPath() { + if (this.path != null) { + return this.path; + } + if(this._parent == null) { + if(this.number == null) { + return ""; + } + else { + return this.number.toString(); + } + } else { + let path = this._parent.getPath(); + if(path.length > 0) { + path = path + "."; + } + return path + this.number; + } + } + + /** + * + * @param {FunctionArgument[]} params + * @returns {TreeNode} + */ + invoke(params) { + if (!this.isFunction()) { + throw new Errors.InvalidEmberNode(this.getPath(), "Invoke only for Ember Function"); + } + const invocation = new Invocation(Invocation.newInvocationID()); + invocation.arguments = params; + const req = this.getCommand(Command.getInvocationCommand(invocation)); + return req; + } + + /** + * + */ + toJSON() { + const res = {nodeType: this.constructor.name}; + const node = this; + if (this.isRoot()) { + const elements = this.getChildren(); + return { + elements: elements.map(e => e.toJSON()) + }; + } + res.number = node.getNumber(); + res.path = node.getPath(); + if (node.contents) { + for(let prop in node.contents) { + if (prop[0] == "_" || node.contents[prop] == null) { + continue; + } + if (node.contents.hasOwnProperty(prop)) { + const type = typeof node.contents[prop]; + if ((type === "string") || (type === "number")) { + res[prop] = node.contents[prop]; + } + else if (node.contents[prop].value != null) { + res[prop] = node.contents[prop].value; + } + else { + res[prop] = node.contents[prop]; + } + } + } + } + if (node.isMatrix()) { + if (node.targets) { + res.targets = node.targets.slice(0); + } + if (node.sources) { + res.sources = node.sources.slice(0); + } + if (node.connections) { + res.connections = {}; + for (let target in node.connections) { + if (node.connections.hasOwnProperty(target)) { + res.connections[target] = {target: target, sources: []}; + if (node.connections[target].sources) { + res.connections[target].sources = node.connections[target].sources.slice(0); + } + } + } + + } + } + const children = node.getChildren(); + if (children) { + res.children = []; + for(let child of children) { + res.children.push(child.toJSON()); + } + } + return res; + } + + /** + * + * @param {function} callback + */ + subscribe(callback) { + if (this._isSubscribable(callback) && this.isStream()) { + this._subscribe(callback); + } + return this.getCommand(new Command(COMMAND_SUBSCRIBE)); + } + + /** + * + * @param {*} callback + */ + unsubscribe(callback) { + this._unsubscribe(callback); + return this.getCommand(new Command(COMMAND_UNSUBSCRIBE)); + } + + /** + * + * @param {TreeNode} other + */ + update(other) { + let modified = false; + if ((other != null) && (other.contents != null)) { + if (this.contents == null) { + this.contents = other.contents; + modified = true; + } + else { + for (var key in other.contents) { + if (key[0] === "_") { continue; } + if (other.contents.hasOwnProperty(key) && + this.contents[key] != other.contents[key]) { + this.contents[key] = other.contents[key]; + modified = true; + } + } + } + } + return modified; + } + + updateSubscribers() { + if (this._subscribers != null) { + for(let cb of this._subscribers) { + cb(this); + } + } + } + + /** + * + * @param {TreeNode} parent + * @param {TreeNode} element + */ + static addElement(parent, element) { + /* + Store element hashed by number direct to the parent. + But if QualifiedElement, it could be directly attached to the root. + In this case, use the path instead of number. + However, if the path is a single number, it is equivalent to number. + */ + element._parent = parent; + if(parent.elements == null) { + parent.elements = new Map(); + } + if (parent.isRoot() && element.isQualified()) { + const path = element.getPath().split("."); + if (path.length > 1) { + parent.elements.set(element.getPath(), element); + return; + } + } + parent.elements.set(element.getNumber(), element); + } + + static path2number(path) { + try { + const numbers = path.split("."); + if (numbers.length > 0) { + return Number(numbers[numbers.length - 1]); + } + } + catch(e) { + // ignore + } + } +} + +module.exports = TreeNode; \ No newline at end of file diff --git a/EmberLib/constants.js b/EmberLib/constants.js new file mode 100755 index 0000000..e49d071 --- /dev/null +++ b/EmberLib/constants.js @@ -0,0 +1,23 @@ +const COMMAND_SUBSCRIBE = 30; +const COMMAND_UNSUBSCRIBE = 31; +const COMMAND_GETDIRECTORY = 32; +const COMMAND_INVOKE = 33; + +const COMMAND_STRINGS = { + [COMMAND_SUBSCRIBE]: "subscribe", + [COMMAND_UNSUBSCRIBE]: "unsubscribe", + [COMMAND_GETDIRECTORY]: "getdirectory", + [COMMAND_INVOKE]: "invoke" +}; + +module.exports = { + COMMAND_SUBSCRIBE, + COMMAND_UNSUBSCRIBE, + COMMAND_GETDIRECTORY, + COMMAND_INVOKE, + Subscribe: COMMAND_SUBSCRIBE, + Unsubscribe: COMMAND_UNSUBSCRIBE, + GetDirectory: COMMAND_GETDIRECTORY, + Invoke: COMMAND_INVOKE, + COMMAND_STRINGS +}; \ No newline at end of file diff --git a/EmberLib/index.js b/EmberLib/index.js new file mode 100755 index 0000000..9dc3813 --- /dev/null +++ b/EmberLib/index.js @@ -0,0 +1,164 @@ +const {Subscribe,COMMAND_SUBSCRIBE,Unsubscribe,COMMAND_UNSUBSCRIBE, + GetDirectory,COMMAND_GETDIRECTORY,Invoke,COMMAND_INVOKE, COMMAND_STRINGS} = require("./constants"); +const BER = require('../ber.js'); +const Errors = require("../Errors"); +const TreeNode = require("./TreeNode"); +const Command = require("./Command"); +const Function = require("./Function"); +const FunctionArgument = require("./FunctionArgument"); +const FunctionContent = require("./FunctionContent"); +const Invocation = require("./Invocation"); +const InvocationResult = require("./InvocationResult"); +const Label = require("./Label"); +const Matrix = require("./Matrix"); +const MatrixNode = require("./MatrixNode"); +const MatrixMode = require("./MatrixMode"); +const MatrixType = require("./MatrixType"); +const MatrixContents = require("./MatrixContents"); +const MatrixConnection = require("./MatrixConnection"); +const MatrixOperation = require("./MatrixOperation"); +const MatrixDisposition = require("./MatrixDisposition"); +const Node = require("./Node"); +const NodeContents = require("./NodeContents"); +const Parameter = require("./Parameter"); +const ParameterContents = require("./ParameterContents"); +const ParameterAccess = require("./ParameterAccess"); +const ParameterType = require("./ParameterType").ParameterType; +const QualifiedFunction = require("./QualifiedFunction"); +const QualifiedMatrix = require("./QualifiedMatrix"); +const QualifiedNode = require("./QualifiedNode"); +const QualifiedParameter = require("./QualifiedParameter"); +const StringIntegerPair = require("./StringIntegerPair"); +const StringIntegerCollection = require("./StringIntegerCollection"); +const StreamFormat = require("./StreamFormat"); +const StreamDescription = require("./StreamDescription"); +const StreamCollection = require("./StreamCollection"); +const Template = require("./Template"); +const TemplateElement = require("./TemplateElement"); +const QualifiedTemplate = require("./QualifiedTemplate"); + +const rootDecode = function(ber) { + const r = new TreeNode(); + let tag = undefined; + while(ber.remain > 0) { + tag = ber.peek(); + if (tag === BER.APPLICATION(0)) { + ber = ber.getSequence(BER.APPLICATION(0)); + tag = ber.peek(); + + if (tag === BER.APPLICATION(11)) { + const seq = ber.getSequence(BER.APPLICATION(11)); + while (seq.remain > 0) { + try { + const rootReader = seq.getSequence(BER.CONTEXT(0)); + while (rootReader.remain > 0) { + r.addElement(childDecode(rootReader)); + } + } + catch (e) { + throw e; + } + } + } + else if (tag === BER.APPLICATION(23)) { // InvocationResult BER.APPLICATION(23) + return InvocationResult.decode(ber) + } + else { + // StreamCollection BER.APPLICATION(6) + // InvocationResult BER.APPLICATION(23) + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + else if (tag === BER.CONTEXT(0)) { + // continuation of previous message + try { + var rootReader = ber.getSequence(BER.CONTEXT(0)); + return childDecode(rootReader) + } + catch (e) { + return r; + } + } + else { + throw new Errors.UnimplementedEmberTypeError(tag); + } + } + return r; +} + +const TreeNodeDecoders = { + [Parameter.BERID]: Parameter.decode, + [Node.BERID]: Node.decode, + [Command.BERID]: Command.decode, + [QualifiedParameter.BERID]: QualifiedParameter.decode, + [QualifiedNode.BERID]: QualifiedNode.decode, + [MatrixNode.BERID]: MatrixNode.decode, + [QualifiedMatrix.BERID]: QualifiedMatrix.decode, + [Function.BERID]: Function.decode, + [QualifiedFunction.BERID]: QualifiedFunction.decode, + [Template.BERID]: Template.decode, + [QualifiedTemplate.BERID]: QualifiedTemplate +}; + +const childDecode = function(ber) { + const tag = ber.peek(); + const decode = TreeNodeDecoders[tag]; + if (decode == null) { + throw new Errors.UnimplementedEmberTypeError(tag); + } + else { + return decode(ber); + } +} + +TreeNode.decode = childDecode; + +const DecodeBuffer = function (packet) { + const ber = new BER.Reader(packet); + return rootDecode(ber); +}; + +module.exports = { + Command, + COMMAND_STRINGS, + childDecode: childDecode, + rootDecode: rootDecode, + DecodeBuffer, + Root: TreeNode, + Function, + FunctionArgument, + FunctionContent, + Invocation, + InvocationResult, + Label, + Matrix, + MatrixNode, + MatrixMode, + MatrixType, + MatrixContents, + MatrixConnection, + MatrixDisposition, + MatrixOperation, + Node, + NodeContents, + Parameter, + ParameterContents, + ParameterAccess, + ParameterType, + QualifiedFunction , + QualifiedMatrix, + QualifiedNode, + QualifiedParameter, + QualifiedTemplate, + StreamFormat, + StreamDescription, + StreamCollection, + StringIntegerPair, + StringIntegerCollection, + Template, + TemplateElement, + Subscribe,COMMAND_SUBSCRIBE, + Unsubscribe,COMMAND_UNSUBSCRIBE, + GetDirectory,COMMAND_GETDIRECTORY, + Invoke,COMMAND_INVOKE +} \ No newline at end of file diff --git a/EmberServer/ElementHandlers.js b/EmberServer/ElementHandlers.js new file mode 100755 index 0000000..e48d60f --- /dev/null +++ b/EmberServer/ElementHandlers.js @@ -0,0 +1,195 @@ +"use strict"; +const QualifiedHandlers = require("./QualifiedHandlers"); +const EmberLib = require('../EmberLib'); +const ServerEvents = require("./ServerEvents"); +const Errors = require("../Errors"); +const winston = require("winston"); + +class ElementHandlers extends QualifiedHandlers{ + /** + * + * @param {EmberServer} server + */ + constructor(server) { + super(server); + } + /** + * + * @param {S101Client} client + * @param {TreeNode} root + * @param {Command} cmd + */ + handleCommand(client, element, cmd) { + let identifier = "root" + if (!element.isRoot()) { + const node = this.server.tree.getElementByPath(element.getPath()); + identifier = node == null || node.contents == null || node.contents.identifier == null ? "unknown" : node.contents.identifier; + } + const src = client == null ? "local" : `${client.socket.remoteAddress}:${client.socket.remotePort}`; + switch(cmd.number) { + case EmberLib.COMMAND_GETDIRECTORY: + this.server.emit("event", ServerEvents.GETDIRECTORY(identifier, element.getPath(), src)); + this.handleGetDirectory(client, element); + break; + case EmberLib.COMMAND_SUBSCRIBE: + this.server.emit("event", ServerEvents.SUBSCRIBE(identifier, element.getPath(), src)); + this.handleSubscribe(client, element); + break; + case EmberLib.COMMAND_UNSUBSCRIBE: + this.server.emit("event", ServerEvents.UNSUBSCRIBE(identifier, element.getPath(), src)); + this.handleUnSubscribe(client, element); + break; + case EmberLib.COMMAND_INVOKE: + this.server.emit("event", ServerEvents.INVOKE(identifier, element.getPath(), src)); + this.handleInvoke(client, cmd.invocation, element); + break; + default: + this.server.emit("error", new Errors.InvalidCommand(cmd.number)); + return; + } + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleGetDirectory(client, element) { + if (client != null) { + if ((element.isMatrix() || element.isParameter()) && + (!element.isStream())) { + // ember spec: parameter without streamIdentifier should + // report their value changes automatically. + this.server.subscribe(client, element); + } + else if (element.isNode()) { + const children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if ((child.isMatrix() || child.isParameter()) && + (!child.isStream())) { + this.server.subscribe(client, child); + } + } + } + } + + const res = this.server.getQualifiedResponse(element); + winston.debug("getDirectory response", res); + client.sendBERNode(res); + } + } + + /** + * + * @param {S101Client} client + * @param {Invocation} invocation + * @param {TreeNode} element + */ + handleInvoke(client, invocation, element) { + const result = new EmberLib.InvocationResult(); + result.invocationId = invocation.id; + if (element == null || !element.isFunction()) { + result.setFailure(); + } + else { + try { + result.setResult(element.func(invocation.arguments)); + } + catch(e){ + this.server.emit("error", e); + result.setFailure(); + } + } + const res = new EmberLib.Root(); + res.addResult(result); + client.sendBERNode(res); + } + + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleNode(client, node) { + // traverse the tree + let element = node; + let path = []; + while(element != null) { + if (element.isCommand()) { + break; + } + if (element.number == null) { + this.server.emit("error", new Errors.MissingElementNumber()); + return; + } + + path.push(element.number); + + const children = element.getChildren(); + if ((! children) || (children.length === 0)) { + break; + } + element = children[0]; + } + let cmd = element; + + if (cmd == null) { + this.server.emit("error", new Errors.InvalidRequest()); + this.server.handleError(client); + return path; + } + + element = this.server.tree.getElementByPath(path.join(".")); + + if (element == null) { + this.server.emit("error", new Errors.UnknownElement(path.join("."))); + return this.server.handleError(client); + } + if (cmd.isCommand()) { + this.handleCommand(client, element, cmd); + return path; + } else if ((cmd.isMatrix()) && (cmd.connections != null)) { + this.handleMatrixConnections(client, element, cmd.connections); + } + else if ((cmd.isParameter()) && + (cmd.contents != null) && (cmd.contents.value != null)) { + winston.debug(`setValue for element at path ${path} with value ${cmd.contents.value}`); + this.server.setValue(element, cmd.contents.value, client); + const res = this.server.getResponse(element); + client.sendBERNode(res) + this.server.updateSubscribers(element.getPath(), res, client); + } + else { + this.server.emit("error", new Errors.InvalidRequesrFormat(path.join("."))); + winston.debug("invalid request format"); + return this.server.handleError(client, element.getTreeBranch()); + } + // for logging purpose, return the path. + return path; + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleSubscribe(client, element) { + winston.debug("subscribe", element); + this.server.subscribe(client, element); + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleUnSubscribe(client, element) { + winston.debug("unsubscribe", element); + this.server.unsubscribe(client, element); + } +} + +module.exports = ElementHandlers; \ No newline at end of file diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js new file mode 100755 index 0000000..741f045 --- /dev/null +++ b/EmberServer/EmberServer.js @@ -0,0 +1,519 @@ +const EventEmitter = require('events').EventEmitter; +const S101Server = require('../EmberSocket').S101Server; +const EmberLib = require('../EmberLib'); +const JSONParser = require("./JSONParser"); +const ElementHandlers = require("./ElementHandlers"); +const ServerEvents = require("./ServerEvents"); +const Errors = require("../Errors"); +const winston = require("winston"); + +class TreeServer extends EventEmitter{ + /** + * + * @param {string} host + * @param {number} port + * @param {TreeNode} tree + */ + constructor(host, port, tree) { + super(); + this._debug = true; + this.timeoutValue = 2000; + this.server = new S101Server(host, port); + this.tree = tree; + this.clients = new Set(); + this.subscribers = {}; + this._handlers = new ElementHandlers(this); + + this.server.on('listening', () => { + winston.debug("listening"); + this.emit('listening'); + }); + + this.server.on('connection', client => { + winston.debug("ember new connection from", client.remoteAddress()); + this.clients.add(client); + client.on("emberTree", (root) => { + winston.debug("ember new request from", client.remoteAddress(), root); + // Queue the action to make sure responses are sent in order. + client.addRequest(() => { + try { + const path = this.handleRoot(client, root); + this.emit("request", {client: client.remoteAddress(), root: root, path: path}); + } + catch(e) { + winston.debug(e.stack) + this.emit("error", e); + } + }); + }); + client.on("disconnected", () => { + this.clients.delete(client); + this.emit('disconnect', client.remoteAddress()); + }); + client.on("error", error => { + this.emit('clientError', { remoteAddress: client.remoteAddress(), error }); + }); + this.emit('connection', client.remoteAddress()); + }); + + this.server.on('disconnected', () => { + this.clients.clear(); + this.emit('disconnected'); + }); + + this.server.on("error", (e) => { + this.emit("error", e); + }); + } + + /** + * @returns {Promise} + */ + close() { + return new Promise((resolve, reject) => { + const cb = e => { + if (e == null) { + return resolve(); + } + return reject(e); + }; + if (this.server.server != null) { + this.server.server.close(cb); + } + else { + cb(); + } + this.clients.clear(); + }); + } + + /** + * + * @param {Matrix} matrix + * @param {number} targetID + * @returns {number} + */ + getDisconnectSource(matrix, targetID) { + return this._handlers.getDisconnectSource(matrix, targetID); + } + + /** + * + * @param {TreeNode} element + * @returns {TreeNode} + */ + getResponse(element) { + return element.getTreeBranch(undefined, node => { + node.update(element); + const children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + node.addChild(children[i].getDuplicate()); + } + } + }); + } + + /** + * + * @param {TreeNode} element + */ + getQualifiedResponse(element) { + const res = new EmberLib.Root(); + let dup; + if (element.isRoot() === false) { + dup = element.toQualified(); + } + const children = element.getChildren(); + if (children != null) { + for (let i = 0; i < children.length; i++) { + res.addChild(children[i].toQualified().getMinimalContent()); + } + } + else { + res.addChild(dup); + } + return res; + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + handleError(client, node) { + if (client != null) { + const res = node == null ? this.tree.getMinimal() : node; + client.sendBERNode(res); + } + } + + /** + * + * @param {S101Socket} client + * @param {TreeNode} root + */ + handleRoot(client, root) { + if ((root == null) || (root.elements == null) || (root.elements.size < 1)) { + // ignore empty requests. + return; + } + + const node = root.getChildren()[0]; + client.request = node; + + if (node.path != null) { + return this._handlers.handleQualifiedNode(client, node); + } + else if (node.isCommand()) { + // Command on root element + this._handlers.handleCommand(client, this.tree, node); + return "root"; + } + else { + return this._handlers.handleNode(client, node); + } + } + + /** + * @returns {Promise} + */ + listen() { + return this.server.listen(); + } + + /** + * + * @param {string} path + * @param {number} target + * @param {number[]} sources + */ + matrixConnect(path, target, sources) { + doMatrixOperation(this, path, target, sources, EmberLib.MatrixOperation.connect); + } + + /** + * + * @param {string} path + * @param {number} target + * @param {number[]} sources + */ + matrixDisconnect(path, target, sources) { + doMatrixOperation(this, path, target, sources, EmberLib.MatrixOperation.disconnect); + } + + /** + * + * @param {string} path + * @param {number} target + * @param {number[]} sources + */ + matrixSet(path, target, sources) { + doMatrixOperation(this, path, target, sources, EmberLib.MatrixOperation.absolute); + } + + /** + * + * @param {Matrix} matrix + * @param {number} target + * @param {number[]} sources + * @param {S101Socket} client + * @param {boolean} response + */ + disconnectMatrixTarget(matrix, target, sources, client, response) { + const disconnect = new EmberLib.MatrixConnection(target); + disconnect.setSources([]); + disconnect.disposition = EmberLib.MatrixDisposition.modified; + matrix.setSources(target, []); + if (response) { + this.emit("matrix-disconnect", { + target: target, + sources: sources, + client: client == null ? null : client.remoteAddress() + }); + } + return disconnect; + } + + /** + * + * @param {Matrix} matrix + * @param {number} target + * @param {number[]} sources + * @param {S101Socket} client + * @param {boolean} response + */ + disconnectSources(matrix, target, sources, client, response) { + const disconnect = new EmberLib.MatrixConnection(target); + disconnect.disposition = EmberLib.MatrixDisposition.modified; + matrix.disconnectSources(target, sources); + if (response) { + this.emit("matrix-disconnect", { + target: target, + sources: sources, + client: client == null ? null : client.remoteAddress() + }); + } + return disconnect; + } + + /** + * + * @param {Matrix} matrix + * @param {MatrixConnection} connection + * @param {Matrix} res - result + * @param {S101Socket} client + * @param {boolean} response + */ + preMatrixConnect(matrix, connection, res, client, response) { + const conResult = res.connections[connection.target]; + + if (matrix.contents.type !== EmberLib.MatrixType.nToN && + connection.operation !== EmberLib.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 1) { + if (matrix.contents.type === EmberLib.MatrixType.oneToOne) { + // if the source is being used already, disconnect it from current target. + const currentTargets = matrix.getSourceConnections(connection.sources[0]); + if (currentTargets.length === 1 && currentTargets[0] !== connection.target) { + res.connections[currentTargets[0]] = + this.disconnectMatrixTarget(matrix, currentTargets[0], connection.sources, client, response); + } + } + // if the target is connected already, disconnect it + if (matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length === 1) { + if (matrix.contents.type === EmberLib.MatrixType.oneToN) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + if (disconnectSource >= 0 && disconnectSource != connection.sources[0]) { + connection.sources = [disconnectSource]; + } + else { + // do nothing => set disposition to bypass further processing + conResult.disposition = EmberLib.MatrixDisposition.tally; + } + } + } + if (matrix.connections[connection.target].sources[0] !== connection.sources[0]) { + this.disconnectMatrixTarget(matrix, connection.target, matrix.connections[connection.target].sources, client, response) + } + else if (matrix.contents.type === EmberLib.MatrixType.oneToOne) { + // let's change the request into a disconnect + connection.operation = EmberLib.MatrixOperation.disconnect; + } + } + } + } + + applyMatrixConnect(matrix, connection, conResult, client, response) { + // Apply changes + let emitType; + if ((connection.operation == null) || + (connection.operation.value == EmberLib.MatrixOperation.absolute)) { + matrix.setSources(connection.target, connection.sources); + emitType = "matrix-change"; + } + else if (connection.operation == EmberLib.MatrixOperation.connect) { + matrix.connectSources(connection.target, connection.sources); + emitType = "matrix-connect"; + } + conResult.disposition = EmberLib.MatrixDisposition.modified; + if (response && emitType != null) { + // We got a request so emit something. + this.emit(emitType, { + target: connection.target, + sources: connection.sources, + client: client == null ? null : client.remoteAddress() + }); + } + } + + /** + * + * @param {Matrix} matrix + * @param {MatrixConnection} connection + * @param {Matrix} res - result + * @param {S101Socket} client + * @param {boolean} response + */ + applyMatrixOneToNDisconnect(matrix, connection, res, client, response) { + const disconnectSource = this.getDisconnectSource(matrix, connection.target); + if (matrix.connections[connection.target].sources[0] == connection.sources[0]) { + const conResult = res.connections[connection.target]; + if (disconnectSource >= 0 && disconnectSource != connection.sources[0]) { + if (response) { + this.server.emit("matrix-disconnect", { + target: connection.target, + sources: matrix.connections[connection.target].sources, + client: client == null ? null : client.remoteAddress() + }); + } + matrix.setSources(connection.target, [disconnectSource]); + conResult.disposition = EmberLib.MatrixDisposition.modified; + } + else { + // do nothing + conResult.disposition = EmberLib.MatrixDisposition.tally; + } + } + } + + /** + * + * @param {TreeNode} element + */ + replaceElement(element) { + const path = element.getPath(); + const existingElement = this.tree.getElementByPath(path); + if (existingElement == null) { + throw new Errors.UnknownElement(path); + } + const parent = existingElement._parent; + if (parent == null) { + throw new Errors.InvalidEmberNode(path, "No parent. Can't execute replaceElement"); + } + // Replace the element at the parent + parent.elements.set(existingElement.getNumber(), element); + // point the new element to parent + element._parent = parent; + const res = this.getResponse(element); + this.updateSubscribers(path,res); + } + + /** + * + * @param {TreeNode} element + * @param {string|number} value + * @param {S101Socket} origin + * @param {string} key + */ + setValue(element, value, origin, key) { + return new Promise(resolve => { + // Change the element value if write access permitted. + winston.debug("New Setvalue request"); + if (element.contents == null) { + return resolve(); + } + if (element.isParameter() || element.isMatrix()) { + if (element.isParameter() && + (element.contents.access != null) && + (element.contents.access.value > 1)) { + element.contents.value = value; + winston.debug("New value ", value, "path", element.getPath()); + const res = this.getResponse(element); + this.updateSubscribers(element.getPath(),res, origin); + } + else if ((key != null) && (element.contents.hasOwnProperty(key))) { + element.contents[key] = value; + const res = this.getResponse(element); + this.updateSubscribers(element.getPath(),res, origin); + } + const src = origin == null ? "local" : `${origin.socket.remoteAddress}:${origin.socket.remotePort}`; + this.emit("value-change", element); + this.emit("event", ServerEvents.SETVALUE(element.contents.identifier,element.getPath(),src)); + } + return resolve(); + }); + } + + /** + * + * @param {S101Socket} client + * @param {TreeNode} root + */ + subscribe(client, element) { + const path = element.getPath(); + if (this.subscribers[path] == null) { + this.subscribers[path] = new Set(); + } + this.subscribers[path].add(client); + } + + /** + * @returns {Object} + */ + toJSON() { + if (this.tree == null) { + return []; + } + const elements = this.tree.getChildren(); + + return elements.map(element => element.toJSON()); + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} root + */ + unsubscribe(client, element) { + const path = element.getPath(); + if (this.subscribers[path] == null) { + return; + } + this.subscribers[path].delete(client); + } + + /** + * + * @param {string} path + * @param {TreeNode} response + * @param {S101Socket} origin + */ + updateSubscribers(path, response, origin) { + if (this.subscribers[path] == null) { + winston.debug("No subscribers for", path); + return; + } + + for (let client of this.subscribers[path]) { + if (client === origin) { + continue; // already sent the response to origin + } + if (this.clients.has(client)) { + winston.debug("Sending new value to", client.remoteAddress()); + client.queueMessage(response); + } + else { + // clean up subscribers - client is gone + winston.debug("deleting client"); + this.subscribers[path].delete(client); + } + } + } + + /** + * + * @param {object} obj + * @returns {TreeNode} + */ + static JSONtoTree(obj) { + const tree = new EmberLib.Root(); + JSONParser.parseObj(tree, obj); + return tree; + } +} + + +const validateMatrixOperation = function(matrix, target, sources) { + if (matrix == null) { + throw new Errors.UnknownElement(`matrix not found`); + } + if (matrix.contents == null) { + throw new Errors.MissingElementContents(matrix.getPath()); + } + matrix.validateConnection(target, sources); +} + +const doMatrixOperation = function(server, path, target, sources, operation) { + const matrix = server.tree.getElementByPath(path); + + validateMatrixOperation(matrix, target, sources); + + const connection = new EmberLib.MatrixConnection(target); + connection.sources = sources; + connection.operation = operation; + server._handlers.handleMatrixConnections(undefined, matrix, [connection], false); +} + +module.exports = TreeServer; diff --git a/EmberServer/JSONParser.js b/EmberServer/JSONParser.js new file mode 100755 index 0000000..dfa5015 --- /dev/null +++ b/EmberServer/JSONParser.js @@ -0,0 +1,170 @@ +"use strict"; +const ember = require('../EmberLib'); +const Errors = require("../Errors"); + +class JSONParser { + /** + * + * @param {MatrixContent} matrixContent + * @param {object} content + */ + static parseMatrixContent(matrixContent, content) { + if (content.labels) { + matrixContent.labels = []; + for(let l = 0; l < content.labels.length; l++) { + if (typeof (content.labels[l]) === "object") { + matrixContent.labels.push( + new ember.Label( + content.labels[l].basePath, + content.labels[l].description + ) + ); + } + else { + // for backward compatibility... Remove in the future + matrixContent.labels.push( + new ember.Label(content.labels[l]) + ); + } + } + delete content.labels; + } + if (content.type != null) { + if (content.type == "oneToN") { + matrixContent.type = ember.MatrixType.oneToN; + } + else if (content.type == "oneToOne") { + matrixContent.type = ember.MatrixType.oneToOne; + } + else if (content.type == "nToN") { + matrixContent.type = ember.MatrixType.nToN; + matrixContent.maximumTotalConnects = content.maximumTotalConnects == null ? + Number(content.targetCount) * Number(content.sourceCount) : Number(content.maximumTotalConnects); + matrixContent.maximumConnectsPerTarget = content.maximumConnectsPerTarget == null ? + Number(content.sourceCount) : Number(content.maximumConnectsPerTarget); + } + else { + throw new Errors.InvalidEmberNode("", `Invalid matrix type ${content.type}`); + } + delete content.type; + } + if (content.mode != null) { + if (content.mode == "linear") { + matrixContent.mode = ember.MatrixMode.linear; + } + else if (content.mode == "nonLinear") { + matrixContent.mode = ember.MatrixMode.nonLinear; + } + else { + throw new Errors.InvalidEmberNode("",`Invalid matrix mode ${content.mode}`); + } + delete content.mode; + } + } + + /** + * + * @param {TreeNode} parent + * @param {object} obj + */ + static parseObj(parent, obj) { + for(let i = 0; i < obj.length; i++) { + let emberElement; + let content = obj[i]; + let number = content.number != null ? content.number : i; + delete content.number; + if (content.value != null) { + emberElement = new ember.Parameter(number); + emberElement.contents = new ember.ParameterContents(content.value); + if (content.type) { + emberElement.contents.type = ember.ParameterType.get(content.type); + delete content.type; + } + else { + emberElement.contents.type = ember.ParameterType.string; + } + if (content.access) { + emberElement.contents.access = ember.ParameterAccess.get(content.access); + delete content.access; + } + else { + emberElement.contents.access = ember.ParameterAccess.read; + } + if (content.streamDescriptor != null) { + if (content.streamDescriptor.offset == null || content.streamDescriptor.format == null) { + throw new Error("Missing offset or format for streamDescriptor"); + } + emberElement.contents.streamDescriptor = new ember.StreamDescription(); + emberElement.contents.streamDescriptor.offset = content.streamDescriptor.offset; + emberElement.contents.streamDescriptor.format = ember.StreamFormat.get(content.streamDescriptor.format); + delete content.streamDescriptor; + } + } + else if (content.func != null) { + emberElement = new ember.Function(number, content.func); + emberElement.contents = new ember.FunctionContent(); + if (content.arguments != null) { + for(let argument of content.arguments) { + emberElement.contents.arguments.push(new ember.FunctionArgument( + argument.type, + argument.value, + argument.name + )); + } + } + if (content.result != null) { + for(let argument of content.result) { + emberElement.contents.result.push(new ember.FunctionArgument( + argument.type, + argument.value, + argument.name + )); + } + } + delete content.result; + } + else if (content.targetCount != null) { + emberElement = new ember.MatrixNode(number); + emberElement.contents = new ember.MatrixContents(); + this.parseMatrixContent(emberElement.contents, content); + if (content.connections) { + emberElement.connections = {}; + for (let c in content.connections) { + if (! content.connections.hasOwnProperty(c)) { + continue; + } + const t = content.connections[c].target != null ? content.connections[c].target : 0; + emberElement.setSources(t, content.connections[c].sources); + } + delete content.connections; + } + else { + emberElement.connections = {}; + for (let t = 0; t < content.targetCount; t++) { + let connection = new ember.MatrixConnection(t); + emberElement.connections[t] = connection; + } + } + } + else { + emberElement = new ember.Node(number); + emberElement.contents = new ember.NodeContents(); + } + for(let id in content) { + if (emberElement.isFunction() && id === "arguments") { + // we did it already. + continue; + } + if ((id !== "children") && (content.hasOwnProperty(id))) { + emberElement.contents[id] = content[id]; + } + else { + this.parseObj(emberElement, content.children); + } + } + parent.addChild(emberElement); + } + } +} + +module.exports = JSONParser; \ No newline at end of file diff --git a/EmberServer/MatrixHandlers.js b/EmberServer/MatrixHandlers.js new file mode 100755 index 0000000..0bb9f9c --- /dev/null +++ b/EmberServer/MatrixHandlers.js @@ -0,0 +1,135 @@ +"use strict"; +const EmberLib = require('../EmberLib'); +const ServerEvents = require("./ServerEvents"); +const winston = require("winston"); + +class MatrixHandlers { + /** + * + * @param {EmberServer} server + */ + constructor(server) { + this.server = server; + } + + /** + * + * @param {Matrix} matrix + * @param {number} targetID + * @return {number} + */ + getDisconnectSource(matrix, targetID) { + if (matrix.defaultSources) { + return matrix.defaultSources[targetID].contents.value; + } + if (matrix.contents.labels == null || matrix.contents.labels.length == 0) { + return null; + } + const basePath = matrix.contents.labels[0].basePath; + const labels = this.server.tree.getElementByPath(basePath); + const number = labels.getNumber() + 1; + const parent = labels.getParent(); + const children = parent.getChildren(); + for(let child of children) { + if (child.getNumber() === number) { + matrix.defaultSources = child.getChildren(); + return matrix.defaultSources[targetID].contents.value; + } + } + return -1; + } + + /** + * + * @param {S101Client} client + * @param {Matrix} matrix + * @param {Object} connections + * @param {boolean} response=true + */ + handleMatrixConnections(client, matrix, connections, response = true) { + let res; // response + let conResult; + let root; // ember message root + winston.debug("Handling Matrix Connection"); + if (client != null && client.request.isQualified()) { + root = new EmberLib.Root(); + res = new EmberLib.QualifiedMatrix(matrix.getPath()); + //root.elements = [res]; // do not use addchild or the element will get removed from the tree. + root.addElement(res); + } + else { + res = new EmberLib.MatrixNode(matrix.number); + root = matrix._parent.getTreeBranch(res); + } + res.connections = {}; + for(let id in connections) { + if (!connections.hasOwnProperty(id)) { + continue; + } + const connection = connections[id]; + const src = client == null ? "local" : `${client.socket.remoteAddress}:${client.socket.remotePort}`; + this.server.emit("event", ServerEvents.MATRIX_CONNECTION( + matrix.contents.identifier,matrix.getPath(),src,id,connection.sources + )); + conResult = new EmberLib.MatrixConnection(connection.target); + res.connections[connection.target] = conResult; + + if (matrix.connections[connection.target].isLocked()) { + conResult.disposition = EmberLib.MatrixDisposition.locked; + } + else { + // Call pre-processing function + this.server.preMatrixConnect(matrix, connection, res, client, response); + } + + if (conResult.disposition == null) { + // No decision made yet + if (connection.operation !== EmberLib.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length > 0 && + matrix.canConnect(connection.target,connection.sources,connection.operation)) { + this.server.applyMatrixConnect(matrix, connection, conResult, client, response); + } + else if (connection.operation !== EmberLib.MatrixOperation.disconnect && + connection.sources != null && connection.sources.length === 0 && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // let's disconnect + conResult = this.server.disconnectMatrixTarget( + matrix, connection.target, + matrix.connections[connection.target].sources, + client, + response); + } + else if (connection.operation === EmberLib.MatrixOperation.disconnect && + matrix.connections[connection.target].sources != null && + matrix.connections[connection.target].sources.length > 0) { + // Disconnect + if (matrix.contents.type === EmberLib.MatrixType.oneToN) { + this.server.applyMatrixOneToNDisconnect(matrix, connection, res, client, response); + } + else { + conResult = this.server.disconnectSources(matrix, connection.target, connection.sources, client, response); + } + } + } + if (conResult.disposition == null){ + winston.debug(`Invalid Matrix operation ${connection.operation} on target ${connection.target} with sources ${JSON.stringify(connection.sources)}`); + conResult.disposition = EmberLib.MatrixDisposition.tally; + } + + // Send response or update subscribers. + conResult.sources = matrix.connections[connection.target].sources; + } + if (client != null) { + client.sendBERNode(root); + } + + if (conResult != null && conResult.disposition !== EmberLib.MatrixDisposition.tally) { + winston.debug("Updating subscribers for matrix change"); + this.server.updateSubscribers(matrix.getPath(), root, client); + } + } + +} + +module.exports = MatrixHandlers; diff --git a/EmberServer/QualifiedHandlers.js b/EmberServer/QualifiedHandlers.js new file mode 100755 index 0000000..43755ff --- /dev/null +++ b/EmberServer/QualifiedHandlers.js @@ -0,0 +1,75 @@ +"use strict"; +const MatrixHandlers = require("./MatrixHandlers"); +const Errors = require("../Errors"); + +class QualifiedHandlers extends MatrixHandlers { + /** + * + * @param {EmberServer} server + */ + constructor(server) { + super(server); + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} element + * @param {QualifiedMatrix} matrix + */ + handleQualifiedMatrix(client, element, matrix) + { + this.handleMatrixConnections(client, element, matrix.connections); + } + + /** + * + * @param {S101Client} client + * @param {QualifiedNode} root + */ + handleQualifiedNode(client, node) { + const path = node.path; + // Find this element in our tree + const element = this.server.tree.getElementByPath(path); + + if (element == null) { + this.server.emit("error", new Errors.UnknownElement(path)); + return this.server.handleError(client); + } + + if (node.hasChildren()) { + for(let child of node.children) { + if (child.isCommand()) { + this.server._handlers.handleCommand(client, element, child); + } + break; + } + } + else { + if (node.isMatrix()) { + this.handleQualifiedMatrix(client, element, node); + } + else if (node.isParameter()) { + this.handleQualifiedParameter(client, element, node); + } + } + return path; + } + + /** + * + * @param {S101Client} client + * @param {TreeNode} element + * @param {QualifiedParameter} parameter + */ + handleQualifiedParameter(client, element, parameter) + { + if (parameter.contents.value != null) { + this.server.setValue(element, parameter.contents.value, client); + let res = this.server.getQualifiedResponse(element); + client.sendBERNode(res) + } + } +} + +module.exports = QualifiedHandlers; diff --git a/EmberServer/ServerEvents.js b/EmberServer/ServerEvents.js new file mode 100755 index 0000000..62b19a5 --- /dev/null +++ b/EmberServer/ServerEvents.js @@ -0,0 +1,124 @@ +"use strict"; + +const Enum = require('enum'); + +const Types = new Enum({ + UNKNOWN: 0, + SETVALUE: 1, + GETDIRECTORY: 2, + SUBSCRIBE: 3, + UNSUBSCRIBE: 4, + INVOKE: 5, + MATRIX_CONNECTION: 6 +}); + +class ServerEvents { + /** + * + * @param {string} txt + * @param {number} type=0 + */ + constructor(txt, type=Types.UNKNOWN) { + /** @type {string} */ + this._txt = txt; + /** @type {number} */ + this._type = type; + this._timestamp = Date.now(); + } + + /** + * @returns {number} + */ + get type() { + return this._type; + } + + /** + * @returns {number} + */ + get timestamp() { + return this._timestamp; + } + + /** + * @returns {string} + */ + toString() { + return this._txt; + } + + /** + * @returns {Enum} + */ + static get Types() { + return Types; + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static SETVALUE(identifier,path, src) { + return new ServerEvents(`set value for ${identifier}(path: ${path}) from ${src}`, Types.SETVALUE); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static GETDIRECTORY(identifier,path, src) { + return new ServerEvents(`getdirectory to ${identifier}(path: ${path}) from ${src}`, Types.GETDIRECTORY); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static SUBSCRIBE(identifier,path, src) { + return new ServerEvents(`subscribe to ${identifier}(path: ${path}) from ${src}`, Types.SUBSCRIBE); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static UNSUBSCRIBE(identifier,path, src) { + return new ServerEvents(`unsubscribe to ${identifier}(path: ${path}) from ${src}`, Types.UNSUBSCRIBE); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + */ + static INVOKE(identifier,path, src) { + return new ServerEvents(`invoke to ${identifier}(path: ${path}) from ${src}`, Types.INVOKE); + } + + /** + * + * @param {number} identifier + * @param {string} path + * @param {string} src + * @param {number} target + * @param {number[]} sources + */ + static MATRIX_CONNECTION(identifier, path, src, target, sources) { + const sourcesInfo = sources == null || sources.length === 0 ? "empty" : sources.toString(); + return new ServerEvents( + `Matrix connection to ${identifier}(path: ${path}) target ${target} connections: ${sourcesInfo} from ${src}`, + Types.MATRIX_CONNECTION + ); + } +} + +module.exports = ServerEvents; \ No newline at end of file diff --git a/EmberServer/index.js b/EmberServer/index.js new file mode 100755 index 0000000..2a6ba5c --- /dev/null +++ b/EmberServer/index.js @@ -0,0 +1,4 @@ +module.exports = { + EmberServer: require("./EmberServer"), + ServerEvents: require("./ServerEvents") +}; \ No newline at end of file diff --git a/EmberSocket/S101Client.js b/EmberSocket/S101Client.js new file mode 100755 index 0000000..d1d2527 --- /dev/null +++ b/EmberSocket/S101Client.js @@ -0,0 +1,49 @@ +"use strict"; +const net = require('net'); +const S101Socket = require("./S101Socket"); + +const DEFAULT_PORT = 9000; +class S101Client extends S101Socket { + /** + * + * @param {string} address + * @param {number} port=9000 + */ + constructor(address, port=DEFAULT_PORT) { + super(); + this.address = address; + this.port = port; + } + + /** + * + * @param {number} timeout + */ + connect(timeout = 2) { + if (this.status !== "disconnected") { + return; + } + this.emit('connecting'); + const connectTimeoutListener = () => { + this.socket.destroy(); + this.emit("error", new Error(`Could not connect to ${this.address}:${this.port} after a timeout of ${timeout} seconds`)); + }; + + this.socket = net.createConnection({ + port: this.port, + host: this.address, + timeout: 1000 * timeout + }, + () => { + // Disable connect timeout to hand-over to keepalive mechanism + this.socket.removeListener("timeout", connectTimeoutListener); + this.socket.setTimeout(0); + this.startKeepAlive(); + this.emit('connected'); + }) + .once("timeout", connectTimeoutListener); + this._initSocket(); + } +} + +module.exports = S101Client; diff --git a/EmberSocket/S101Server.js b/EmberSocket/S101Server.js new file mode 100755 index 0000000..d4e80ec --- /dev/null +++ b/EmberSocket/S101Server.js @@ -0,0 +1,55 @@ +"use strict"; +const EventEmitter = require('events').EventEmitter; +const S101Socket = require("./S101Socket"); +const net = require('net'); +const Errors = require("../Errors"); + +class S101Server extends EventEmitter { + /** + * + * @param {string} address + * @param {number} port + */ + constructor(address, port) { + super(); + this.address = address; + this.port = Number(port); + this.server = null; + this.status = "disconnected"; + } + /** + * + * @param {Socket} socket - tcp socket + */ + addClient(socket) { + // Wrap the tcp socket into an S101Socket. + const client = new S101Socket(socket); + this.emit("connection", client); + } + /** + * @returns {Promise} + */ + listen() { + return new Promise((resolve, reject) => { + if (this.status !== "disconnected") { + return reject(new Errors.S101SocketError("Already listening")); + } + this.server = net.createServer((socket) => { + this.addClient(socket); + }).on("error", (e) => { + this.emit("error", e); + if (this.status === "disconnected") { + return reject(e); + } + }).on("listening", () => { + this.emit("listening"); + this.status = "listening"; + resolve(); + }); + this.server.listen(this.port, this.address); + }); + } + +} + +module.exports = S101Server; \ No newline at end of file diff --git a/EmberSocket/S101Socket.js b/EmberSocket/S101Socket.js new file mode 100755 index 0000000..9035be7 --- /dev/null +++ b/EmberSocket/S101Socket.js @@ -0,0 +1,218 @@ +"use strict"; + +const EventEmitter = require('events').EventEmitter; +const BER = require('../ber.js'); +const ember = require('../EmberLib'); +const S101Codec = require('../s101.js'); + + +class S101Socket extends EventEmitter{ + /** + * + * @param {Socket} socket + */ + constructor(socket = null) { + super(); + this.socket = socket; + this.keepaliveInterval = 10; + this.keepaliveIntervalTimer = null; + this.pendingRequests = []; + this.activeRequest = null; + this.status = this.isConnected() ? "connected" : "disconnected"; + + this.codec = new S101Codec(); + this.codec.on('keepaliveReq', () => { + this.sendKeepaliveResponse(); + }); + + this.codec.on('emberPacket', (packet) => { + this.emit('emberPacket', packet); + const ber = new BER.Reader(packet); + try { + const root = ember.rootDecode(ber); + if (root != null) { + this.emit('emberTree', root); + } + } catch (e) { + this.emit("error", e); + } + }); + + this._initSocket(); + } + + _initSocket() { + if (this.socket != null) { + this.socket.on('data', (data) => { + this.codec.dataIn(data); + }); + + this.socket.on('close', () => { + this.emit('disconnected'); + this.status = "disconnected"; + this.socket = null; + }); + + this.socket.on('error', (e) => { + this.emit("error", e); + }); + } + } + + /** + * + * @param {function} cb + */ + addRequest(cb) { + this.pendingRequests.push(cb); + this._makeRequest(); + } + + _makeRequest() { + if (this.activeRequest === null && this.pendingRequests.length > 0) { + this.activeRequest = this.pendingRequests.shift(); + this.activeRequest(); + this.activeRequest = null; + } + } + + /** + * + * @param {TreeNode} node + */ + queueMessage(node) { + this.addRequest(() => this.sendBERNode(node)); + } + + /** + * @returns {string} - ie: "10.1.1.1:9000" + */ + remoteAddress() { + if (this.socket == null) { + return "not connected"; + } + return `${this.socket.remoteAddress}:${this.socket.remotePort}` + } + + /** + * @param {number} timeout=2 + */ + disconnect(timeout = 2) { + if (!this.isConnected()) { + return Promise.resolve(); + } + return new Promise((resolve, reject) => { + if (this.keepaliveIntervalTimer != null) { + clearInterval(this.keepaliveIntervalTimer); + this.keepaliveIntervalTimer = null; + } + let done = false; + const cb = (data, error) => { + if (done) { return; } + done = true; + if (timer != null) { + clearTimeout(timer); + timer = null; + } + if (error == null) { + resolve(); + } + else { + reject(error); + } + }; + let timer; + if (timeout != null && (!isNaN(timeout)) && timeout > 0) { + timer = setTimeout(cb, 100 * timeout); + } + this.socket.end(cb); + this.status = "disconnected"; + }); + } + + /** + * + */ + handleClose() { + this.socket = null; + clearInterval(this.keepaliveIntervalTimer); + this.status = "disconnected"; + this.emit('disconnected'); + } + + /** + * @returns {boolean} + */ + isConnected() { + return ((this.socket !== null) && (this.socket != null)); + } + + /** + * + * @param {Buffer} data + */ + sendBER(data) { + if (this.isConnected()) { + try { + const frames = this.codec.encodeBER(data); + for (let i = 0; i < frames.length; i++) { + this.socket.write(frames[i]); + } + } + catch(e){ + this.handleClose(); + } + } + } + + /** + * + */ + sendKeepaliveRequest() { + if (this.isConnected()) { + try { + this.socket.write(this.codec.keepAliveRequest()); + } + catch(e){ + this.handleClose(); + } + } + } + + /** + * + */ + sendKeepaliveResponse() { + if (this.isConnected()) { + try { + this.socket.write(this.codec.keepAliveResponse()); + } + catch(e){ + this.handleClose(); + } + } + } + + /** + * + * @param {TreeNode} node + */ + sendBERNode(node) { + if (!node) return; + const writer = new BER.Writer(); + node.encode(writer); + this.sendBER(writer.buffer); + } + + startKeepAlive() { + this.keepaliveIntervalTimer = setInterval(() => { + try { + this.sendKeepaliveRequest(); + } catch (e) { + this.emit("error", e); + } + }, 1000 * this.keepaliveInterval); + } +} + +module.exports = S101Socket; \ No newline at end of file diff --git a/EmberSocket/index.js b/EmberSocket/index.js new file mode 100755 index 0000000..d755a5b --- /dev/null +++ b/EmberSocket/index.js @@ -0,0 +1,7 @@ +const S101Socket = require("./S101Socket"); +const S101Server = require("./S101Server"); +const S101Client = require("./S101Client"); + +module.exports = { + S101Client, S101Server, S101Socket +}; \ No newline at end of file diff --git a/Errors.js b/Errors.js new file mode 100755 index 0000000..dcffe41 --- /dev/null +++ b/Errors.js @@ -0,0 +1,228 @@ + +/**************************************************************************** + * UnimplementedEmberType error + ***************************************************************************/ + +class UnimplementedEmberTypeError extends Error { + constructor(tag) { + super(); + this.name = this.constructor.name; + var identifier = (tag & 0xC0) >> 6; + var value = (tag & 0x1F).toString(); + var tagStr = tag.toString(); + if(identifier == 0) { + tagStr = "[UNIVERSAL " + value + "]"; + } else if(identifier == 1) { + tagStr = "[APPLICATION " + value + "]"; + } else if(identifier == 2) { + tagStr = "[CONTEXT " + value + "]"; + } else { + tagStr = "[PRIVATE " + value + "]"; + } + this.message = "Unimplemented EmBER type " + tagStr; + } +} + +module.exports.UnimplementedEmberTypeError = UnimplementedEmberTypeError; + +class S101SocketError extends Error { + constructor(message) { + super(message); + } +} +module.exports.S101SocketError = S101SocketError; + +class ASN1Error extends Error { + constructor(message) { + super(message); + } +} + +module.exports.ASN1Error = ASN1Error; + +class EmberAccessError extends Error { + constructor(message) { + super(message); + } +} + +module.exports.EmberAccessError = EmberAccessError; + +class EmberTimeoutError extends Error { + constructor(message) { + super(message); + } +} + +module.exports.EmberTimeoutError = EmberTimeoutError; + +class InvalidCommand extends Error { + /** + * + * @param {number} number + */ + constructor(number) { + super(`Invalid command ${number}`); + } +} + +module.exports.InvalidCommand = InvalidCommand; + +class MissingElementNumber extends Error { + constructor() { + super("Missing element number"); + } +} + +module.exports.MissingElementNumber = MissingElementNumber; + +class MissingElementContents extends Error { + /** + * + * @param {string} path + */ + constructor(path) { + super(`Missing element contents at ${path}`); + } +} + +module.exports.MissingElementContents = MissingElementContents; + +class UnknownElement extends Error { + /** + * + * @param {string} path + */ + constructor(path) { + super(`No element at path ${path}`); + } +} + +module.exports.UnknownElement = UnknownElement; + +class InvalidRequest extends Error { + constructor() { + super("Invalid Request"); + } +} + +module.exports.InvalidRequest = InvalidRequest; + +class InvalidRequestFormat extends Error { + /** + * + * @param {string} path + */ + constructor(path) { + super(`Invalid Request Format with path ${path}`); + } +} + +module.exports.InvalidRequestFormat = InvalidRequestFormat; + +class InvalidEmberNode extends Error { + /** + * + * @param {string} path + * @param {string} info + */ + constructor(path="unknown", info="") { + super(`Invalid Ember Node at ${path}: ${info}`); + } +} +module.exports.InvalidEmberNode = InvalidEmberNode; + +class InvalidEmberResponse extends Error { + /** + * + * @param {string} req + */ + constructor(req) { + super(`Invalid Ember Response to ${req}`); + } +} +module.exports.InvalidEmberResponse = InvalidEmberResponse; + +class PathDiscoveryFailure extends Error { + /** + * + * @param {string} path + */ + constructor(path) { + super(PathDiscoveryFailure.getMessage(path)); + } + + /** + * + * @param {string} path + */ + setPath(path) { + this.message = PathDiscoveryFailure.getMessage(path); + } + + static getMessage(path) { + return `Failed path discovery at ${path}`; + } +} +module.exports.PathDiscoveryFailure = PathDiscoveryFailure; + +class InvalidSourcesFormat extends Error { + constructor() { + super("Sources should be an array"); + } +} +module.exports.InvalidSourcesFormat = InvalidSourcesFormat; + +class InvalidBERFormat extends Error { + /** + * + * @param {string} info + */ + constructor(info="") { + super(`Invalid BER format: ${info}`); + } +} +module.exports.InvalidBERFormat = InvalidBERFormat; + +class InvalidResultFormat extends Error { + /** + * + * @param {string} info + */ + constructor(info="") { + super(`Invalid Result format: ${info}`); + } +} +module.exports.InvalidResultFormat = InvalidResultFormat; + +class InvalidMatrixSignal extends Error { + /** + * + * @param {number} value + * @param {string} info + */ + constructor(value, info) { + super(`Invalid Matrix Signal ${value}: ${info}`); + } +} +module.exports.InvalidMatrixSignal = InvalidMatrixSignal; + +class InvalidStringPair extends Error { + /** + * + */ + constructor() { + super("Invalid StringPair Value"); + } +} +module.exports.InvalidStringPair = InvalidStringPair; + +class InvalidRequesrFormat extends Error { + /** + * @param {string} path + */ + constructor(path) { + super(`Can't process request for node ${path}`); + } +} +module.exports.InvalidRequesrFormat = InvalidRequesrFormat; \ No newline at end of file diff --git a/README.md b/README.md index 2464cc9..e2c77ae 100755 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # node-emberplus -This is an implementation of [Lawo's -Ember+](https://github.com/Lawo/ember-plus) control protocol for Node. One of -Node's great strengths is the ready availability of frameworks for various +This is version 2 of emberplus library. +An implementation of [Lawo's Ember+](https://github.com/Lawo/ember-plus) control protocol for Node. +One of Node's great strengths is the ready availability of frameworks for various communication protocols and user interfaces; this module allows those to be integrated with Ember+ somewhat more easily than the reference libember C++ implementation. -This version support following ember objects : Node, Parameter, Matrix, QualifiedNode, +This version support following ember objects : Node, Parameter, Matrix, Function, QualifiedNode, QualifiedParameter, QualifiedMatrix, QualifiedFunction. It has been tested with EVS XT4k and Embrionix IP solutions. @@ -17,37 +17,288 @@ the way the lib is used so that now it uses Promise Server has been added in version 1.6.0. +Support for StreamCollection, UDP, packet stats/rate, support for tree with size higher than 8M +is available in a private branch. +If you want access to full version and/or would like support for new features or integration +with your projects, please contact Gilles Dufour - dufour.gilles@gmail.com + ## Example usage +### Client + +Get Full tree: + ```javascript -const DeviceTree = require('emberplus').DeviceTree; -var root; -var tree = new DeviceTree("10.9.8.7", 9000); -tree.connect() -.then(() => { - return tree.getDirectory(); -}) -.then((r) => { - root = r ; - return tree.expand(r.elements[0]); -}) -.then(() => { - console.log("done"); -}) -.catch((e) => { - console.log(e.stack); +const EmberClient = require('node-emberplus').EmberClient; +const client = new EmberClient("10.9.8.7", 9000); +client.on("error", e => { + console.log(e); }); +client.connect() + // Get Root info + .then(() => client.getDirectory()) + // Get a Specific Node + .then(() => client.getElementByPath("0.0.2")) + .then(node => { + console.log(node); + }) + // Get a node by its path identifiers + .then(() => client.getElementByPath("path/to/node")) + .then(node => { + console.log(node); + }) + // Expand entire tree under node 0 + .then(() => client.expand(client.root.getElementByNumber(0))) + .catch((e) => { + console.log(e.stack); + }); +``` + +Subsribe to changes + +```javascript +const {EmberClient, EmberLib} = require('node-emberplus'); + +const client = new EmberClient(HOST, PORT); +client.connect()) + .then(() => client.getDirectory()) + .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) + .then(() => client.getElementByPath("scoreMaster/router/labels/group 1")) + .then(node => { + // For streams, use subscribe + return client.subscribe(node, update => { + console.log(udpate); + }); + }) + .then(() => client.getElementByPath("0.2")) + .then(node => { + // For non-streams a getDirectory will automatically subscribe for update + return client.getDirectory(node, update => { + console.log(udpate); + }); + }) + // You can also provide a callback to the getElementByPath + // Be carefull that subscription will be done for all elements in the path + .then(() => client.getElementByPath("0.3", update => {console.log(update);})) + ; +``` + +### Setting new value + +```javascript +client = new EmberClient(LOCALHOST, PORT); +await client.connect() +await client.getDirectory(); +await client.getElementByPath("0.0.1"); +await client.setValue(client.root.getElementByPath("0.0.1"), "gdnet"); +console.log("result", server.tree.getElementByPath("0.0.1").contents.value) +return client.disconnect().then(() => { console.log("disconnected")}); +``` + +### Invoking Function + +```javascript +const {EmberClient, EmberLib} = require('node-emberplus'); + +const client = new EmberClient(HOST, PORT); +client.connect()) + .then(() => client.getDirectory()) + .then(() => {console.log(JSON.stringify(client.root.toJSON(), null, 4));}) + .then(() => client.expand(client.root.getElementByNumber(0))) + .then(() => { + console.log(JSON.stringify(client.root.getElementByNumber(0).toJSON(), null, 4)); + const funcNode = client.root.getElementByNumber(0).getElementByNumber(5).getElementByNumber(0); + return client.invokeFunction(funcNode, [ + new ember.FunctionArgument(EmberLib.ParameterType.integer, 1), + new ember.FunctionArgument(EmberLib.ParameterType.integer, 7) + ]); + }); +``` +### Matrix Connection +```javascript +const {EmberClient, EmberLib} = require('node-emberplus'); + + +const client = new EmberClient(HOST, PORT); +client.connect() + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("0.1.0")) + .then(matrix => { + console.log("Connecting source 1 to target 0); + return client.matrixConnect(matrix, 0, [1]); + }) + .then(() => client.matrixDisconnect(matrix, 0, [1])) + .then(() => client.matrixSetConnection(matrix, 0, [0,1])) + .then(matrix => client.getElementByPath(matrix.getPath())) + .then(() => client.disconnect()); + +``` + +### Packet decoder + +```javascript // Simple packet decoder -const Decoder = require('emberplus').Decoder; +const Decoder = require('node-emberplus').Decoder; const fs = require("fs"); fs.readFile("tree.ember", (e,data) => { var root = Decoder(data); }); +``` +### Server + +```javascript // Server -const TreeServer = require("emberplus").TreeServer; -const server = new TreeServer("127.0.0.1", 9000, root); +const EmberServer = require("node-emberplus").EmberServer; +const server = new EmberServer("127.0.0.1", 9000, root); +server.on("error", e => { + console.log("Server Error", e); +}); +server.on("clientError", info => { + console.log("clientError", info); +}); +server.on("matrix-disconnect", info => { + console.log(`Client ${info.client} disconnected ${info.target} and ${info.sources}`); +} +server.on("matrix-connect", info => { + console.log(`Client ${info.client} connected ${info.target} and ${info.sources}`); +} +server.on("matrix-change", info => { + console.log(`Client ${info.client} changed ${info.target} and ${info.sources}`); +} +server.on("event", txt => { + console.log("event: " + txt); +}) server.listen().then(() => { console.log("listening"); }).catch((e) => { console.log(e.stack); }); +``` + +### Construct Tree + +```javascript +const EmberServer = require("node-emberplus").EmberServer; +const {ParameterType, FunctionArgument} = require("node-emberplus").EmberLib; + +const targets = [ "tgt1", "tgt2", "tgt3" ]; +const sources = [ "src1", "src2", "src3" ]; +const defaultSources = [ + {identifier: "t-0", value: -1, access: "readWrite" }, + {identifier: "t-1", value: 0, access: "readWrite"}, + {identifier: "t-2", value: 0, access: "readWrite"} +]; +const labels = function(endpoints, type) { + let labels = []; + for (let i = 0; i < endpoints.length; i++) { + let endpoint = endpoints[i]; + let l = { identifier: `${type}-${i}` }; + if (endpoint) { + l.value = endpoint; + } + labels.push(l); + } + return labels; +}; + +const buildConnections = function(s, t) { + let connections = []; + for (let i = 0; i < t.length; i++) { + connections.push({target: `${i}`}); + } + return connections; +}; +const jsonTree = [ + { + // path "0" + identifier: "GDNet Tree", + children: [ + { + // path "0.0" + identifier: "identity", + children: [ + {identifier: "product", value: "gdnet core"}, + {identifier: "company", value: "GDNet", access: "readWrite"}, + {identifier: "version", value: "1.2.0"}, + {identifier: "author", value: "dufour.gilles@gmail.com"}, + ] + }, + { + // path "0.1" + identifier: "router", + children: [ + { + // path 0.1.0 + identifier: "matrix", + type: "oneToN", + mode: "linear", + targetCount: targets.length, + sourceCount: sources.length, + connections: buildConnections(sources, targets), + labels: [{basePath: "0.1.1000", description: "primary"}] + }, + { + identifier: "labels", + // path "0.1.1000" + number: 1000, + children: [ + { + identifier: "targets", + // Must be 1 + number: 1, + children: labels(targets, "t") + }, + { + identifier: "sources", + // Must be 2 + number: 2, + children: labels(sources, "s") + }, + { + identifier: "group 1", + children: [ {identifier: "sdp A", value: "A"}, {identifier: "sdp B", value: "B"}] + } + ] + }, + { + identifier: "disconnect sources", + // must be labels + 1 + number: 1001, + children: defaultSources + } + ] + }, + { + // path "0.2" + identifier: "addFunction", + func: args => { + const res = new FunctionArgument(); + res.type = ParameterType.integer; + res.value = args[0].value + args[1].value; + return [res]; + }, + arguments: [ + { + type: ParameterType.integer, + value: null, + name: "arg1" + }, + { + type: ParameterType.integer, + value: null, + name: "arg2" + } + ], + result: [ + { + type: ParameterType.integer, + value: null, + name: "changeCount" + } + ] + } + ] + } +]; +const root = EmberServer.JSONtoTree(jsonTree); +``` diff --git a/ber.js b/ber.js index 8518c39..72ae011 100755 --- a/ber.js +++ b/ber.js @@ -22,7 +22,7 @@ ***************************************************************************/ const BER = require('asn1').Ber; -const errors = require('./errors.js'); +const errors = require('./Errors.js'); const util = require('util'); const Long = require('long'); @@ -74,17 +74,21 @@ function ExtendedReader(data) { util.inherits(ExtendedReader, BER.Reader); module.exports.Reader = ExtendedReader; - -readBlock = function(ber) { - -} - - ExtendedReader.prototype.getSequence = function(tag) { var buf = this.readString(tag, true); return new ExtendedReader(buf); } +/** +Value ::= + CHOICE { + integer Integer64, + real REAL, + string EmberString, + boolean BOOLEAN, + octets OCTET STRING, + null NULL + } */ ExtendedReader.prototype.readValue = function() { var tag = this.peek(); @@ -108,7 +112,7 @@ ExtendedReader.prototype.readValue = function() { ExtendedReader.prototype.readReal = function(tag) { - if(tag !== undefined) { + if(tag != null) { tag = UNIVERSAL(9); } @@ -313,7 +317,7 @@ ExtendedWriter.prototype.writeValue = function(value, tag) { } ExtendedWriter.prototype.writeIfDefined = function(property, writer, outer, inner) { - if(property !== undefined) { + if(property != null) { this.startSequence(CONTEXT(outer)); writer.call(this, property, inner); this.endSequence(); @@ -321,9 +325,9 @@ ExtendedWriter.prototype.writeIfDefined = function(property, writer, outer, inne } ExtendedWriter.prototype.writeIfDefinedEnum = function(property, type, writer, outer, inner) { - if(property !== undefined) { + if(property != null) { this.startSequence(CONTEXT(outer)); - if(property.value !== undefined) { + if(property.value != null) { writer.call(this, property.value, inner); } else { writer.call(this, type.get(property), inner); diff --git a/client.js b/client.js deleted file mode 100755 index a60916a..0000000 --- a/client.js +++ /dev/null @@ -1,291 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const winston = require('winston'); -const net = require('net'); -const BER = require('./ber.js'); -const ember = require('./ember.js'); - -const S101Codec = require('./s101.js'); - - -function S101Socket(address, port) { - var self = this; - S101Socket.super_.call(this); - - self.address = address; - self.port = port; - self.socket = null; - self.keepaliveInterval = 10; - self.codec = null; - self.status = "disconnected"; - -} - -util.inherits(S101Socket, EventEmitter); - - -function S101Client(socket, server) { - var self = this; - S101Client.super_.call(this); - - self.request = null; - self.server = server; - self.socket = socket; - - self.pendingRequests = []; - self.activeRequest = null; - - self.status = "connected"; - - self.codec = new S101Codec(); - self.codec.on('keepaliveReq', () => { - self.sendKeepaliveResponse(); - }); - - self.codec.on('emberPacket', (packet) => { - self.emit('emberPacket', packet); - var ber = new BER.Reader(packet); - try { - var root = ember.Root.decode(ber); - if (root !== undefined) { - self.emit('emberTree', root); - } - } catch (e) { - self.emit("error", e); - } - }); - - if (socket !== undefined) { - self.socket.on('data', (data) => { - self.codec.dataIn(data); - }); - - self.socket.on('close', () => { - self.emit('disconnected'); - self.status = "disconnected"; - self.socket = null; - }); - - self.socket.on('error', (e) => { - self.emit("error", e); - }); - } -} - -util.inherits(S101Client, S101Socket); - - -/********************************************** - * SERVER - **********************************************/ - -function S101Server(address, port) { - var self = this; - S101Server.super_.call(this); - - self.address = address; - self.port = port; - self.server = null; - self.status = "disconnected"; -} - -util.inherits(S101Server, EventEmitter); - -S101Server.prototype.listen = function () { - var self = this; - if (self.status !== "disconnected") { - return; - } - - self.server = net.createServer((socket) => { - self.addClient(socket); - }); - - self.server.on("error", (e) => { - self.emit("error", e); - }); - - self.server.on("listening", () => { - self.emit("listening"); - self.status = "listening"; - }); - - self.server.listen(self.port, self.address); -} - - -S101Server.prototype.addClient = function (socket) { - var client = new S101Client(socket, this); - this.emit("connection", client); -} - - -/***************************************************** - * Client - *****************************************************/ - -S101Client.prototype.remoteAddress = function () { - if (this.socket === undefined) { - return; - } - return `${this.socket.remoteAddress}:${this.socket.remotePort}` -} - -S101Client.prototype.queueMessage = function (node) { - const self = this; - this.addRequest(() => { - self.sendBERNode(node); - }); -} - -S101Client.prototype.makeRequest = function () { - if (this.activeRequest === null && this.pendingRequests.length > 0) { - this.activeRequest = this.pendingRequests.shift(); - this.activeRequest(); - this.activeRequest = null; - } -}; - -S101Client.prototype.addRequest = function (cb) { - this.pendingRequests.push(cb); - this.makeRequest(); -} - - -/***************************************************** - * Socket - *****************************************************/ - -S101Socket.prototype.connect = function (timeout = 2) { - var self = this; - if (self.status !== "disconnected") { - return; - } - - self.emit('connecting'); - - self.codec = new S101Codec(); - - const connectTimeoutListener = () => { - self.socket.destroy(); - self.emit("error", new Error(`Could not connect to ${self.address}:${self.port} after a timeout of ${timeout} seconds`)); - }; - - self.socket = net.createConnection({ - port: self.port, - host: self.address, - timeout: 1000 * timeout - }, - () => { - winston.debug('socket connected'); - - // Disable connect timeout to hand-over to keepalive mechanism - self.socket.removeListener("timeout", connectTimeoutListener); - self.socket.setTimeout(0); - - self.keepaliveIntervalTimer = setInterval(() => { - try { - self.sendKeepaliveRequest(); - } catch (e) { - self.emit("error", e); - } - }, 1000 * self.keepaliveInterval); - - self.codec.on('keepaliveReq', () => { - self.sendKeepaliveResponse(); - }); - - self.codec.on('emberPacket', (packet) => { - self.emit('emberPacket', packet); - - var ber = new BER.Reader(packet); - try { - var root = ember.Root.decode(ber); - if (root !== undefined) { - self.emit('emberTree', root); - } - } catch (e) { - self.emit("error", e); - } - }); - - self.emit('connected'); - }) - .on('error', (e) => { - self.emit("error", e); - }) - .once("timeout", connectTimeoutListener) - .on('data', (data) => { - if (self.isConnected()) { - self.codec.dataIn(data); - } - }) - .on('close', () => { - clearInterval(self.keepaliveIntervalTimer); - self.emit('disconnected'); - self.status = "disconnected"; - self.socket = null; - }); -} - -S101Socket.prototype.isConnected = function () { - return ((this.socket !== null) && (this.socket !== undefined)); -} - -S101Socket.prototype.disconnect = function () { - var self = this; - - if (!self.isConnected()) { - return Promise.resolve(); - } - return new Promise((resolve, reject) => { - self.socket.once('close', () => { - self.codec = null; - self.socket = null; - resolve(); - }); - self.socket.once('error', reject); - clearInterval(self.keepaliveIntervalTimer); - self.socket.end(); - self.status = "disconnected"; - } - ); -}; - -S101Socket.prototype.sendKeepaliveRequest = function () { - var self = this; - if (self.isConnected()) { - self.socket.write(self.codec.keepAliveRequest()); - winston.debug('sent keepalive request'); - } -} - -S101Socket.prototype.sendKeepaliveResponse = function () { - var self = this; - if (self.isConnected()) { - self.socket.write(self.codec.keepAliveResponse()); - winston.debug('sent keepalive response'); - } -} - -S101Socket.prototype.sendBER = function (data) { - var self = this; - if (self.isConnected()) { - var frames = self.codec.encodeBER(data); - for (var i = 0; i < frames.length; i++) { - self.socket.write(frames[i]); - } - } -} - -S101Socket.prototype.sendBERNode = function (node) { - var self = this; - if (!node) return; - var writer = new BER.Writer(); - node.encode(writer); - self.sendBER(writer.buffer); -} - - -module.exports = { S101Socket, S101Server, S101Client }; - diff --git a/device.js b/device.js deleted file mode 100755 index a238066..0000000 --- a/device.js +++ /dev/null @@ -1,518 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const S101Client = require('./client.js').S101Socket; -const ember = require('./ember.js'); -const BER = require('./ber.js'); -const errors = require('./errors.js'); - - -function DeviceTree(host, port = 9000) { - DeviceTree.super_.call(this); - var self = this; - self._debug = false; - self.timeoutValue = 3000; - self.client = new S101Client(host, port); - self.root = new ember.Root(); - self.pendingRequests = []; - self.activeRequest = null; - self.timeout = null; - self.callback = undefined; - self.requestID = 0; - - self.client.on('connecting', () => { - self.emit('connecting'); - }); - - self.client.on('connected', () => { - self.emit('connected'); - if (self.callback !== undefined) { - self.callback(); - } - }); - - self.client.on('disconnected', () => { - self.emit('disconnected'); - }); - - self.client.on("error", (e) => { - if (self.callback !== undefined) { - self.callback(e); - } - self.emit("error", e); - }); - - self.client.on('emberTree', (root) => { - try { - if (root instanceof ember.InvocationResult) { - self.emit('invocationResult', root); - if (self._debug) { - console.log("Received InvocationResult", root); - } - } else { - self.handleRoot(root); - if (self._debug) { - console.log("Received root", root); - } - } - if (self.callback) { - self.callback(undefined, root); - } - } - catch(e) { - if (self._debug) { - console.log(e, root); - } - if (self.callback) { - self.callback(e); - } - } - }); -} - -util.inherits(DeviceTree, EventEmitter); - - -var DecodeBuffer = function (packet) { - var ber = new BER.Reader(packet); - return ember.Root.decode(ber); -}; - -DeviceTree.prototype.saveTree = function (f) { - var writer = new BER.Writer(); - this.root.encode(writer); - f(writer.buffer); -}; - -DeviceTree.prototype.isConnected = function () { - return ((this.client !== undefined) && (this.client.isConnected())); -}; - -DeviceTree.prototype.connect = function (timeout = 2) { - return new Promise((resolve, reject) => { - this.callback = (e) => { - this.callback = undefined; - if (e === undefined) { - return resolve(); - } - return reject(e); - }; - if ((this.client !== undefined) && (this.client.isConnected())) { - this.client.disconnect(); - } - this.client.connect(timeout); - }); -}; - -DeviceTree.prototype.expand = function (node) { - let self = this; - if (node == null) { - return Promise.reject(new Error("Invalid null node")); - } - if (node.isParameter() || node.isMatrix() || node.isFunction()) { - return self.getDirectory(node); - } - return self.getDirectory(node).then((res) => { - let children = node.getChildren(); - if ((res === undefined) || (children === undefined) || (children === null)) { - if (self._debug) { - console.log("No more children for ", node); - } - return; - } - let p = Promise.resolve(); - for (let child of children) { - if (child.isParameter()) { - // Parameter can only have a single child of type Command. - continue; - } - if (self._debug) { - console.log("Expanding child", child); - } - p = p.then(() => { - return self.expand(child).catch((e) => { - // We had an error on some expansion - // let's save it on the child itself - child.error = e; - }); - }); - } - return p; - }); -}; - -function isDirectSubPathOf(path, parent) { - return path.lastIndexOf('.') === parent.length && path.startsWith(parent) -} - -DeviceTree.prototype.getDirectory = function (qnode) { - var self = this; - if (qnode == null) { - self.root.clear(); - qnode = self.root; - } - return new Promise((resolve, reject) => { - self.addRequest({node: qnode, func: (error) => { - if (error) { - self.finishRequest(); - reject(error); - return; - } - - self.callback = (error, node) => { - const requestedPath = qnode.getPath(); - if (node == null) { - if (self._debug) { - console.log(`received null response for ${requestedPath}`); - } - return; - } - if (error) { - if (self._debug) { - console.log("Received getDirectory error", error); - } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(); - reject(error); - return; - } - if (qnode instanceof ember.Root) { - if (qnode.elements == null || qnode.elements.length === 0) { - if (self._debug) { - console.log("getDirectory response", node); - } - return self.callback(new Error("Invalid qnode for getDirectory")); - } - - const nodeElements = node == null ? null : node.elements; - - if (nodeElements != null - && nodeElements.every(el => el._parent instanceof ember.Root)) { - if (self._debug) { - console.log("Received getDirectory response", node); - } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(); - resolve(node); // make sure the info is treated before going to next request. - } - else { - return self.callback(new Error(`Invalid response for getDirectory ${requestedPath}`)); - } - } else { - const nodeElements = node == null ? null : node.elements; - if (nodeElements != null && - ((qnode.isMatrix() && nodeElements.length === 1 && nodeElements[0].getPath() === requestedPath) || - (!qnode.isMatrix() && nodeElements.every(el => isDirectSubPathOf(el.getPath(), requestedPath))))) { - if (self._debug) { - console.log("Received getDirectory response", node); - } - self.clearTimeout(); // clear the timeout now. The resolve below may take a while. - self.finishRequest(); - resolve(node); // make sure the info is treated before going to next request. - } - else if (self._debug) { - console.log(node); - console.log(new Error(requestedPath)); - } - } - }; - - if (self._debug) { - console.log("Sending getDirectory", qnode); - } - self.client.sendBERNode(qnode.getDirectory()); - }}); - }); -}; - -DeviceTree.prototype.invokeFunction = function (fnNode, params) { - var self = this; - return new Promise((resolve, reject) => { - self.addRequest({node: fnNode, func: (error) => { - if (error) { - reject(error); - self.finishRequest(); - return; - } - - let cb = (error, result) => { - self.clearTimeout(); - if (error) { - reject(error); - } - else { - if (self._debug) { - console.log("InvocationResult", result); - } - resolve(result); - } - self.finishRequest(); - }; - - if (self._debug) { - console.log("Invocking function", fnNode); - } - self.callback = cb; - self.client.sendBERNode(fnNode.invoke(params)); - }}); - }) -}; - -DeviceTree.prototype.disconnect = function () { - if (this.client !== undefined) { - return this.client.disconnect(); - } -}; - -DeviceTree.prototype.makeRequest = function () { - var self = this; - if (self.activeRequest === null && self.pendingRequests.length > 0) { - self.activeRequest = self.pendingRequests.shift(); - - const t = function (id) { - var path = self.activeRequest.path == null ? - self.activeRequest.node.getPath() : - self.activeRequest.path; - var req = `${id} - ${path}`; - if (self._debug) { - console.log(`Making request ${req}`, Date.now()); - } - self.timeout = setTimeout(() => { - self.timeoutRequest(req); - }, self.timeoutValue); - }; - - t(self.requestID++); - self.activeRequest.func(); - } -}; - -DeviceTree.prototype.addRequest = function (req) { - var self = this; - self.pendingRequests.push(req); - self.makeRequest(); -}; - -DeviceTree.prototype.clearTimeout = function () { - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; - } -}; - -DeviceTree.prototype.finishRequest = function () { - var self = this; - self.callback = undefined; - self.clearTimeout(); - self.activeRequest = null; - try { - self.makeRequest(); - } catch(e) { - console.log("warn:" + e.message) - } -}; - -DeviceTree.prototype.timeoutRequest = function (id) { - var self = this; - self.root.cancelCallbacks(); - self.activeRequest.func(new errors.EmberTimeoutError(`Request ${id !== undefined ? id : ""} timed out`)); -}; - -DeviceTree.prototype.handleRoot = function (root) { - var self = this; - - if (self._debug) { - console.log("handling root", JSON.stringify(root)); - } - var callbacks = self.root.update(root); - if (root.elements !== undefined) { - for (var i = 0; i < root.elements.length; i++) { - if (root.elements[i].isQualified()) { - callbacks = callbacks.concat(this.handleQualifiedNode(this.root, root.elements[i])); - } - else { - callbacks = callbacks.concat(this.handleNode(this.root, root.elements[i])); - } - } - - // Fire callbacks once entire tree has been updated - for (var j = 0; j < callbacks.length; j++) { - //console.log('hr cb'); - callbacks[j](); - } - } -}; - -DeviceTree.prototype.handleQualifiedNode = function (parent, node) { - var self = this; - var callbacks = []; - var element = parent.getElementByPath(node.path); - if (element !== null) { - self.emit("value-change", node); - callbacks = element.update(node); - } - else { - var path = node.path.split("."); - if (path.length === 1) { - this.root.addChild(node); - } - else { - // Let's try to get the parent - path.pop(); - parent = this.root.getElementByPath(path.join(".")); - if (parent === null) { - return callbacks; - } - parent.addChild(node); - callbacks = parent.update(parent); - } - element = node; - } - - var children = node.getChildren(); - if (children !== null) { - for (var i = 0; i < children.length; i++) { - if (children[i].isQualified()) { - callbacks = callbacks.concat(this.handleQualifiedNode(element, children[i])); - } - else { - callbacks = callbacks.concat(this.handleNode(element, children[i])); - } - } - } - - return callbacks; -}; - -DeviceTree.prototype.handleNode = function (parent, node) { - var self = this; - var callbacks = []; - - var n = parent.getElementByNumber(node.getNumber()); - if (n === null) { - parent.addChild(node); - n = node; - } else { - callbacks = n.update(node); - } - - var children = node.getChildren(); - if (children !== null) { - for (var i = 0; i < children.length; i++) { - callbacks = callbacks.concat(this.handleNode(n, children[i])); - } - } - else { - self.emit("value-change", node); - } - - return callbacks; -}; - -DeviceTree.prototype.getNodeByPath = function (path, timeout = 2) { - var self = this; - if (typeof path === 'string') { - path = path.split('/'); - } - var timeoutError = new Error("Request timeout"); - return new Promise((resolve, reject) => { - self.addRequest({path: path, func: (error) => { - if (error) { - reject(error); - self.finishRequest(); - return; - } - var timedOut = false; - var cb = (error, node) => { - if (timer) { - clearTimeout(timer); - } - if (timedOut) { return; } - if (error) { - reject(error); - } else { - resolve(node); - } - self.finishRequest(); - }; - var cbTimeout = () => { - timedOut = true; - reject(timeoutError); - } - var timer = timeout === 0 ? null : setTimeout(cbTimeout, timeout * 1000); - self.root.getNodeByPath(self.client, path, cb); - }}); - }); -}; - -DeviceTree.prototype.subscribe = function (node, callback) { - if (node instanceof ember.Parameter && node.isStream()) { - // TODO: implement - } else { - node.addCallback(callback); - } -}; - -DeviceTree.prototype.unsubscribe = function (node, callback) { - if (node instanceof ember.Parameter && node.isStream()) { - // TODO: implement - } else { - node.addCallback(callback); - } -}; - -DeviceTree.prototype.setValue = function (node, value) { - var self = this; - return new Promise((resolve, reject) => { - if ((!(node instanceof ember.Parameter)) && - (!(node instanceof ember.QualifiedParameter))) { - reject(new errors.EmberAccessError('not a property')); - } - else { - // if (this._debug) { console.log('setValue', node.getPath(), value); } - self.addRequest({node: node, func: (error) => { - if (error) { - self.finishRequest(); - reject(error); - return; - } - - let cb = (error, node) => { - //console.log('setValue complete...', node.getPath(), value); - self.finishRequest(); - if (error) { - reject(error); - } - else { - resolve(node); - } - }; - - self.callback = cb; - if (this._debug) { - console.log('setValue sending ...', node.getPath(), value); - } - self.client.sendBERNode(node.setValue(value)); - }}); - } - }); -}; - -function TreePath(path) { - this.identifiers = []; - this.numbers = []; - - if (path !== undefined) { - for (var i = 0; i < path.length; i++) { - if (Number.isInteger(path[i])) { - this.numbers.push(path[i]); - this.identifiers.push(null); - } else { - this.identifiers.push(path[i]); - this.numbers.push(null); - } - } - } -} - - -module.exports = { DeviceTree, DecodeBuffer }; diff --git a/ember.js b/ember.js deleted file mode 100755 index 3e594bb..0000000 --- a/ember.js +++ /dev/null @@ -1,2708 +0,0 @@ -const BER = require('./ber.js'); -const errors = require('./errors.js'); -const util = require('util'); -const Enum = require('enum'); - -const COMMAND_SUBSCRIBE = 30; -const COMMAND_UNSUBSCRIBE = 31; -const COMMAND_GETDIRECTORY = 32; -const COMMAND_INVOKE = 33; -module.exports.Subscribe = COMMAND_SUBSCRIBE; -module.exports.Unsubscribe = COMMAND_UNSUBSCRIBE; -module.exports.GetDirectory = COMMAND_GETDIRECTORY; -module.exports.Invoke = COMMAND_INVOKE; - -DEBUG = false; - -module.exports.DEBUG = function(d) { - DEBUG = d; -}; - -/**************************************************************************** - * Root - ***************************************************************************/ - -function Root() { - Root.super_.call(this); - - //Object.defineProperty(this, '_parent', {value: null, enumerable: false}); -}; - -util.inherits(Root, TreeNode); - - -Root.decode = function(ber) { - let r = new Root(); - let tag = undefined; - - while(ber.remain > 0) { - if (DEBUG) { console.log("Reading root"); } - tag = ber.peek(); - if (tag === BER.APPLICATION(0)) { - ber = ber.getSequence(BER.APPLICATION(0)); - tag = ber.peek(); - if (DEBUG) { - console.log("Application 0 start"); - } - - if (tag === BER.APPLICATION(11)) { - if (DEBUG) { - console.log("Application 11 start"); - } - var seq = ber.getSequence(BER.APPLICATION(11)); - r.elements = []; - while (seq.remain > 0) { - try { - var rootReader = seq.getSequence(BER.CONTEXT(0)); - while (rootReader.remain > 0) { - r.addElement(RootElement.decode(rootReader)); - } - } - catch (e) { - if (DEBUG) { - console.log("Decode ERROR", e.stack); - return r; - } - throw e; - } - } - } - else if (tag === BER.APPLICATION(23)) { // InvocationResult BER.APPLICATION(23) - return InvocationResult.decode(ber) - } - else { - // StreamCollection BER.APPLICATION(6) - // InvocationResult BER.APPLICATION(23) - throw new errors.UnimplementedEmberTypeError(tag); - } - } - else if (tag === BER.CONTEXT(0)) { - // continuation of previous message - try { - return RootElement.decode(rootReader) - } - catch (e) { - console.log(e.stack); - return r; - } - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return r; -} - -Root.prototype.addElement = function(ele) { - ele._parent = this; - if(this.elements === undefined) { - this.elements = []; - } - this.elements.push(ele); -} - - - -Root.prototype.addChild = function(child) { - this.addElement(child); -} - -Root.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(0)); - if(this.elements !== undefined) { - ber.startSequence(BER.APPLICATION(11)); - for(var i=0; i { callback(error, node) }); - } - return this.getTreeBranch(new Command(COMMAND_GETDIRECTORY)); -} - -TreeNode.prototype.subscribe = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - return this.getTreeBranch(new Command(COMMAND_SUBSCRIBE)); -} - -TreeNode.prototype.unsubscribe = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - return this.getTreeBranch(new Command(COMMAND_UNSUBSCRIBE)); -} - -TreeNode.prototype.getChildren = function() { - if(this.children !== undefined) { - return this.children; - } - return null; -} - -function path2number(path) { - try { - let numbers = path.split("."); - if (numbers.length > 0) { - return Number(numbers[numbers.length - 1]); - } - } - catch(e) { - // ignore - } -} - -TreeNode.prototype.getNumber = function() { - if (this.isQualified()) { - return path2number(this.getPath()); - } - else { - return this.number; - } -} - -_getElementByPath = function(children, pathArray, path) { - if ((children === null)||(children === undefined)||(pathArray.length < 1)) { - return null; - } - var currPath = pathArray.join("."); - var number = pathArray[pathArray.length - 1]; - - for (var i = 0; i < children.length; i++) { - if ((children[i].path == currPath)|| - (children[i].number == number)) { - if (path.length === 0) { - return children[i]; - } - pathArray.push(path.splice(0,1)); - return _getElementByPath(children[i].getChildren(), pathArray, path); - } - } - - return null; -} - -TreeNode.prototype.toJSON = function() { - let res = {}; - const node = this; - if (node.number) { - res.number = node.number - } - if (node.path) { - res.path = node.path; - } - if (node.contents) { - for(let prop in node.contents) { - if (node.contents.hasOwnProperty(prop)) { - let type = typeof node.contents[prop]; - if ((type === "string") || (type === "number")) { - res[prop] = node.contents[prop]; - } - else if (node.contents[prop].value !== undefined) { - res[prop] = node.contents[prop].value; - } - else { - res[prop] = node.contents[prop]; - } - } - } - } - if (node.isMatrix()) { - if (node.targets) { - res.targets = node.targets.slice(0); - } - if (node.sources) { - res.sources = node.sources.slice(0); - } - if (node.connections) { - res.connections = {}; - for (let target in node.connections) { - if (node.connections.hasOwnProperty(target)) { - res.connections[target] = {target: target, sources: []}; - if (node.connections[target].sources) { - res.connections[target].sources = node.connections[target].sources.slice(0); - } - } - } - - } - } - let children = node.getChildren(); - if (children) { - res.children = []; - for(let child of children) { - res.children.push(child.toJSON()); - } - } - return res; -}; - -TreeNode.prototype.getElementByPath = function(path) { - var children = this.getChildren(); - if ((children === null)||(children === undefined)) { - return null; - } - - var myPath = this.getPath(); - if (path == myPath) { - return this; - } - var myPathArray = []; - if (this._parent) { - myPathArray = myPath.split("."); - } - path = path.split("."); - - if (path.length > myPathArray.length) { - pathArray = path.splice(0, myPath.length + 1); - for(var i = 0; i < pathArray.length - 1; i++) { - if (pathArray[i] != myPathArray[i]) { - return null; - } - } - } - else { - return null; - } - return _getElementByPath(children, pathArray, path); -} - -TreeNode.prototype.getElementByNumber = function(index) { - var children = this.getChildren(); - if(children === null) return null; - for(var i=0; i 0) { - (function(cb) { - callbacks.push(() => { - cb(null, self) - }); - })(self._directoryCallbacks.shift()); - } - - for(var i=0; i { - cb(self) - }); - })(self._callbacks[i]); - } - - return callbacks; -} - -TreeNode.prototype.getNodeByPath = function(client, path, callback) { - var self=this; - - if(path.length == 0) { - callback(null, self); - return; - } - - - var child = self.getElement(path[0]); - if(child !== null) { - child.getNodeByPath(client, path.slice(1), callback); - } else { - var cmd = self.getDirectory((error, node) => { - if(error) { - callback(error); - } - child = node.getElement(path[0]); - if(child === null) { - //let e = new Error('invalid path: "' + path[0] + '"'); - // console.log(e.message ,"Self:", self, "Node:", node); - //callback(e); - //DO NOT REJECT !!!! We could still be updating the tree. - return; - } else { - child.getNodeByPath(client, path.slice(1), callback); - } - }); - if(cmd !== null) { - client.sendBERNode(cmd); - } - } -} - -TreeNode.prototype.getPath = function() { - if (this.path !== undefined) { - return this.path; - } - if(this._parent === null) { - if(this.number === undefined) { - return ""; - } else { - return this.number.toString(); - } - } else { - var path = this._parent.getPath(); - if(path.length > 0) { - path = path + "."; - } - return path + this.number; - } -} - -/**************************************************************************** - * RootElement - ***************************************************************************/ - -function RootElement() {}; - -RootElement.decode = function(ber) { - return Element.decode(ber); - - // TODO: handle qualified types -} - -/**************************************************************************** - * Element - ***************************************************************************/ - -function Element() {}; - -Element.decode = function(ber) { - var tag = ber.peek(); - if(tag == BER.APPLICATION(1)) { - if (DEBUG) { console.log("Parameter decode");} - return Parameter.decode(ber); - } else if(tag == BER.APPLICATION(3)) { - if (DEBUG) { console.log("Node decode");} - return Node.decode(ber); - } else if(tag == BER.APPLICATION(2)) { - if (DEBUG) { console.log("Command decode");} - return Command.decode(ber); - } else if(tag == BER.APPLICATION(9)) { - if (DEBUG) { console.log("QualifiedParameter decode");} - return QualifiedParameter.decode(ber); - } else if(tag == BER.APPLICATION(10)) { - if (DEBUG) { console.log("QualifiedNode decode");} - return QualifiedNode.decode(ber); - } else if(tag == BER.APPLICATION(13)) { - if (DEBUG) { console.log("MatrixNode decode");} - return MatrixNode.decode(ber); - } - else if(tag == BER.APPLICATION(17)) { - if (DEBUG) { console.log("QualifiedMatrix decode");} - return QualifiedMatrix.decode(ber); - } - else if(tag == BER.APPLICATION(19)) { - if (DEBUG) { console.log("Function decode");} - return Function.decode(ber); - } else if (tag == BER.APPLICATION(20)) { - if (DEBUG) { console.log("QualifiedFunction decode");} - return QualifiedFunction.decode(ber); - } - else if(tag == BER.APPLICATION(24)) { - // Template - throw new errors.UnimplementedEmberTypeError(tag); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } -} - -/**************************************************************************** - * ElementCollection - ***************************************************************************/ - - - -/**************************************************************************** - * QualifiedNode - ***************************************************************************/ - -function QualifiedNode(path) { - QualifiedNode.super_.call(this); - if (path != undefined) { - this.path = path; - } -} - -util.inherits(QualifiedNode, TreeNode); - - -QualifiedNode.decode = function(ber) { - var qn = new QualifiedNode(); - ber = ber.getSequence(BER.APPLICATION(10)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - qn.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else if(tag == BER.CONTEXT(1)) { - qn.contents = NodeContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - qn.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - qn.addChild(Element.decode(nodeSeq)); - } - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("QualifiedNode", qn); } - return qn; -} - -QualifiedNode.prototype.getMinimal = function(complete = false) { - const number = this.getNumber(); - const n = new Node(number); - if (complete && (this.contents != null)) { - n.contents = this.contents; - } - return n; -} - -QualifiedNode.prototype.update = function(other) { - callbacks = QualifiedNode.super_.prototype.update.apply(this); - if((other !== undefined) && (other.contents !== undefined)) { - if (this.contents == null) { - this.contents = other.contents; - } - else { - for(var key in other.contents) { - if(other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; - } - } - } - } - return callbacks; -} - -function QualifiedNodeCommand(self, cmd, callback) { - var r = new Root(); - var qn = new QualifiedNode(); - qn.path = self.path; - r.addElement(qn); - qn.addChild(new Command(cmd)); - if(callback !== undefined) { - self._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - return r; -} - -QualifiedNode.prototype.getDirectory = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedNodeCommand(this, COMMAND_GETDIRECTORY, callback) -} - -QualifiedNode.prototype.subscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedNodeCommand(this, COMMAND_SUBSCRIBE, callback) -} - -QualifiedNode.prototype.unsubscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedNodeCommand(this, COMMAND_UNSUBSCRIBE, callback) -} - -QualifiedNode.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(10)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - n.number = seq.readInt(); - } else if(tag == BER.CONTEXT(1)) { - n.contents = NodeContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - seq = seq.getSequence(BER.APPLICATION(4)); - n.children = []; - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - n.addChild(Element.decode(nodeSeq)); - } - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("Node", n); } - return n; -} - -Node.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(3)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if (tag == BER.CONTEXT(0)) { - m.number = seq.readInt(); - } - else if (tag == BER.CONTEXT(1)) { - m.contents = MatrixContents.decode(seq); - - } else if (tag == BER.CONTEXT(2)) { - m.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while (seq.remain > 0) { - var childSeq = seq.getSequence(BER.CONTEXT(0)); - m.addChild(Element.decode(childSeq)); - } - } else if (tag == BER.CONTEXT(3)) { - m.targets = decodeTargets(seq); - } else if (tag == BER.CONTEXT(4)) { - m.sources = decodeSources(seq); - } else if (tag == BER.CONTEXT(5)) { - m.connections = decodeConnections(seq); - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("MatrixNode", m); } - return m; -}; - - -MatrixNode.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(13)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - - if(tag == BER.CONTEXT(0)) { - mc.identifier = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - mc.description = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(2)) { - mc.type = MatrixType.get(seq.readInt()); - } else if(tag == BER.CONTEXT(3)) { - mc.mode = MatrixMode.get(seq.readInt()); - } else if(tag == BER.CONTEXT(4)) { - mc.targetCount = seq.readInt(); - } else if(tag == BER.CONTEXT(5)) { - mc.sourceCount = seq.readInt(); - } else if(tag == BER.CONTEXT(6)) { - mc.maximumTotalConnects = seq.readInt(); - } else if(tag == BER.CONTEXT(7)) { - mc.maximumConnectsPerTarget = seq.readInt(); - } else if(tag == BER.CONTEXT(8)) { - tag = seq.peek(); - if (tag === BER.EMBER_RELATIVE_OID) { - mc.parametersLocation = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else { - mc.parametersLocation = seq.readInt(); - } - } else if(tag == BER.CONTEXT(9)) { - mc.gainParameterNumber = seq.readInt(); - } else if(tag == BER.CONTEXT(10)) { - mc.labels = []; - seq = seq.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - var lSeq = seq.getSequence(BER.CONTEXT(0)); - mc.labels.push(Label.decode(lSeq)); - } - } else if(tag == BER.CONTEXT(11)) { - mc.schemaIdentifiers = seq.readInt(); - } else if(tag == BER.CONTEXT(12)) { - mc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return mc; -}; - -MatrixContents.prototype.encode = function(ber) { - ber.startSequence(BER.EMBER_SET); - if (this.identifier !== undefined) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeString(this.identifier, BER.EMBER_STRING); - ber.endSequence(); - } - if (this.description !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); - } - if (this.type !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.writeInt(this.type.value); - ber.endSequence(); - } - if (this.mode !== undefined) { - ber.startSequence(BER.CONTEXT(3)); - ber.writeInt(this.mode.value); - ber.endSequence(); - } - if (this.targetCount !== undefined) { - ber.startSequence(BER.CONTEXT(4)); - ber.writeInt(this.targetCount); - ber.endSequence(); - } - if (this.sourceCount !== undefined) { - ber.startSequence(BER.CONTEXT(5)); - ber.writeInt(this.sourceCount); - ber.endSequence(); - } - if (this.maximumTotalConnects !== undefined) { - ber.startSequence(BER.CONTEXT(6)); - ber.writeInt(this.maximumTotalConnects); - ber.endSequence(); - } - if (this.maximumConnectsPerTarget !== undefined) { - ber.startSequence(BER.CONTEXT(7)); - ber.writeInt(this.maximumConnectsPerTarget); - ber.endSequence(); - } - if (this.parametersLocation !== undefined) { - ber.startSequence(BER.CONTEXT(8)); - let param = Number(this.parametersLocation) - if (isNaN(param)) { - ber.writeRelativeOID(this.parametersLocation, BER.EMBER_RELATIVE_OID); - } - else { - ber.writeInt(param); - } - ber.endSequence(); - } - if (this.gainParameterNumber !== undefined) { - ber.startSequence(BER.CONTEXT(9)); - ber.writeInt(this.gainParameterNumber); - ber.endSequence(); - } - if (this.labels !== undefined) { - ber.startSequence(BER.CONTEXT(10)); - ber.startSequence(BER.EMBER_SEQUENCE); - for(var i =0; i < this.labels.length; i++) { - ber.startSequence(BER.CONTEXT(0)); - this.labels[i].encode(ber); - ber.endSequence(); - } - ber.endSequence(); - ber.endSequence(); - } - if (this.schemaIdentifiers !== undefined) { - ber.startSequence(BER.CONTEXT(11)); - ber.writeInt(this.schemaIdentifiers, BER.EMBER_STRING); - ber.endSequence(); - } - if (this.templateReference !== undefined) { - ber.startSequence(BER.CONTEXT(12)); - ber.writeRelativeOID(this.templateReference, BER.EMBER_RELATIVE_OID); - ber.endSequence(); - } - ber.endSequence(); -} - -decodeTargets = function(ber) { - let targets = []; - - ber = ber.getSequence(BER.EMBER_SEQUENCE); - - - while(ber.remain > 0) { - var seq = ber.getSequence(BER.CONTEXT(0)); - seq = seq.getSequence(BER.APPLICATION(14)); - seq = seq.getSequence(BER.CONTEXT(0)); - targets.push(seq.readInt()); - } - - return targets; -} - -decodeSources = function(ber) { - let sources = []; - - ber = ber.getSequence(BER.EMBER_SEQUENCE); - - while(ber.remain > 0) { - var seq = ber.getSequence(BER.CONTEXT(0)); - seq = seq.getSequence(BER.APPLICATION(15)); - seq = seq.getSequence(BER.CONTEXT(0)); - sources.push(seq.readInt()); - } - - return sources; -}; - -decodeConnections = function(ber) { - let connections = {}; - - let seq = ber.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - var conSeq = seq.getSequence(BER.CONTEXT(0)); - var con = MatrixConnection.decode(conSeq); - if (con.target !== undefined) { - connections[con.target] = (con); - } - } - return connections; -} - -module.exports.MatrixContents = MatrixContents; - - - -function MatrixConnection(target) { - if (target) { - target = Number(target); - if (isNaN(target)) { target = 0; } - this.target = target; - } - else { - this.target = 0; - } -} - -// ConnectionOperation ::= -// INTEGER { -// absolute (0), -- default. sources contains absolute information -// connect (1), -- nToN only. sources contains sources to add to connection -// disconnect (2) -- nToN only. sources contains sources to remove from -// connection -// } -var MatrixOperation = new Enum({ - absolute: 0, - connect: 1, - disconnect: 2 -}); - -// ConnectionDisposition ::= -// INTEGER { -// tally (0), -- default -// modified (1), -- sources contains new current state -// pending (2), -- sources contains future state -// locked (3) -- error: target locked. sources contains current state -// -- more tbd. -// } -var MatrixDisposition = new Enum({ - tally: 0, - modified: 1, - pending: 2, - locked: 3 -}); - -module.exports.MatrixOperation = MatrixOperation; -module.exports.MatrixDisposition = MatrixDisposition; - -MatrixConnection.prototype.setSources = function(sources) { - if (sources === undefined) { - delete this.sources; - return; - } - let s = new Set(sources.map(i => Number(i))); - this.sources = [...s].sort(); // sources should be an array -} - -MatrixConnection.prototype.connectSources = function(sources) { - if (sources === undefined) { - return; - } - let s = new Set(this.sources); - for(let item of sources) { - s.add(item); - } - this.sources = [...s].sort(); -} - -MatrixConnection.prototype.disconnectSources = function(sources) { - if (sources === undefined) { - return; - } - let s = new Set(this.sources); - for(let item of sources) { - s.delete(item); - } - this.sources = [...s].sort(); -} - -MatrixConnection.decode = function(ber) { - var c = new MatrixConnection(); - ber = ber.getSequence(BER.APPLICATION(16)); - while (ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if (tag == BER.CONTEXT(0)) { - c.target = seq.readInt(); - } - else if (tag == BER.CONTEXT(1)) { - c.sources = seq.readRelativeOID(BER.EMBER_RELATIVE_OID).split(".").map(i => Number(i)); - } else if (tag == BER.CONTEXT(2)) { - c.operation = MatrixOperation.get(seq.readInt()); - - } else if (tag == BER.CONTEXT(3)) { - c.disposition = MatrixDisposition.get(seq.readInt()); - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return c; -} - -MatrixConnection.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(16)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.target); - ber.endSequence(); - - if ((this.sources !== undefined)&& (this.sources.length > 0)) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeRelativeOID(this.sources.join("."), BER.EMBER_RELATIVE_OID); - ber.endSequence(); - } - if (this.operation !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.writeInt(this.operation.value); - ber.endSequence(); - } - if (this.disposition !== undefined) { - ber.startSequence(BER.CONTEXT(3)); - ber.writeInt(this.disposition.value); - ber.endSequence(); - } - ber.endSequence(); -} - -module.exports.MatrixConnection = MatrixConnection; - -function Label(path) { - if (path) { - this.basePath = path; - } -} - -Label.decode = function(ber) { - var l = new Label(); - - ber = ber.getSequence(BER.APPLICATION(18)); - - while (ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if (tag == BER.CONTEXT(0)) { - l.basePath = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); - } else if (tag == BER.CONTEXT(1)) { - l.description = seq.readString(BER.EMBER_STRING); - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return l; -}; - -Label.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(18)); - if (this.basePath !== undefined) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.basePath, BER.EMBER_RELATIVE_OID); - ber.endSequence(); - } - if (this.description !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); - } - ber.endSequence(); -} - -module.exports.Label = Label; - - -function ParametersLocation() { -} - -ParametersLocation.decode = function(ber) { - var tag = ber.peek(); - ber = ber.getSequence(tag); - this.value = ber.readValue(); -} - -module.exports.ParametersLocation = ParametersLocation; - - - -var MatrixType = new Enum({ - oneToN: 0, - oneToOne: 1, - nToN: 2 -}); - - -module.exports.MatrixType = MatrixType; - - -var MatrixMode = new Enum({ - linear: 0, - nonLinear: 1 -}); - - -module.exports.MatrixMode = MatrixMode; - - -/**************************************************************************** - * QualifiedMatrix - ***************************************************************************/ - -function QualifiedMatrix(path) { - QualifiedMatrix.super_.call(this); - if (path != undefined) { - this.path = path; - } -} - -util.inherits(QualifiedMatrix, TreeNode); - -QualifiedMatrix.prototype.getMinimal = function(complete = false) { - const number = this.getNumber(); - const m = new MatrixNode(number); - if (complete) { - if (this.contents != null) { - m.contents = this.contents; - } - if (this.targets != null) { - m.targets = this.targets; - } - if (this.sources != null) { - m.sources = this.sources; - } - if (this.connections != null) { - m.connections = this.connections; - } - } - return m; -} - - -QualifiedMatrix.decode = function(ber) { - var qm = new QualifiedMatrix(); - ber = ber.getSequence(BER.APPLICATION(17)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - qm.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else if(tag == BER.CONTEXT(1)) { - qm.contents = MatrixContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - qm.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - qm.addChild(Element.decode(nodeSeq)); - } - } else if (tag == BER.CONTEXT(3)) { - qm.targets = decodeTargets(seq); - } else if (tag == BER.CONTEXT(4)) { - qm.sources = decodeSources(seq); - } else if (tag == BER.CONTEXT(5)) { - qm.connections = {}; - seq = seq.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - var conSeq = seq.getSequence(BER.CONTEXT(0)); - var con = MatrixConnection.decode(conSeq); - if (con.target !== undefined) { - qm.connections[con.target] = con; - } - } - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("QualifiedMatrix", qm); } - return qm; -} - -function MatrixUpdate(matrix, newMatrix) { - if (newMatrix != null) { - if (newMatrix.contents != null) { - if (matrix.contents == null) { - matrix.contents = newMatrix.contents; - } - else { - for (var key in newMatrix.contents) { - if (newMatrix.contents.hasOwnProperty(key)) { - matrix.contents[key] = newMatrix.contents[key]; - } - } - } - } - if (newMatrix.targets != null) { - matrix.targets = newMatrix.targets; - } - if (newMatrix.sources != null) { - matrix.sources = newMatrix.sources; - } - if (newMatrix.connections != null) { - if (matrix.connections == null) { - matrix.connections = {}; - } - for(let id in newMatrix.connections) { - if (newMatrix.connections.hasOwnProperty(id)) { - let connection = newMatrix.connections[id]; - if ((connection.target < matrix.contents.targetCount) && - (connection.target >= 0)) { - if (matrix.connections[connection.target] == null) { - matrix.connections[connection.target] = new MatrixConnection(connection.target); - } - matrix.connections[connection.target].setSources(connection.sources); - } - } - } - } - } -} - -QualifiedMatrix.prototype.update = function(other) { - callbacks = QualifiedMatrix.super_.prototype.update.apply(this); - MatrixUpdate(this, other); - return callbacks; -} - -function QualifiedMatrixCommand(self, cmd, callback) { - var r = new Root(); - var qn = new QualifiedMatrix(); - qn.path = self.path; - r.addElement(qn); - qn.addChild(new Command(cmd)); - if(callback !== undefined) { - self._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - return r; -} - -QualifiedMatrix.prototype.getDirectory = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedMatrixCommand(this, COMMAND_GETDIRECTORY, callback); -} - -QualifiedMatrix.prototype.subscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedMatrixCommand(this, COMMAND_SUBSCRIBE, callback); -} - -QualifiedMatrix.prototype.unsubscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedMatrixCommand(this, COMMAND_UNSUBSCRIBE, callback); -} - -QualifiedMatrix.prototype.connect = function(connections) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - var r = new Root(); - var qn = new QualifiedMatrix(); - qn.path = this.path; - r.addElement(qn); - qn.connections = connections; - return r; -} - -QualifiedMatrix.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(17)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - tag = ber.peek(); - var seq = ber.getSequence(tag); - if (tag === BER.CONTEXT(0)) { - tuple.type = ParameterType.get(seq.readInt()); - } - else if (tag === BER.CONTEXT(1)) { - tuple.name = seq.readString(BER.EMBER_STRING); - } - } - return tuple; -} - -module.exports.FunctionArgument = FunctionArgument; - -/**************************************************************************** - * FunctionContent - ***************************************************************************/ - -function FunctionContent() { -} - - -FunctionContent.decode = function(ber) { - var fc = new FunctionContent(); - ber = ber.getSequence(BER.EMBER_SET); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - fc.identifier = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - fc.description = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(2)) { - fc.arguments = []; - seq = seq.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - tag = seq.peek(); - var dataSeq = seq.getSequence(BER.CONTEXT(0)); - if (tag === BER.CONTEXT(0)) { - fc.arguments.push(FunctionArgument.decode(dataSeq)); - } - } - } else if(tag == BER.CONTEXT(3)) { - fc.result = []; - while(seq.remain > 0) { - tag = seq.peek(); - var dataSeq = seq.getSequence(tag); - if (tag === BER.CONTEXT(0)) { - fc.result.push(FunctionArgument.decode(dataSeq)); - } - } - } else if(tag == BER.CONTEXT(4)) { - fc.templateReference = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return fc; -} - -FunctionContent.prototype.encode = function(ber) { - ber.startSequence(BER.EMBER_SET); - - if(this.identifier != null) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeString(this.identifier, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(0) - } - - if(this.description != null) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.arguments != null) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.EMBER_SEQUENCE); - for(var i =0; i < this.arguments.length; i++) { - this.arguments[i].encode(ber); - } - ber.endSequence(); - ber.endSequence(); // BER.CONTEXT(2) - } - - if(this.result != null) { - ber.startSequence(BER.CONTEXT(3)); - ber.startSequence(BER.EMBER_SEQUENCE); - for(var i =0; i < this.result; i++) { - ber.startSequence(BER.CONTEXT(0)); - this.result[i].encode(ber); - ber.endSequence(); - } - ber.endSequence(); - ber.endSequence(); // BER.CONTEXT(3) - } - - ber.endSequence(); // BER.EMBER_SET -} - -module.exports.FunctionContent = FunctionContent; - -/**************************************************************************** - * QualifiedFunction - ***************************************************************************/ - -function QualifiedFunction(path) { - QualifiedFunction.super_.call(this); - if (path != undefined) { - this.path = path; - } -} - -util.inherits(QualifiedFunction, TreeNode); - - -QualifiedFunction.decode = function(ber) { - var qf = new QualifiedFunction(); - ber = ber.getSequence(BER.APPLICATION(20)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - qf.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else if(tag == BER.CONTEXT(1)) { - qf.contents = FunctionContent.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - qf.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - qf.addChild(Element.decode(nodeSeq)); - } - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return qf; -} - -QualifiedFunction.prototype.update = function(other) { - callbacks = QualifiedFunction.super_.prototype.update.apply(this); - if ((other !== undefined) && (other.contents !== undefined)) { - if (this.contents == null) { - this.contents = other.contents; - } - else { - for (var key in other.contents) { - if (other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; - } - } - } - } - return callbacks; -} - -function QualifiedFunctionCommand(self, cmd, callback) { - var r = new Root(); - var qf = new QualifiedFunction(); - qf.path = self.path; - r.addElement(qf); - qf.addChild(new Command(cmd)); - if(callback !== undefined) { - self._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - return r; -} - -QualifiedFunction.prototype.invoke = function(params, callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - var QualifiedFunctionNode = QualifiedFunctionCommand(this, COMMAND_INVOKE, callback); - var invocation = new Invocation() - invocation.arguments = params - QualifiedFunctionNode.elements[0].children[0].invocation = invocation - return QualifiedFunctionNode -} - -QualifiedFunction.prototype.getDirectory = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedFunctionCommand(this, COMMAND_GETDIRECTORY, callback); -} - -QualifiedFunction.prototype.subscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedFunctionCommand(this, COMMAND_SUBSCRIBE, callback); -} - -QualifiedFunction.prototype.unsubscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedFunctionCommand(this, COMMAND_UNSUBSCRIBE, callback); -} - -QualifiedFunction.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(20)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - f.number = seq.readInt(); - } else if(tag == BER.CONTEXT(1)) { - f.contents = FunctionContent.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - seq = seq.getSequence(BER.APPLICATION(4)); - f.children = []; - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - f.addChild(Element.decode(nodeSeq)); - } - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("Function", f); } - return f; -} - -Function.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(19)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i { callback(error, node) }); - } - - return this.getTreeBranch(undefined, (m) => { - m.addChild(new Command(COMMAND_INVOKE)) - }); -} - - - -/**************************************************************************** - * NodeContents - ***************************************************************************/ - -function NodeContents() { - this.isOnline = true; -}; - - - -NodeContents.decode = function(ber) { - var nc = new NodeContents(); - ber = ber.getSequence(BER.EMBER_SET); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - nc.identifier = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - nc.description = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(2)) { - nc.isRoot = seq.readBoolean(); - } else if(tag == BER.CONTEXT(3)) { - nc.isOnline = seq.readBoolean(); - } else if(tag == BER.CONTEXT(4)) { - nc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return nc; -} - -NodeContents.prototype.encode = function(ber) { - ber.startSequence(BER.EMBER_SET); - - if(this.identifier !== undefined) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeString(this.identifier, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(0) - } - - if(this.description !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.isRoot !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.writeBoolean(this.isRoot); - ber.endSequence(); // BER.CONTEXT(2) - } - - if(this.isOnline !== undefined) { - ber.startSequence(BER.CONTEXT(3)); - ber.writeBoolean(this.isOnline); - ber.endSequence(); // BER.CONTEXT(3) - } - - if(this.schemaIdentifiers !== undefined) { - ber.startSequence(BER.CONTEXT(4)); - ber.writeString(this.schemaIdentifiers, BER.EMBER_STRING); - ber.endSequence(); // BER.CONTEXT(4) - } - - ber.endSequence(); // BER.EMBER_SET -} - -module.exports.NodeContents = NodeContents; - -/**************************************************************************** - * Command - ***************************************************************************/ - -function Command(number) { - if(number !== undefined) - this.number = number; - if(number == COMMAND_GETDIRECTORY) { - this.fieldFlags = FieldFlags.all; - } -} - -var FieldFlags = new Enum({ - sparse: -2, - all: -1, - default: 0, - identifier: 1, - description: 2, - tree: 3, - value: 4, - connections: 5 -}); - -Command.decode = function(ber) { - var c = new Command(); - ber = ber.getSequence(BER.APPLICATION(2)); - - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - c.number = seq.readInt(); - } - else if(tag == BER.CONTEXT(1)) { - c.fieldFlags = FieldFlags.get(seq.readInt()); - } - else if(tag == BER.CONTEXT(2)) { - c.invocation = Invocation.decode(ber); - } - else { - // TODO: options - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return c; -} - -Command.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(2)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.number); - ber.endSequence(); // BER.CONTEXT(0) - - if (this.number === COMMAND_GETDIRECTORY && this.fieldFlags) { - ber.startSequence(BER.CONTEXT(1)); - ber.writeInt(this.fieldFlags.value); - ber.endSequence(); - } - - if (this.number === COMMAND_INVOKE && this.invocation) { - ber.startSequence(BER.CONTEXT(2)); - this.invocation.encode(ber); - ber.endSequence(); - } - // TODO: options - - ber.endSequence(); // BER.APPLICATION(2) -} - -module.exports.Command = Command; - -/**************************************************************************** - * Invocation - ***************************************************************************/ -function Invocation(id = null) { - this.id = id == null ? Invocation._id++ : id; - this.arguments = []; -} - -Invocation._id = 1 - -Invocation.prototype.decode = function(ber) { - let invocation = new Invocation(); - ber = ber.getSequence(BER.APPLICATION(22)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - invocation.invocationId = seq.readInt(); - } - if(tag == BER.CONTEXT(1)) { - invocation.arguments = []; - const seq = ber.getSequence(BER.EMBER_SEQUENCE); - while(seq.remain > 0) { - invocation.arguments.push(FunctionArgument.decode(seq)); - } - } - else { - // TODO: options - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return invocation; -} - - -Invocation.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(22)); - // ber.startSequence(BER.EMBER_SEQUENCE); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeInt(this.id) - ber.endSequence(); - - ber.startSequence(BER.CONTEXT(1)); - ber.startSequence(BER.EMBER_SEQUENCE); - for(var i = 0; i < this.arguments.length; i++) { - ber.startSequence(BER.CONTEXT(0)); - ber.writeValue( - this.arguments[i].value, - ParameterTypetoBERTAG(this.arguments[i].type - )); - ber.endSequence(); - } - ber.endSequence(); - ber.endSequence(); - - ber.endSequence(); // BER.APPLICATION(22) - -} -/**************************************************************************** - * InvocationResult - ***************************************************************************/ -function InvocationResult() { -} -module.exports.InvocationResult = InvocationResult; - -InvocationResult.decode = function(ber) { - let invocationResult = new InvocationResult(); - ber = ber.getSequence(BER.APPLICATION(23)); - while(ber.remain > 0) { - tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { // invocationId - invocationResult.invocationId = seq.readInt(); - } else if(tag == BER.CONTEXT(1)) { // success - invocationResult.success = seq.readBoolean() - }else if(tag == BER.CONTEXT(2)) { - invocationResult.result = []; - let res = seq.getSequence(BER.EMBER_SEQUENCE); - while(res.remain > 0) { - tag = res.peek(); - var resTag = res.getSequence(BER.CONTEXT(0)); - if (tag === BER.CONTEXT(0)) { - invocationResult.result.push(resTag.readValue()); - } - } - continue - } else { - // TODO: options - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return invocationResult; -} -/**************************************************************************** - * QualifiedParameter - ***************************************************************************/ - -function QualifiedParameter(path) { - QualifiedParameter.super_.call(this); - if(path !== undefined) - this.path = path; -} - -util.inherits(QualifiedParameter, TreeNode); -module.exports.QualifiedParameter = QualifiedParameter; - -QualifiedParameter.prototype.getMinimal = function(complete = false) { - const number = this.getNumber(); - const p = new Parameter(number); - if (complete) { - if (this.contents != null) { - p = this.contents; - } - } - return p; -} - - -QualifiedParameter.decode = function(ber) { - var qp = new QualifiedParameter(); - ber = ber.getSequence(BER.APPLICATION(9)); - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - qp.path = seq.readRelativeOID(BER.EMBER_RELATIVE_OID); // 13 => relative OID - } - else if(tag == BER.CONTEXT(1)) { - qp.contents = ParameterContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - qp.children = []; - seq = seq.getSequence(BER.APPLICATION(4)); - while(seq.remain > 0) { - var nodeSeq = seq.getSequence(BER.CONTEXT(0)); - qp.addChild(Element.decode(nodeSeq)); - } - } else { - return qp; - } - } - if (DEBUG) { console.log("QualifiedParameter", qp); } - return qp; -} - -QualifiedParameter.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(9)); - - ber.startSequence(BER.CONTEXT(0)); - ber.writeRelativeOID(this.path, BER.EMBER_RELATIVE_OID); - ber.endSequence(); // BER.CONTEXT(0) - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); // BER.CONTEXT(1) - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i { callback(error, node) }); - } - return r; -} - -QualifiedParameter.prototype.getDirectory = function(callback) { - if(callback !== undefined) { - this._directoryCallbacks.push((error, node) => { callback(error, node) }); - } - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedParameterCommand(this, COMMAND_GETDIRECTORY, callback); -} - -QualifiedParameter.prototype.subscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedParameterCommand(this, COMMAND_SUBSCRIBE, callback); -} - -QualifiedParameter.prototype.unsubscribe = function(callback) { - if (this.path === undefined) { - throw new Error("Invalid path"); - } - return QualifiedParameterCommand(this, COMMAND_UNSUBSCRIBE, callback); -} - -QualifiedParameter.prototype.setValue = function(value, callback) { - if(callback !== undefined) { - this._directoryCallbacks.push(callback); - } - - let r = new Root(); - let qp = new QualifiedParameter(this.path); - r.addElement(qp); - qp.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); - return r; -} - -/**************************************************************************** - * Parameter - ***************************************************************************/ - -function Parameter(number) { - Parameter.super_.call(this); - if(number !== undefined) - this.number = number; -} - -util.inherits(Parameter, TreeNode); -module.exports.Parameter = Parameter; - -Parameter.decode = function(ber) { - var p = new Parameter(); - ber = ber.getSequence(BER.APPLICATION(1)); - - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - p.number = seq.readInt(); - - } else if(tag == BER.CONTEXT(1)) { - p.contents = ParameterContents.decode(seq); - } else if(tag == BER.CONTEXT(2)) { - seq = seq.getSequence(BER.APPLICATION(4)); - p.children = []; - while(seq.remain > 0) { - var paramSeq = seq.getSequence(BER.CONTEXT(0)); - p.addChild(Element.decode(paramSeq)); - } - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - if (DEBUG) { console.log("Parameter", p); } - return p; -} - -Parameter.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(1)); - - ber.writeIfDefined(this.number, ber.writeInt, 0); - - if(this.contents !== undefined) { - ber.startSequence(BER.CONTEXT(1)); - this.contents.encode(ber); - ber.endSequence(); - } - - if(this.children !== undefined) { - ber.startSequence(BER.CONTEXT(2)); - ber.startSequence(BER.APPLICATION(4)); - for(var i=0; i { - m.contents = (value instanceof ParameterContents) ? value : new ParameterContents(value); - }); -} - -Parameter.prototype.toQualified = function() { - let qp = new QualifiedParameter(this.getPath()); - qp.update(this); - return qp; -} - -Parameter.prototype.update = function(other) { - callbacks = Parameter.super_.prototype.update.apply(this); - if ((other !== undefined) && (other.contents !== undefined)) { - if (this.contents == null) { - this.contents = other.contents; - } - else { - for (var key in other.contents) { - if (other.contents.hasOwnProperty(key)) { - this.contents[key] = other.contents[key]; - } - } - } - } - return callbacks; -} - - -var ParameterAccess = new Enum({ - none: 0, - read: 1, - write: 2, - readWrite: 3 -}); - - -/* -BER VAlue -Value ::= - CHOICE { - integer Integer64, - real REAL, - string EmberString, - boolean BOOLEAN, - octets OCTET STRING, - null NULL - }*/ - -var ParameterType = new Enum({ - integer: 1, - real: 2, - string: 3, - boolean: 4, - trigger: 5, - enum: 6, - octets: 7 -}); - -function ParameterTypetoBERTAG(type) { - switch (type.value) { - case 1: return BER.EMBER_INTEGER; - case 2: return BER.EMBER_REAL; - case 3: return BER.EMBER_STRING; - case 4: return BER.EMBER_BOOLEAN; - case 7: return BER.EMBER_OCTETSTRING; - default: - throw new Error(`Unhandled ParameterType ${type}`); - } -} - -module.exports.ParameterAccess = ParameterAccess; -module.exports.ParameterType = ParameterType; - -function ParameterContents(value, type) { - if(value !== undefined) { - this.value = value; - } - if(type !== undefined) { - if((type = ParameterType.get(type)) !== undefined){ - this.type = type - } - } -}; - -module.exports.ParameterContents = ParameterContents; - -ParameterContents.decode = function(ber) { - var pc = new ParameterContents(); - ber = ber.getSequence(BER.EMBER_SET); - - while(ber.remain > 0) { - var tag = ber.peek(); - var seq = ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - pc.identifier = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - pc.description = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(2)) { - pc.value = seq.readValue(); - } else if(tag == BER.CONTEXT(3)) { - pc.minimum = seq.readValue(); - } else if(tag == BER.CONTEXT(4)) { - pc.maximum = seq.readValue(); - } else if(tag == BER.CONTEXT(5)) { - pc.access = ParameterAccess.get(seq.readInt()); - } else if(tag == BER.CONTEXT(6)) { - pc.format = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(7)) { - pc.enumeration = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(8)) { - pc.factor = seq.readInt(); - } else if(tag == BER.CONTEXT(9)) { - pc.isOnline = seq.readBoolean(); - } else if(tag == BER.CONTEXT(10)) { - pc.formula = seq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(11)) { - pc.step = seq.readInt(); - } else if(tag == BER.CONTEXT(12)) { - pc.default = seq.readValue(); - } else if(tag == BER.CONTEXT(13)) { - pc.type = ParameterType.get(seq.readInt()); - } else if(tag == BER.CONTEXT(14)) { - pc.streamIdentifier = seq.readInt(); - } else if(tag == BER.CONTEXT(15)) { - pc.enumMap = StringIntegerCollection.decode(seq); - } else if(tag == BER.CONTEXT(16)) { - pc.streamDescriptor = StreamDescription.decode(seq); - } else if(tag == BER.CONTEXT(17)) { - pc.schemaIdentifiers = seq.readString(BER.EMBER_STRING); - } else if (tag == null) { - break; - } - else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - return pc; -} - -ParameterContents.prototype.encode = function(ber) { - ber.startSequence(BER.EMBER_SET); - - ber.writeIfDefined(this.identifier, ber.writeString, 0, BER.EMBER_STRING); - ber.writeIfDefined(this.description, ber.writeString, 1, BER.EMBER_STRING); - ber.writeIfDefined(this.value, ber.writeValue, 2); - ber.writeIfDefined(this.minimum, ber.writeValue, 3); - ber.writeIfDefined(this.maximum, ber.writeValue, 4); - ber.writeIfDefinedEnum(this.access, ParameterAccess, ber.writeInt, 5); - ber.writeIfDefined(this.format, ber.writeString, 6, BER.EMBER_STRING); - ber.writeIfDefined(this.enumeration, ber.writeString, 7, BER.EMBER_STRING); - ber.writeIfDefined(this.factor, ber.writeInt, 8); - ber.writeIfDefined(this.isOnline, ber.writeBoolean, 9); - ber.writeIfDefined(this.formula, ber.writeString, 10, BER.EMBER_STRING); - ber.writeIfDefined(this.step, ber.writeInt, 11); - ber.writeIfDefined(this.default, ber.writeValue, 12); - ber.writeIfDefinedEnum(this.type, ParameterType, ber.writeInt, 13); - ber.writeIfDefined(this.streamIdentifier, ber.writeInt, 14); - - if(this.emumMap !== undefined) { - ber.startSequence(BER.CONTEXT(15)); - StringIntegerCollection.encode(ber, this.enumMap); - ber.endSequence(); - } - - if(this.streamDescriptor !== undefined) { - ber.startSequence(BER.CONTEXT(16)); - this.streamDescriptor.encode(ber); - ber.endSequence(); - } - - ber.writeIfDefined(this.schemaIdentifiers, ber.writeString, 17, BER.EMBER_STRING); - - ber.endSequence(); -} - -/**************************************************************************** - * StringIntegerCollection - ***************************************************************************/ - -// This is untested, VPB doesn't seem to use this that I've seen so far - -function StringIntegerCollection() {}; - -StringIntegerCollection.decode = function(ber) { - var enumMap = {}; - ber = ber.getSequence(BER.APPLICATION(8)); - while(ber.remain > 0) { - var seq = ber.getSequence(BER.CONTEXT(0)); - seq = seq.getSequence(BER.APPLICATION(7)); - var entryString, entryInteger; - while(seq.remain > 0) { - var tag = seq.peek(); - var dataSeq = seq.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - entryString = dataSeq.readString(BER.EMBER_STRING); - } else if(tag == BER.CONTEXT(1)) { - entryInteger = dataSeq.readInt(); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - enumMap[entryString] = entryInteger; - } - - return new Enum(enumMap); -} - -StringIntegerCollection.encode = function(ber, e) { - ber.startSequence(BER.APPLICATION(8)); - ber.startSequence(BER.CONTEXT(0)); - e.enums.forEach((item) => { - ber.startSequence(BER.APPLICATION(7)); - ber.startSequence(BER.CONTEXT(0)); - ber.writeString(item.key, BER.EMBER_STRING); - ber.endSequence(); - ber.startSequence(BER.CONTEXT(1)); - ber.writeInt(item.value); - ber.endSequence(); - ber.endSequence(); - }); - ber.endSequence(); - ber.endSequence(); -} - -/**************************************************************************** - * StreamDescription - ***************************************************************************/ - -var StreamFormat = new Enum({ - unsignedInt8: 0, - unsignedInt16BigEndian: 2, - unsignedInt16LittleEndian: 3, - unsignedInt32BigEndian: 4, - unsignedInt32LittleEndian: 5, - unsignedInt64BigEndian: 6, - unsignedInt64LittleENdian: 7, - signedInt8: 8, - signedInt16BigEndian: 10, - signedInt16LittleEndian: 11, - signedInt32BigEndian: 12, - signedInt32LittleEndian: 13, - signedInt64BigEndian: 14, - signedInt64LittleEndian: 15, - ieeeFloat32BigEndian: 20, - ieeeFloat32LittleEndian: 21, - ieeeFloat64BigEndian: 22, - ieeeFloat64LittleEndian: 23 -}); - -function StreamDescription() {}; - -StreamDescription.decode = function(ber) { - var sd = new StreamDescription(); - ber = ber.getSequence(BER.APPLICATION(12)); - - while(ber.remain > 0) { - var tag = ber.peek(); - var seq =ber.getSequence(tag); - if(tag == BER.CONTEXT(0)) { - sd.format = StreamFormat.get(seq.readInt()); - } else if(tag == BER.CONTEXT(1)) { - sd.offset = seq.readInt(); - } else { - throw new errors.UnimplementedEmberTypeError(tag); - } - } - - return sd; -} - -StreamDescription.prototype.encode = function(ber) { - ber.startSequence(BER.APPLICATION(12)); - - ber.writeIfDefinedEnum(this.format, StreamFormat, ber.writeInt, 0); - ber.writeIfDefined(this.offset, ber.writeInt, 1); - - ber.endSequence(); -} - - diff --git a/errors.js b/errors.js deleted file mode 100755 index 9aad982..0000000 --- a/errors.js +++ /dev/null @@ -1,59 +0,0 @@ -const util = require('util'); - -/**************************************************************************** - * UnimplementedEmberType error - ***************************************************************************/ - -function UnimplementedEmberTypeError(tag) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - var identifier = (tag & 0xC0) >> 6; - var value = (tag & 0x1F).toString(); - var tagStr = tag.toString(); - if(identifier == 0) { - tagStr = "[UNIVERSAL " + value + "]"; - } else if(identifier == 1) { - tagStr = "[APPLICATION " + value + "]"; - } else if(identifier == 2) { - tagStr = "[CONTEXT " + value + "]"; - } else { - tagStr = "[PRIVATE " + value + "]"; - } - this.message = "Unimplemented EmBER type " + tagStr; -} - -util.inherits(UnimplementedEmberTypeError, Error); -module.exports.UnimplementedEmberTypeError = UnimplementedEmberTypeError; - - -function ASN1Error(message) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.message = message; -} - -util.inherits(ASN1Error, Error); -module.exports.ASN1Error = ASN1Error; - -function EmberAccessError(message) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - if(this.message !== undefined) { - this.message = message; - } else { - this.message("Parameter access error"); - } -} - -util.inherits(EmberAccessError, Error); -module.exports.EmberAccessError = EmberAccessError; - -function EmberTimeoutError(message) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.message = message; -} - -util.inherits(EmberTimeoutError, Error); -module.exports.EmberTimeoutError = EmberTimeoutError; - diff --git a/index.js b/index.js index 924f192..6727f4f 100755 --- a/index.js +++ b/index.js @@ -1,7 +1,8 @@ -const DeviceTree = require('./device.js').DeviceTree; -const Decoder = require('./device.js').DecodeBuffer; -const Ember = require("./ember.js"); +const EmberClient = require('./EmberClient'); +const EmberLib = require("./EmberLib"); +const Decoder = EmberLib.DecodeBuffer; const S101 = require("./s101"); -const TreeServer = require("./server"); -const {S101Client} = require("./client"); -module.exports = {DeviceTree, Decoder, Ember, TreeServer, S101, S101Client}; +const {EmberServer,ServerEvents} = require("./EmberServer"); +const {S101Client} = require("./EmberSocket"); +const BER = require('./ber.js') +module.exports = {EmberClient, Decoder, EmberLib, EmberServer,ServerEvents, S101, S101Client, BER}; diff --git a/package.json b/package.json index 270514d..2d5206e 100755 --- a/package.json +++ b/package.json @@ -1,36 +1,47 @@ { - "name": "emberplus", - "version": "1.8.0", + "name": "node-emberplus", + "version": "2.5.9", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { - "test": "jest test", - "eslint": "eslint ./" + "test": "jest test --coverage", + "eslint": "eslint ./", + "start": "node server.js" }, - "author": "Brian Mayton (http://bdm.cc)", + "author": "Gilles Dufour (www.gdnet.be)", "contributors": [ - { - "name": "Gilles Dufour", - "email": "dufour.gilles@gmail.com" - } + "Gilles Dufour (www.gdnet.be)", + "Brian Mayton (http://bdm.cc)" ], "repository": { "type": "git", - "url": "https://github.com/bmayton/node-emberplus" + "url": "git+https://github.com/dufourgilles/node-emberplus.git" }, "license": "MIT", "dependencies": { - "asn1": "evs-broadcast/node-asn1#date_2018_01_02", + "asn1": "github:evs-broadcast/node-asn1", "enum": "^2.4.0", "long": "^3.2.0", "smart-buffer": "^3.0.3", "winston": "^2.1.1", - "winston-color": "^1.0.0" + "winston-color": "^1.0.0", + "yargs": "^15.1.0" }, "devDependencies": { "eslint": "^5.5.0", "jest": "^23.5.0", "jest-cli": "^24.9.0", "sinon": "^7.4.1" - } + }, + "bugs": { + "url": "https://github.com/dufourgilles/node-emberplus/issues" + }, + "homepage": "https://github.com/dufourgilles/node-emberplus#readme", + "directories": { + "test": "test" + }, + "keywords": [ + "emberplus", + "lawo" + ] } diff --git a/serve.js b/serve.js new file mode 100755 index 0000000..9447250 --- /dev/null +++ b/serve.js @@ -0,0 +1,55 @@ +const yargs = require('yargs'); +const { EmberServer, Decoder } = require('./index'); +const { readFileSync } = require('fs'); + +const argv = yargs.options({ + host: { + alias: 'h', + description: 'host name|ip', + default: '0.0.0.0' + }, + + port: { + alias: 'p', + default: 9000, + type: 'number', + description: 'port', + demandOption: true + }, + + file: { + alias: 'f', + description: 'file containing the ber (default) or json tree', + demandOption: true + }, + + json: { + alias: 'j', + type: 'boolean', + description: 'file format is json' + }, + debug: { + alias: 'd', + type: 'boolean', + description: 'debug' + } + +}).help().argv; + +const main = async () => { + const data = readFileSync(argv.file); + const tree = argv.json ? EmberServer.JSONtoTree(JSON.parse(data.toString())) : Decoder(data); + const server = new EmberServer(argv.host, argv.port, tree); + server._debug = true; + console.log(Date.now(), 'starting server'); + if (argv.debug) { + server._debug = true; + } + try { + server.listen(); + } catch (e) { + console.log(e); + } +}; + +main(); diff --git a/server.js b/server.js deleted file mode 100755 index 8bdfee5..0000000 --- a/server.js +++ /dev/null @@ -1,611 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const S101Server = require('./client.js').S101Server; -const ember = require('./ember.js'); - -function TreeServer(host, port, tree) { - TreeServer.super_.call(this); - var self = this; - self._debug = false; - - self.callback = undefined; - self.timeoutValue = 2000; - self.server = new S101Server(host, port); - self.tree = tree; - self.clients = new Set(); - self.subscribers = {}; - - self.server.on('listening', () => { - if (self._debug) { console.log("listening"); } - self.emit('listening'); - if (self.callback !== undefined) { - self.callback(); - self.callback = undefined; - } - }); - - self.server.on('connection', (client) => { - if (self._debug) { console.log("ember new connection from", client.remoteAddress()); } - self.clients.add(client); - client.on("emberTree", (root) => { - if (self._debug) { console.log("ember new request from", client.remoteAddress(), root); } - // Queue the action to make sure responses are sent in order. - client.addRequest(() => { - try { - let path = self.handleRoot(client, root); - self.emit("request", {client: client.remoteAddress(), root: root, path: path}); - } - catch(e) { - if (self._debug) { console.log(e.stack); } - self.emit("error", e); - } - }); - }); - client.on("disconnected", () => { - self.clients.delete(client); - self.emit('disconnect', client.remoteAddress()); - }); - client.on("error", error => { - self.emit('clientError', { remoteAddress: client.remoteAddress(), error }); - }); - self.emit('connection', client.remoteAddress()); - }); - - self.server.on('disconnected', () => { - self.emit('disconnected', client.remoteAddress()); - }); - - self.server.on("error", (e) => { - self.emit("error", e); - if (self.callback !== undefined) { - self.callback(e); - } - }); - -} - -util.inherits(TreeServer, EventEmitter); - - -TreeServer.prototype.listen = function() { - return new Promise((resolve, reject) => { - this.callback = (e) => { - if (e === undefined) { - return resolve(); - } - return reject(e); - }; - this.server.listen(); - }); -}; - -TreeServer.prototype.close = function () { - return new Promise((resolve, reject) => { - this.callback = (e) => { - if (e === undefined) { - return resolve(); - } - return reject(e); - }; - this.server.server.close(); - }); -}; - -TreeServer.prototype.handleRoot = function(client, root) { - if ((root === undefined) || (root.elements === undefined) || (root.elements < 1)) { - this.emit("error", new Error("invalid request")); - return; - } - - - const node = root.elements[0]; - client.request = node; - - if (node.path !== undefined) { - return this.handleQualifiedNode(client, node); - } - else if (node instanceof ember.Command) { - // Command on root element - this.handleCommand(client, this.tree, node.number); - return "root"; - } - else { - return this.handleNode(client, node); - } -} - -TreeServer.prototype.handleError = function(client, node) { - if (client !== undefined) { - let res = node == null ? this.tree._root.getMinimal() : node; - client.sendBERNode(res); - } -} - - -TreeServer.prototype.handleQualifiedNode = function(client, node) { - const path = node.path; - // Find this element in our tree - const element = this.tree.getElementByPath(path); - - if ((element === null) || (element === undefined)) { - this.emit("error", new Error(`unknown element at path ${path}`)); - return this.handleError(client); - } - - if ((node.children !== undefined) && (node.children.length === 1) && - (node.children[0] instanceof ember.Command)) { - this.handleCommand(client, element, node.children[0].number); - } - else { - if (node instanceof ember.QualifiedMatrix) { - this.handleQualifiedMatrix(client, element, node); - } - else if (node instanceof ember.QualifiedParameter) { - this.handleQualifiedParameter(client, element, node); - } - } - return path; -} - - -TreeServer.prototype.handleNode = function(client, node) { - // traverse the tree - let element = node; - let path = []; - while(element !== undefined) { - if (element.number === undefined) { - this.emit("error", "invalid request"); - return; - } - if (element instanceof ember.Command) { - break; - } - path.push(element.number); - - let children = element.getChildren(); - if ((! children) || (children.length === 0)) { - break; - } - element = element.children[0]; - } - let cmd = element; - - if (cmd === undefined) { - this.emit("error", "invalid request"); - return this.handleError(client); - } - - element = this.tree.getElementByPath(path.join(".")); - - if (element == null) { - this.emit("error", new Error(`unknown element at path ${path}`)); - return this.handleError(client); - } - - if (cmd instanceof ember.Command) { - this.handleCommand(client, element, cmd.number); - } - else if ((cmd instanceof ember.MatrixNode) && (cmd.connections !== undefined)) { - this.handleMatrixConnections(client, element, cmd.connections); - } - else if ((cmd instanceof ember.Parameter) && - (cmd.contents !== undefined) && (cmd.contents.value !== undefined)) { - if (this._debug) { console.log(`setValue for element at path ${path} with value ${cmd.contents.value}`); } - this.setValue(element, cmd.contents.value, client); - let res = this.getResponse(element); - client.sendBERNode(res) - this.updateSubscribers(element.getPath(), res, client); - } - else { - this.emit("error", new Error("invalid request format")); - if (this._debug) { console.log("invalid request format"); } - return this.handleError(client, element.getTreeBranch()); - } - return path; -} - -TreeServer.prototype.handleQualifiedMatrix = function(client, element, matrix) -{ - this.handleMatrixConnections(client, element, matrix.connections); -} - -TreeServer.prototype.handleQualifiedParameter = function(client, element, parameter) -{ - if (parameter.contents.value !== undefined) { - this.setValue(element, parameter.contents.value, client); - let res = this.getQualifiedResponse(element); - client.sendBERNode(res) - this.updateSubscribers(element.getPath(), res, client); - } -} - - -TreeServer.prototype.handleMatrixConnections = function(client, matrix, connections, response = true) { - var res; - var root; // ember message root - if (matrix.isQualified()) { - root = new ember.Root(); - res = new ember.QualifiedMatrix(matrix.getPath()); - root.elements = [res]; // do not use addchild or the element will get removed from the tree. - } - else { - res = new ember.MatrixNode(matrix.number); - root = matrix._parent.getTreeBranch(res); - } - res.connections = {}; - for(let id in connections) { - if (!connections.hasOwnProperty(id)) { - continue; - } - let connection = connections[id]; - let conResult = new ember.MatrixConnection(connection.target); - let emitType; - res.connections[connection.target] = conResult; - - - // Apply changes - - if ((connection.operation === undefined) || - (connection.operation.value == ember.MatrixOperation.absolute)) { - matrix.connections[connection.target].setSources(connection.sources); - emitType = "matrix-change"; - } - else if (connection.operation == ember.MatrixOperation.connect) { - matrix.connections[connection.target].connectSources(connection.sources); - emitType = "matrix-connect"; - } - else { // Disconnect - matrix.connections[connection.target].disconnectSources(connection.sources); - emitType = "matrix-disconnect"; - } - - // Send response or update subscribers. - - if (response) { - conResult.sources = matrix.connections[connection.target].sources; - conResult.disposition = ember.MatrixDisposition.modified; - // We got a request so emit something. - this.emit(emitType, { - target: connection.target, - sources: connection.sources, - client: client.remoteAddress() - }); - } - else { - // the action has been applied. So we should either send the current state (absolute) - // or send the action itself (connection.sources) - conResult.sources = matrix.connections[connection.target].sources; - conResult.operation = ember.MatrixOperation.absolute; - } - } - if (client !== undefined) { - client.sendBERNode(root); - } - if (this._debug) { console.log("Updating subscribers for matrix change"); } - this.updateSubscribers(matrix.getPath(), root, client); -} - -const validateMatrixOperation = function(matrix, target, sources) { - if (matrix === undefined) { - throw new Error(`matrix not found with path ${path}`); - } - if (matrix.contents === undefined) { - throw new Error(`invalid matrix at ${path} : no contents`); - } - if (matrix.contents.targetCount === undefined) { - throw new Error(`invalid matrix at ${path} : no targetCount`); - } - if ((target < 0) || (target >= matrix.contents.targetCount)) { - throw new Error(`target id ${target} out of range 0 - ${matrix.contents.targetCount}`); - } - if (sources.length === undefined) { - throw new Error("invalid sources format"); - } -} - -const doMatrixOperation = function(server, path, target, sources, operation) { - let matrix = server.tree.getElementByPath(path); - - validateMatrixOperation(matrix, target, sources); - - let connection = new ember.MatrixConnection(target); - connection.sources = sources; - connection.operation = operation; - server.handleMatrixConnections(undefined, matrix, [connection], false); -} - -TreeServer.prototype.matrixConnect = function(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.connect); -} - -TreeServer.prototype.matrixDisconnect = function(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.disconnect); -} - -TreeServer.prototype.matrixSet = function(path, target, sources) { - doMatrixOperation(this, path, target, sources, ember.MatrixOperation.absolute); -} - -TreeServer.prototype.handleCommand = function(client, element, cmd) { - if (cmd === ember.GetDirectory) { - this.handleGetDirectory(client, element); - } - else if (cmd === ember.Subscribe) { - this.handleSubscribe(client, element); - } - else if (cmd === ember.Unsubscribe) { - this.handleUnSubscribe(client, element); - } - else { - this.emit("error", new Error(`invalid command ${cmd}`)); - } -} - -TreeServer.prototype.getResponse = function(element) { - return element.getTreeBranch(undefined, function(node) { - node.update(element); - let children = element.getChildren(); - if (children != null) { - for (let i = 0; i < children.length; i++) { - node.addChild(children[i].getDuplicate()); - } - } - else if (this._debug) { - console.log("getResponse","no children"); - } - }); -} - -TreeServer.prototype.getQualifiedResponse = function(element) { - let res = new ember.Root(); - let dup; - if (element.isRoot() === false) { - dup = element.toQualified(); - } - let children = element.getChildren(); - if (children != null) { - for (let i = 0; i < children.length; i++) { - res.addChild(children[i].toQualified().getMinimalContent()); - } - } - else { - res.addChild(dup); - } - return res; -} - -TreeServer.prototype.handleGetDirectory = function(client, element) { - if (client !== undefined) { - if ((element.isMatrix() || element.isParameter()) && - (!element.isStream())) { - // ember spec: parameter without streamIdentifier should - // report their value changes automatically. - this.subscribe(client, element); - } - else if (element.isNode()) { - const children = element.getChildren(); - if (children != null) { - for (let i = 0; i < children.length; i++) { - const child = children[i]; - if ((child.isMatrix() || child.isParameter()) && - (!child.isStream())) { - this.subscribe(client, child); - } - } - } - } - - let res = this.getQualifiedResponse(element); - if (this._debug) { - console.log("getDirectory response", res); - } - client.sendBERNode(res); - } -} - -TreeServer.prototype.handleSubscribe = function(client, element) { - this.subscribe(client, element); -} - -TreeServer.prototype.handleUnSubscribe = function(client, element) { - this.unsubscribe(client, element); -} - - -TreeServer.prototype.subscribe = function(client, element) { - const path = element.getPath(); - if (this.subscribers[path] === undefined) { - this.subscribers[path] = new Set(); - } - this.subscribers[path].add(client); -} - -TreeServer.prototype.unsubscribe = function(client, element) { - const path = element.getPath(); - if (this.subscribers[path] === undefined) { - return; - } - this.subscribers[path].delete(client); -} - -TreeServer.prototype.setValue = function(element, value, origin, key) { - return new Promise((resolve, reject) => { - // Change the element value if write access permitted. - if (element.contents !== undefined) { - if (element.isParameter()) { - if ((element.contents.access !== undefined) && - (element.contents.access.value > 1)) { - element.contents.value = value; - this.emit("value-change", element); - } - } - else if (element.isMatrix()) { - if ((key !== undefined) && (element.contents.hasOwnProperty(key))) { - element.contents[key] = value; - this.emit("value-change", element); - } - } - } - }); -} - -TreeServer.prototype.replaceElement = function(element) { - let path = element.getPath(); - let parent = this.tree.getElementByPath(path); - if ((parent === undefined)||(parent._parent === undefined)) { - throw new Error(`Could not find element at path ${path}`); - } - parent = parent._parent; - let children = parent.getChildren(); - let newList = []; - for(let i = 0; i <= children.length; i++) { - if (children[i] && children[i].getPath() == path) { - element._parent = parent; // move it to new tree. - children[i] = element; - let res = this.getResponse(element); - this.updateSubscribers(path,res); - return; - } - } -} - - -TreeServer.prototype.updateSubscribers = function(path, response, origin) { - if (this.subscribers[path] === undefined) { - return; - } - - for (let client of this.subscribers[path]) { - if (client === origin) { - continue; // already sent the response to origin - } - if (this.clients.has(client)) { - client.queueMessage(response); - } - else { - // clean up subscribers - client is gone - this.subscribers[path].delete(client); - } - } -} - -const parseMatrixContent = function(matrixContent, content) { - if (content.labels) { - matrixContent.labels = []; - for(let l = 0; l < content.labels.length; l++) { - matrixContent.labels.push( - new ember.Label(content.labels[l]) - ); - } - delete content.labels; - } - if (content.type != null) { - if (content.type == "oneToN") { - matrixContent.type = ember.MatrixType.oneToN; - } - else if (content.type == "oneToOne") { - matrixContent.type = ember.MatrixType.oneToOne; - } - else if (content.type == "nToN") { - matrixContent.type = ember.MatrixType.nToN; - } - else { - throw new Error(`Invalid matrix type ${content.type}`); - } - delete content.type; - } - if (content.mode != null) { - if (content.mode == "linear") { - matrixContent.mode = ember.MatrixMode.linear; - } - else if (content.mode == "nonLinear") { - matrixContent.mode = ember.MatrixMode.nonLinear; - } - else { - throw new Error(`Invalid matrix mode ${content.mode}`); - } - delete content.mode; - } -} - -const parseObj = function(parent, obj) { - let path = parent.getPath(); - for(let i = 0; i < obj.length; i++) { - let emberElement; - let content = obj[i]; - let number = content.number !== undefined ? content.number : i; - delete content.number; - if (content.value !== undefined) { - emberElement = new ember.Parameter(number); - emberElement.contents = new ember.ParameterContents(content.value); - if (content.type) { - emberElement.contents.type = ember.ParameterType.get(content.type); - delete content.type; - } - else { - emberElement.contents.type = ember.ParameterType.string; - } - if (content.access) { - emberElement.contents.access = ember.ParameterAccess.get(content.access); - delete content.access; - } - else { - emberElement.contents.access = ember.ParameterAccess.read; - } - } - else if (content.targetCount !== undefined) { - emberElement = new ember.MatrixNode(number); - emberElement.contents = new ember.MatrixContents(); - parseMatrixContent(emberElement.contents, content); - if (content.connections) { - emberElement.connections = {}; - for (let c in content.connections) { - if (! content.connections.hasOwnProperty(c)) { - continue; - } - let t = content.connections[c].target !== undefined ? content.connections[c].target : 0; - let connection = new ember.MatrixConnection(t); - connection.setSources(content.connections[c].sources); - emberElement.connections[t] = connection; - } - delete content.connections; - } - else { - emberElement.connections = {}; - for (let t = 0; t < content.targetCount; t++) { - let connection = new ember.MatrixConnection(t); - emberElement.connections[t] = connection; - } - } - } - else { - emberElement = new ember.Node(number); - emberElement.contents = new ember.NodeContents(); - } - for(let id in content) { - if ((id !== "children") && (content.hasOwnProperty(id))) { - emberElement.contents[id] = content[id]; - } - else { - parseObj(emberElement, content.children); - } - } - parent.addChild(emberElement); - } -} - -TreeServer.JSONtoTree = function(obj) { - let tree = new ember.Root(); - parseObj(tree, obj); - return tree; -} - - -TreeServer.prototype.toJSON = function() { - if ((!this.tree) || (!this.tree.elements) || (this.tree.elements.length == 0)) { - return []; - } - return [].push(this.tree.elements[0].toJSON()); -}; - -module.exports = TreeServer; diff --git a/test/DeviceTree.test.js b/test/DeviceTree.test.js index 85a0037..a1c645e 100755 --- a/test/DeviceTree.test.js +++ b/test/DeviceTree.test.js @@ -1,21 +1,21 @@ const fs = require("fs"); const sinon = require("sinon"); -const Decoder = require('../').Decoder; -const DeviceTree = require("../").DeviceTree; -const TreeServer = require("../").TreeServer; +const Decoder = require('../EmberLib').DecodeBuffer; +const EmberClient = require("../EmberClient"); +const {EmberServer} = require("../EmberServer"); const LOCALHOST = "127.0.0.1"; const UNKNOWN_HOST = "192.168.99.99"; const PORT = 9008; -describe("DeviceTree", () => { +describe("EmberClient", () => { describe("With server", () => { - /** @type {TreeServer} */ + /** @type {EmberServer} */ let server; beforeAll(() => { return Promise.resolve() .then(() => new Promise((resolve, reject) => { - fs.readFile("./embrionix.ember", (e, data) => { + fs.readFile("./test/embrionix.ember", (e, data) => { if (e) { reject(e); } @@ -23,17 +23,15 @@ describe("DeviceTree", () => { }); })) .then(root => { - server = new TreeServer(LOCALHOST, PORT, root); + server = new EmberServer(LOCALHOST, PORT, root); return server.listen(); }); }); - afterAll(() => server.close()); - }); it("should gracefully connect and disconnect", () => { return Promise.resolve() .then(() => { - let tree = new DeviceTree(LOCALHOST, PORT); + let tree = new EmberClient(LOCALHOST, PORT); return Promise.resolve() .then(() => tree.connect()) .then(() => tree.getDirectory()) @@ -41,12 +39,50 @@ describe("DeviceTree", () => { .then(() => tree.connect()) .then(() => tree.getDirectory()) .then(() => tree.disconnect()) + }); + }); + + it("should not disconnect after 5 seconds of inactivity", () => { + return Promise.resolve() + .then(() => { + let tree = new EmberClient(LOCALHOST, PORT); + + tree.on("error", error => { + throw error; + }); + + return Promise.resolve() + .then(() => tree.connect()) + .then(() => new Promise(resolve => setTimeout(resolve, 5000))) + .then(() => tree.disconnect()) }) - }); - + }, 7000); + + it("timeout should be taken into account when connecting to unknown host", () => { + let tree = new EmberClient(UNKNOWN_HOST, PORT); + tree.on("error", () => { + }); + const expectedTimeoutInSec = 2; + const initialTime = Date.now(); + return tree.connect(expectedTimeoutInSec) + .then(() => { + throw new Error("Should have thrown"); + }, + error => { + const durationMs = Date.now() - initialTime; + const deltaMs = Math.abs(expectedTimeoutInSec * 1000 - durationMs); + expect(deltaMs).toBeLessThan(10); + expect(error.message).toBe(`Could not connect to ${UNKNOWN_HOST}:${PORT} after a timeout of ${expectedTimeoutInSec} seconds`) + }); + }); it("should gracefully connect and getDirectory", () => { - let tree = new DeviceTree(LOCALHOST, PORT); - let stub = sinon.stub(tree.client, "sendBER"); + let tree = new EmberClient(LOCALHOST, PORT); + tree.on("error", () => { + // ignore + }); + let stub = sinon.stub(tree._client, "sendBER"); + tree._debug = true; + server._debug = true; stub.onFirstCall().returns(); stub.onSecondCall().throws(new Error("blah")); stub.callThrough(); @@ -57,45 +93,11 @@ describe("DeviceTree", () => { .then(() => { stub.restore(); tree.disconnect(); - }, error => { + }, () => { stub.restore(); tree.disconnect(); - throw error; - }) - - }, 10000); - it("should not disconnect after 5 seconds of inactivity", () => { - return Promise.resolve() - .then(() => { - let tree = new DeviceTree(LOCALHOST, PORT); - - tree.on("error", error => { - throw error; - }); - - return Promise.resolve() - .then(() => tree.connect()) - .then(() => new Promise(resolve => setTimeout(resolve, 5000))) - .then(() => tree.disconnect()) - }) - }, 7000); - }); - - it("timeout should be taken into account when connecting to unknown host", () => { - let tree = new DeviceTree(UNKNOWN_HOST, PORT); - tree.on("error", () => { - }); - const expectedTimeoutInSec = 2; - const initialTime = performance.now(); - return tree.connect(expectedTimeoutInSec) - .then(() => { - throw new Error("Should have thrown"); - }, - error => { - const durationMs = performance.now() - initialTime; - const deltaMs = Math.abs(expectedTimeoutInSec * 1000 - durationMs); - expect(deltaMs).toBeLessThan(10); - expect(error.message).toBe(`Could not connect to ${UNKNOWN_HOST}:${PORT} after a timeout of ${expectedTimeoutInSec} seconds`) + // do nothinf }); - }); + }, 10000); + }); }); diff --git a/test/Ember.test.js b/test/Ember.test.js index e1c6e23..f96b136 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -1,32 +1,1628 @@ const expect = require("expect"); -const { S101Client } = require("../index"); +const { S101Client } = require("../EmberSocket"); const s101Buffer = Buffer.from("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); -const ember = require("../ember"); +const ember = require("../EmberLib"); const BER = require('../ber.js'); -const errors = require('../errors.js'); - +const Errors = require('../Errors.js'); +const EmberLib = require("../EmberLib"); +const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("../EmberLib/ParameterType"); +const identifier = "node_identifier"; +const description = "node_description"; describe("Ember", () => { - let client; + describe("generic", () => { + let client; + + beforeEach(() => { + client = new S101Client(); + }); + + it("should parse S101 message without error", (done) => { + client.on("emberPacket", () => { + done(); + }); + client.on("error", e => { + // eslint-disable-next-line no-console + console.log(e); + expect(e).toBeUndefined(); + done(); + }); + client.codec.dataIn(s101Buffer); + }); + + it("should handle Errors in message", () => { + var ber = new BER.Reader(errorBuffer); + expect(() => ember.Root.decode(ber)).toThrow(Errors.UnimplementedEmberTypeError); + }); + it("Should have a toJSON()", () => { + const node = new EmberLib.Node(); + node.addChild(new EmberLib.Node(0)); + node.getElementByNumber(0).addChild(new EmberLib.Parameter(1)); + const matrix = new EmberLib.MatrixNode(2); + matrix.targets = [0,3,6,7]; + matrix.sources = [2,6,8]; + matrix.contents = new EmberLib.MatrixContents(EmberLib.MatrixType.oneToN, EmberLib.MatrixMode.nonLinear); + node.getElementByNumber(0).addChild(matrix); + const js = node.toJSON(); + expect(js).toBeDefined(); + expect(js.elements.length).toBe(1); + expect(js.elements[0].number).toBe(0); + expect(js.elements[0].children[0].number).toBe(1); + expect(js.elements[0].children[1].number).toBe(2); + expect(js.elements[0].children[1].targets.length).toBe(matrix.targets.length); + }); + it("should have a getElement()", () => { + const node = new EmberLib.Node(); + node.addChild(new EmberLib.Node(0)); + let res = node.getElement(0); + expect(res).toBeDefined(); + }); + it("should have a isCommand(), isRoot() ... functions", () => { + const root = new EmberLib.Root(); + const node = new EmberLib.Node(); + root.addElement(node); + expect(node.isCommand()).toBeFalsy(); + expect(node.isRoot()).toBeFalsy(); + expect(node.isStream()).toBeFalsy(); + expect(node.isTemplate()).toBeFalsy(); + }); + it("should have function getElement", () => { + const node = new EmberLib.Node(0); + const identifier = "node_identifier"; + const description = "node_description"; + node.contents = new EmberLib.NodeContents(identifier, description); + const root = new EmberLib.Root(); + root.addElement(node); + let res = root.getElement(identifier); + expect(res).toBeDefined(); + expect(res.contents.identifier).toBe(identifier); + }); + + it("should throw error if function getElement called from a node with longer parh", () => { + const root = new EmberLib.Root(); + root.addChild(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(1)); + root.getElementByPath("0.1").addChild(new EmberLib.Node(1)); + const node = new EmberLib.Node(0); + root.getElementByPath("0.1.1").addChild(node); + const identifier = "node_identifier"; + const description = "node_description"; + node.contents = new EmberLib.NodeContents(identifier, description); + let res = root.getElementByPath("0.1").getElementByPath("0"); + expect(res).toBe(null); + + res = root.getElementByPath("0.1").getElementByPath("0.2.0"); + expect(res).toBe(null); + + res = root.getElementByPath("0.1").getElementByPath("0.1"); + expect(res).toBeDefined(); + }); + it("should have a getRoot function", () => { + const root = new EmberLib.Root(); + root.addChild(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(1)); + root.getElementByPath("0.1").addChild(new EmberLib.Node(1)); + const node = new EmberLib.Node(0); + root.getElementByPath("0.1.1").addChild(node); + let res = node.getRoot(); + expect(res).toBe(root); + }); + it("should have a getDirectory() and accept a callback for subscribers", () => { + const parameter = new EmberLib.Parameter(0); + parameter.contents = new EmberLib.ParameterContents(7, "integer"); + parameter.contents.streamIdentifier = 12345; + let res = parameter.getDirectory(0, () => {}); + expect(res).toBeDefined(); + expect(parameter._subscribers.size).toBe(1); + }); + it("should have a getDuplicate function", () => { + const parameter = new EmberLib.Parameter(0); + parameter.contents = new EmberLib.ParameterContents("test", "string"); + let res = parameter.getDuplicate(); + expect(res).toBeDefined(); + + const qp = new EmberLib.QualifiedParameter("0.1"); + qp.contents = parameter.contents; + res = qp.getDuplicate(); + expect(res).toBeDefined(); + }); + it("should decode continuation messages", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.CONTEXT(0)); + const qp = new EmberLib.QualifiedParameter("0.1"); + qp.encode(writer); + writer.endSequence(); + const res = EmberLib.rootDecode(new BER.Reader(writer.buffer)); + expect(res).toBeDefined(); + expect(res.getElementByPath("0.1")).toBeDefined(); + }); + it("should throw an error if not able to decode root", () => { + let writer = new BER.Writer(); + writer.startSequence(BER.CONTEXT(0)); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.rootDecode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(error){ + expect(error instanceof Errors.UnimplementedEmberTypeError); + } + + writer = new BER.Writer(); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + try { + EmberLib.rootDecode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(error){ + expect(error instanceof Errors.UnimplementedEmberTypeError); + } + + writer = new BER.Writer(); + writer.startSequence(BER.APPLICATION(0)); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.rootDecode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(error){ + expect(error instanceof Errors.UnimplementedEmberTypeError); + } + }); + }); + describe("Command", () => { + it("should throw error if unknown context found", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.APPLICATION(2)); + writer.startSequence(BER.CONTEXT(0)); + writer.writeInt(EmberLib.COMMAND_GETDIRECTORY); + writer.endSequence(); // BER.CONTEXT(0) + writer.startSequence(BER.CONTEXT(1)); + writer.writeInt(0); + writer.endSequence(); + writer.startSequence(BER.CONTEXT(3)); + writer.writeInt(0); + writer.endSequence(); + writer.endSequence(); // BER.APPLICATION(2) + try { + EmberLib.Command.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e).not.toBe("Should not succeed"); + } + }); + it("should have a toJSON", () => { + const command = new EmberLib.Command(EmberLib.COMMAND_GETDIRECTORY); + let jsonCommand = command.toJSON(); + expect(jsonCommand.number).toBe(EmberLib.COMMAND_GETDIRECTORY); + command.invocation = new EmberLib.Invocation(1, [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1, "arg1") + ]); + jsonCommand = command.toJSON(); + expect(jsonCommand.invocation.arguments.length).toBe(1); + }); + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Command.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Command.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should have a getElementByIdentifier", () => { + const node = new EmberLib.Node(0); + const identifier = "node_identifier"; + const description = "node_description"; + node.contents = new EmberLib.NodeContents(identifier, description); + const root = new EmberLib.Root(); + root.addElement(node); + let res = root.getElementByIdentifier(identifier); + expect(res).toBeDefined(); + expect(res.contents.identifier).toBe(identifier); + + res = root.getElementByIdentifier("unknown"); + expect(res).toBe(null); + }); + }); + describe("Node", () => { + it("should have an encoder", () => { + const node = new EmberLib.Node(0); + const identifier = "node_identifier"; + const description = "node_description"; + node.contents = new EmberLib.NodeContents(identifier, description); + node.contents.isRoot = true; + node.contents.isOnline = true; + node.contents.schemaIdentifiers = "schema1"; + const root = new EmberLib.Node(0); + root.addChild(node); + let writer = new BER.Writer(); + root.encode(writer); + expect(writer.buffer.size).not.toBe(0); + node.contents.isOnline = null; + node.contents.identifier = null; + writer = new BER.Writer(); + root.encode(writer); + expect(writer.buffer.size).not.toBe(0); + }); + it("should have a decoder", () => { + const node = new EmberLib.Node(0); + node.contents = new EmberLib.NodeContents(identifier, description); + node.contents.isRoot = true; + node.contents.isOnline = true; + node.contents.schemaIdentifiers = "schema1"; + const writer = new BER.Writer(); + node.encode(writer); + const n = EmberLib.Node.decode(new BER.Reader(writer.buffer)); + expect(n.number).toBe(node.number); + expect(n.contents.identifier).toBe(identifier); + expect(n.contents.description).toBe(description); + }); + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Node.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Node.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should throw an error if unable to decode content", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.NodeContents.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + }); + describe("Function", () => { + let func; + beforeEach(() => { + func = new EmberLib.Function(0, args => { + const res = new EmberLib.FunctionArgument(); + res.type = EmberLib.ParameterType.integer; + res.value = args[0].value + args[1].value; + return [res]; + }); + }); + it("should be able to encode FunctionArgument with no name", () => { + const res = new EmberLib.FunctionArgument(); + res.type = EmberLib.ParameterType.integer; + const writer = new BER.Writer(); + res.encode(writer); + expect(writer.buffer.length > 0).toBeTruthy(); + }); + it("should throw an Error if encoding FunctionArgument with no type", () => { + const res = new EmberLib.FunctionArgument(); + const writer = new BER.Writer(); + try { + res.encode(writer); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode); + } + }); + it("should throw an Error if unable to decode", () => { + const writer = new BER.Writer(); + try { + writer.startSequence(EmberLib.Function.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); // BER.CONTEXT(0) + writer.endSequence(); // BER.CONTEXT(0) + EmberLib.Function.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError); + } + }); + it("should return true when calling isFunction", () => { + expect(func.isFunction()).toBeTruthy(); + }); + it("should have an invoke function", () => { + const invoke = func.invoke(); + const children = invoke.getChildren(); + expect(children.length).toBe(1); + expect(children[0].isCommand()).toBeTruthy(); + }); + it("should have a encoder/decoder", () => { + func.contents = new EmberLib.FunctionContent(identifier, description); + func.contents.templateReference = "1.2.3"; + func.addChild(new EmberLib.Node(1)); + func.contents.arguments = [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer,null, "arg1"), + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer,null, "arg2") + ]; + func.contents.result = [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer,null, "result") + ]; + let writer = new BER.Writer(); + func.encode(writer); + let f = EmberLib.Function.decode(new BER.Reader(writer.buffer)); + expect(f.number).toBe(func.number); + expect(f.contents.identifier).toBe(identifier); + expect(f.contents.description).toBe(description); + expect(f.contents.result.length).toBe(1); + expect(f.contents.templateReference).toBe(func.contents.templateReference); - beforeAll(() => { - client = new S101Client(); + writer = new BER.Writer(); + func.contents.identifier = null; + func.contents.arguments = null; + func.contents.result = null; + func.encode(writer); + f = EmberLib.Function.decode(new BER.Reader(writer.buffer)); + expect(f.number).toBe(func.number); + expect(f.contents.identifier == null).toBeTruthy(); + expect(f.contents.result == null || f.contents.result.length == 0).toBeTruthy(); + }); + it("should throw an error if unable to decode result", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(3)); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.FunctionContent.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should throw an error if unable to decode content", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.FunctionContent.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should throw an error if unable to decode FunctionArgument", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.FunctionArgument.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.FunctionArgument.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); }); + describe("Parameter", () => { + it("should through an error if decoding unknown parameter type", () => { + try { + ParameterTypefromBERTAG(99); + } + catch(e) { + expect(e instanceof Errors.InvalidBERFormat).toBeTruthy(); + } + try { + ParameterTypetoBERTAG(99); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + it("should have an update function", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = 1; + parameter.contents = new EmberLib.ParameterContents(VALUE, "integer"); + const newParameter = new EmberLib.Parameter(0); + const NEW_VALUE = VALUE + 1; + newParameter.contents = new EmberLib.ParameterContents(NEW_VALUE, "integer"); + parameter.update(newParameter); + expect(parameter.contents.value).toBe(NEW_VALUE); + }); + it("should have setValue function", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = 1; + parameter.contents = new EmberLib.ParameterContents(VALUE, "integer"); + let NEW_VALUE = VALUE + 1; + let setVal = parameter.setValue(NEW_VALUE); + expect(setVal.contents.value).toBe(NEW_VALUE); + NEW_VALUE = NEW_VALUE + 1; + setVal = parameter.setValue(new EmberLib.ParameterContents(NEW_VALUE)); + expect(setVal.contents.value).toBe(NEW_VALUE); + }); + it("should have decoder function", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = 1; + parameter.contents = new EmberLib.ParameterContents(VALUE, "integer"); + parameter.contents.minimum = 0; + parameter.contents.maximum = 100; + parameter.contents.access = EmberLib.ParameterAccess.readWrite; + parameter.contents.format = "db"; + parameter.contents.factor = 10; + parameter.contents.isOnline = true; + parameter.contents.formula = "x10"; + const STEP = 2; + parameter.contents.step = STEP; + const DEFAULT = 0; + parameter.contents.default = DEFAULT; + parameter.contents.type = EmberLib.ParameterType.integer; + parameter.contents.enumeration = "enumeration"; + parameter.contents.description = "description"; + parameter.contents.enumMap = new EmberLib.StringIntegerCollection(); + const KEY = "one"; + const KEY_VAL = 1; + parameter.contents.enumMap.addEntry(KEY, new EmberLib.StringIntegerPair(KEY, KEY_VAL)); + parameter.contents.streamDescriptor = new EmberLib.StreamDescription(); + parameter.contents.streamDescriptor.format = EmberLib.StreamFormat.signedInt8; + const OFFSET = 4; + parameter.contents.streamDescriptor.offset = OFFSET; - it("should parse S101 message without error", (done) => { - client.on("emberPacket", () => { - done(); + const SCHEMA = "schema"; + parameter.contents.schemaIdentifiers = SCHEMA; + const node = new EmberLib.Node(0); + parameter.addChild(node); + const writer = new BER.Writer(); + parameter.encode(writer); + const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + expect(newParameter.getChildren().length).toBe(1); + expect(newParameter.contents.streamDescriptor.offset).toBe(OFFSET); + expect(newParameter.contents.step).toBe(STEP); + expect(newParameter.contents.default).toBe(DEFAULT); + expect(newParameter.contents.enumMap.get(KEY).value).toBe(KEY_VAL); + expect(newParameter.contents.schemaIdentifiers).toBe(SCHEMA); + }); + it("should support type real", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = 1.1; + parameter.contents = new EmberLib.ParameterContents(VALUE, "real"); + const writer = new BER.Writer(); + parameter.encode(writer); + const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + expect(newParameter.contents.value).toBe(VALUE); + }); + it("should support type string", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE ="string support"; + parameter.contents = new EmberLib.ParameterContents(VALUE, "string"); + const writer = new BER.Writer(); + parameter.encode(writer); + const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + expect(newParameter.contents.value).toBe(VALUE); }); - client.on("error", e => { - console.log(e); - expect(e).toBeUndefined(); - done(); + it("should support type boolean", () => { + const parameter = new EmberLib.Parameter(0); + const VALUE = true; + parameter.contents = new EmberLib.ParameterContents(VALUE, "boolean"); + const writer = new BER.Writer(); + parameter.encode(writer); + const newParameter = EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + expect(newParameter.contents.value).toBe(VALUE); + }); + it("should throw an error if fails to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Parameter.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Parameter.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should throw an error if fails to decode StringIntegerPair", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.StringIntegerPair.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.StringIntegerPair.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should throw an error if fails to decode StringIntegerCollection", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.StringIntegerCollection.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.StringIntegerCollection.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should throw an error if fails to decode ParameterContents", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.ParameterContents.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } }); - client.codec.dataIn(s101Buffer); }); + describe("Matrix", () => { + describe("validateConnection", () => { + const PATH = "0.0.0"; + let matrixNode; + let qMatrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeEach(() => { + matrixNode = new EmberLib.MatrixNode(0); + qMatrixNode = new EmberLib.QualifiedMatrix(PATH); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + qMatrixNode.contents = matrixNode.contents; + }); + it("should have encoder/decoder", () => { + matrixNode.addChild(new EmberLib.Node(0)); + let writer = new BER.Writer(); + matrixNode.encode(writer); + let newMatrixNode = EmberLib.MatrixNode.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.getChildren().length).toBe(1); + + writer = new BER.Writer(); + qMatrixNode.encode(writer); + newMatrixNode = EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.path).toBe(PATH); + + matrixNode.contents.identifier = null; + matrixNode.contents.type = null; + matrixNode.contents.mode = null; + writer = new BER.Writer(); + matrixNode.encode(writer); + newMatrixNode = EmberLib.MatrixNode.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.contents.identifier == null).toBeTruthy(); + + writer = new BER.Writer(); + qMatrixNode.encode(writer); + newMatrixNode = EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.contents.identifier == null).toBeTruthy(); + + matrixNode.contents = null; + writer = new BER.Writer(); + matrixNode.encode(writer); + newMatrixNode = EmberLib.MatrixNode.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.contents == null).toBeTruthy(); + + qMatrixNode.contents = null; + writer = new BER.Writer(); + qMatrixNode.encode(writer); + newMatrixNode = EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.contents == null).toBeTruthy(); + }); + it("should throw an error if target is negative", () => { + try { + EmberLib.Matrix.validateConnection(matrixNode, -1, []); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should throw an error if source is negative", () => { + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [-1]); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should throw an error if target higher than max target", () => { + try { + EmberLib.Matrix.validateConnection(matrixNode, TARGETCOUNT, [0]); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should throw an error if target higher than max target", () => { + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [SOURCECOUNT]); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should throw an error if non-Linear Matrix without targets", () => { + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [0]); + throw new Error("Should not succeed"); + } + catch(e) { + matrixNode.contents.mode = EmberLib.MatrixMode.linear; + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + it("should throw an error if non-Linear Matrix without sources", () => { + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.targets = [0, 3]; + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [0]); + throw new Error("Should not succeed"); + } + catch(e) { + matrixNode.contents.mode = EmberLib.MatrixMode.linear; + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + it("should throw an error if non-Linear Matrix and not valid target", () => { + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.targets = [0, 3]; + matrixNode.sources = [0, 3]; + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + } + const min = matrixNode.getMinimal(true); + expect(min.sources).toBeDefined(); + try { + EmberLib.Matrix.validateConnection(matrixNode, 1, [0]); + throw new Error("Should not succeed"); + } + catch(e) { + matrixNode.contents.mode = EmberLib.MatrixMode.linear; + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should have getMinimal function", () => { + matrixNode.contents = null; + matrixNode.connections = null; + const min = matrixNode.getMinimal(true); + expect(min.number).toBe(matrixNode.getNumber()); + }); + it("should throw an error if non-Linear Matrix and not valid source", () => { + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.targets = [0, 3]; + matrixNode.sources = [0, 3]; + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [1]); + throw new Error("Should not succeed"); + } + catch(e) { + matrixNode.contents.mode = EmberLib.MatrixMode.linear; + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should not throw an error on valid non-linear connect", () => { + let error = null; + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.targets = [0, 3]; + matrixNode.sources = [0, 3]; + try { + EmberLib.Matrix.validateConnection(matrixNode, 0, [0]); + } + catch(e) { + error = e; + } + expect(error == null).toBeTruthy(); + }); + it("should not throw an error if can't decode MatrixContent", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.EMBER_SET); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.MatrixContents.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + }); + describe("MatrixUpdate", () => { + let matrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeEach(() => { + matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + }); + it("should update connections", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; - it("should handle errors in message", () => { - var ber = new BER.Reader(errorBuffer); - expect(() => ember.Root.decode(ber)).toThrow(errors.UnimplementedEmberTypeError); - }) + const newMatrixNode = new EmberLib.MatrixNode(0); + newMatrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + newMatrixNode.contents.identifier = "matrix"; + newMatrixNode.contents.description = "matrix"; + matrixNode.connections[0].sources = [1]; + newMatrixNode.connections = { + 0: matrixNode.connections[0], + 1: new EmberLib.MatrixConnection(1) + }; + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + expect(matrixNode.connections[1]).toBeDefined(); + matrixNode.connections = null; + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + expect(matrixNode.connections[1]).toBeDefined(); + }); + it("should ignore empty connections request", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + + const newMatrixNode = new EmberLib.MatrixNode(0); + newMatrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + newMatrixNode.contents.identifier = "matrix"; + newMatrixNode.contents.description = "matrix"; + newMatrixNode.connections = null; + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + expect(matrixNode.connections[0]).toBeDefined(); + }); + it("should throw error if invalid target inside new connections", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + + const newMatrixNode = new EmberLib.MatrixNode(0); + newMatrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + newMatrixNode.contents.identifier = "matrix"; + newMatrixNode.contents.description = "matrix"; + newMatrixNode.connections = { + 7: new EmberLib.MatrixConnection(7) + }; + try { + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + it("should not throw an error on valid non-linear connect", () => { + let error = null; + const newMatrixNode = new EmberLib.MatrixNode(0); + newMatrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + newMatrixNode.targets = [0, 3]; + newMatrixNode.sources = [0, 3]; + newMatrixNode.contents.identifier = "matrix"; + newMatrixNode.contents.description = "matrix"; + + + matrixNode.contents.mode = EmberLib.MatrixMode.nonLinear; + matrixNode.connections = null; + try { + EmberLib.Matrix.MatrixUpdate(matrixNode, newMatrixNode); + } + catch(e) { + error = e; + } + expect(error == null).toBeTruthy(); + expect(matrixNode.targets).toBeDefined(); + expect(matrixNode.targets.length).toBe(newMatrixNode.targets.length); + expect(matrixNode.sources.length).toBe(newMatrixNode.sources.length); + }); + }); + describe("disconnectSources", () => { + let matrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeEach(() => { + matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + }); + it("should generate the connection structure if not existent", () => { + EmberLib.Matrix.disconnectSources(matrixNode, 0, [1]); + }); + it("should disconnect existing connection", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + EmberLib.Matrix.connectSources(matrixNode, 0, [1]); + EmberLib.Matrix.connectSources(matrixNode, 1, [1]); + expect(matrixNode._numConnections).toBe(2); + EmberLib.Matrix.disconnectSources(matrixNode, 0, [1]); + expect(matrixNode.connections[0]).toBeDefined(); + expect(matrixNode.connections[0].sources.length).toBe(0); + expect(matrixNode._numConnections).toBe(1); + }); + it("should ignore disconnect with no source", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + EmberLib.Matrix.connectSources(matrixNode, 0, [1]); + expect(matrixNode._numConnections).toBe(1); + EmberLib.Matrix.disconnectSources(matrixNode, 0, null); + expect(matrixNode.connections[0]).toBeDefined(); + expect(matrixNode.connections[0].sources.length).toBe(1); + }); + it("should ignore disconnect with not connected source", () => { + matrixNode.connections = { + 0: new EmberLib.MatrixConnection(0) + }; + EmberLib.Matrix.connectSources(matrixNode, 0, [1]); + EmberLib.Matrix.connectSources(matrixNode, 1, [0]); + expect(matrixNode._numConnections).toBe(2); + EmberLib.Matrix.disconnectSources(matrixNode, 0, [0]); + expect(matrixNode.connections[0]).toBeDefined(); + expect(matrixNode.connections[0].sources.length).toBe(1); + expect(matrixNode._numConnections).toBe(2); + }); + }); + describe("decodeConnections", () => { + let matrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeEach(() => { + matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + }); + it("should generate the connection structure if not existent", () => { + const SOURCEID = 0; + EmberLib.Matrix.connectSources(matrixNode, 0, [SOURCEID]); + const writer = new BER.Writer(); + matrixNode.encodeConnections(writer); + const ber = new BER.Reader(writer.buffer); + const seq = ber.getSequence(BER.CONTEXT(5)); + const connections = EmberLib.Matrix.decodeConnections(seq); + expect(connections[0].sources).toBeDefined(); + expect(connections[0].sources.length).toBe(1); + expect(connections[0].sources[0]).toBe(SOURCEID); + }); + }); + describe("encodeConnections", () => { + it ("should ignore empty/null connections", () => { + const matrixNode = new EmberLib.MatrixNode(0); + matrixNode.connections = null; + const writer = new BER.Writer(); + matrixNode.encodeConnections(writer); + expect(writer.buffer.length).toBe(0); + }); + }); + describe("canConnect", () => { + let matrixNode; + const TARGETCOUNT = 5; + const SOURCECOUNT = 5; + beforeEach(() => { + matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.targetCount = TARGETCOUNT; + matrixNode.contents.sourceCount = SOURCECOUNT; + }); + it("should consider default type as 1toN", () => { + matrixNode.connections = null; + matrixNode.contents.type = null; + matrixNode.contents.maximumTotalConnects = 1; + const res = EmberLib.Matrix.canConnect(matrixNode, 0, [0,3]); + expect(res).toBeFalsy(); + }); + it("should return false if more than 1 source in 1toN", () => { + matrixNode.connections = null; + matrixNode.contents.maximumTotalConnects = 1; + const res = EmberLib.Matrix.canConnect(matrixNode, 0, [0,3]); + expect(res).toBeFalsy(); + }); + it("should always return true if NtoN and no limits", () => { + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.nToN, + EmberLib.MatrixMode.linear + ); + matrixNode.connections = null; + matrixNode.contents.maximumConnectsPerTarget = null; + matrixNode.contents.maximumTotalConnects = null; + const res = EmberLib.Matrix.canConnect(matrixNode, 0, [0,3]); + expect(res).toBeTruthy(); + }); + it("should check maximumTotalConnects in NtoN and reject on limit pass", () => { + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.nToN, + EmberLib.MatrixMode.linear + ); + matrixNode.contents.maximumConnectsPerTarget = null; + matrixNode.contents.maximumTotalConnects = 2; + EmberLib.Matrix.connectSources(matrixNode, 0, [1,2]); + const res = EmberLib.Matrix.canConnect(matrixNode, 1, [3]); + expect(res).toBeFalsy(); + }); + it("should check maximumTotalConnects in NtoN and accept if below limit", () => { + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.nToN, + EmberLib.MatrixMode.linear + ); + matrixNode.connections = null; + matrixNode.contents.maximumConnectsPerTarget = null; + matrixNode.contents.maximumTotalConnects = 2; + const res = EmberLib.Matrix.canConnect(matrixNode, 1, [3]); + expect(res).toBeTruthy(); + }); + it("should check locked connection", () => { + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.nToN, + EmberLib.MatrixMode.linear + ); + matrixNode.connections = null; + matrixNode.contents.maximumConnectsPerTarget = null; + matrixNode.contents.maximumTotalConnects = 2; + EmberLib.Matrix.connectSources(matrixNode, 0, [1]); + matrixNode.connections[0].lock(); + let res = EmberLib.Matrix.canConnect(matrixNode, 0, [3]); + expect(res).toBeFalsy(); + matrixNode.connections[0].unlock(); + res = EmberLib.Matrix.canConnect(matrixNode, 0, [3]); + expect(res).toBeTruthy(); + }); + }); + describe("Matrix Non-Linear", () => { + it("should have encoder / decoder", () => { + const PATH = "0.1.2"; + const matrixNode = new EmberLib.MatrixNode(0); + const qMatrixNode = new EmberLib.QualifiedMatrix(PATH); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + matrixNode.contents.gainParameterNumber = 4; + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.contents.maximumTotalConnects = 5; + matrixNode.contents.maximumConnectsPerTarget = 1; + matrixNode.contents.parametersLocation = "1.2.3"; + matrixNode.contents.schemaIdentifiers = "de.l-s-b.emberplus.schema1"; + matrixNode.contents.templateReference = "0.1.2.3"; + qMatrixNode.contents = matrixNode.contents; + matrixNode.targets = [0,3]; + qMatrixNode.targets = matrixNode.targets; + matrixNode.sources = [1,2]; + qMatrixNode.sources = matrixNode.sources; + let writer = new BER.Writer(); + matrixNode.encode(writer); + let newMatrixNode = EmberLib.Matrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.targets).toBeDefined(); + + writer = new BER.Writer(); + qMatrixNode.encode(writer); + newMatrixNode = EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.targets).toBeDefined(); + + + // Should support int + matrixNode.contents.parametersLocation = 123; + writer = new BER.Writer(); + matrixNode.encode(writer); + newMatrixNode = EmberLib.Matrix.decode(new BER.Reader(writer.buffer)); + expect(newMatrixNode.targets).toBeDefined(); + expect(newMatrixNode.contents.parametersLocation).toBe(matrixNode.contents.parametersLocation); + }); + it("should have connect function", () => { + const root = new EmberLib.Root(); + const matrixNode = new EmberLib.MatrixNode(0); + matrixNode.contents = new EmberLib.MatrixContents( + EmberLib.MatrixType.onetoN, + EmberLib.MatrixMode.nonLinear + ); + matrixNode.contents.identifier = "matrix"; + matrixNode.contents.description = "matrix"; + matrixNode.targets = [0,3]; + matrixNode.sources = [1,2]; + root.addChild(matrixNode); + const connect = matrixNode.connect({0: new EmberLib.MatrixConnection(0)}); + expect(connect).toBeDefined(); + }); + it("should throw an error if can't decode", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.APPLICATION(13)); + writer.startSequence(BER.CONTEXT(0)); + writer.writeInt(1); + writer.endSequence(); // BER.CONTEXT(0) + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.MatrixNode.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + }); + describe("Label", () => { + it ("should throw an error if it fails to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.APPLICATION(18)); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Label.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it ("should throw an error if no basePath", () => { + const label = new EmberLib.Label(null, "test"); + const writer = new BER.Writer(); + try { + label.encode(writer); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + it ("should throw an error if no description", () => { + const label = new EmberLib.Label("1.2.3", null); + const writer = new BER.Writer(); + try { + label.encode(writer); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + it ("should be able to encode/decode a valid label", () => { + const label = new EmberLib.Label("1.2.3", "primary"); + const writer = new BER.Writer(); + label.encode(writer); + const reader = new BER.Reader(writer.buffer); + const newLabel = EmberLib.Label.decode(reader); + expect(newLabel.description).toBe(label.description); + expect(newLabel.basePath).toBe(label.basePath); + }); + }) + }); + describe("Invocation", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Invocation.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Invocation.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("Should be able to encode even if no id", () => { + const invocation = new EmberLib.Invocation(); + const writer = new BER.Writer(); + invocation.encode(writer); + const i = EmberLib.Invocation.decode(new BER.Reader(writer.buffer)); + expect(i.id == null).toBeTruthy(); + }); + it("Should have a toJSON", () => { + const invocation = new EmberLib.Invocation(1, [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 10, "arg1" ) + ]); + let js = invocation.toJSON(); + expect(js.id).toBe(invocation.id); + expect(js.arguments.length).toBe(invocation.arguments.length); + invocation.arguments = null; + js = invocation.toJSON(); + expect(js.id).toBe(invocation.id); + expect(js.arguments).toBe(null); + }); + }); + describe("InvocationResult", () => { + it("should support all types of result", () => { + const invocationResult = new EmberLib.InvocationResult(); + invocationResult.invocationId = 100; + const valBuf = [0xa, 0x1, 0x2]; + invocationResult.setFailure(); + expect(invocationResult.success).toBeFalsy(); + invocationResult.setSuccess(); + expect(invocationResult.success).toBeTruthy(); + try { + invocationResult.setResult(new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1)); + throw new Error("should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidResultFormat).toBeTruthy(); + } + expect(invocationResult.toQualified()).toBe(invocationResult); + invocationResult.setResult([ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1), + new EmberLib.FunctionArgument(EmberLib.ParameterType.real, 1.1), + new EmberLib.FunctionArgument(EmberLib.ParameterType.string, "one"), + new EmberLib.FunctionArgument(EmberLib.ParameterType.boolean, false), + new EmberLib.FunctionArgument(EmberLib.ParameterType.octets, Buffer.from(valBuf)) + ]); + const writer = new BER.Writer(); + invocationResult.encode(writer); + const newInvocationRes = EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + expect(newInvocationRes.success).toBe(invocationResult.success); + expect(newInvocationRes.invocationId).toBe(invocationResult.invocationId); + expect(newInvocationRes.result.length).toBe(invocationResult.result.length); + expect(newInvocationRes.result[4].value.length).toBe(valBuf.length); + expect(newInvocationRes.result[4].value[0]).toBe(valBuf[0]); + }); + it("should be able to encode with not result, no success", () => { + const invocationResult = new EmberLib.InvocationResult(); + invocationResult.invocationId = 100; + invocationResult.result = null; + invocationResult.sucess = null; + const writer = new BER.Writer(); + invocationResult.encode(writer); + const newInvocationRes = EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + expect(newInvocationRes.result).not.toBeDefined(); + }); + it("should be able to encode with no invocationId", () => { + const invocationResult = new EmberLib.InvocationResult(); + invocationResult.invocationId = null; + invocationResult.result = null; + invocationResult.sucess = null; + const writer = new BER.Writer(); + invocationResult.encode(writer); + const newInvocationRes = EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + expect(newInvocationRes.invocationId == null).toBeTruthy(); + }); + it("should throw an error if can't decode", () => { + let writer = new BER.Writer(); + writer.startSequence(EmberLib.InvocationResult.BERID); + writer.startSequence(BER.CONTEXT(3)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + writer = new BER.Writer(); + writer.startSequence(EmberLib.InvocationResult.BERID); + writer.startSequence(BER.CONTEXT(2)); + writer.startSequence(BER.EMBER_SEQUENCE); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.InvocationResult.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + }); + describe("MatrixConnection", () => { + it("should have a decoder and throw error if can't decode", () => { + let writer = new BER.Writer(); + writer.startSequence(EmberLib.MatrixConnection.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.MatrixConnection.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should decode connection with no source", () => { + const matrixConnection = new EmberLib.MatrixConnection(0); + matrixConnection.sources = []; + const writer = new BER.Writer(); + matrixConnection.encode(writer); + const newMC = EmberLib.MatrixConnection.decode(new BER.Reader(writer.buffer)); + expect(newMC.sources).toBeDefined(); + expect(newMC.sources.length).toBe(0); + }); + it("should throw an error if invalid target", () => { + try { + new EmberLib.MatrixConnection("zero"); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidMatrixSignal).toBeTruthy(); + } + }); + }); + describe("QualifiedFunction", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedFunction.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedFunction.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + }); + describe("QualifiedMatrix", () => { + const PATH = "1.2.3"; + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedMatrix.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedMatrix.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("Should have a subscribe/unsubscribe function", () => { + const qMatrixNode = new EmberLib.QualifiedMatrix(PATH); + qMatrixNode.contents = new EmberLib.MatrixContents(); + const cb = function() {}; + let cmd = qMatrixNode.subscribe(cb); + expect(cmd).toBeDefined(); + expect(cmd instanceof EmberLib.Root).toBeTruthy(); + }); + }); + describe("QualifiedNode", () => { + const PATH = "0.1.2"; + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedNode.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedNode.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("Should return true to isNode() call", () => { + const qNode = new EmberLib.QualifiedNode(PATH); + expect(qNode.isNode()).toBeTruthy(); + }); + }); + describe("QualifiedParameter", () => { + const PATH = "0.1.2"; + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedParameter.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedParameter.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should update and ignore key starting with _", () => { + const NEW_VAL = 15; + const qp = new EmberLib.QualifiedParameter(PATH); + qp.contents = new EmberLib.ParameterContents(5, "integer"); + const dup = new EmberLib.QualifiedParameter(PATH); + dup.contents = new EmberLib.ParameterContents(NEW_VAL, "integer"); + dup.contents["_ignore"] = "test"; + qp.update(dup); + expect(qp.contents._ignore).not.toBeDefined(); + expect(qp.contents.value).toBe(NEW_VAL); + }); + it("Should return true to isParameter() call", () => { + const qNode = new EmberLib.QualifiedParameter(PATH); + expect(qNode.isParameter()).toBeTruthy(); + }); + it("should have setValue function", () => { + const qp = new EmberLib.QualifiedParameter(PATH); + const VALUE = 1; + qp.contents = new EmberLib.ParameterContents(VALUE, "integer"); + let NEW_VALUE = VALUE + 1; + let setVal = qp.setValue(NEW_VALUE); + let dup = setVal.getElementByPath(PATH); + expect(dup).toBeDefined(); + expect(dup.contents.value).toBe(NEW_VALUE); + NEW_VALUE = NEW_VALUE + 1; + setVal = qp.setValue(new EmberLib.ParameterContents(NEW_VALUE)); + expect(setVal.getElementByPath(PATH).contents.value).toBe(NEW_VALUE); + }); + it("should accept subscribers and have a function to update them", () => { + const qp = new EmberLib.QualifiedParameter(PATH); + const VALUE = 1; + qp.contents = new EmberLib.ParameterContents(VALUE, "integer"); + qp.contents.streamIdentifier = 12345; + let updatedValue = null; + const handleUpdate = function(param) { + updatedValue = param.contents.value; + } + qp.subscribe(handleUpdate); + qp.updateSubscribers(); + expect(updatedValue).toBe(VALUE); + }); + }); + describe("StreamDescription", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.StreamDescription.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.StreamDescription.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("Should have a toJSON", () => { + const streamDescriptor = new EmberLib.StreamDescription(); + streamDescriptor.format = EmberLib.StreamFormat.signedInt8; + const OFFSET = 4; + streamDescriptor.offset = OFFSET; + + let js = streamDescriptor.toJSON(); + expect(js).toBeDefined(); + expect(js.format).toBeDefined(); + expect(js.offset).toBe(OFFSET); + + streamDescriptor.format = null; + js = streamDescriptor.toJSON(); + expect(js).toBeDefined(); + expect(js.format).toBe(null); + expect(js.offset).toBe(OFFSET); + }); + }); + describe("StringIntegerCollection", () => { + it("should reject invalid value", () => { + const sic = new EmberLib.StringIntegerCollection(); + try { + sic.addEntry("test", 4); + } + catch(e) { + expect(e instanceof Errors.InvalidStringPair).toBeTruthy(); + } + }); + it("should have a toJSON", () => { + const KEY = "test"; + const VAL = 4; + const sic = new EmberLib.StringIntegerCollection(); + sic.addEntry("test", new EmberLib.StringIntegerPair(KEY, VAL)); + const js = sic.toJSON(); + expect(js).toBeDefined(); + expect(js.length).toBe(1); + }); + }); + describe("StringIntegerPair", () => { + it("should throw an error if trying to encode invalid key/value", () => { + const sp = new EmberLib.StringIntegerPair(); + const writer = new BER.Writer(); + try { + sp.encode(writer); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.InvalidEmberNode).toBeTruthy(); + } + }); + }); + describe("rootDecode", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + try { + EmberLib.rootDecode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + }); + describe("QualifiedTemplate", () => { + const PATH = "0.1.2"; + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.QualifiedTemplate.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.QualifiedTemplate.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should have encoder/decoder", () => { + const qp = new EmberLib.QualifiedTemplate(PATH, new EmberLib.Node(0)); + let writer = new BER.Writer(); + qp.encode(writer); + let dup = EmberLib.QualifiedTemplate.decode(new BER.Reader(writer.buffer)); + expect(dup).toBeDefined(); + expect(dup.getPath()).toBe(PATH); + expect(dup.element instanceof EmberLib.Node).toBeTruthy(); + + const DESCRIPTION = "description"; + qp.description = DESCRIPTION; + writer = new BER.Writer(); + qp.encode(writer); + dup = EmberLib.QualifiedTemplate.decode(new BER.Reader(writer.buffer)); + expect(dup).toBeDefined(); + expect(dup.getPath()).toBe(PATH); + expect(dup.element instanceof EmberLib.Node).toBeTruthy(); + expect(dup.description).toBe(DESCRIPTION); + }); + + it("Should return true to isTemplate() call", () => { + const qp = new EmberLib.QualifiedTemplate(PATH, new EmberLib.Node(0)); + expect(qp.isTemplate()).toBeTruthy(); + }); + }); + describe("Template", () => { + it("Should throw an error if unable to decode", () => { + const writer = new BER.Writer(); + writer.startSequence(EmberLib.Template.BERID); + writer.startSequence(BER.CONTEXT(99)); + writer.endSequence(); + writer.endSequence(); + try { + EmberLib.Template.decode(new BER.Reader(writer.buffer)); + throw new Error("Should not succeed"); + } + catch(e) { + expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + } + }); + it("should have encoder/decoder", () => { + const qp = new EmberLib.Template(10, new EmberLib.Node(0)); + let writer = new BER.Writer(); + qp.encode(writer); + let dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup).toBeDefined(); + expect(dup.getNumber()).toBe(10); + + const DESCRIPTION = "description"; + qp.description = DESCRIPTION; + writer = new BER.Writer(); + qp.encode(writer); + dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup).toBeDefined(); + expect(dup.element instanceof EmberLib.Node).toBeTruthy(); + expect(dup.description).toBe(DESCRIPTION); + + writer = new BER.Writer(); + qp.element = new EmberLib.Function(0, null); + qp.encode(writer); + dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup.element instanceof EmberLib.Function).toBeTruthy(); + + writer = new BER.Writer(); + qp.element = new EmberLib.Parameter(0); + qp.encode(writer); + dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup.element instanceof EmberLib.Parameter).toBeTruthy(); + + writer = new BER.Writer(); + qp.element = new EmberLib.MatrixNode(0); + qp.encode(writer); + dup = EmberLib.Template.decode(new BER.Reader(writer.buffer)); + expect(dup.element instanceof EmberLib.MatrixNode).toBeTruthy(); + + }); + + it("Should return true to isTemplate() call", () => { + const qp = new EmberLib.Template(10, new EmberLib.Node(0)); + expect(qp.isTemplate()).toBeTruthy(); + }); + + it("Should have toQualified function", () => { + const template = new EmberLib.Template(10, new EmberLib.Node(0)); + const qp = template.toQualified(); + expect(qp.isTemplate()).toBeTruthy(); + }); + + it("Should have update function", () => { + const template = new EmberLib.Template(10, new EmberLib.Node(0)); + const DUP_NUM = 5; + const dup = new EmberLib.Template(10, new EmberLib.Node(DUP_NUM)); + template.update(dup); + expect(template.element.getNumber()).toBe(DUP_NUM); + }); + }); }); diff --git a/test/EmberClient.test.js b/test/EmberClient.test.js new file mode 100755 index 0000000..66f66cd --- /dev/null +++ b/test/EmberClient.test.js @@ -0,0 +1,66 @@ +const fs = require("fs"); +const {EmberServer} = require("../EmberServer"); +const Decoder = require('../EmberLib').DecodeBuffer; +const EmberClient = require("../EmberClient"); + +const HOST = "127.0.0.1"; +const PORT = 9010; + +function getRoot() { + return new Promise((resolve, reject) => { + fs.readFile("test/embrionix.ember", (e, data) => { + if (e) { + reject(e); + } + try { + resolve(Decoder(data)); + } + catch(error) { + reject(error); + } + }); + }); +} + +let server; +describe("EmberClient", () => { + + beforeEach(() => { + return getRoot() + .then(root => { + server = new EmberServer(HOST, PORT, root); + //server._debug = true; + server.on("error", e => { + console.log("Server Error", e); + }); + server.on("clientError", info => { + console.log("clientError", info.error); + }); + server.on("event", event => { + console.log("Event: " + event); + }); + return server.listen() + }); + }); + + afterEach(() => { + return server.close(); + }); + + it("should be able to fetch a specific node", () => { + const client = new EmberClient(HOST, PORT); + const PATH = "0.1"; + client.on("error", () => { + // ignore + }); + return Promise.resolve() + .then(() => client.connect()) + .then(() => client.getDirectory()) + .then(() => client.getElementByPath(PATH)) + .then(node => { + expect(node).toBeDefined(); + expect(node.getPath()).toBe(PATH); + return client.disconnect(); + }); + }); +}); diff --git a/test/Server.test.js b/test/Server.test.js index ed44416..0ec6105 100755 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -1,211 +1,1051 @@ const expect = require("expect"); -const TreeServer = require("../server"); -const DeviceTree = require("../").DeviceTree; -const ember = require("../ember"); +const {EmberServer, ServerEvents} = require("../EmberServer"); +const EmberClient = require("../EmberClient"); +const EmberLib = require("../EmberLib"); const {jsonRoot} = require("./utils"); +const MatrixHandlers = require("../EmberServer/MatrixHandlers"); +const Errors = require("../Errors"); const LOCALHOST = "127.0.0.1"; -const PORT = 9009; - -const wait = function(t) { - return new Promise(resolve => { - setTimeout(resolve, t); - }); -} +let PORT = 9009; describe("server", function() { - describe("JSONtoTree", function() { let jsonTree; - beforeAll(function() { + beforeEach(function() { jsonTree = jsonRoot(); }); it("should generate an ember tree from json", function() { - const root = TreeServer.JSONtoTree(jsonTree); + const root = EmberServer.JSONtoTree(jsonTree); + // JSONtoTree will modify the json object. + jsonTree = jsonRoot(); expect(root).toBeDefined(); expect(root.elements).toBeDefined(); - expect(root.elements.length).toBe(1); - console.log("root", root.elements[0].contents); - expect(root.elements[0].contents.identifier).toBe("scoreMaster"); - expect(root.elements[0].children.length).toBe(2); + expect(root.elements.size).toBe(jsonTree.length); + expect(root.getElementByNumber(0).contents.identifier).toBe("scoreMaster"); + expect(root.getElementByNumber(0).elements.size).toBe(jsonTree[0].children.length); + expect(root.getElementByNumber(1).contents.streamDescriptor instanceof EmberLib.StreamDescription).toBeTruthy(); + expect(root.getElementByNumber(1).contents.streamDescriptor.offset).toBe(jsonTree[1].streamDescriptor.offset); + }); + it("should throw an error if invalid matrix mode", function() { + jsonTree[0].children[1].children[0].mode = "invalid"; + let error; + try { + const root = EmberServer.JSONtoTree(jsonTree); + } + catch(e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof Errors.InvalidEmberNode).toBeDefined(); + }); + it("should support matrix type nToN nonLinear", function() { + jsonTree[0].children[1].children[0].type = "nToN"; + jsonTree[0].children[1].children[0].mode = "nonLinear"; + jsonTree[0].children[1].children[0].maximumConnectsPerTarget = 10; + jsonTree[0].children[1].children[0].maximumTotalConnects = 100; + const root = EmberServer.JSONtoTree(jsonTree); + const matrix = root.getElementByPath("0.1.0"); + expect(matrix).toBeDefined(); + expect(matrix.contents.maximumConnectsPerTarget).toBe(jsonTree[0].children[1].children[0].maximumConnectsPerTarget); + expect(matrix.contents.maximumTotalConnects).toBe(jsonTree[0].children[1].children[0].maximumTotalConnects); + expect(matrix.contents.type).toBe(EmberLib.MatrixType.nToN); + const jMatrix = matrix.toJSON(); + expect(jMatrix.type).toBeDefined(); + expect(jMatrix.mode).toBeDefined(); + }); + it("should support matrix type oneToOne", function() { + jsonTree[0].children[1].children[0].type = "oneToOne"; + const root = EmberServer.JSONtoTree(jsonTree); + const matrix = root.getElementByPath("0.1.0"); + expect(matrix).toBeDefined(); + expect(matrix.contents.type).toBe(EmberLib.MatrixType.oneToOne); + }); + it("should throw an error if invalid matrix type", function() { + jsonTree[0].children[1].children[0].type = "invalid"; + let error; + try { + const root = EmberServer.JSONtoTree(jsonTree); + } + catch(e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof Errors.InvalidEmberNode).toBeDefined(); + }); + it("should generate a matrix with a valid toJSON", function() { + const root = EmberServer.JSONtoTree(jsonTree); + const matrix = root.getElementByPath("0.1.0"); + matrix.connectSources(0, [0]); + matrix.connectSources(1, [1]); + const jMatrix = matrix.toJSON(); + expect(jMatrix.type).toBeDefined(); + expect(jMatrix.type).toBe(matrix.contents.type.value); + expect(jMatrix.mode).toBeDefined(); + expect(jMatrix.mode).toBe(matrix.contents.mode.value); + expect(jMatrix.connections[0].sources.length).toBe(1); + expect(jMatrix.connections[0].sources[0]).toBe(0); }); }); describe("Server - Client communication", function() { - let server,client; - beforeAll(function() { + let server,client,jsonTree; + beforeEach(() => { jsonTree = jsonRoot(); - const root = TreeServer.JSONtoTree(jsonTree); - server = new TreeServer(LOCALHOST, PORT, root); - //server._debug = true; - return server.listen().then(() => { - console.log("server listening"); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.on("error", e => { + // ignore + }); + server.on("clientError", e => { + // ignore }); + //server._debug = true; + return server.listen(); + }); + afterEach(() => { + return server.close(); }); - afterAll(function() { - client.disconnect(); - server.close(); - }) - it("should receive and decode the full tree", function () { - client = new DeviceTree(LOCALHOST, PORT); + it("should receive and decode the full tree", () => { + client = new EmberClient(LOCALHOST, PORT); //client._debug = true; return Promise.resolve() .then(() => client.connect()) - .then(() => { - console.log("client connected"); - return client.getDirectory(); - }) + .then(() => client.getDirectory()) .then(() => { expect(client.root).toBeDefined(); expect(client.root.elements).toBeDefined(); - expect(client.root.elements.length).toBe(1); - expect(client.root.elements[0].contents.identifier).toBe("scoreMaster"); - return client.getDirectory(client.root.elements[0]); + expect(client.root.elements.size).toBe(jsonTree.length); + expect(client.root.getElementByNumber(0).contents.identifier).toBe("scoreMaster"); + return client.getDirectory(client.root.getElementByNumber(0)); }) .then(() => { - expect(client.root.elements[0].children.length).toBe(2); - return client.getDirectory(client.root.elements[0].children[0]); + expect(client.root.getElementByNumber(0).elements.size).toBe(jsonTree[0].children.length); + return client.getDirectory(client.root.getElementByPath("0.0")); }) .then(() => { - expect(client.root.elements[0].children[0].children.length).toBe(4); - expect(client.root.elements[0].children[0].children[3].contents.identifier).toBe("author"); - // Issue #33 TreeServer.handleGetDirectory does not subscribe to child parameters + expect(client.root.getElementByPath("0.0").elements.size).toBe(4); + expect(client.root.getElementByPath("0.0.3").contents.identifier).toBe("author"); + // Issue #33 EmberServer.handleGetDirectory does not subscribe to child parameters expect(server.subscribers["0.0.0"]).toBeDefined(); - // Keepalive - return client.disconnect(); + return client.disconnect(); }); }); - it("should be able to modify a parameter", () => { - client = new DeviceTree(LOCALHOST, PORT); + it("should be able to modify a parameter", async () => { + client = new EmberClient(LOCALHOST, PORT); + await client.connect() + await client.getDirectory(); + await client.getElementByPath("0.0.1"); + expect(server.tree.getElementByPath("0.0.1").contents.value).not.toBe("gdnet"); + await client.setValue(client.root.getElementByPath("0.0.1"), "gdnet"); + expect(server.tree.getElementByPath("0.0.1").contents.value).toBe("gdnet"); + console.log("result", server.tree.getElementByPath("0.0.1").contents.value) + return client.disconnect().then(() => { console.log("disconnected")}); + }); + + it("should be able to call a function with parameters", () => { + client = new EmberClient(LOCALHOST, PORT); //client._debug = true; - return Promise.resolve() - .then(() => client.connect()) + return client.connect() + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("0.2")) .then(() => { - return client.getDirectory(); + const func = client.root.getElementByPath("0.2"); + return client.invokeFunction(func, [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1), + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 7) + ]); }) - .then(() => client.expand(client.root.elements[0])) + .then(result => { + expect(result).toBeDefined(); + expect(result.result).toBeDefined(); + expect(result.result.length).toBe(1); + expect(result.result[0].value).toBe(8); + return client.disconnect(); + }); + }); + + it("should be able to get child with client.getElement", function() { + client = new EmberClient(LOCALHOST, PORT); + return Promise.resolve() + .then(() => client.connect()) + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("scoreMaster/identity/product")) + .then(() => client.getElementByPath("scoreMaster/router/labels/group 1")) + .then(() => client.disconnect()); + }); + it("should be able to get child with getElementByPath", function() { + client = new EmberClient(LOCALHOST, PORT); + return Promise.resolve() + .then(() => client.connect()) + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("scoreMaster/identity/product")) + .then(() => client.getElementByPath("scoreMaster/router/labels/group 1")) + .then(() => client.disconnect()); + }); + it("should throw an error if getElementByPath for unknown path", function() { + client = new EmberClient(LOCALHOST, PORT); + return Promise.resolve() + .then(() => client.connect()) + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("scoreMaster/router/labels/group")) .then(() => { - expect(server.tree.elements[0].children[0].children[1].contents.value).not.toBe("gdnet"); - return client.setValue(client.root.elements[0].children[0].children[1], "gdnet"); + throw new Error("Should not succeed"); }) - .then(() => { - expect(server.tree.elements[0].children[0].children[1].contents.value).toBe("gdnet"); - return client.disconnect(); + .catch(e => { + expect(e.message).toMatch(/Failed path discovery/); + return client.disconnect(); }); }); it("should be able to make a matrix connection", () => { - client = new DeviceTree(LOCALHOST, PORT); - //client._debug = true; + client = new EmberClient(LOCALHOST, PORT); + return Promise.resolve() + .then(() => client.connect()) + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("0.1.0")) + .then(matrix => client.matrixConnect(matrix, 0, [1])) + .then(matrix => client.getElementByPath(matrix.getPath())) + .then(matrix => { + expect(matrix.connections['0'].sources).toBeDefined(); + expect(matrix.connections['0'].sources.length).toBe(1); + expect(matrix.connections['0'].sources[0]).toBe(1); + }) + .then(() => client.disconnect()); + }); + it("should generate events on command and matrix connection", () => { + client = new EmberClient(LOCALHOST, PORT); + let count = 0; + let receivedEvent = null; + const eventHandler = event => { + count++; + receivedEvent = event; + } return Promise.resolve() .then(() => client.connect()) .then(() => { + count = 0; + server.on("event", eventHandler); return client.getDirectory(); }) - .then(() => client.expand(client.root.elements[0])) .then(() => { - console.log(client.root.elements[0].children[1].children[0]); - const matrix = client.root.elements[0].children[1].children[0]; - const connections = {} - const target0Connection = new ember.MatrixConnection(0); - target0Connection.operation = ember.MatrixOperation.connect; - target0Connection.setSources([1]); // connect with src 1 - connections[0] = target0Connection; - const p = new Promise(resolve => { - client.on("value-change", node => { - resolve(node); - }); - }); - client.client.sendBERNode(matrix.connect(connections)); - return p; - }) - .then(node => { - console.log(client.root.elements[0].children[1].children[0]); - expect(client.root.elements[0].children[1].children[0].connections['0'].sources).toBeDefined(); - expect(client.root.elements[0].children[1].children[0].connections['0'].sources.length).toBe(1); - expect(client.root.elements[0].children[1].children[0].connections['0'].sources[0]).toBe(1); - return client.disconnect(); - }); - }); - - it("should be able to get child with getNodeByPath", function() { - //server._debug = true; - client = new DeviceTree(LOCALHOST, PORT); - //client._debug = true; - //client._debug = true; - return Promise.resolve() - .then(() => client.connect()) + expect(count).toBe(1); + expect(receivedEvent.type).toBe(ServerEvents.Types.GETDIRECTORY); + return client.getElementByPath("0.1.0"); + }) + .then(matrix => { + count = 0; + return client.matrixConnect(matrix, 0, [1]); + }) .then(() => { - console.log("client connected"); - return client.getDirectory(); - }) - .then(() => { - return new Promise((resolve, reject) => { - client.root.getNodeByPath(client.client, ["scoreMaster", "identity", "product"], (err, child) => { - if (err) { reject(err) } - else { - resolve(child); - } - }); - }); + expect(count).toBe(1); + expect(receivedEvent.type).toBe(ServerEvents.Types.MATRIX_CONNECTION); + }) + .then(() => { + count = 0; + const func = client.root.getElementByPath("0.2"); + return client.invokeFunction(func, [ + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 1), + new EmberLib.FunctionArgument(EmberLib.ParameterType.integer, 7) + ]); + }) + .then(() => { + expect(count).toBe(1); + expect(receivedEvent.type).toBe(ServerEvents.Types.INVOKE); }) - .then(child => { - console.log(child); - }) - .then(() => { - return new Promise((resolve, reject) => { - client.root.getNodeByPath(client.client, ["scoreMaster", "router", "labels"], (err, child) => { - if (err) { reject(err) } - else { - resolve(child); - } - }); + .then(() => client.getElementByPath("0.0.2")) + .then(parameter => { + server._subscribe = server.subscribe; + let _resolve; + const p = new Promise((resolve) => { + _resolve = resolve; }); + server.subscribe = (c,e) => { + server._subscribe(c,e); + _resolve(); + }; + count = 0; + return client.subscribe(parameter).then(() => (p)) }) - .then(child => { - console.log(child); - client.disconnect(); + .then(() => { + expect(count).toBe(1); + expect(receivedEvent.type).toBe(ServerEvents.Types.SUBSCRIBE); + }) + .then(() => { + server.off("event", eventHandler); + }) + .then(() => client.disconnect()); + }); + }); + describe("Matrix Connect", function() { + let jsonTree; + let server; + beforeEach(function() { + jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + }); + afterEach(() => { + return server.close(); + }); + it("should verify if connection allowed in 1-to-N", function() { + let disconnectCount = 0; + const handleDisconnect = () => { + disconnectCount++; + } + server.on("matrix-disconnect", handleDisconnect.bind(this)); + const matrix = server.tree.getElementByPath("0.1.0"); + let connection = new EmberLib.MatrixConnection(0); + connection.setSources([1]); + connection.operation = EmberLib.MatrixOperation.connect; + let res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + matrix.setSources(0, [0]); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + connection.operation = EmberLib.MatrixOperation.absolute; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + // We can't connect. But server will disconnect existing source and connect new one. + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); + expect(matrix.connections[0].sources[0]).toBe(1); + expect(disconnectCount).toBe(1); + // But if connecting same source and dest this is a disconnect. But not possible in 1toN. + // instead connect with defaultSource or do nothing + const matrixHandlers = new MatrixHandlers(server); + matrixHandlers.getDisconnectSource(matrix, 0); + matrix.defaultSources[0].contents.value = 222; + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); + expect(disconnectCount).toBe(2); + expect(matrix.connections[0].sources[0]).toBe(222); + matrix.setSources(0, [0]); + connection = new EmberLib.MatrixConnection(1); + connection.operation = EmberLib.MatrixOperation.absolute; + connection.setSources([1]); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + }); + it("should verify if connection allowed in 1-to-1", function() { + const matrix = server.tree.getElementByPath("0.1.0"); + let disconnectCount = 0; + const handleDisconnect = () => { + disconnectCount++; + } + server.on("matrix-disconnect", handleDisconnect.bind(this)); + matrix.contents.type = EmberLib.MatrixType.oneToOne; + const connection = new EmberLib.MatrixConnection(0); + connection.setSources([1]); + connection.operation = EmberLib.MatrixOperation.connect; + let res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + matrix.setSources(0, [0]); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + // We can't connect but in 1-on-1 server should disconnect existing source and connect new one. + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); + expect(matrix.connections[0].sources[0]).toBe(1); + expect(disconnectCount).toBe(1); + // But if connecting same source and dest. just disconnect and do not reconnect. + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); + expect(disconnectCount).toBe(2); + connection.operation = EmberLib.MatrixOperation.absolute; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + matrix.setSources(0, []); + matrix.setSources(1, [1]); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + server.off("matrix-disconnect", handleDisconnect); + }); + it("should disconnect if trying to connect same source and target in 1-to-1", function() { + const matrix = server.tree.getElementByPath("0.1.0"); + let disconnectCount = 0; + const handleDisconnect = () => { + disconnectCount++; + } + server.on("matrix-disconnect", handleDisconnect.bind(this)); + matrix.contents.type = EmberLib.MatrixType.oneToOne; + matrix.setSources(0, [1]); + const connection = new EmberLib.MatrixConnection(0); + connection.setSources([1]); + connection.operation = EmberLib.MatrixOperation.connect; + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); + expect(matrix.connections[0].sources.length).toBe(0); + expect(disconnectCount).toBe(1); + }); + it("should be able to lock a connection", function() { + const matrix = server.tree.getElementByPath("0.1.0"); + let disconnectCount = 0; + const handleDisconnect = () => { + disconnectCount++; + } + server.on("matrix-disconnect", handleDisconnect.bind(this)); + matrix.contents.type = EmberLib.MatrixType.oneToOne; + matrix.setSources(0, [1]); + matrix.connections[0].lock(); + const connection = new EmberLib.MatrixConnection(0); + connection.setSources([0]); + connection.operation = EmberLib.MatrixOperation.connect; + server._handlers.handleMatrixConnections(null, matrix, {0: connection}); + expect(matrix.connections[0].sources.length).toBe(1); + expect(matrix.connections[0].sources[0]).toBe(1); + expect(disconnectCount).toBe(0); + }); + it("should verify if connection allowed in N-to-N", function() { + const matrix = server.tree.getElementByPath("0.1.0"); + matrix.contents.type = EmberLib.MatrixType.nToN; + matrix.contents.maximumTotalConnects = 2; + matrix.setSources(0, [0,1]); + + const connection = new EmberLib.MatrixConnection(0); + connection.setSources([2]); + connection.operation = EmberLib.MatrixOperation.connect; + let res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + + matrix.setSources(2, [2]); + matrix.setSources(1, [1]); + matrix.setSources(0, []); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + + matrix.setSources(1, []); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + + matrix.setSources(0, [1,2]); + matrix.setSources(1, []); + connection.operation = EmberLib.MatrixOperation.absolute; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + + + matrix.contents.maximumTotalConnects = 20; + matrix.contents.maximumConnectsPerTarget = 1; + + matrix.setSources(2, [2]); + matrix.setSources(1, [1]); + matrix.setSources(0, [0]); + connection.setSources([2]); + connection.operation = EmberLib.MatrixOperation.connect; + + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeFalsy(); + + matrix.setSources(0, []); + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + + matrix.setSources(0, [0]); + connection.operation = EmberLib.MatrixOperation.absolute; + res = matrix.canConnect(connection.target,connection.sources,connection.operation); + expect(res).toBeTruthy(); + + }); + it("should return modified answer on absolute connect", function() { + let client; + server.on("error", () => { + // ignore + }); + server.on("clientError", () => { + // ignore + }); + //server._debug = true; + return server.listen() + .then(() => { + client = new EmberClient(LOCALHOST, PORT); + return Promise.resolve() + }) + .then(() => client.connect()) + .then(() => client.getDirectory()) + .then(() => client.getElementByPath("0.1.0")) + .then(matrix => client.matrixSetConnection(matrix, 0, [1])) + .then(result => { + expect(result).toBeDefined(); + expect(result.connections).toBeDefined(); + expect(result.connections[0]).toBeDefined(); + expect(result.connections[0].disposition).toBe(EmberLib.MatrixDisposition.modified); + return client.disconnect(); }); - }); - it("should be able to get child with tree.getNodeByPath", function() { - //server._debug = true; - client = new DeviceTree(LOCALHOST, PORT); - //client._debug = true; + }); + }); + describe("Parameters subscribe/unsubscribe", function( ){ + let jsonTree; + let server; + beforeEach(function() { + jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.on("error", () => { + // ignore + }); + server.on("clientError", () => { + // ignore + }); + return server.listen(); + }); + afterEach(function() { + return server.close(); + }); + it("should not auto subscribe stream parameter", function() { + const parameter = server.tree.getElementByPath("0.0.2"); + expect(parameter.isStream()).toBeTruthy(); + expect(server.subscribers["0.0.2"]).not.toBeDefined(); + }); + it("should be able subscribe to parameter changes", function() { + const client = new EmberClient(LOCALHOST, PORT); + const cb = () => { + return "updated"; + } //client._debug = true; return Promise.resolve() .then(() => client.connect()) .then(() => { - console.log("client connected"); return client.getDirectory(); }) - .then(() => client.getNodeByPath("scoreMaster/identity/product")) - .then(child => { - console.log(child); - return client.getNodeByPath("scoreMaster/router/labels/group 1"); - }) - .then(child => { - console.log("router/labels", child); - client.disconnect(); - }); - }); - it("should throw an erro if getNodeByPath for unknown path", function() { - //server._debug = true; - client = new DeviceTree(LOCALHOST, PORT); - return Promise.resolve() - .then(() => client.connect()) - .then(() => { - console.log("client connected"); - return client.getDirectory(); + .then(() => client.getElementByPath("0.0.2")) + .then(parameter => { + expect(server.subscribers["0.0.2"]).not.toBeDefined(); + expect(parameter._subscribers).toBeDefined(); + expect(parameter._subscribers.size).toBe(0); + server._subscribe = server.subscribe; + let _resolve; + const p = new Promise(resolve => { + _resolve = resolve; + }); + server.subscribe = (c,e) => { + server._subscribe(c,e); + _resolve(); + }; + return client.subscribe(parameter, cb).then(() => (p)) + }) + .then(() => { + expect(server.subscribers["0.0.2"]).toBeDefined(); + expect(server.subscribers["0.0.2"].size).toBe(1); + return client.getElementByPath("0.0.2"); }) - .then(() => client.getNodeByPath("scoreMaster/router/labels/group 1")) - .then(child => { - console.log("router/labels", child); - throw new Error("Should not succeed"); - }) - .catch(e => { - client.disconnect(); - console.log(e); - expect(e.message).toMatch(/timeout/); - }); - }); + .then(parameter => { + expect(parameter._subscribers).toBeDefined(); + expect(parameter._subscribers.size).toBe(1); + server._unsubscribe = server.unsubscribe; + let _resolve; + const p = new Promise(resolve => { + _resolve = resolve; + }); + server.unsubscribe = (c,e) => { + server._unsubscribe(c,e); + _resolve(); + }; + return client.unsubscribe(parameter, cb).then(() => (p)) + }) + .then(() => { + expect(server.subscribers["0.0.2"]).toBeDefined(); + return client.getElementByPath("0.0.2"); + }) + .then(parameter => { + expect(server.subscribers["0.0.2"]).toBeDefined(); + expect(server.subscribers["0.0.2"].size).toBe(0); + expect(parameter._subscribers).toBeDefined(); + expect(parameter._subscribers.size).toBe(0); + }) + .then(() => client.disconnect()); + }); + }); + describe("Handlers", () => { + let jsonTree; + let server; + beforeEach(function() { + jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.on("error", () => { + //ignore + }); + server.on("clientError", () => { + //ignore + }); + return server.listen(); + }); + afterEach(function() { + return server.close(); + }); + it("Should through an error if can't process request", () => { + const root = new EmberLib.Root(); + root.addElement(new EmberLib.Node(0)); + let error; + const errorHandler = function(e) { + error = e; + } + server.on("error", errorHandler); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + server.handleRoot(client._client, root); + expect(error instanceof Errors.InvalidRequesrFormat); + client.disconnect(); + }); + }); + it("should ignore empty or null tree", () => { + const root = new EmberLib.Root(); + let error; + try { + server.handleRoot(null, root); + } + catch(e) { + error = e; + } + expect(error).not.toBeDefined(); + }); + it("should generate responses which include children", () => { + const node = server.tree.getElementByNumber(0); + server.getResponse(node); + expect(node.getChildren().length > 0).toBeTruthy(); + }); + it("Should update parameter value if new parameter value received", () => { + const root = new EmberLib.Root(); + const parameter = new EmberLib.Parameter(2); + const VALUE = "3.4.5"; + parameter.contents = new EmberLib.ParameterContents(VALUE, "string"); + root.addElement(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(0)); + root.getElementByPath("0.0").addChild(parameter); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + server.handleRoot(client._client, root); + const res = server.tree.getElementByPath("0.0.2"); + expect(res.contents.value).toBe(VALUE); + return client.disconnect(); + }); + }); + it("Should throw an error if element not found during request process", () => { + const root = new EmberLib.Root(); + const parameter = new EmberLib.Parameter(99); + const VALUE = "3.4.5"; + parameter.contents = new EmberLib.ParameterContents(VALUE, "string"); + root.addElement(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(0)); + root.getElementByPath("0.0").addChild(parameter); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + let count = 0; + server.handleError = () => { + count++; + } + server.handleRoot(client._client, root); + expect(count).toBe(1); + return client.disconnect(); + }); + }); + it("Should throw an error if element contains null child", () => { + const root = new EmberLib.Root(); + const node = new EmberLib.Node(0); + root.addElement(node); + node.elements = new Map(); + node.elements.set(0, null); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + let count = 0; + server.handleError = () => { + count++; + } + server.handleRoot(client._client, root); + expect(count).toBe(1); + return client.disconnect(); + }); + }); + it("should handle commands embedded in Node", () => { + const root = new EmberLib.Root(); + const node = new EmberLib.Node(0); + root.addElement(node); + node.elements = new Map(); + node.elements.set(EmberLib.COMMAND_GETDIRECTORYGETDIRECTORY, new EmberLib.Command(EmberLib.COMMAND_GETDIRECTORYGETDIRECTORY)); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + let count = 0; + server._handlers.handleCommand = () => { + count++; + } + server.handleRoot(client._client, root); + expect(count).toBe(1); + return client.disconnect(); + }); + }); + it("should catch unknown commands", () => { + const command = new EmberLib.Command(99); + let count = 0; + server.on("error", e => { + expect(e instanceof Errors.InvalidCommand); + count++; + }); + server._handlers.handleCommand(null, new EmberLib.Root(), command); + expect(count).toBe(1); + }); + it("should catch invalid node with no number", () => { + const node = new EmberLib.Node(99); + node.number = null; + let count = 0; + server.on("error", e => { + expect(e instanceof Errors.MissingElementNumber); + count++; + }); + server._handlers.handleNode(null, node); + expect(count).toBe(1); + }); + it("should handle matrix connections embedded in Node", () => { + const root = new EmberLib.Root(); + const node = new EmberLib.Node(0); + root.addElement(node); + const matrix = new EmberLib.MatrixNode(0); + matrix.connections = [ + new EmberLib.MatrixConnection(0) + ]; + node.elements = new Map(); + node.elements.set(0, matrix); + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + let count = 0; + server._handlers.handleMatrixConnections = () => { + count++; + } + server.handleRoot(client._client, root); + expect(count).toBe(1); + return client.disconnect(); + }); + }); + it("should catch function invocation errors and set success to false", () => { + const client = new EmberClient(LOCALHOST, PORT); + return client.connect() + .then(() => { + const root = new EmberLib.Root(); + const func = new EmberLib.Function(0, () => { throw Error("function error")}); + root.addElement(func); + server.tree = root; + return client.invokeFunction(func, []); + }) + .then(result => { + expect(result).toBeDefined(); + expect(result.success).toBeFalsy(); + }) + .then(() => client.disconnect()); + }); + it("should catch invoke to non function", () => { + const client = new EmberClient(LOCALHOST, PORT); + let result; + client.sendBERNode = function(res) { + result = res; + } + const root = new EmberLib.Root(); + const func = new EmberLib.Node(0); + root.addElement(func); + server.tree = root; + const command = EmberLib.Command.getInvocationCommand(new EmberLib.Invocation(1, [])); + server._handlers.handleInvoke(client, func, command); + expect(result).toBeDefined(); + expect(result.success).toBeFalsy(); + return client.disconnect(); + }); + }); + describe("Matrix", () => { + let jsonTree; + const MATRIX_PATH = "0.1.0"; + /** @type {EmberServer} */ + let server; + beforeEach(function() { + jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.on("error", () => { + //ignore + }); + server.on("clientError", () => { + //ignore + }); + return server.listen(); + }); + afterEach(function() { + return server.close(); + }); + it("should generate connections structure if none provided when calling JSONtoStree", () => { + const js = jsonRoot(); + js[0].children[1].children[0].connections = null; + const tree = EmberServer.JSONtoTree(js); + const matrix = tree.getElementByPath(MATRIX_PATH); + expect(matrix.connections).toBeDefined(); + for(let i = 0; i < matrix.contents.targetCount; i++) { + expect(matrix.connections[i]).toBeDefined(); + expect(matrix.connections[i].target).toBe(i); + } + }); + it("should have a matrixConnect function", () => { + const matrix = server.tree.getElementByPath(MATRIX_PATH); + matrix.connections[0].setSources([]); + server.matrixConnect(MATRIX_PATH, 0, [1]); + expect(matrix.connections[0].sources).toBeDefined(); + expect(matrix.connections[0].sources.length).toBe(1); + expect(matrix.connections[0].sources[0]).toBe(1); + }); + it("should throw an error if can't find matrix", () => { + try { + server.matrixConnect("0.99.0", 0, [1]); + throw new Error("Should not succeed"); + } + catch(error) { + expect(error instanceof Errors.UnknownElement); + } + }); + it("should throw an error if invalid matrix", () => { + const matrix = server.tree.getElementByPath(MATRIX_PATH); + matrix.contents = null; + try { + server.matrixConnect(MATRIX_PATH, 0, [1]); + throw new Error("Should not succeed"); + } + catch(error) { + expect(error instanceof Errors.MissingElementContents); + } + }); + it("should have a matrixSet operation on matrix", () => { + const matrix = server.tree.getElementByPath(MATRIX_PATH); + matrix.connections[0].setSources([0]); + server.matrixSet(MATRIX_PATH, 0, [1]); + expect(matrix.connections[0].sources).toBeDefined(); + expect(matrix.connections[0].sources.length).toBe(1); + expect(matrix.connections[0].sources[0]).toBe(1); + }); + it("should have a matrixDisconnect operation on matrix", () => { + const matrix = server.tree.getElementByPath(MATRIX_PATH); + matrix.contents.type = EmberLib.MatrixType.nToN; + matrix.connections[0].setSources([1]); + server.matrixDisconnect(MATRIX_PATH, 0, [1]); + expect(matrix.connections[0].sources).toBeDefined(); + expect(matrix.connections[0].sources.length).toBe(0); + }); + }); + describe("subscribers", () => { + let jsonTree; + const PARAMETER_PATH = "0.0.1"; + const MATRIX_PATH = "0.1.0"; + /** @type {EmberServer} */ + let server; + beforeEach(function() { + jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + server = new EmberServer(LOCALHOST, PORT, root); + server.on("error", () => { + //ignore + }); + server.on("clientError", () => { + //ignore + }); + return server.listen(); + }); + afterEach(function() { + return server.close(); + }); + it("should accept client to subscribe to parameter and update those who subscribed", () => { + const client = new EmberClient(LOCALHOST, PORT); + const VALUE = "The new Value"; + return client.connect() + .then(() => client.getDirectory()) + .then(() => client.getElementByPath(PARAMETER_PATH)) + .then(() => { + // A get directory on non stream is auto subscribe + expect(server.subscribers).toBeDefined(); + expect(server.subscribers[PARAMETER_PATH]).toBeDefined(); + expect(server.subscribers[PARAMETER_PATH].size).toBe(1); + let res; + for(let c of server.subscribers[PARAMETER_PATH]) { + c.queueMessage = message => { + res = message; + } + } + server.setValue(server.tree.getElementByPath(PARAMETER_PATH), VALUE, null, null); + expect(res).toBeDefined(); + const resParam = res.getElementByPath(PARAMETER_PATH); + expect(resParam).toBeDefined(); + expect(resParam.getPath()).toBe(PARAMETER_PATH); + expect(resParam.contents).toBeDefined(); + expect(resParam.contents.value).toBe(VALUE); + return client.disconnect(); + }); + }); + it("should accept client to subscribe to matrix and update those who subscribed", () => { + const client = new EmberClient(LOCALHOST, PORT); + const VALUE = 17; + const MatrixParamName = "maximumTotalConnects"; + server.tree.getElementByPath(MATRIX_PATH).contents[MatrixParamName] = 0; + return client.connect() + .then(() => client.getDirectory()) + .then(() => client.getElementByPath(MATRIX_PATH)) + .then(() => { + // A get directory on non stream is auto subscribe + expect(server.subscribers).toBeDefined(); + expect(server.subscribers[MATRIX_PATH]).toBeDefined(); + expect(server.subscribers[MATRIX_PATH].size).toBe(1); + let res; + for(let c of server.subscribers[MATRIX_PATH]) { + c.queueMessage = message => { + res = message; + } + } + server.setValue(server.tree.getElementByPath(MATRIX_PATH), VALUE, null, MatrixParamName); + expect(res).toBeDefined(); + const resParam = res.getElementByPath(MATRIX_PATH); + expect(resParam).toBeDefined(); + expect(resParam.getPath()).toBe(MATRIX_PATH); + expect(resParam.contents).toBeDefined(); + expect(resParam.contents[MatrixParamName]).toBe(VALUE); + return client.disconnect(); + }); + }); + it("should clean up subscribers if client not connected anymore", () => { + const client = new EmberClient(LOCALHOST, PORT); + const VALUE = "The new Value"; + server.subscribers[PARAMETER_PATH] = new Set(); + server.subscribers[PARAMETER_PATH].add(client); + expect(server.subscribers[PARAMETER_PATH].size).toBe(1); + let res; + for(let c of server.subscribers[PARAMETER_PATH]) { + c.queueMessage = message => { + res = message; + } + } + server.setValue(server.tree.getElementByPath(PARAMETER_PATH), VALUE, null, null); + expect(res).not.toBeDefined(); + expect(server.subscribers[PARAMETER_PATH].has(client)).toBeFalsy(); + }); + it("should ignore unsubscribe if no subcribers", () => { + const client = new EmberClient(LOCALHOST, PORT); + let error; + try { + server.unsubscribe(client, server.tree.getElementByPath(PARAMETER_PATH)); + } + catch(e) { + error =e ; + } + expect(error).not.toBeDefined(); + }); + it("should ignore serValue on element with no contents", () => { + const param = server.tree.getElementByPath(PARAMETER_PATH); + const VALUE = "The new Value"; + param.contents = null; + let error; + try { + server.setValue(param, VALUE, null, null); + } + catch(e) { + error =e ; + } + expect(error).not.toBeDefined(); + }); + }); + describe("EmberServer toJSON", () => { + it("should have a toJSON", () => { + const PARAMETER_PATH = "0.0.1"; + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const server = new EmberServer(LOCALHOST, PORT, root); + const js = server.toJSON(); + expect(js[0].children[0].children[1].path).toBe(PARAMETER_PATH); + }); + it("should have a toJSON and return empty array if no tree", () => { + const server = new EmberServer(LOCALHOST, PORT, null); + const js = server.toJSON(); + expect(js).toBeDefined(); + expect(js.length).toBe(0); + }); + }); + describe("replaceElement", () => { + it("should replace existing element with new one", () => { + const PARAMETER_PATH = "0.0.1"; + const VALUE = "Gilles Dufour" + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const server = new EmberServer(LOCALHOST, PORT, root); + const newParam = new EmberLib.Parameter(1); + newParam.contents = new EmberLib.ParameterContents(VALUE); + newParam.path = PARAMETER_PATH; + server.replaceElement(newParam); + expect(server.tree.getElementByPath(PARAMETER_PATH).contents.value).toBe(VALUE); + }); + it("should throw an error if unknown element path", () => { + const VALUE = "Gilles Dufour" + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const server = new EmberServer(LOCALHOST, PORT, root); + const newParam = new EmberLib.Parameter(1000); + newParam.contents = new EmberLib.ParameterContents(VALUE); + let error; + try { + server.replaceElement(newParam); + } + catch(e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof Errors.UnknownElement).toBeTruthy(); + }); + it("should throw an error if trying to replace root or unattached element", () => { + const PARAMETER_PATH = "0.0.1"; + const VALUE = "Gilles Dufour" + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const server = new EmberServer(LOCALHOST, PORT, root); + server.tree.getElementByPath(PARAMETER_PATH)._parent = null; + const newParam = new EmberLib.Parameter(1); + newParam.contents = new EmberLib.ParameterContents(VALUE); + newParam.path = PARAMETER_PATH; + let error; + try { + server.replaceElement(newParam); + } + catch(e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof Errors.InvalidEmberNode).toBeTruthy(); + }); + }); + describe("Events", () => { + it("should catch error emitted by internal tcp server", () => { + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const ERROR_MESSAGE = "gdnet internal error"; + server = new EmberServer(LOCALHOST, PORT, root); + let error; + server.on("error", e => {error = e;}); + server.server.emit("error", new Error(ERROR_MESSAGE)); + expect(error).toBeDefined(); + expect(error.message).toBe(ERROR_MESSAGE); + }); + it("should catch tcp server disconnected message, and clean up clients", () => { + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const server = new EmberServer(LOCALHOST, PORT, root); + server.clients.add(new EmberClient(LOCALHOST, PORT)); + let count = 0; + server.on("disconnected", () => {count++;}); + server.server.emit("disconnected"); + expect(count).toBe(1); + expect(server.clients.size).toBe(0); + }); + it("should catch error from connection to clients", () => { + const jsonTree = jsonRoot(); + const root = EmberServer.JSONtoTree(jsonTree); + const ERROR_MESSAGE = "gdnet internal error"; + const server = new EmberServer(LOCALHOST, PORT, root); + const client = new EmberClient(LOCALHOST, PORT); + client.remoteAddress = () => {return "address";} + let info; + server.on("clientError", data => {info = data;}); + server.server.emit("connection", client); + client.emit("error", new Error(ERROR_MESSAGE)); + expect(info).toBeDefined(); + expect(info.error.message).toBe(ERROR_MESSAGE); + }); }); }); diff --git a/test/client.js b/test/client.js deleted file mode 100755 index 880e0cc..0000000 --- a/test/client.js +++ /dev/null @@ -1,21 +0,0 @@ -const DeviceTree = require("../").DeviceTree; -const ember = require("../ember"); - -const HOST = "192.168.4.4"; -const PORT = 9092; - -client = new DeviceTree(HOST, PORT); -client.connect() -.then(() => client.getDirectory()) -.then(() => client.expand(client.root.elements[0])) -.then(() => { - //console.log(client.root.elements[0].children[4].children[2].toJSON()); - console.log(JSON.stringify(client.root.elements[0].children[4].toJSON(), null, 4)); - return client.invokeFunction(client.root.elements[0].children[4].children[0], [ - new ember.FunctionArgument(ember.ParameterType.integer, 1), - new ember.FunctionArgument(ember.ParameterType.integer, 7) - ]); -}) -.then(result => { - console.log(result); -}); diff --git a/embrionix.ember b/test/embrionix.ember similarity index 100% rename from embrionix.ember rename to test/embrionix.ember diff --git a/test/utils.js b/test/utils.js index 74b6eb9..9cca4a4 100755 --- a/test/utils.js +++ b/test/utils.js @@ -1,13 +1,19 @@ -const {ParameterAccess} = require("../ember"); +const {ParameterType, FunctionArgument} = require("../EmberLib"); + const init = function(_src,_tgt) { const targets = _tgt === undefined ? [ "tgt1", "tgt2", "tgt3" ] : _tgt; const sources = _src === undefined ? [ "src1", "src2", "src3" ] : _src; - const labels = function(endpoints) { + const defaultSources = [ + {identifier: "t-0", value: -1, access: "readWrite" }, + {identifier: "t-1", value: 0, access: "readWrite"}, + {identifier: "t-2", value: 0, access: "readWrite"} + ]; + const labels = function(endpoints, type) { let labels = []; for (let i = 0; i < endpoints.length; i++) { let endpoint = endpoints[i]; - let l = { identifier: `Label-${i}` }; + let l = { identifier: `${type}-${i}` }; if (endpoint) { l.value = endpoint; } @@ -33,9 +39,9 @@ const init = function(_src,_tgt) { // path "0.0" identifier: "identity", children: [ - {identifier: "product", value: "S-CORE Master"}, + {identifier: "product", value: "S-CORE Master", type: "string"}, {identifier: "company", value: "EVS", access: "readWrite"}, - {identifier: "version", value: "1.2.0"}, + {identifier: "version", value: "1.2.0", access: "readWrite", streamIdentifier: 1234567}, {identifier: "author", value: "g.dufour@evs.com"}, ] }, @@ -49,9 +55,9 @@ const init = function(_src,_tgt) { type: "oneToN", mode: "linear", targetCount: targets.length, - sourceCount: sources.length, + sourceCount: sources.length, connections: buildConnections(sources, targets), - labels: ["0.1.1000"] + labels: [{basePath: "0.1.1000", description: "primary"}], }, { identifier: "labels", @@ -62,23 +68,70 @@ const init = function(_src,_tgt) { identifier: "targets", // Must be 1 number: 1, - children: labels(targets) + children: labels(targets, "t") }, { identifier: "sources", // Must be 2 number: 2, - children: labels(sources) + children: labels(sources, "s") }, { identifier: "group 1", children: [ {identifier: "sdp A", value: "A"}, {identifier: "sdp B", value: "B"}] } ] + }, + { + identifier: "disconnect sources", + number: 1001, + children: defaultSources + } + ] + }, + { + // path "0.2" + identifier: "addFunction", + func: args => { + const res = new FunctionArgument(); + res.type = ParameterType.integer; + res.value = args[0].value + args[1].value; + return [res]; + }, + arguments: [ + { + type: ParameterType.integer, + value: null, + name: "arg1" + }, + { + type: ParameterType.integer, + value: null, + name: "arg2" + } + ], + result: [ + { + type: ParameterType.integer, + value: null, + name: "changeCount" } ] } ] + }, + { + identifier: "PeakValue_2", + type: 2, + streamIdentifier: 4, + streamDescriptor: { + format: "ieeeFloat32LittleEndian", + offset: 4 + }, + access: 1, + maximum: 20, + minimum: -200, + value: -200 } ]; } From 3248a957fd6f566436abf9360cb4cbbce9156a23 Mon Sep 17 00:00:00 2001 From: Thomas Silvestre Date: Thu, 10 Sep 2020 16:56:46 +0200 Subject: [PATCH 157/162] Fixed TreeNode and tests TreeNode.js: fix toJSON() when there are no children Ember.test.js: fix tests, cleaned up imports --- EmberLib/TreeNode.js | 8 +++----- test/Ember.test.js | 25 ++++++++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index 69b6ebe..2ecefe0 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -3,7 +3,7 @@ const BER = require('../ber.js'); const ElementInterface = require("./ElementInterface"); const Invocation = require("./Invocation"); const Command = require("./Command"); -const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE, COMMAND_INVOKE} = require("./constants"); +const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); const Errors = require("../Errors"); class TreeNode extends ElementInterface { @@ -410,9 +410,7 @@ class TreeNode extends ElementInterface { const node = this; if (this.isRoot()) { const elements = this.getChildren(); - return { - elements: elements.map(e => e.toJSON()) - }; + return elements ? {elements: elements.map(e => e.toJSON())}: {elements: []}; } res.number = node.getNumber(); res.path = node.getPath(); @@ -557,4 +555,4 @@ class TreeNode extends ElementInterface { } } -module.exports = TreeNode; \ No newline at end of file +module.exports = TreeNode; diff --git a/test/Ember.test.js b/test/Ember.test.js index f96b136..90140f0 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -1,14 +1,15 @@ const expect = require("expect"); const { S101Client } = require("../EmberSocket"); -const s101Buffer = Buffer.from("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); -const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); -const ember = require("../EmberLib"); const BER = require('../ber.js'); const Errors = require('../Errors.js'); const EmberLib = require("../EmberLib"); const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("../EmberLib/ParameterType"); + +const s101Buffer = Buffer.from("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); +const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); const identifier = "node_identifier"; const description = "node_description"; + describe("Ember", () => { describe("generic", () => { let client; @@ -32,7 +33,7 @@ describe("Ember", () => { it("should handle Errors in message", () => { var ber = new BER.Reader(errorBuffer); - expect(() => ember.Root.decode(ber)).toThrow(Errors.UnimplementedEmberTypeError); + expect(() => EmberLib.Root.decode(ber)).toThrow(Errors.UnimplementedEmberTypeError); }); it("Should have a toJSON()", () => { const node = new EmberLib.Node(); @@ -78,7 +79,7 @@ describe("Ember", () => { expect(res.contents.identifier).toBe(identifier); }); - it("should throw error if function getElement called from a node with longer parh", () => { + it("should throw error if function getElement called from a node with longer path", () => { const root = new EmberLib.Root(); root.addChild(new EmberLib.Node(0)); root.getElement(0).addChild(new EmberLib.Node(1)); @@ -107,11 +108,17 @@ describe("Ember", () => { let res = node.getRoot(); expect(res).toBe(root); }); + it("Root should have a toJSON function", () =>{ + const root = new EmberLib.Root(); + expect(root.toJSON()).toEqual({"elements": []}); + root.addChild(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(1)); + expect(root.toJSON()).toEqual({"elements": [{"children": [{"nodeType": "Node", "number": 1, "path": "0.1"}], "nodeType": "Node", "number": 0, "path": "0"}]}); + }); it("should have a getDirectory() and accept a callback for subscribers", () => { const parameter = new EmberLib.Parameter(0); parameter.contents = new EmberLib.ParameterContents(7, "integer"); - parameter.contents.streamIdentifier = 12345; - let res = parameter.getDirectory(0, () => {}); + let res = parameter.getDirectory(() => {}); expect(res).toBeDefined(); expect(parameter._subscribers.size).toBe(1); }); @@ -391,8 +398,8 @@ describe("Ember", () => { EmberLib.FunctionContent.decode(new BER.Reader(writer.buffer)); throw new Error("Should not succeed"); } - catch(e) { - expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + catch(e) { + expect(e.name).toEqual('InvalidAsn1Error'); } }); it("should throw an error if unable to decode content", () => { From b759b699fc532a7ec59197154d48a128bcfd12f7 Mon Sep 17 00:00:00 2001 From: Thomas Silvestre Date: Fri, 11 Sep 2020 11:48:01 +0200 Subject: [PATCH 158/162] Fixed TreeNode and tests (#41) * Fixed TreeNode and tests TreeNode.js: fix toJSON() when there are no children Ember.test.js: fix tests, cleaned up imports * rewrote line in better js Co-authored-by: Thomas Silvestre --- EmberLib/TreeNode.js | 9 ++++----- test/Ember.test.js | 27 +++++++++++++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index 69b6ebe..1c20001 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -3,7 +3,7 @@ const BER = require('../ber.js'); const ElementInterface = require("./ElementInterface"); const Invocation = require("./Invocation"); const Command = require("./Command"); -const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE, COMMAND_INVOKE} = require("./constants"); +const {COMMAND_GETDIRECTORY, COMMAND_SUBSCRIBE, COMMAND_UNSUBSCRIBE} = require("./constants"); const Errors = require("../Errors"); class TreeNode extends ElementInterface { @@ -410,9 +410,7 @@ class TreeNode extends ElementInterface { const node = this; if (this.isRoot()) { const elements = this.getChildren(); - return { - elements: elements.map(e => e.toJSON()) - }; + return { elements: elements ? elements.map(e => e.toJSON()) : [] }; } res.number = node.getNumber(); res.path = node.getPath(); @@ -557,4 +555,5 @@ class TreeNode extends ElementInterface { } } -module.exports = TreeNode; \ No newline at end of file +module.exports = TreeNode; + diff --git a/test/Ember.test.js b/test/Ember.test.js index f96b136..2c4a205 100755 --- a/test/Ember.test.js +++ b/test/Ember.test.js @@ -1,14 +1,16 @@ const expect = require("expect"); const { S101Client } = require("../EmberSocket"); -const s101Buffer = Buffer.from("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); -const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); -const ember = require("../EmberLib"); const BER = require('../ber.js'); const Errors = require('../Errors.js'); const EmberLib = require("../EmberLib"); const {ParameterTypefromBERTAG, ParameterTypetoBERTAG} = require("../EmberLib/ParameterType"); + +const s101Buffer = Buffer.from("fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); +const errorBuffer = Buffer.from("76fe000e0001c001021f026082008d6b820089a0176a15a0050d03010201a10c310aa0080c066c6162656c73a01b6a19a0050d03010202a110310ea00c0c0a706172616d6574657273a051714fa0050d03010203a1463144a0080c066d6174726978a403020104a503020104aa183016a0147212a0050d03010201a1090c075072696d617279a203020102a303020101a8050d03010202a903020101f24cff", "hex"); const identifier = "node_identifier"; const description = "node_description"; + + describe("Ember", () => { describe("generic", () => { let client; @@ -32,7 +34,7 @@ describe("Ember", () => { it("should handle Errors in message", () => { var ber = new BER.Reader(errorBuffer); - expect(() => ember.Root.decode(ber)).toThrow(Errors.UnimplementedEmberTypeError); + expect(() => EmberLib.Root.decode(ber)).toThrow(Errors.UnimplementedEmberTypeError); }); it("Should have a toJSON()", () => { const node = new EmberLib.Node(); @@ -78,7 +80,7 @@ describe("Ember", () => { expect(res.contents.identifier).toBe(identifier); }); - it("should throw error if function getElement called from a node with longer parh", () => { + it("should throw error if function getElement called from a node with longer path", () => { const root = new EmberLib.Root(); root.addChild(new EmberLib.Node(0)); root.getElement(0).addChild(new EmberLib.Node(1)); @@ -107,11 +109,17 @@ describe("Ember", () => { let res = node.getRoot(); expect(res).toBe(root); }); + it("Root should have a toJSON function", () =>{ + const root = new EmberLib.Root(); + expect(root.toJSON()).toEqual({"elements": []}); + root.addChild(new EmberLib.Node(0)); + root.getElement(0).addChild(new EmberLib.Node(1)); + expect(root.toJSON()).toEqual({"elements": [{"children": [{"nodeType": "Node", "number": 1, "path": "0.1"}], "nodeType": "Node", "number": 0, "path": "0"}]}); + }); it("should have a getDirectory() and accept a callback for subscribers", () => { const parameter = new EmberLib.Parameter(0); parameter.contents = new EmberLib.ParameterContents(7, "integer"); - parameter.contents.streamIdentifier = 12345; - let res = parameter.getDirectory(0, () => {}); + let res = parameter.getDirectory(() => {}); expect(res).toBeDefined(); expect(parameter._subscribers.size).toBe(1); }); @@ -368,7 +376,6 @@ describe("Ember", () => { expect(f.contents.description).toBe(description); expect(f.contents.result.length).toBe(1); expect(f.contents.templateReference).toBe(func.contents.templateReference); - writer = new BER.Writer(); func.contents.identifier = null; func.contents.arguments = null; @@ -391,8 +398,8 @@ describe("Ember", () => { EmberLib.FunctionContent.decode(new BER.Reader(writer.buffer)); throw new Error("Should not succeed"); } - catch(e) { - expect(e instanceof Errors.UnimplementedEmberTypeError).toBeTruthy(); + catch(e) { + expect(e.name).toEqual('InvalidAsn1Error'); } }); it("should throw an error if unable to decode content", () => { From 1ec38e28efbabef35e20392096a630cffdc3e4cb Mon Sep 17 00:00:00 2001 From: Thomas Silvestre Date: Fri, 11 Sep 2020 12:05:18 +0200 Subject: [PATCH 159/162] fixed README.md --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index e2c77ae..953ebf1 100755 --- a/README.md +++ b/README.md @@ -17,10 +17,7 @@ the way the lib is used so that now it uses Promise Server has been added in version 1.6.0. -Support for StreamCollection, UDP, packet stats/rate, support for tree with size higher than 8M -is available in a private branch. -If you want access to full version and/or would like support for new features or integration -with your projects, please contact Gilles Dufour - dufour.gilles@gmail.com +This version doesn't have support for StreamCollection, UDP, packet stats/rate or for tree with size higher than 8M. ## Example usage From e1bfca2f43f494e08f43ce3ab9325d31c4aa7b57 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Sat, 9 Jan 2021 14:38:15 +0100 Subject: [PATCH 160/162] Fix: Do not crash if label does not have a description --- EmberLib/Label.js | 10 ++++++---- EmberServer/EmberServer.js | 4 ++-- package.json | 2 +- serve.js | 3 +++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/EmberLib/Label.js b/EmberLib/Label.js index 8a41235..02d7a36 100755 --- a/EmberLib/Label.js +++ b/EmberLib/Label.js @@ -25,11 +25,13 @@ class Label { ber.writeRelativeOID(this.basePath, BER.EMBER_RELATIVE_OID); ber.endSequence(); if (this.description == null) { - throw new Errors.InvalidEmberNode("", "Missing label description"); + //throw new Errors.InvalidEmberNode("", "Missing label description"); + } + else { + ber.startSequence(BER.CONTEXT(1)); + ber.writeString(this.description, BER.EMBER_STRING); + ber.endSequence(); } - ber.startSequence(BER.CONTEXT(1)); - ber.writeString(this.description, BER.EMBER_STRING); - ber.endSequence(); ber.endSequence(); } diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index 741f045..c6e5e5c 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -41,7 +41,7 @@ class TreeServer extends EventEmitter{ this.emit("request", {client: client.remoteAddress(), root: root, path: path}); } catch(e) { - winston.debug(e.stack) + winston.debug(e.stack); this.emit("error", e); } }); @@ -131,7 +131,7 @@ class TreeServer extends EventEmitter{ } } else { - res.addChild(dup); + res.addChild(dup); } return res; } diff --git a/package.json b/package.json index 2d5206e..9e6b182 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.9", + "version": "2.5.10", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": { diff --git a/serve.js b/serve.js index 9447250..936346e 100755 --- a/serve.js +++ b/serve.js @@ -40,6 +40,9 @@ const main = async () => { const data = readFileSync(argv.file); const tree = argv.json ? EmberServer.JSONtoTree(JSON.parse(data.toString())) : Decoder(data); const server = new EmberServer(argv.host, argv.port, tree); + server.on('error', (e) => { + console.log(e); + }); server._debug = true; console.log(Date.now(), 'starting server'); if (argv.debug) { From 5b01219cd65bfe628e65992fdfe9e88f076dd932 Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 12 Aug 2022 23:00:53 +0200 Subject: [PATCH 161/162] fix matrix update connection validation --- EmberLib/Matrix.js | 43 ++++-------- EmberLib/QualifiedElement.js | 1 - EmberLib/QualifiedFunction.js | 2 - EmberLib/QualifiedMatrix.js | 5 +- EmberLib/TreeNode.js | 1 + EmberServer/._JSONParser.js | Bin 0 -> 4096 bytes EmberServer/EmberServer.js | 3 + EmberServer/JSONParser.js | 4 ++ s101.js | 4 +- test/function.js | 113 ++++++++++++++++++++++++++++++++ test/matrixUpdate.test.js | 76 +++++++++++++++++++++ test/validateConnection.test.js | 53 +++++++++++++++ 12 files changed, 268 insertions(+), 37 deletions(-) create mode 100755 EmberServer/._JSONParser.js create mode 100755 test/function.js create mode 100755 test/matrixUpdate.test.js create mode 100755 test/validateConnection.test.js diff --git a/EmberLib/Matrix.js b/EmberLib/Matrix.js index 9f8ac2f..d72552b 100755 --- a/EmberLib/Matrix.js +++ b/EmberLib/Matrix.js @@ -1,7 +1,7 @@ "use strict"; const MatrixConnection = require("./MatrixConnection"); const TreeNode = require("./TreeNode"); -const BER = require('../ber.js'); +const BER = require('../ber'); const MatrixMode = require("./MatrixMode"); const MatrixOperation = require("./MatrixOperation"); const MatrixType = require("./MatrixType"); @@ -13,7 +13,9 @@ class Matrix extends TreeNode super(); this._connectedSources = {}; this._numConnections = 0; + /**@type number[] | null */ this.targets = null; + /**@type number[] | null */ this.sources = null; this.connections = {}; } @@ -364,19 +366,14 @@ class Matrix extends TreeNode for(let id in newMatrix.connections) { if (newMatrix.connections.hasOwnProperty(id)) { const connection = newMatrix.connections[id]; - if ((connection.target < matrix.contents.targetCount) && - (connection.target >= 0)) { - if (matrix.connections[connection.target] == null) { - matrix.connections[connection.target] = new MatrixConnection(connection.target); - modified = true; - } - if (matrix.connections[connection.target].isDifferent(connection.sources)) { - matrix.connections[connection.target].setSources(connection.sources); - modified = true; - } + this.validateConnection(matrix, connection.target, connection.sources); + if (matrix.connections[connection.target] == null) { + matrix.connections[connection.target] = new MatrixConnection(connection.target); + modified = true; } - else { - throw new Errors.InvalidMatrixSignal(connection.target, "Invalid target") + if (matrix.connections[connection.target].isDifferent(connection.sources)) { + matrix.connections[connection.target].setSources(connection.sources); + modified = true; } } } @@ -431,29 +428,15 @@ class Matrix extends TreeNode throw new Errors.InvalidEmberNode(matrixNode.getPath(),"Non-Linear matrix should have targets and sources"); } else { - let found = false; - for(let i = 0; i < matrixNode.targets.length; i++) { - if (matrixNode.targets[i] === targetID) { - found = true; - break; - } - } - if (!found) { + if (!matrixNode.targets.includes(targetID)) { throw new Errors.InvalidMatrixSignal(targetID, "Not part of existing targets"); } - found = false; for(let i = 0; i < sources.length; i++) { - for(let j = 0; j < matrixNode.sources.length; j++) { - if (matrixNode.sources[j] === sources[i]) { - found = true; - break; - } - } - if (!found) { + if (!matrixNode.sources.includes(sources[i])) { throw new Errors.InvalidMatrixSignal(sources[i],`Unknown source at index ${i}`); } } - } + } } } diff --git a/EmberLib/QualifiedElement.js b/EmberLib/QualifiedElement.js index 98e45f4..7c9be2c 100755 --- a/EmberLib/QualifiedElement.js +++ b/EmberLib/QualifiedElement.js @@ -2,7 +2,6 @@ const TreeNode = require("./TreeNode"); const BER = require('../ber.js'); const Command = require("./Command"); -const {COMMAND_GETDIRECTORY} = require("./constants"); class QualifiedElement extends TreeNode { /** diff --git a/EmberLib/QualifiedFunction.js b/EmberLib/QualifiedFunction.js index 8e4ac84..65384f8 100755 --- a/EmberLib/QualifiedFunction.js +++ b/EmberLib/QualifiedFunction.js @@ -2,9 +2,7 @@ const QualifiedElement = require("./QualifiedElement"); const FunctionContent = require("./FunctionContent"); -const {COMMAND_GETDIRECTORY} = require("./constants"); const BER = require('../ber.js'); -const Command = require("./Command"); const Errors = require("../Errors"); class QualifiedFunction extends QualifiedElement { diff --git a/EmberLib/QualifiedMatrix.js b/EmberLib/QualifiedMatrix.js index 3d4f890..b3f2a6a 100755 --- a/EmberLib/QualifiedMatrix.js +++ b/EmberLib/QualifiedMatrix.js @@ -1,12 +1,11 @@ "use strict"; const Matrix = require("./Matrix"); -const BER = require('../ber.js'); -const Command = require("./Command"); +const BER = require('../ber'); const MatrixContents = require("./MatrixContents"); const MatrixConnection = require("./MatrixConnection"); const errors = require("../Errors"); - +console.log(Matrix) class QualifiedMatrix extends Matrix { /** * diff --git a/EmberLib/TreeNode.js b/EmberLib/TreeNode.js index 206dd06..e84f314 100755 --- a/EmberLib/TreeNode.js +++ b/EmberLib/TreeNode.js @@ -12,6 +12,7 @@ class TreeNode extends ElementInterface { /** @type {TreeNode} */ this._parent = null; this._subscribers = new Set(); + this.hidden = false; } _isSubscribable(callback) { diff --git a/EmberServer/._JSONParser.js b/EmberServer/._JSONParser.js new file mode 100755 index 0000000000000000000000000000000000000000..56a2ca84b9e64dc65384cecc95064c5f59647d82 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vTCCrx z1ku5;0H|C5O$#HC4;7b6&d=3LEGWoH)yqjNE-5WeO-V^CNmULA2I*PPloSV|w@&`5 z0HR01Xb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD;0ggyXA^|MKrSRBvsj@h zwK%`DC^=OjEx#yRAv3QeHLoNyKQA#Sr&1v&HLXM;DJL;68`u|y>Kf7%s{i3$kztVg G{~rLb6)Q>r literal 0 HcmV?d00001 diff --git a/EmberServer/EmberServer.js b/EmberServer/EmberServer.js index c6e5e5c..b3ce9e0 100755 --- a/EmberServer/EmberServer.js +++ b/EmberServer/EmberServer.js @@ -127,6 +127,9 @@ class TreeServer extends EventEmitter{ const children = element.getChildren(); if (children != null) { for (let i = 0; i < children.length; i++) { + if (children[i].hidden) { + continue; + } res.addChild(children[i].toQualified().getMinimalContent()); } } diff --git a/EmberServer/JSONParser.js b/EmberServer/JSONParser.js index dfa5015..52b66bf 100755 --- a/EmberServer/JSONParser.js +++ b/EmberServer/JSONParser.js @@ -148,6 +148,10 @@ class JSONParser { } else { emberElement = new ember.Node(number); + if (content.hidden === 'true' || content.hidden === true) { + emberElement.hidden = true; + } + delete content.hidden; emberElement.contents = new ember.NodeContents(); } for(let id in content) { diff --git a/s101.js b/s101.js index 0537b8f..e663335 100755 --- a/s101.js +++ b/s101.js @@ -257,7 +257,6 @@ var finalizeBuffer = function(smartbuf) { var crc = (~calculateCRCCE(smartbuf.toBuffer().slice(1, smartbuf.length))) & 0xFFFF; var crc_hi = crc >> 8; var crc_lo = crc & 0xFF; - if(crc_lo < S101_INV) { smartbuf.writeUInt8(crc_lo); } else { @@ -284,6 +283,7 @@ var calculateCRC = function(buf) { } return crc; } +S101Codec.prototype.calculateCRC = calculateCRC; var calculateCRCCE = function(buf) { var crc = 0xFFFF; @@ -297,6 +297,8 @@ var calculateCRCCE = function(buf) { return crc; } +S101Codec.prototype.calculateCRCCE = calculateCRCCE; + var validateFrame = function(buf) { return calculateCRC(buf) == 0xF0B8; } diff --git a/test/function.js b/test/function.js new file mode 100755 index 0000000..e518ff7 --- /dev/null +++ b/test/function.js @@ -0,0 +1,113 @@ +const EmberClient = require("../EmberClient"); +const EmberLib = require("../EmberLib"); +const TreeServer = require("../EmberServer").EmberServer; +const {jsonRoot} = require("./utils"); +const BER = require("../ber"); +const Decoder = EmberLib.DecodeBuffer; +const S101Client = require("../EmberSocket").S101Client; +const fs = require("fs"); + +let HOST; +let PORT; +const TINYEMBER = false; + + +const jsonTree = jsonRoot(); +const eee = TreeServer.JSONtoTree(jsonTree); + + +const PATH = "0.0.1"; + +// function getRoot() { +// return new Promise((resolve, reject) => { +// fs.readFile("test/embrionix.ember", (e, data) => { +// if (e) { +// reject(e); +// } +// try { +// resolve(Decoder(data)); +// } +// catch(error) { +// reject(error); +// } +// }); +// }); +// } + +const wait = function(t) { + return new Promise(resolve => { + setTimeout(resolve, t); + }); +} + +if (TINYEMBER) { + HOST = "192.168.4.4"; + PORT = 9092; + //HOST = "192.168.0.235"; + //PORT = 9000; +} +else { + HOST = "127.0.0.1"; + PORT = 9000; +} + + + + + +const client = new EmberClient(HOST, PORT); +const codec = new S101Client(); +const MATRIX_PATH = "0.1.0"; + +function clientData(node) { + console.log("received event for ", node.toJSON()); +} + +let server; +let start = Promise.resolve(); +start = start +//.then(() => getRoot()) +.then(root => { + root = TreeServer.JSONtoTree(jsonRoot()); + //root.addElement(new EmberLib.Node(1)); + server = new TreeServer(HOST, PORT, root); + //server._debug = true; + server.on("error", e => { + console.log("Server Error", e); + }); + server.on("clientError", info => { + console.log("clientError", info.error); + }); + server.on("event", event => { + console.log("Event: " + event); + }); + return server.listen() +}) + +.then(() => client.connect()) +.then(() => client.expand()) +.then(() => client.getElementByPath("1")) +.then(node1 => { + console.log(node1); + // const streamCollection = new EmberLib.StreamCollection(); + // const children = node1.getChildren(); + // let count = 0; + // for(let child of children) { + // client.subscribe(child, () => { count++; }); + // streamCollection.addEntry(new EmberLib.StreamEntry(child.contents.streamIdentifier, 999)); + // } + // // prepare StreamCollection + // const root = new EmberLib.Root(); + // root.setStreams(streamCollection); + // client._client.emit('emberTree', root); +}) +.catch(e => { + console.log(e.stack); + console.log(e); +}) +.then(() => { + client.disconnect(); + if (!TINYEMBER) { + server.close(); + } +}); diff --git a/test/matrixUpdate.test.js b/test/matrixUpdate.test.js new file mode 100755 index 0000000..f282bbe --- /dev/null +++ b/test/matrixUpdate.test.js @@ -0,0 +1,76 @@ +const { Matrix, MatrixContents, MatrixType, MatrixMode, MatrixConnection } = require("../EmberLib"); + +describe("Matrix Update", () => { + describe("linear matrix", () => { + let matrix; + beforeEach(() => { + matrix = new Matrix(); + matrix.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.linear); + matrix.contents.targetCount = 5; + matrix.contents.sourceCount = 2; + matrix.connectSources(3, [1]); + }); + it("update matrix connections", () => { + const newMatrix = new Matrix(); + newMatrix.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.linear); + newMatrix.contents.targetCount = 5; + newMatrix.contents.sourceCount = 2; + newMatrix.connectSources(3, [0]); + Matrix.MatrixUpdate(matrix, newMatrix); + expect(matrix.connections[3].sources).toHaveLength(1); + expect(matrix.connections[3].sources[0]).toBe(0); + }); + it("reject invalid connections (source) during matrix update", () => { + const newMatrix = new Matrix(); + newMatrix.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.linear); + newMatrix.contents.targetCount = 5; + newMatrix.contents.sourceCount = 2; + newMatrix.connectSources(3, [2]); + expect(() => Matrix.MatrixUpdate(matrix, newMatrix)).toThrow + }); + it("reject invalid connections (target) during matrix update", () => { + const newMatrix = new Matrix(); + newMatrix.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.linear); + newMatrix.contents.targetCount = 5; + newMatrix.contents.sourceCount = 2; + newMatrix.connectSources(14, [0]); + expect(() => Matrix.MatrixUpdate(matrix, newMatrix)).toThrow + }); + }); + describe("non-linear matrix", () => { + let matrix; + beforeEach(() => { + matrix = new Matrix(); + matrix.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.nonLinear); + matrix.targets = [1, 3, 5, 6]; + matrix.sources = [0,1,4]; + matrix.connectSources(3, [0]); + }); + it("update matrix connections", () => { + const newMatrix = new Matrix(); + newMatrix.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.linear); + newMatrix.targets = [1, 3, 5, 6]; + newMatrix.sources = [0,1,4]; + newMatrix.connectSources(3, [4]); + Matrix.MatrixUpdate(matrix, newMatrix); + expect(matrix.connections[3].sources).toHaveLength(1); + expect(matrix.connections[3].sources[0]).toBe(4); + }); + it("reject invalid connections (source) during matrix update", () => { + const newMatrix = new Matrix(); + newMatrix.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.linear); + newMatrix.targets = [1, 3, 5, 6]; + newMatrix.sources = [0,1,4]; + newMatrix.connectSources(3, [2]); + expect(() => Matrix.MatrixUpdate(matrix, newMatrix)).toThrow + }); + it("reject invalid connections (target) during matrix update", () => { + const newMatrix = new Matrix(); + newMatrix.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.linear); + newMatrix.targets = [1, 3, 5, 6]; + newMatrix.sources = [0,1,4]; + newMatrix.connectSources(4, [4]); + expect(() => Matrix.MatrixUpdate(matrix, newMatrix)).toThrow + }); + }); +}) \ No newline at end of file diff --git a/test/validateConnection.test.js b/test/validateConnection.test.js new file mode 100755 index 0000000..11f7552 --- /dev/null +++ b/test/validateConnection.test.js @@ -0,0 +1,53 @@ +const { Matrix, MatrixContents, MatrixNode, MatrixMode, MatrixType, MatrixConnection } = require("../EmberLib"); + +describe("validateConnection", () => { + describe("linear matrix", () => { + let matrixNode; + beforeEach(() => { + matrixNode = new MatrixNode(1); + matrixNode.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.linear); + matrixNode.contents.targetCount = 3; + matrixNode.contents.sourceCount = 2; + }); + it("accept valid connection", () => { + const connection = new MatrixConnection(2); + connection.setSources([1]); + expect(() => Matrix.validateConnection(matrixNode, connection.target, connection.sources)).not.toThrow(); + }); + it("reject invalid source", () => { + const connection = new MatrixConnection(2); + connection.setSources([4]); + expect(() => Matrix.validateConnection(matrixNode, connection.target, connection.sources)).toThrow(); + }); + it("reject invalid target", () => { + const connection = new MatrixConnection(10); + connection.setSources([4]); + expect(() => Matrix.validateConnection(matrixNode, connection.target, connection.sources)).toThrow(); + }); + }); + describe("non-linear matrix", () => { + let matrixNode; + beforeEach(() => { + matrixNode = new MatrixNode(1); + matrixNode.contents = new MatrixContents(MatrixType.oneToN, MatrixMode.nonLinear); + matrixNode.targets = [1, 3, 7]; + matrixNode.sources = [1,2,4,8]; + }); + test.each([1,2,4,8]) + ("accept valid connection with source %p", (sourceID) => { + const connection = new MatrixConnection(3); + connection.setSources([sourceID]); + expect(() => Matrix.validateConnection(matrixNode, connection.target, connection.sources)).not.toThrow(); + }); + test.each([-1,3,5,18])("reject invalid source %p", (sourceID) => { + const connection = new MatrixConnection(1); + connection.setSources([sourceID]); + expect(() => Matrix.validateConnection(matrixNode, connection.target, connection.sources)).toThrow(); + }); + it("reject invalid target", () => { + const connection = new MatrixConnection(10); + connection.setSources([4]); + expect(() => Matrix.validateConnection(matrixNode, connection.target, connection.sources)).toThrow(); + }); + }); +}); \ No newline at end of file From 40eb951f7f57438b1c3c9dd1b446bc6103f1436d Mon Sep 17 00:00:00 2001 From: Gilles Dufour Date: Fri, 12 Aug 2022 23:02:34 +0200 Subject: [PATCH 162/162] version 2.5.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9e6b182..b2e1cdb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-emberplus", - "version": "2.5.10", + "version": "2.5.11", "description": "Javascript implementation of the Ember+ automation protocol", "main": "index.js", "scripts": {