diff --git a/lib/message.js b/lib/message.js deleted file mode 100644 index 282d590..0000000 --- a/lib/message.js +++ /dev/null @@ -1,130 +0,0 @@ -var Transform = require("stream").Transform; - -/** - * A Transform Stream implementation that output full VNB protocol messages - * - * @constructor - * @this {VNBMessageParser} - */ -function VNBMessageTransform(opts) { - "use strict"; - Transform.call(this,opts); - - var buffer; - - /** - * Transform stream Write implementation - * @param {string} data Upstream data already converted as string - * @param {string} encoding String encoding (should be utf8, but should be checked upward) - * @param {function} callback Downstream Stream receiving only full VNB messages - **/ - this._transform = function(data, encoding, callback) { - var endpos = data.indexOf("\n"); - - if (endpos > -1) { - if (buffer !== undefined) { - this.push(buffer); - buffer = undefined; - } - - if (endpos > 0) { - this.push(data.substr(0, endpos)); - } - callback(); - - if (endpos < data.length - 1) { - buffer = data.substr(endpos + 1); - } - } - else { - buffer = buffer.concat(data); - } - }; - - /** - * Flush still data. Remaining data is lost since it can only be an incomplete message - * @param {function} callback Callback to call when data has been flushed - **/ - this._flush = function (callback) { - buffer = undefined; - callback(); - }; -} - - -/** - * VNB Client implementation (communication from vim to the netbeans server) - * @constructor - * @this {VNBMessageClient} - * @param {stream.Readable} data Incomming stream - * @param {function} onEvent Callback function called when an Event message is received - * @param {function} onReply Callback function called when a Reply to a previous Function called is received - */ -function VNBMessageClient(data, onEvent, onReply) { - "use strict"; - - data.setEncoding("utf8"); - - var messageTransform = new VNBMessageTransform(); - - messageTransform.on("data",function (message) { - var iName = 0; //Index of the name separator in case of an Event - var iSeqno = 0; //Index of the seqno separator in case of an Event - - //Header of the message - for (var i = 0 ; i < message.length && message[i] !== " "; ++i) { - if (message[i] === ":") { - iName = i; - } - else if (message[i] === "=") { - iSeqno = i; - } - } - - var isEvent = false; - var args = []; - - if (iName > 0 && iSeqno > 0) { - isEvent = true; - args.push(Number.parseInt(message.substr(0, iName), 10)); - args.push(message.substring(iName + 1, iSeqno)); - args.push(Number.parseInt(message.substring(iSeqno + 1, i), 10)); - } - else if (iName === 0 && iSeqno === 0) { - args.push(Number.parseInt(message.substr(0, i), 10)); - } - else { - //Unknown message type - } - - //Arguments of the message - for (++i ; i < message.length ; ++i) { - - } - - if (isEvent) { - onEvent(...args); - } - else { - onReply(...args); - } - }); - - messageTransform.on("error", function (error) { - - }); - - messageTransform.on("end",function () { - - }); - - messageTransform.on("close", function () { - - }); - - - data.pipe(messageTransform); - - -} - diff --git a/lib/vim-nb-protocol.js b/lib/vim-nb-protocol.js new file mode 100644 index 0000000..9f92976 --- /dev/null +++ b/lib/vim-nb-protocol.js @@ -0,0 +1,244 @@ +var Transform = require("stream").Transform; +var EventEmitter = require("events").EventEmitter; + +/** + * A Transform Stream implementation that output full VNB protocol messages + * + * @constructor + * @this {VNBMessageParser} + */ +function VNBMessageTransform(opts) { + "use strict"; + Transform.call(this,opts); + + var buffer; + + /** + * Transform stream Write implementation + * @param {string} data Upstream data already converted as string + * @param {string} encoding String encoding (should be utf8, but should be checked upward) + * @param {function} callback Downstream Stream receiving only full VNB messages + **/ + this._transform = function(data, encoding, callback) { + var endpos = data.indexOf("\n"); + + if (endpos > -1) { + if (buffer !== undefined) { + this.push(buffer); + buffer = undefined; + } + + if (endpos > 0) { + this.push(data.substr(0, endpos)); + } + callback(); + + if (endpos < data.length - 1) { + buffer = data.substr(endpos + 1); + } + } + else { + buffer = buffer.concat(data); + } + }; + + /** + * Flush still data. Remaining data is lost since it can only be an incomplete message + * @param {function} callback Callback to call when data has been flushed + **/ + this._flush = function (callback) { + buffer = undefined; + callback(); + }; +} + +/** + * Authentication callback + * @callback authenticationCallback + * @param {string} password Password given by the authenticating client + * @return {bool} Result of the authentication + */ + + +/** + * VNB Client implementation (communication from vim to the netbeans server) + * @constructor + * @this {VNBMessageClient} + * @param {net.Socket} socket Incomming stream + * @param {authenticationCallback} authentication Callback function called when the AUTH message is received + */ +function VNBClient(socket, authentication) { + "use strict"; + + EventEmitter.call(this); + + var self = this; + var callbacks = new Map(); + var currentSeqno = 0; + + //TODO check if this is needed + socket.setEncoding("utf8"); + + var messageTransform = new VNBMessageTransform(); + + messageTransform.on("data",function (message) { + + //The first message must be an authentication one + if (message.startsWith("AUTH ") && authentication(message.substring(message.indexOf(" ") + 1))) { + messageTransform.removeAllListeners("data"); + messageTransform.on("data", function(message) { + + try { + var iName = 0; //Index of the name separator in case of an Event + var iSeqno = 0; //Index of the seqno separator in case of an Event + + //Header of the message + for (var i = 0 ; i < message.length && message[i] !== " "; ++i) { + if (message[i] === ":") { + iName = i; + } + else if (message[i] === "=") { + iSeqno = i; + } + } + + var isEvent = false; + var args = []; + var seqno = 0; + + if (iName > 0 && iSeqno > 0) { + isEvent = true; + args.push("event"); + args.push(Number.parseInt(message.substr(0, iName), 10)); + args.push(message.substring(iName + 1, iSeqno)); + //args.push(Number.parseInt(message.substring(iSeqno + 1, i), 10)); + } + else if (iName === 0 && iSeqno === 0) { + seqno = Number.parseInt(message.substr(0, i), 10); + } + else { + throw "Unknown message type"; + } + + var argStartOffset = ++i; + + //Arguments of the message + for ( ; i < message.length ; ++i) { + if (message[i] === "\"" || message[i] === "!") { + var parsedString = ""; + i += 1; + + while (i < message.length && message[i] !== "\"") { + if (message[i] === "\\") { + i += 1; + + if (i < message.length) { + throw "Unfinished string in message"; + } + else { + switch (message[i]) { + case "n" : + parsedString += "\n"; + break; + case "\\" : + parsedString += "\\"; + break; + case "\"" : + parsedString += "\""; + break; + case "r" : + parsedString += "\r"; + break; + case "t" : + parsedString += "\t"; + break; + default : + throw "Invalid escaped character in string"; + } + } + } + else { + parsedString += message[i]; + } + + ++i; + } + + args.push(parsedString); + + i += 1; + } + else { + while (i < message.length && message[i] !== " ") { + i += 1; + } + + if (i - argStartOffset === 1 && (message[i] === "T" || message[i] === "F")) { + args.push(message[i] === "T"); + } + else { + var argument = message.substr(argStartOffset, i); + + if (Number.NaN(argument)) { + if (argument.contains("/")) { + args.push(argument.split("/")); + } + else { + args.push(argument); + } + } + else { + args.push(Number.parseInt(argument)); + } + } + } + } + + if (isEvent) { + //Emit the event + self.emit(...args); + } + else if (callbacks.has(seqno)) { + callbacks.get(seqno)(...args); + callbacks.delete(seqno); + } + else { + throw "Received reply to unknown function call"; + } + } + catch (ex) { + self.emit("error", ex.message); + } + + }); + } + else { + socket.end(); + } + }); + + messageTransform.on("error", function (error) { + + }); + + messageTransform.on("end",function () { + + }); + + messageTransform.on("close", function () { + + }); + + self.sendCommand = new function (args...) { + + }; + + self.callFunction = new function (args...) { + + }; + + socket.pipe(messageTransform); +} + + +VNBClient.prototype = EventEmitter;