UNPKG

hap-controller

Version:

Library to implement a HAP (HomeKit) controller

227 lines 8.36 kB
"use strict"; /** * Class to represent a multi-request GATT connection. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const gatt_utils_1 = require("./gatt-utils"); const libsodium_wrappers_1 = __importDefault(require("libsodium-wrappers")); const debug_1 = __importDefault(require("debug")); const queue_1 = require("../../utils/queue"); const debug = (0, debug_1.default)('hap-controller:gatt-connection'); class GattConnection extends events_1.EventEmitter { /** * Initialize the GattConnection object. * * @param {Object} peripheral - Peripheral object from noble */ constructor(peripheral) { super(); this.peripheral = peripheral; this.sessionKeys = null; this.a2cCounter = 0; this.c2aCounter = 0; this.queue = new queue_1.OpQueue(); } /** * Queue an operation for the connection. * * @param {function} op - Function to add to the queue * @returns {Promise} Promise which resolves when the function is called. */ _queueOperation(op) { return this.queue.queue(op); } /** * Set the session keys for the connection. * * @param {Object} keys - The session key object obtained from PairingProtocol */ setSessionKeys(keys) { this.sessionKeys = keys; } /** * Get the State of the peripheral connection * Deprecated, please change to "isConnected" * * @returns {Boolean} Connection State * @deprecated */ isPeripheralConnected() { return this.isConnected(); } /** * Get the State of the peripheral connection * * @returns {Boolean} Connection State */ isConnected() { var _a; return ((_a = this.peripheral) === null || _a === void 0 ? void 0 : _a.state) === 'connected'; } /** * Connect to the peripheral if necessary. * * @returns {Promise} Promise which resolves when the connection is * established. */ async connect() { if (this.peripheral.state === 'connected') { return; } if (this.peripheral.state !== 'disconnected') { debug('disconnect peripheral to reconnect'); await new gatt_utils_1.Watcher(this.peripheral, this.peripheral.disconnectAsync()).getPromise(); } debug('connect peripheral'); await new gatt_utils_1.Watcher(this.peripheral, this.peripheral.connectAsync()).getPromise(); this.emit('connected'); this.peripheral.once('disconnect', () => { debug('Peripheral disconnected'); this.emit('disconnected'); }); } /** * Disconnect from the peripheral if necessary. * * @returns {Promise} Promise which resolves when the connection is destroyed. */ async disconnect() { if (this.peripheral.state !== 'disconnected') { debug('disconnect peripheral'); await new gatt_utils_1.Watcher(this.peripheral, this.peripheral.disconnectAsync()).getPromise(); } } /** * Encrypt a series of PDUs. * * @param {Buffer[]} pdus - List of PDUs to encrypt * @returns {Buffer[]} List of encrypted PDUs. */ _encryptPdus(pdus) { const encryptedPdus = []; for (const pdu of pdus) { let position = 0; while (position < pdu.length) { const writeNonce = Buffer.alloc(12); writeNonce.writeUInt32LE(this.c2aCounter++, 4); const frameLength = Math.min(pdu.length - position, 496); const frame = Buffer.from(libsodium_wrappers_1.default.crypto_aead_chacha20poly1305_ietf_encrypt(pdu.slice(position, position + frameLength), null, null, writeNonce, this.sessionKeys.ControllerToAccessoryKey)); encryptedPdus.push(frame); position += frameLength; } } return encryptedPdus; } /** * Decrypt a series of PDUs. * * @param {Buffer} pdu - PDU to decrypt * @returns {Buffer} Decrypted PDU. */ _decryptPdu(pdu) { const readNonce = Buffer.alloc(12); readNonce.writeUInt32LE(this.a2cCounter++, 4); try { const decryptedData = Buffer.from(libsodium_wrappers_1.default.crypto_aead_chacha20poly1305_ietf_decrypt(null, pdu, null, readNonce, this.sessionKeys.AccessoryToControllerKey)); return decryptedData; } catch (e) { return pdu; } } /** * Write a series of PDUs to a characteristic. * * @param {Object} characteristic - Characteristic object to write to * @param {Buffer[]} pdus - List of PDUs to send * @returns {Promise} Promise which resolves to a list of responses when all * writes are sent. */ writeCharacteristic(characteristic, pdus) { return this._queueOperation(async () => { await libsodium_wrappers_1.default.ready; await this.connect(); const queue = new queue_1.OpQueue(); let lastOp = Promise.resolve(); if (this.sessionKeys) { pdus = this._encryptPdus(pdus); } for (const pdu of pdus) { lastOp = queue.queue(async () => { debug(`${this.peripheral.id}/${this.peripheral.address} Write for characteristic ${characteristic.uuid} ${pdu.toString('hex')}`); await new gatt_utils_1.Watcher(this.peripheral, characteristic.writeAsync(pdu, false)).getPromise(); }); } await lastOp; return await this._readCharacteristicInner(characteristic, []); }); } /** * Read a series of PDUs from a characteristic. * * @param {Object} characteristic - Characteristic object to write to * @param {Buffer[]} pdus - List of PDUs already read * @returns {Promise} Promise which resolves to a list of PDUs. */ async _readCharacteristicInner(characteristic, pdus = []) { let data = await new gatt_utils_1.Watcher(this.peripheral, characteristic.readAsync()).getPromise(); if (this.sessionKeys) { data = this._decryptPdu(data); } debug(`${this.peripheral.id}/${this.peripheral.address} Received data for characteristic ${characteristic.uuid} ${data.toString('hex')}`); pdus.push(data); let complete = false; if (!data || data.length === 0) { complete = true; } else { const controlField = data.readUInt8(0); if ((controlField & 0x80) === 0) { // not fragmented or first pdu if (data.length >= 5) { const length = data.readUInt16LE(3); if (length <= data.length - 5) { complete = true; } } else { complete = true; } } else if (pdus.length > 1) { const length = pdus[0].readUInt16LE(3); let totalRead = pdus[0].length - 5; if (pdus.length > 1) { pdus.slice(1).forEach((pdu) => { totalRead += pdu.length - 2; }); } if (totalRead >= length) { complete = true; } } } if (!complete) { return await this._readCharacteristicInner(characteristic, pdus); } return pdus; } /** * Read a series of PDUs from a characteristic. * * @param {Object} characteristic - Characteristic object to write to * @returns {Promise} Promise which resolves to a list of PDUs. */ readCharacteristic(characteristic) { return this._queueOperation(async () => { await this.connect(); return this._readCharacteristicInner(characteristic, []); }); } } exports.default = GattConnection; //# sourceMappingURL=gatt-connection.js.map