From af02f607918d741e5c7d042eaf079ff991b58492 Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 30 Mar 2018 10:27:01 -0500 Subject: [PATCH] Code cleanup Much better organization and ease-of-use now that the proof of concept is established. Still an awful lot to do here, but I'm getting the hang of the whole node.js thing. Also moved the actual test functionality out to a test script so the module itself doesn't execute anything. --- index.js | 238 +++++++++++++++++++++++++++++++++++++------------------ test.js | 18 +++++ 2 files changed, 179 insertions(+), 77 deletions(-) create mode 100755 test.js diff --git a/index.js b/index.js index 37de83f..71b43a6 100644 --- a/index.js +++ b/index.js @@ -1,108 +1,186 @@ -var dgram = require('dgram'); +var dgram = require('dgram'); var net = require('net'); +const SmartBuffer = require('smart-buffer').SmartBuffer; +const EventEmitter = require('events'); -var server = dgram.createSocket("udp4"); -server.bind( function() { - server.setBroadcast(true) - server.setMulticastTTL(128); - broadcastNew(); -}); +class SLMessage extends SmartBuffer { + constructor(senderId, messageId) { + super(); + this.writeUInt16LE(senderId); + this.writeUInt16LE(messageId); -server.on('message', function (message, remote) { - console.log('Got a response.'); - var server = { - address: remote.address, - type: message.readInt32LE(0), - port: message.readInt16LE(8), - gatewayType: message.readUInt8(10), - gatewaySubtype: message.readUInt8(11), - gatewayName: message.toString('utf8', 12, 28) - }; - - console.log(' type: ' + server.type + ', host: ' + server.address + ':' + server.port + ', identified as ' + server.gatewayName); - if (server.type === 2) { - connectTo(server); + this._wroteSize = false; } -}); -function broadcastNew() { - var message = new Uint8Array(8); - message[0] = 1; - server.send(message, 0, message.length, 1444, "255.255.255.255"); - console.log("Looking for ScreenLogic hosts..."); + toBuffer() { + if (this._wroteSize === false) { + this.insertInt32LE(this.length - 4, 4); + this._wroteSize = true; + } else { + this.writeInt32LE(this.length - 8, 4); + } + + return super.toBuffer(); + } + + writeSLString(str) { + this.writeInt32LE(str.length); + this.writeString(str); + this.skip(4 - (str.length % 4)); + } + + writeSLBuffer(buf) { + this.writeInt32LE(buf.length); + this.writeBuffer(buf); + } + + skip(num) { + if (num > 0) { + this.writeBuffer(Buffer.alloc(num)); + } + } } -function connectTo(server) { - console.log("connecting..."); - var client = new net.Socket(); - client.connect(server.port, server.address, function() { +class FindUnits extends EventEmitter { + constructor() { + super(); + this.finder = dgram.createSocket('udp4'); + var _this = this; + this.finder.on('message', function (message, remote) { + _this.foundServer(message, remote); + }).on('close', function() { + console.log('finder closed'); + }); + } + + search() { + var _this = this; + this.finder.bind(function() { + _this.finder.setBroadcast(true); + _this.finder.setMulticastTTL(128); + _this.sendServerBroadcast(); + }); + } + + foundServer(message, remote) { + console.log('Found something'); + var server = { + address: remote.address, + type: message.readInt32LE(0), + port: message.readInt16LE(8), + gatewayType: message.readUInt8(10), + gatewaySubtype: message.readUInt8(11), + gatewayName: message.toString('utf8', 12, 28) + }; + + console.log(' type: ' + server.type + ', host: ' + server.address + ':' + server.port + ', identified as ' + server.gatewayName); + if (server.type === 2) { + this.emit('serverFound', server); + } + } + + sendServerBroadcast() { + var message = new Uint8Array(8); + message[0] = 1; + this.finder.send(message, 0, message.length, 1444, "255.255.255.255"); + console.log("Looking for ScreenLogic hosts..."); + } + + close() { + this.finder.close(); + } +} + +class UnitConnection extends EventEmitter { + constructor(server) { + super(); + this.server = server; + + this.client = new net.Socket(); + var _this = this; + this.client.on('data', function(msg) { + _this.onClientMessage(msg); + }).on('close', function(had_error) { + console.log('unit connection closed'); + }); + } + + close() { + this.client.end(); + } + + connect() { + console.log("connecting..."); + var _this = this; + this.client.connect(this.server.port, this.server.address, function() { + _this.onConnected(); + }); + } + + onConnected() { console.log('connected'); - console.log('sending connection string...'); - var buf = Buffer.from('CONNECTSERVERHOST\r\n\r\n'); - client.write(buf); + console.log('sending init message...'); + this.client.write('CONNECTSERVERHOST\r\n\r\n'); - console.log('sending challenge string...'); - buf = Buffer.alloc(8); - buf.writeUInt16LE(14, 2); - client.write(buf); + console.log('sending challenge message...'); + var buf = new SLMessage(0, 14); + this.client.write(buf.toBuffer()); + } - console.log('sending login string...'); - buf = Buffer.alloc(72); - buf.writeUInt16LE(0); - buf.writeUInt16LE(27, 2); - buf.writeInt32LE(64, 4); // length - buf.writeInt32LE(348, 8); // schema - buf.writeInt32LE(0, 12); // connection type - var name = 'ScreenLogicConnect library'; - buf.writeInt32LE(name.length, 16); - buf.write(name, 20); - var pos = 20 + name.length; - for (var i = 0; i < 4 - (name.length % 4); i++) { - buf.writeUInt8(0, 20 + name.length + i); - pos = pos + 1; - } - buf.writeInt32LE(16, pos); - pos += 4; - pos += 16; - buf.writeInt32LE(2, pos); - client.write(buf); + login() { + console.log('sending login message...'); + var buf = new SLMessage(0, 27); + buf.writeInt32LE(348); // schema + buf.writeInt32LE(0); // connection type + buf.writeSLString('ScreenLogicConnect library'); // version + buf.writeSLBuffer(Buffer.alloc(16)); // encoded password. empty/unused for local connections + buf.writeInt32LE(2); // procID + this.client.write(buf.toBuffer()); + } + getPoolStatus() { console.log('sending pool status query...'); - buf = Buffer.alloc(12); - buf.writeUInt16LE(12526, 2); - buf.writeInt32LE(4, 4); - client.write(buf); + var buf = new SLMessage(0, 12526); + buf.writeInt32LE(0); + this.client.write(buf.toBuffer()); + } + parsePoolStatus(msg) { + // todo: actually parse the message + this.emit('poolStatus', {}); + } + + getControllerConfig() { console.log('sending controller config query...'); - buf = Buffer.alloc(16); - buf.writeUInt16LE(12532, 2); - buf.writeInt32LE(8, 4); - client.write(buf); + var buf = new SLMessage(0, 12532); + buf.writeInt32LE(0); + buf.writeInt32LE(0); + this.client.write(buf.toBuffer()); + } - setTimeout(function() { - console.log('destroying'); - client.destroy(); - }, 3000); - }); + parseControllerConfig(msg) { + // todo: actually parse the message + this.emit('controllerConfig', {}); + } - client.on('close', function() { - console.log('closed'); - }); - - client.on('data', function(msg) { + onClientMessage(msg) { console.log('received message of length ' + msg.length); var msgType = msg.readInt16LE(2); if (msgType === 15) { console.log(" it's a challenge response"); + this.login(); } else if (msgType === 28) { console.log(" it's a login response"); + this.emit('loggedIn'); } else if (msgType === 12527) { console.log(" it's pool status"); + this.parsePoolStatus(msg); } else if (msgType === 12533) { console.log(" it's controller configuration"); + this.parseControllerConfig(msg); } - }); + } } /* debug print full buffer contents: @@ -110,3 +188,9 @@ for (const value of buf.values()) { console.log(value.toString(16)); } */ + +module.exports = { + FindUnits, + SLMessage, + UnitConnection +} diff --git a/test.js b/test.js new file mode 100755 index 0000000..ffa3a9c --- /dev/null +++ b/test.js @@ -0,0 +1,18 @@ +const ScreenLogic = require('./index'); + +var finder = new ScreenLogic.FindUnits(); +finder.on('serverFound', function(server) { + var client = new ScreenLogic.UnitConnection(server); + client.on('loggedIn', function() { + this.getPoolStatus(); + this.getControllerConfig(); + }).on('poolStatus', function(status) { + }).on('controllerConfig', function(config) { + client.close(); + finder.close(); + }); + + client.connect(); +}); + +finder.search();