diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..71efec2 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,17 @@ +engines: + eslint: + enabled: true + duplication: + enabled: true + config: + languages: + javascript: + mass_threshold: 65 + shellcheck: + enabled: true +checks: + method-count: + enabled: false +ratings: + paths: + - "*.js" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4eb1a23 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM node:9-alpine + +WORKDIR /app + +RUN apk add python make g++ --update + +ADD package.json ./ +RUN npm install + +ADD src ./src +ADD bin ./bin +RUN npm link + +ENTRYPOINT [ "node", "bin/client.js" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ee134cd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 UnknownSKL (Jim Kroon) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ca8ab17 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +run: build + docker run xbox-smartglass-core-node + +run_discovery: build + docker run xbox-smartglass-core-node -d + +run_boot: build + docker run xbox-smartglass-core-node -b -i 127.0.0.1 -l FD000000000000 + +run_help: build + docker run xbox-smartglass-core-node --help + +build: + docker build -t xbox-smartglass-core-node . diff --git a/README.md b/README.md index d38573a..b3e2c53 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,48 @@ # Xbox-Smartglass-Core-Node NodeJS smartglass library for controlling a Xbox + +## Dependencies + +- NodeJS 9 (X509 package is not compatible with Node 10 yet) +- NPM + +## How to install + +```npm install xbox-smartglass-core-node --save``` + +## How to use + +### Boot the Xbox console + +``` +Smartglass.power_on({ + live_id: 'FD000000000000', // Put your console's live id here (Required) + tries: 4, // Number of packets too issue the boot command (Optional) + ip: '127.0.0.1' // Your consoles ip address (Optional) +}, function(result){ + if(result) + console.log('Device booted successfully'); + else + console.log('Failed to boot device'); +}); +``` + +### Discover consoles on network + +``` +Smartglass.discovery({ + ip: '127.0.0.1' // Your consoles ip address (Optional) +}, function(device, address){ + console.log('- Device found: ' + device.device_name); + console.log('Address: '+ address.address + ':' + address.port); + console.log('LiveID: ' + device.device_certificate.subject.commonName); + console.log('Certificate valid: ' + device.device_certificate.notBefore + ' - ' + device.device_certificate.notAfter); + console.log('Certificate fingerprint: ' + device.device_certificate.fingerPrint); +}); +``` + +## Known Issues + +- Broadcasting does not work properly yet +- Callback when sending a power_on command always returns true for now. diff --git a/bin/client.js b/bin/client.js new file mode 100755 index 0000000..f0098bb --- /dev/null +++ b/bin/client.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node +var Smartglass = require('../src/smartglass'); +var commander = require('commander'); +var assert = require('assert'); +var pkgInfo = require('../package.json'); + +const ADDR_BROADCAST = '255.255.255.255'; + +console.log('Xbox-Smartglass v'+pkgInfo['version']+' ('+pkgInfo['homepage']+')'); + +commander + .usage('[-b -d -s] -i -l [-t ]') + .option('-b, --boot', 'Boot Xbox console') + .option('-d, --discover', 'Discover Xbox on the network') + //.option('-s, --shutdown', 'Shutdown Xbox console') + .option('-i, --ip ', 'Xbox One IP address', ADDR_BROADCAST) + .option('-l, --live_id ', 'Xbox One live id (Example: FD000000000000)') + .option('-t, --tries ', 'Timeout inn seconds (Default: 4)', 4) + .version(pkgInfo['version']) + .parse(process.argv); + +if(process.argv.length <= 2) + commander.help(); + +if(commander.boot == true) +{ + if(commander.live_id == undefined) + console.error('--live_id parameter required'); + else { + console.log('Trying to boot device...'); + Smartglass.power_on({ + live_id: commander.live_id, + tries: commander.tries, + ip: commander.ip + }, function(result){ + if(result) + console.log('Device booted successfully'); + else + console.log('Failed to boot device'); + }); + } + +} else if(commander.discover) +{ + console.log('Trying to discover devices...'); + Smartglass.discovery({ + ip: commander.ip + }, function(device, address){ + console.log('- Device found: ' + device.device_name); + console.log('Address: '+ address.address + ':' + address.port); + console.log('LiveID: ' + device.device_certificate.subject.commonName); + console.log('Certificate valid: ' + device.device_certificate.notBefore + ' - ' + device.device_certificate.notAfter); + console.log('Certificate fingerprint: ' + device.device_certificate.fingerPrint); + }); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fe06fb1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,185 @@ +{ + "name": "xbox-smartglass-core-node", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.2.0.tgz", + "integrity": "sha1-d5wHE1YpUDz2p7fmqrMwSbPDhTw=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "x509": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/x509/-/x509-0.3.3.tgz", + "integrity": "sha512-03sJu1FxG3cKM6xHqDiZqD2Y4yaf50ylPAT0yYpu2TFQ6SFdmiqK7v8pA3koXX8YV6Zdys8/AHCt2wwHGVS8Cg==", + "requires": { + "nan": "2.2.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3f697ea --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "xbox-smartglass-core-node", + "version": "0.1.0", + "description": "NodeJS smartglass library for controlling a Xbox", + "main": "index.js", + "scripts": { + "test": "./node_modules/mocha/bin/mocha tests/" + }, + "repository": { + "type": "git", + "url": "git@github.com:unknownskl/xbox-smartglass-core-node.git" + }, + "author": "UnknownSKL (Jim Kroon)", + "license": "MIT", + "bin": { + "smartglass-client": "./bin/client.js" + }, + "bugs": { + "url": "https://github.com/unknownskl/xbox-smartglass-core-node/issues" + }, + "homepage": "https://github.com/unknownskl/xbox-smartglass-core-node", + "keywords": [ + "xbox", + "xbox one", + "smartglass" + ], + "dependencies": { + "commander": "^2.15.1", + "mocha": "^5.2.0", + "x509": "^0.3.3" + } +} diff --git a/src/packet/structure.js b/src/packet/structure.js new file mode 100644 index 0000000..1fec873 --- /dev/null +++ b/src/packet/structure.js @@ -0,0 +1,80 @@ +module.exports = function(packet) +{ + if(packet == undefined) + packet = new Buffer(''); + + return { + _packet: packet, + _offset: 0, + + writeSGString: function(data) + { + var lengthBuffer = Buffer.allocUnsafe(2); + lengthBuffer.writeUInt16BE(data.length, 0); + + var dataBuffer = new Buffer(data + '\x00'); + + this._add(Buffer.concat([ + lengthBuffer, + dataBuffer + ])); + }, + + readSGString: function(buffer = false) + { + var dataLength = this.readUInt16(); + var data = this._packet.slice(this._offset, this._offset+dataLength); + + this._offset = (this._offset+1+dataLength); + + if(buffer == false) + return data.toString(); + else + return data; + }, + + writeUInt16: function(data) + { + var tempBuffer = Buffer.allocUnsafe(2); + tempBuffer.writeUInt16BE(data, 0); + this._add(tempBuffer); + }, + + readUInt16: function() + { + var data = this._packet.readUInt16BE(this._offset); + this._offset = (this._offset+2); + + return data; + }, + + writeUInt32: function(data) + { + var tempBuffer = Buffer.allocUnsafe(4); + tempBuffer.writeUInt32BE(data, 0); + this._add(tempBuffer); + }, + + readUInt32: function() + { + var data = this._packet.readUInt32BE(this._offset); + this._offset = (this._offset+4); + + return data; + }, + + toBuffer: function() + { + return new Buffer(this._packet); + }, + + /* Private functions */ + _add(data) + { + this._packet = new Buffer.concat([ + this._packet, + data + ]); + } + }; +} diff --git a/src/simplepacket.js b/src/simplepacket.js new file mode 100644 index 0000000..7e1eb91 --- /dev/null +++ b/src/simplepacket.js @@ -0,0 +1,99 @@ +var PacketStructure = require('./packet/structure.js'); + +const x509 = require('x509'); +var EOL = require('os').EOL; + +const PACKET_TYPE_DISCOVERY_REQUEST = new Buffer('dd00', 'hex'); +const PACKET_TYPE_DISCOVERY_RESPONSE = new Buffer('dd01', 'hex'); + +module.exports = { + + power_on: function(live_id) + { + var payload = PacketStructure(); + payload.writeSGString(live_id); + var packet = this._pack(new Buffer('dd02', 'hex'), payload.toBuffer(), new Buffer('0000', 'hex')) + + return packet; + }, + + discovery: function() + { + var payload = PacketStructure(); + + payload.writeUInt32(0x00000000); + payload.writeUInt16('3'); // Client Type: Windows Desktop + payload.writeUInt16('0'); + payload.writeUInt16('2'); + + var packet = this._pack(PACKET_TYPE_DISCOVERY_REQUEST, payload.toBuffer(), new Buffer('0000', 'hex')) + + return packet; + }, + + unpack: function(payload) + { + return this._unpack(payload); + }, + + /* Private functions */ + + _pack: function(type, payload, version = 0) + { + var payloadLength = Buffer.concat([ + new Buffer('00', 'hex'), + new Buffer(String.fromCharCode(payload.length)) + ]); + + return Buffer.concat([type, payloadLength, new Buffer('\x00' + String.fromCharCode(version)), payload]) + }, + + _unpack: function(payload) + { + var type = payload.slice(0,2).toString('hex'); + var payloadLength = payload.readUInt16BE(2); + var version = payload.readUInt16BE(4); + var unpackedPayload = payload.slice(6); + + // console.log('type: '+type); + // console.log('payloadLength: '+payloadLength); + // console.log('version: '+version); + + if(type == PACKET_TYPE_DISCOVERY_RESPONSE.toString('hex')) + { + var recvPayload = PacketStructure(unpackedPayload); + + var deviceFlags = recvPayload.readUInt32(); + var clientType = recvPayload.readUInt16(); + var consoleName = recvPayload.readSGString(); + var udid = recvPayload.readSGString(); + var lastError = recvPayload.readUInt32(); + var certificate = recvPayload.readSGString(true); + + try { + var certInfo = x509.parseCert('-----BEGIN CERTIFICATE-----'+EOL+certificate.toString('base64').match(/.{0,64}/g).join('\n')+'-----END CERTIFICATE-----'); + } + catch(error) + { + var certInfo = { 'error:': error }; + } + + var data = { + device_flags: deviceFlags, + device_type: clientType, + device_name: consoleName, + device_udid: udid, + device_certificate: certInfo, + device_certificate_pem: '-----BEGIN CERTIFICATE-----'+EOL+certificate.toString('base64').match(/.{0,64}/g).join('\n')+'-----END CERTIFICATE-----', + device_certificate_raw: certificate, + last_error: deviceFlags, + } + + return { + type: 'DISCOVERY_RESPONSE', + payload: data + }; + } + return false; + } +} diff --git a/src/smartglass.js b/src/smartglass.js new file mode 100644 index 0000000..dd67c83 --- /dev/null +++ b/src/smartglass.js @@ -0,0 +1,120 @@ +const dgram = require('dgram'); +const SimplePacket = require('./simplepacket'); + +module.exports = { + _consoles: [], + _client: false, + + _on_discovery_response: [], + + discovery: function(options, callback) + { + var client = this._init_client(); + var message = SimplePacket.discovery(); + + this._on_discovery_response.push(function(response, device, smartglass){ + callback(response.payload, device, smartglass); + }.bind(callback)); + + this._send({ + ip: options.ip, + port: 5050 + }, message); + + setTimeout(function(client){ + client._close_client(); + }, 1000, this); + }, + + power_on: function(options, callback) + { + var client = this._init_client(); + var message = SimplePacket.power_on(options.live_id); + + var try_num = 0; + + var sendBoot = function(client, callback) + { + client._send({ + ip: options.ip, + port: 5050 + }, message); + + try_num = try_num+1; + if(try_num <= options.tries) + { + setTimeout(sendBoot, 500, client, callback); + } else { + // console.log('@TODO: Check if console is booted... return true'); + callback(true); + client._close_client(); + } + } + setTimeout(sendBoot, 1000, this, callback); + }, + + getConsoles: function() + { + return this._consoles; + }, + + /* Private functions */ + _receive: function(message, remote, client) + { + var type = message.slice(0,2).toString('hex'); + if(type != 'd00d') + { + // Discovery Response + var response = SimplePacket.unpack(message); + if(response == false) + { + console.log('Warning: UNKNOWN PACKET RECEIVED'); + } else { + var func = '_on_' + response.type.toLowerCase(); + if(this[func] != undefined) + { + for (trigger in this[func]){ + this[func][trigger](response, remote, client); + } + } else { + console.log('Error: UNKNOWN CALLBACK: ' + func); + } + } + } else { + console.log('Encrypted packet not implemented yet'); + } + }, + + _send: function(options, message) + { + if(options.ip == undefined) + console.log('smartglass._send: ip missing'); + + if(options.port == undefined) + console.log('smartglass._send: port missing'); + + this._client.send(message, 0, message.length, options.port, options.ip, function(err, bytes) { + // console.log('Sending packet...'); + }); + }, + + _init_client: function() + { + this._client = dgram.createSocket('udp4'); + this._client.bind(); + // this._client.on("listening", function () { + // //this._client_info = this._client.address(); + // }); + + this._client.on('message', function(message, remote){ + this._receive(message, remote, this); + }.bind(this)); + + return this._client; + }, + + _close_client: function() + { + this._client.close(); + } +} diff --git a/tests/packet_structure.js b/tests/packet_structure.js new file mode 100644 index 0000000..442c9dd --- /dev/null +++ b/tests/packet_structure.js @@ -0,0 +1,51 @@ +var assert = require('assert'); +var PacketStructure = require('../src/packet/structure'); + +describe('packet/structure', function(){ + it('obj._packet should be empty on new instance without parameters', function(){ + var packet = PacketStructure(); + assert.deepStrictEqual(packet._packet, new Buffer('')); + }); + it('obj._packet should be not empty on new instance with parameters', function(){ + var packet = PacketStructure(new Buffer('0x0001')); + assert.deepStrictEqual(packet._packet, new Buffer('0x0001')); + }); + + + describe('test write and read types', function(){ + var lPacket = PacketStructure(); + + it('should write UInt16 to the packet and check packet', function(){ + lPacket.writeUInt16(10); + assert.deepEqual(lPacket._packet, new Buffer('\x00\x0a')); + }.bind(lPacket)); + + it('should write UInt32 to the packet and check packet', function(){ + lPacket.writeUInt32(10); + assert.deepEqual(lPacket._packet, new Buffer('\x00\x0a\x00\x00\x00\x0a')); + }.bind(lPacket)); + + it('should write SGString to the packet and check packet', function(){ + lPacket.writeSGString('test'); + assert.deepEqual(lPacket._packet, new Buffer('\x00\x0a\x00\x00\x00\x0a\x00\x04\x74\x65\x73\x74\x00')); + }.bind(lPacket)); + + it('should read UInt16 from the packet and check value and offset', function(){ + var uint16 = lPacket.readUInt16(10); + assert.equal(uint16, 10); + assert.equal(lPacket._offset, 2); + }.bind(lPacket)); + + it('should read UInt32 from the packet and check value and offset', function(){ + var uint32 = lPacket.readUInt32(10); + assert.equal(uint32, 10); + assert.equal(lPacket._offset, 6); + }.bind(lPacket)); + + it('should read SGString from the packet and check value and offset', function(){ + var sgstring = lPacket.readSGString('test'); + assert.equal(sgstring, 'test'); + assert.equal(lPacket._offset, 13); + }.bind(lPacket)); + }); +}) diff --git a/tests/simplepacket.js b/tests/simplepacket.js new file mode 100644 index 0000000..033bc5e --- /dev/null +++ b/tests/simplepacket.js @@ -0,0 +1,26 @@ +var assert = require('assert'); +var SimplePacket = require('../src/simplepacket'); + +describe('simplepacket', function(){ + describe('power_on()', function(){ + it('should return a poweron packet (FD000000000000)', function(){ + var packet = SimplePacket.power_on('FD000000000000'); + assert.deepStrictEqual(packet, Buffer('dd0200110000000e464430303030303030303030303000', 'hex')); + }); + it('should return a poweron packet (FD123456789123)', function(){ + var packet = SimplePacket.power_on('FD123456789123'); + assert.deepStrictEqual(packet, Buffer('dd0200110000000e464431323334353637383931323300', 'hex')); + }); + it('should return a poweron packet (FD9999999999)', function(){ + var packet = SimplePacket.power_on('FD9999999999'); + assert.deepStrictEqual(packet, Buffer('dd02000f0000000c46443939393939393939393900', 'hex')); + }); + }); + + describe('discovery()', function(){ + it('should return a discovery request packet', function(){ + var packet = SimplePacket.discovery(); + assert.deepStrictEqual(packet, Buffer('dd00000a000000000000000300000002', 'hex')); + }); + }); +}) diff --git a/tests/smartglass.js b/tests/smartglass.js new file mode 100644 index 0000000..845cbac --- /dev/null +++ b/tests/smartglass.js @@ -0,0 +1,41 @@ +var assert = require('assert'); +var Smartglass = require('../src/smartglass'); +var SimplePacket = require('../src/simplepacket'); +var PacketStructure = require('../src/packet/structure'); + +describe('smartglass', function(){ + describe('getConsoles()', function(){ + it('should return an ampty array on init', function(){ + assert(Smartglass.getConsoles().length == 0); + }); + }); + describe('_on_discovery_response()', function(){ + it('should test callback on DISCOVERY_REQUEST packet', function(done){ + var smartglass = Smartglass._on_discovery_response.push(function(payload, device, smartglass){ + assert.equal(payload.payload.device_name, 'Xbox-Smartglass-Test'); + assert.equal(payload.payload.device_udid, 'UUID_TEST'); + assert.equal(payload.payload.device_certificate_raw, 'MOCK_TEST_CERT'); + + done(); + }); + + var remote = { + 'address': '127.0.0.1', + 'port': 5050 + } + + var payload = PacketStructure(); + payload.writeUInt32('6'); + payload.writeUInt16('1'); + payload.writeSGString('Xbox-Smartglass-Test'); + payload.writeSGString('UUID_TEST'); + payload.writeUInt32('0'); + payload.writeSGString('MOCK_TEST_CERT'); + + var message = SimplePacket._pack(new Buffer('dd01', 'hex'), payload.toBuffer()); + + Smartglass._receive(message, remote, smartglass); + + }); + }); +})