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();