diff --git a/index.js b/index.js index df1d477..b31e9c8 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,11 @@ const crypto = require('crypto') -const noble = global.noble = require('noble') -let mac = '00:21:4d:03:20:1b' -const name = 'Smart Light' -const password = '234' -let colors = [ - 0xff, - 0xff, - 0xff, - 0xff -] -let packetCount = Math.random() * 0xffff | 0 -packetCount = 1 -let light = null -let ch = null +const noble = require('noble') + +const nobleReady = new Promise(resolve => + noble.on('stateChange', state => { + state === 'poweredOn' && resolve() + }) +) const range = to => Array(to).fill().map((_, i) => i) @@ -37,20 +30,20 @@ function generateSk (name, password, data1, data2) { return encrypt(key, data) } -function keyEncrypt (name, password, data) { +function encryptKey (name, password, data) { name = Buffer.from(name.padEnd(16, '\u0000')) password = Buffer.from(password.padEnd(16, '\u0000')) const key = [] - ;[].forEach.call(name, (byte, index) => { + key.forEach.call(name, (byte, index) => { key.push(byte ^ password[index]) }) return encrypt(data, key) } +// mutate me mor function encryptPacket (sk, mac, packet) { - let tmp = [mac[0], mac[1], mac[2], mac[3], 0x01, packet[0], packet[1], packet[2], 15, 0, 0, 0, 0, 0, 0, 0] + let tmp = [...mac.slice(0, 4), 0x01, ...packet.slice(0, 3), 15, 0, 0, 0, 0, 0, 0, 0] tmp = encrypt(sk, tmp) - let i = 0 range(15).forEach(i => { tmp[i] = tmp[i] ^ packet[i + 5] @@ -62,7 +55,7 @@ function encryptPacket (sk, mac, packet) { packet[i + 3] = tmp[i] }) - tmp = [0, mac[0], mac[1], mac[2], mac[3], 0x01, packet[0], packet[1], packet[2], 0, 0, 0, 0, 0, 0, 0] + tmp = [0, ...mac.slice(0, 4), 0x01, ...packet.slice(0, 3), 0, 0, 0, 0, 0, 0, 0] tmp2 = [] @@ -77,88 +70,85 @@ function encryptPacket (sk, mac, packet) { return Buffer.from(packet) } -const nobleReady = new Promise(resolve => - noble.on('stateChange', state => { - state === 'poweredOn' && resolve() - }) -) +function connect (light, callback) { + return light.connect(() => callback(light)) +} -global.connect = async function connect () { - global.light = light = await new Promise(resolve => { - noble.on('discover', thing => { - console.log(thing.advertisement.localName, name) - if (thing.advertisement.localName === name) { - console.log('thinging') - mac = thing.address - resolve(thing) +function discover (options, callback) { + if (callback == null) { + callback = options || Function.prototype + options = null + } + options == null && (options = {}) + const {name = 'Smart Light', password = '234', mac} = options + noble.on('discover', thing => { + if (thing.advertisement.localName === name) { + thing.password = password + if (mac) { + mac === thing.address && connect(thing, callback) + } else { + connect(thing, callback) } - }) - noble.startScanning() + } }) - .then(light => new Promise(resolve => light.connect(() => resolve(light)))) - await new Promise(resolve => light.discoverAllServicesAndCharacteristics(() => resolve())) - ch = light.services[1].characteristics[1] - pairch = light.services[1].characteristics[3] - const pairHandle = 0x001b - - // light now has .services each with .characteristics - const data = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0, 0, 0, 0, 0, 0, 0, 0] - const encryptedData = keyEncrypt(name, password, data) - const packet = [0x0c] - .concat(data.slice(0, 8)) - .concat(Array.from(encryptedData).slice(0, 8)) - await new Promise(resolve => pairch.write(new Buffer(packet), true, resolve)) - const received = await new Promise((resolve, reject) => pairch.read((error, data) => - error ? reject(error) : resolve(data) - )) - light.sk = generateSk(name, password, data.slice(0, 8), received.slice(1, 9)) - return light + noble.startScanning() } -global.sendPacket = async function sendPacket (id, command, data) { - const packet = Array(20).fill(0) - packet[0] = packetCount & 0xff - packet[1] = packetCount >> 8 & 0xff - packet[5] = id & 0xff - packet[6] = id & 0xff | 0x80 - packet[7] = command - packet[8] = 0x69 - packet[9] = 0x69 - packet[10] = data[0] - packet[11] = data[1] - packet[12] = data[2] - packet[13] = data[3] - const macArray = mac.split(':') - const macKey = Buffer.from([macArray[5], macArray[4], macArray[3], macArray[2], macArray[1], macArray[0]].map(n => parseInt(n, 16))) - const encryptedPacket = encryptPacket(light.sk, macKey, [...packet]) - packetCount += 1 - if (packetCount > 0xffff) packetCount = 1 - - return new Promise(resolve => ch.write(encryptedPacket, false, resolve)) +function pair (light, callback = Function.prototype) { + let packetCount = Math.random() * 0xffff | 0 + const name = light.advertisement.localName + const password = light.password + const mac = light.address + light.discoverAllServicesAndCharacteristics(() => { + const commandChar = light.services[1].characteristics[1] + const pairChar = light.services[1].characteristics[3] + const data = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0, 0, 0, 0, 0, 0, 0, 0] + const encryptedKey = encryptKey(name, password, data) + const packet = [0x0c] + .concat(data.slice(0, 8)) + .concat([...encryptedKey].slice(0, 8)) + pairChar.write(new Buffer(packet), true, () => { + pairChar.read((error, received) => { + const sk = generateSk(name, password, data.slice(0, 8), received.slice(1, 9)) + callback(function dispatch ([id, command, data], callback = Function.prototype) { + const packet = Array(20).fill(0) + packet[0] = packetCount & 0xff + packet[1] = packetCount >> 8 & 0xff + packet[5] = id & 0xff + packet[6] = id & 0xff | 0x80 + packet[7] = command + packet[8] = 0x69 + packet[9] = 0x69 + packet[10] = data[0] + packet[11] = data[1] + packet[12] = data[2] + packet[13] = data[3] + const macKey = Buffer.from(mac.split(':').slice(0, 6).reverse().map(n => parseInt(n, 16))) + const encryptedPacket = encryptPacket(sk, macKey, [...packet]) + packetCount = packetCount > 0xffff ? 1 : packetCount + 1 + commandChar.write(encryptedPacket, false, callback) + }) + }) + }) + }) } -global.setState = function setState (red, green, blue, brightness) { - colors = [red, green, blue, brightness] - return sendPacket(0xffff, 0xc1, colors) +function sendCommand (command, ...args) { + return [0xffff, command, args] } -function setDefaultState (...colors) { - return sendPacket(0xffff, 0xc4, colors) +// r, g, b, brightness +function setColors (...colors) { + return sendCommand(0xc1, ...colors) } -/// brightness, speed, mode, loop -function setRainbow (...args) { - return sendPacket(0xffff, 0xca, args) +function setDefaultColors (...colors) { + return sendCommand(0xc4, ...colors) } -function setMosquito (brightness) { - return sendPacket(0xffff, 0xcb, [brightness, 0, 0, 0]) +module.exports = { + discover: (...args) => nobleReady.then(() => discover(...args)), + pair, + setColors, + setDefaultColors } - -const byte = () => Math.random() * 0xff | 0 - -nobleReady.then(global.connect).then(() => { - setInterval(() => { - setState(byte(), byte(), byte(), byte()) - }, 300) -})