From 7b050e02d6dea55767322e83b53d712310d4a8fb Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Fri, 23 Jun 2023 23:48:25 +0200 Subject: [PATCH] Change to ES6 Classes (#40) * Change to ES6 Classes * fix merge --- index.js | 2 +- lib/bleno.js | 345 ++++---- lib/characteristic.js | 133 +-- lib/descriptor.js | 26 +- lib/hci-socket/acl-stream.js | 59 +- lib/hci-socket/bindings.js | 301 +++---- lib/hci-socket/crypto.js | 26 +- lib/hci-socket/gap.js | 293 ++++--- lib/hci-socket/gatt.js | 1579 +++++++++++++++++----------------- lib/hci-socket/hci.js | 1216 +++++++++++++------------- lib/hci-socket/mgmt.js | 119 ++- lib/hci-socket/smp.js | 257 +++--- lib/primary-service.js | 32 +- test-ibeacon.js | 2 +- test-matter.js | 162 ++++ test.js | 322 +++---- test/test-characteristic.js | 72 +- test/test-descriptor.js | 16 +- test/test-primary-service.js | 12 +- 19 files changed, 2559 insertions(+), 2415 deletions(-) create mode 100644 test-matter.js diff --git a/index.js b/index.js index 0a299ad8..2fb8457a 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,3 @@ -var Bleno = require('./lib/bleno'); +const Bleno = require('./lib/bleno'); module.exports = new Bleno(); diff --git a/lib/bleno.js b/lib/bleno.js index 0919ee02..2b176e16 100644 --- a/lib/bleno.js +++ b/lib/bleno.js @@ -1,240 +1,237 @@ -var debug = require('debug')('bleno'); +const debug = require('debug')('bleno'); -var events = require('events'); -var os = require('os'); -var util = require('util'); +const { EventEmitter } = require('events'); +const os = require('os'); -var UuidUtil = require('./uuid-util'); +const UuidUtil = require('./uuid-util'); -var PrimaryService = require('./primary-service'); -var Characteristic = require('./characteristic'); -var Descriptor = require('./descriptor'); +const PrimaryService = require('./primary-service'); +const Characteristic = require('./characteristic'); +const Descriptor = require('./descriptor'); -var bindings = null; +let bindings = null; -var platform = os.platform(); +const platform = os.platform(); if (platform === 'darwin') { bindings = require('./mac/bindings'); } else if (platform === 'linux' || platform === 'freebsd' || platform === 'win32' || platform === 'android') { bindings = require('./hci-socket/bindings'); } else { - throw new Error('Unsupported platform'); + throw new Error('Unsupported platform ' + platform); } -function Bleno() { - this.initialized = false; - this.platform = 'unknown'; - this.state = 'unknown'; - this.address = 'unknown'; - this.rssi = 0; - this.mtu = 20; - - this._bindings = bindings; - - this._bindings.on('stateChange', this.onStateChange.bind(this)); - this._bindings.on('platform', this.onPlatform.bind(this)); - this._bindings.on('addressChange', this.onAddressChange.bind(this)); - this._bindings.on('advertisingStart', this.onAdvertisingStart.bind(this)); - this._bindings.on('advertisingStop', this.onAdvertisingStop.bind(this)); - this._bindings.on('servicesSet', this.onServicesSet.bind(this)); - this._bindings.on('accept', this.onAccept.bind(this)); - this._bindings.on('mtuChange', this.onMtuChange.bind(this)); - this._bindings.on('disconnect', this.onDisconnect.bind(this)); - - this._bindings.on('rssiUpdate', this.onRssiUpdate.bind(this)); - - this.on('newListener', function(event) { - if (event === 'stateChange' && !this.initialized) { - this._bindings.init(); - - this.initialized = true; - } - }.bind(this)); -} - -util.inherits(Bleno, events.EventEmitter); - -Bleno.prototype.PrimaryService = PrimaryService; -Bleno.prototype.Characteristic = Characteristic; -Bleno.prototype.Descriptor = Descriptor; +class Bleno extends EventEmitter { + constructor() { + super(); + this.initialized = false; + this.platform = 'unknown'; + this.state = 'unknown'; + this.address = 'unknown'; + this.rssi = 0; + this.mtu = 20; + + this._bindings = bindings; + + this._bindings.on('stateChange', this.onStateChange.bind(this)); + this._bindings.on('platform', this.onPlatform.bind(this)); + this._bindings.on('addressChange', this.onAddressChange.bind(this)); + this._bindings.on('advertisingStart', this.onAdvertisingStart.bind(this)); + this._bindings.on('advertisingStop', this.onAdvertisingStop.bind(this)); + this._bindings.on('servicesSet', this.onServicesSet.bind(this)); + this._bindings.on('accept', this.onAccept.bind(this)); + this._bindings.on('mtuChange', this.onMtuChange.bind(this)); + this._bindings.on('disconnect', this.onDisconnect.bind(this)); + this._bindings.on('rssiUpdate', this.onRssiUpdate.bind(this)); + + this.on('newListener', (event) => { + if (event === 'stateChange' && !this.initialized) { + this._bindings.init(); + + this.initialized = true; + } + }); + } -Bleno.prototype.onPlatform = function(platform) { - debug('platform ' + platform); + onPlatform(platform) { + debug('platform ' + platform); - this.platform = platform; -}; + this.platform = platform; + } -Bleno.prototype.onStateChange = function(state) { - debug('stateChange ' + state); + onStateChange(state) { + debug('stateChange ' + state); - this.state = state; + this.state = state; - this.emit('stateChange', state); -}; + this.emit('stateChange', state); + } -Bleno.prototype.onAddressChange = function(address) { - debug('addressChange ' + address); + onAddressChange(address) { + debug('addressChange ' + address); - this.address = address; -}; + this.address = address; + } -Bleno.prototype.onAccept = function(clientAddress) { - debug('accept ' + clientAddress); - this.emit('accept', clientAddress); -}; + onAccept(clientAddress) { + debug('accept ' + clientAddress); + this.emit('accept', clientAddress); + } -Bleno.prototype.onMtuChange = function(mtu) { - debug('mtu ' + mtu); + onMtuChange(mtu) { + debug('mtu ' + mtu); - this.mtu = mtu; + this.mtu = mtu; - this.emit('mtuChange', mtu); -}; + this.emit('mtuChange', mtu); + } -Bleno.prototype.onDisconnect = function(clientAddress) { - debug('disconnect ' + clientAddress); - this.emit('disconnect', clientAddress); -}; + onDisconnect(clientAddress) { + debug('disconnect ' + clientAddress); + this.emit('disconnect', clientAddress); + } -Bleno.prototype.startAdvertising = function(name, serviceUuids, callback) { - if (this.state !== 'poweredOn') { - var error = new Error('Could not start advertising, state is ' + this.state + ' (not poweredOn)'); + startAdvertising(name, serviceUuids, callback) { + if (this.state !== 'poweredOn') { + const error = new Error('Could not start advertising, state is ' + this.state + ' (not poweredOn)'); - if (typeof callback === 'function') { - callback(error); + if (typeof callback === 'function') { + callback(error); + } else { + throw error; + } } else { - throw error; - } - } else { - if (callback) { - this.once('advertisingStart', callback); - } + if (typeof callback === 'function') { + this.once('advertisingStart', callback); + } - var undashedServiceUuids = []; + const undashedServiceUuids = []; - if (serviceUuids && serviceUuids.length) { - for (var i = 0; i < serviceUuids.length; i++) { - undashedServiceUuids[i] = UuidUtil.removeDashes(serviceUuids[i]); + if (serviceUuids && serviceUuids.length) { + for (let i = 0; i < serviceUuids.length; i++) { + undashedServiceUuids[i] = UuidUtil.removeDashes(serviceUuids[i]); + } } - } - this._bindings.startAdvertising(name, undashedServiceUuids); + this._bindings.startAdvertising(name, undashedServiceUuids); + } } -}; -Bleno.prototype.startAdvertisingIBeacon = function(uuid, major, minor, measuredPower, callback) { - if (this.state !== 'poweredOn') { - var error = new Error('Could not start advertising, state is ' + this.state + ' (not poweredOn)'); + startAdvertisingIBeacon(uuid, major, minor, measuredPower, callback) { + if (this.state !== 'poweredOn') { + const error = new Error('Could not start advertising, state is ' + this.state + ' (not poweredOn)'); - if (typeof callback === 'function') { - callback(error); + if (typeof callback === 'function') { + callback(error); + } else { + throw error; + } } else { - throw error; - } - } else { - var undashedUuid = UuidUtil.removeDashes(uuid); - var uuidData = Buffer.from(undashedUuid, 'hex'); - var uuidDataLength = uuidData.length; - var iBeaconData = Buffer.alloc(uuidData.length + 5); - - for (var i = 0; i < uuidDataLength; i++) { - iBeaconData[i] = uuidData[i]; - } + const undashedUuid = UuidUtil.removeDashes(uuid); + const uuidData = Buffer.from(undashedUuid, 'hex'); + const uuidDataLength = uuidData.length; + const iBeaconData = Buffer.alloc(uuidData.length + 5); - iBeaconData.writeUInt16BE(major, uuidDataLength); - iBeaconData.writeUInt16BE(minor, uuidDataLength + 2); - iBeaconData.writeInt8(measuredPower, uuidDataLength + 4); - - if (callback) { - this.once('advertisingStart', callback); - } + for (let i = 0; i < uuidDataLength; i++) { + iBeaconData[i] = uuidData[i]; + } - debug('iBeacon data = ' + iBeaconData.toString('hex')); + iBeaconData.writeUInt16BE(major, uuidDataLength); + iBeaconData.writeUInt16BE(minor, uuidDataLength + 2); + iBeaconData.writeInt8(measuredPower, uuidDataLength + 4); - this._bindings.startAdvertisingIBeacon(iBeaconData); - } -}; + if (typeof callback === 'function') { + this.once('advertisingStart', callback); + } -Bleno.prototype.onAdvertisingStart = function(error) { - debug('advertisingStart: ' + error); + debug('iBeacon data = ' + iBeaconData.toString('hex')); - if (error) { - this.emit('advertisingStartError', error); + this._bindings.startAdvertisingIBeacon(iBeaconData); + } } - this.emit('advertisingStart', error); -}; + onAdvertisingStart(error) { + debug('advertisingStart: ' + error); -Bleno.prototype.startAdvertisingWithEIRData = function(advertisementData, scanData, callback) { - if (typeof scanData === 'function') { - callback = scanData; - scanData = null; + if (error) { + this.emit('advertisingStartError', error); + } else { + this.emit('advertisingStart', error); + } } - if (this.state !== 'poweredOn') { - var error = new Error('Could not advertising scanning, state is ' + this.state + ' (not poweredOn)'); + startAdvertisingWithEIRData(advertisementData, scanData, callback) { + if (typeof scanData === 'function') { + callback = scanData; + scanData = null; + } + + if (this.state !== 'poweredOn') { + const error = new Error('Could not advertising scanning, state is ' + this.state + ' (not poweredOn)'); - if (typeof callback === 'function') { - callback(error); + if (typeof callback === 'function') { + callback(error); + } else { + throw error; + } } else { - throw error; - } - } else { - if (callback) { - this.once('advertisingStart', callback); - } + if (typeof callback === 'function') { + this.once('advertisingStart', callback); + } - this._bindings.startAdvertisingWithEIRData(advertisementData, scanData); + this._bindings.startAdvertisingWithEIRData(advertisementData, scanData); + } } -}; -Bleno.prototype.stopAdvertising = function(callback) { - if (callback) { - this.once('advertisingStop', callback); + stopAdvertising(callback) { + if (typeof callback === 'function') { + this.once('advertisingStop', callback); + } + this._bindings.stopAdvertising(); } - this._bindings.stopAdvertising(); -}; -Bleno.prototype.onAdvertisingStop = function() { - debug('advertisingStop'); - this.emit('advertisingStop'); -}; + onAdvertisingStop() { + debug('advertisingStop'); + this.emit('advertisingStop'); + } -Bleno.prototype.setServices = function(services, callback) { - if (callback) { - this.once('servicesSet', callback); + setServices(services, callback) { + if (typeof callback === 'function') { + this.once('servicesSet', callback); + } + this._bindings.setServices(services); } - this._bindings.setServices(services); -}; -Bleno.prototype.onServicesSet = function(error) { - debug('servicesSet'); + onServicesSet(error) { + debug('servicesSet'); - if (error) { - this.emit('servicesSetError', error); + if (error) { + this.emit('servicesSetError', error); + } else { + this.emit('servicesSet', error); + } } - this.emit('servicesSet', error); -}; + disconnect() { + debug('disconnect'); + this._bindings.disconnect(); + } -Bleno.prototype.disconnect = function() { - debug('disconnect'); - this._bindings.disconnect(); -}; + updateRssi(callback) { + if (typeof callback === 'function') { + this.once('rssiUpdate', (rssi) => callback(null, rssi)); + } -Bleno.prototype.updateRssi = function(callback) { - if (callback) { - this.once('rssiUpdate', function(rssi) { - callback(null, rssi); - }); + this._bindings.updateRssi(); } - this._bindings.updateRssi(); -}; + onRssiUpdate(rssi) { + this.emit('rssiUpdate', rssi); + } +} -Bleno.prototype.onRssiUpdate = function(rssi) { - this.emit('rssiUpdate', rssi); -}; +Bleno.prototype.PrimaryService = PrimaryService; +Bleno.prototype.Characteristic = Characteristic; +Bleno.prototype.Descriptor = Descriptor; module.exports = Bleno; diff --git a/lib/characteristic.js b/lib/characteristic.js index 60d43a31..759eb904 100644 --- a/lib/characteristic.js +++ b/lib/characteristic.js @@ -1,93 +1,94 @@ -var events = require('events'); -var util = require('util'); +const {EventEmitter} = require('events'); -var debug = require('debug')('characteristic'); +const debug = require('debug')('characteristic'); -var UuidUtil = require('./uuid-util'); +const UuidUtil = require('./uuid-util'); -function Characteristic(options) { - this.uuid = UuidUtil.removeDashes(options.uuid); - this.properties = options.properties || []; - this.secure = options.secure || []; - this.value = options.value || null; - this.descriptors = options.descriptors || []; +class Characteristic extends EventEmitter { + constructor(options) { + super(); - if (this.value && (this.properties.length !== 1 || this.properties[0] !== 'read')) { - throw new Error('Characteristics with value can be read only!'); + this.uuid = UuidUtil.removeDashes(options.uuid); + this.properties = options.properties || []; + this.secure = options.secure || []; + this.value = options.value || null; + this.descriptors = options.descriptors || []; + + if (this.value && (this.properties.length !== 1 || this.properties[0] !== 'read')) { + throw new Error('Characteristics with value can be read only!'); + } + + if (options.onReadRequest) { + this.onReadRequest = options.onReadRequest; + } + + if (options.onWriteRequest) { + this.onWriteRequest = options.onWriteRequest; + } + + if (options.onSubscribe) { + this.onSubscribe = options.onSubscribe; + } + + if (options.onUnsubscribe) { + this.onUnsubscribe = options.onUnsubscribe; + } + + if (options.onNotify) { + this.onNotify = options.onNotify; + } + + if (options.onIndicate) { + this.onIndicate = options.onIndicate; + } + + this.on('readRequest', this.onReadRequest.bind(this)); + this.on('writeRequest', this.onWriteRequest.bind(this)); + this.on('subscribe', this.onSubscribe.bind(this)); + this.on('unsubscribe', this.onUnsubscribe.bind(this)); + this.on('notify', this.onNotify.bind(this)); + this.on('indicate', this.onIndicate.bind(this)); } - if (options.onReadRequest) { - this.onReadRequest = options.onReadRequest; + toString() { + return JSON.stringify({ + uuid: this.uuid, + properties: this.properties, + secure: this.secure, + value: this.value, + descriptors: this.descriptors + }); } - if (options.onWriteRequest) { - this.onWriteRequest = options.onWriteRequest; + onReadRequest(offset, callback) { + callback(this.RESULT_UNLIKELY_ERROR, null); } - if (options.onSubscribe) { - this.onSubscribe = options.onSubscribe; + onWriteRequest(data, offset, withoutResponse, callback) { + callback(this.RESULT_UNLIKELY_ERROR); } - if (options.onUnsubscribe) { - this.onUnsubscribe = options.onUnsubscribe; + onSubscribe(maxValueSize, updateValueCallback) { + this.maxValueSize = maxValueSize; + this.updateValueCallback = updateValueCallback; } - if (options.onNotify) { - this.onNotify = options.onNotify; + onUnsubscribe() { + this.maxValueSize = null; + this.updateValueCallback = null; } - if (options.onIndicate) { - this.onIndicate = options.onIndicate; + onNotify() { } - this.on('readRequest', this.onReadRequest.bind(this)); - this.on('writeRequest', this.onWriteRequest.bind(this)); - this.on('subscribe', this.onSubscribe.bind(this)); - this.on('unsubscribe', this.onUnsubscribe.bind(this)); - this.on('notify', this.onNotify.bind(this)); - this.on('indicate', this.onIndicate.bind(this)); + onIndicate() { + } } -util.inherits(Characteristic, events.EventEmitter); - Characteristic.RESULT_SUCCESS = Characteristic.prototype.RESULT_SUCCESS = 0x00; Characteristic.RESULT_INVALID_OFFSET = Characteristic.prototype.RESULT_INVALID_OFFSET = 0x07; Characteristic.RESULT_ATTR_NOT_LONG = Characteristic.prototype.RESULT_ATTR_NOT_LONG = 0x0b; Characteristic.RESULT_INVALID_ATTRIBUTE_LENGTH = Characteristic.prototype.RESULT_INVALID_ATTRIBUTE_LENGTH = 0x0d; Characteristic.RESULT_UNLIKELY_ERROR = Characteristic.prototype.RESULT_UNLIKELY_ERROR = 0x0e; -Characteristic.prototype.toString = function() { - return JSON.stringify({ - uuid: this.uuid, - properties: this.properties, - secure: this.secure, - value: this.value, - descriptors: this.descriptors - }); -}; - -Characteristic.prototype.onReadRequest = function(offset, callback) { - callback(this.RESULT_UNLIKELY_ERROR, null); -}; - -Characteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) { - callback(this.RESULT_UNLIKELY_ERROR); -}; - -Characteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) { - this.maxValueSize = maxValueSize; - this.updateValueCallback = updateValueCallback; -}; - -Characteristic.prototype.onUnsubscribe = function() { - this.maxValueSize = null; - this.updateValueCallback = null; -}; - -Characteristic.prototype.onNotify = function() { -}; - -Characteristic.prototype.onIndicate = function() { -}; - module.exports = Characteristic; diff --git a/lib/descriptor.js b/lib/descriptor.js index 8dda1c00..35abe222 100644 --- a/lib/descriptor.js +++ b/lib/descriptor.js @@ -1,17 +1,19 @@ -var debug = require('debug')('descriptor'); +const debug = require('debug')('descriptor'); -var UuidUtil = require('./uuid-util'); +const UuidUtil = require('./uuid-util'); -function Descriptor(options) { - this.uuid = UuidUtil.removeDashes(options.uuid); - this.value = options.value || Buffer.alloc(0); -} +class Descriptor { + constructor(options) { + this.uuid = UuidUtil.removeDashes(options.uuid); + this.value = options.value || Buffer.alloc(0); + } -Descriptor.prototype.toString = function() { - return JSON.stringify({ - uuid: this.uuid, - value: Buffer.isBuffer(this.value) ? this.value.toString('hex') : this.value - }); -}; + toString() { + return JSON.stringify({ + uuid: this.uuid, + value: Buffer.isBuffer(this.value) ? this.value.toString('hex') : this.value + }); + } +} module.exports = Descriptor; diff --git a/lib/hci-socket/acl-stream.js b/lib/hci-socket/acl-stream.js index 0fef864f..bf5a230b 100644 --- a/lib/hci-socket/acl-stream.js +++ b/lib/hci-socket/acl-stream.js @@ -1,42 +1,41 @@ -var debug = require('debug')('acl-att-stream'); +const debug = require('debug')('acl-att-stream'); -var events = require('events'); -var util = require('util'); +const { EventEmitter } = require('events'); -var crypto = require('./crypto'); -var Smp = require('./smp'); +const crypto = require('./crypto'); +const Smp = require('./smp'); -var AclStream = function(hci, handle, localAddressType, localAddress, remoteAddressType, remoteAddress) { - this._hci = hci; - this._handle = handle; - this.encypted = false; - - this._smp = new Smp(this, localAddressType, localAddress, remoteAddressType, remoteAddress); -}; - -util.inherits(AclStream, events.EventEmitter); +class AclStream extends EventEmitter { + constructor(hci, handle, localAddressType, localAddress, remoteAddressType, remoteAddress) { + super(); + this._hci = hci; + this._handle = handle; + this.encypted = false; + this._smp = new Smp(this, localAddressType, localAddress, remoteAddressType, remoteAddress); + } -AclStream.prototype.write = function(cid, data) { - this._hci.queueAclDataPkt(this._handle, cid, data); -}; + write(cid, data) { + this._hci.queueAclDataPkt(this._handle, cid, data); + } -AclStream.prototype.push = function(cid, data) { - if (data) { - this.emit('data', cid, data); - } else { - this.emit('end'); + push(cid, data) { + if (data) { + this.emit('data', cid, data); + } else { + this.emit('end'); + } } -}; -AclStream.prototype.pushEncrypt = function(encrypt) { - this.encrypted = encrypt ? true : false; + pushEncrypt(encrypt) { + this.encrypted = !!encrypt; - this.emit('encryptChange', this.encrypted); -}; + this.emit('encryptChange', this.encrypted); + } -AclStream.prototype.pushLtkNegReply = function() { - this.emit('ltkNegReply'); -}; + pushLtkNegReply() { + this.emit('ltkNegReply'); + } +} module.exports = AclStream; diff --git a/lib/hci-socket/bindings.js b/lib/hci-socket/bindings.js index 1135c5dd..5dfea42a 100644 --- a/lib/hci-socket/bindings.js +++ b/lib/hci-socket/bindings.js @@ -1,214 +1,215 @@ -var debug = require('debug')('bindings'); +const debug = require('debug')('bindings'); -var events = require('events'); -var util = require('util'); -var os = require('os'); +const { EventEmitter } = require('events'); +const os = require('os'); -var AclStream = require('./acl-stream'); -var Hci = require('./hci'); -var Gap = require('./gap'); -var Gatt = require('./gatt'); +const AclStream = require('./acl-stream'); +const Hci = require('./hci'); +const Gap = require('./gap'); +const Gatt = require('./gatt'); -var BlenoBindings = function() { - this._state = null; +class BlenoBindings extends EventEmitter { + constructor() { + super(); - this._advertising = false; + this._state = null; - this._hci = new Hci(); - this._gap = new Gap(this._hci); - this._gatt = new Gatt(this._hci); + this._advertising = false; - this._address = null; - this._handle = null; - this._aclStream = null; -}; + this._hci = new Hci(); + this._gap = new Gap(this._hci); + this._gatt = new Gatt(this._hci); -util.inherits(BlenoBindings, events.EventEmitter); + this._address = null; + this._handle = null; + this._aclStream = null; + } -BlenoBindings.prototype.startAdvertising = function(name, serviceUuids) { - this._advertising = true; + startAdvertising(name, serviceUuids) { + this._advertising = true; - this._gap.startAdvertising(name, serviceUuids); -}; + this._gap.startAdvertising(name, serviceUuids); + } -BlenoBindings.prototype.startAdvertisingIBeacon = function(data) { - this._advertising = true; + startAdvertisingIBeacon(data) { + this._advertising = true; - this._gap.startAdvertisingIBeacon(data); -}; + this._gap.startAdvertisingIBeacon(data); + } -BlenoBindings.prototype.startAdvertisingWithEIRData = function(advertisementData, scanData) { - this._advertising = true; + startAdvertisingWithEIRData(advertisementData, scanData) { + this._advertising = true; - this._gap.startAdvertisingWithEIRData(advertisementData, scanData); -}; + this._gap.startAdvertisingWithEIRData(advertisementData, scanData); + } -BlenoBindings.prototype.stopAdvertising = function() { - this._advertising = false; + stopAdvertising() { + this._advertising = false; - this._gap.stopAdvertising(); -}; + this._gap.stopAdvertising(); + } -BlenoBindings.prototype.setServices = function(services) { - this._gatt.setServices(services); + setServices(services) { + this._gatt.setServices(services); - this.emit('servicesSet'); -}; + this.emit('servicesSet'); + } -BlenoBindings.prototype.disconnect = function() { - if (this._handle) { - debug('disconnect by server'); + disconnect() { + if (this._handle) { + debug('disconnect by server'); - this._hci.disconnect(this._handle); + this._hci.disconnect(this._handle); + } } -}; -BlenoBindings.prototype.updateRssi = function() { - if (this._handle) { - this._hci.readRssi(this._handle); + updateRssi() { + if (this._handle) { + this._hci.readRssi(this._handle); + } } -}; -BlenoBindings.prototype.init = function() { - this.onSigIntBinded = this.onSigInt.bind(this); + init() { + this.onSigIntBinded = this.onSigInt.bind(this); - process.on('SIGINT', this.onSigIntBinded); - process.on('exit', this.onExit.bind(this)); + process.on('SIGINT', this.onSigIntBinded); + process.on('exit', this.onExit.bind(this)); - this._gap.on('advertisingStart', this.onAdvertisingStart.bind(this)); - this._gap.on('advertisingStop', this.onAdvertisingStop.bind(this)); + this._gap.on('advertisingStart', this.onAdvertisingStart.bind(this)); + this._gap.on('advertisingStop', this.onAdvertisingStop.bind(this)); - this._gatt.on('mtuChange', this.onMtuChange.bind(this)); + this._gatt.on('mtuChange', this.onMtuChange.bind(this)); - this._hci.on('stateChange', this.onStateChange.bind(this)); - this._hci.on('addressChange', this.onAddressChange.bind(this)); - this._hci.on('readLocalVersion', this.onReadLocalVersion.bind(this)); + this._hci.on('stateChange', this.onStateChange.bind(this)); + this._hci.on('addressChange', this.onAddressChange.bind(this)); + this._hci.on('readLocalVersion', this.onReadLocalVersion.bind(this)); - this._hci.on('leConnComplete', this.onLeConnComplete.bind(this)); - this._hci.on('leConnUpdateComplete', this.onLeConnUpdateComplete.bind(this)); - this._hci.on('rssiRead', this.onRssiRead.bind(this)); - this._hci.on('disconnComplete', this.onDisconnComplete.bind(this)); - this._hci.on('encryptChange', this.onEncryptChange.bind(this)); - this._hci.on('leLtkNegReply', this.onLeLtkNegReply.bind(this)); - this._hci.on('aclDataPkt', this.onAclDataPkt.bind(this)); + this._hci.on('leConnComplete', this.onLeConnComplete.bind(this)); + this._hci.on('leConnUpdateComplete', this.onLeConnUpdateComplete.bind(this)); + this._hci.on('rssiRead', this.onRssiRead.bind(this)); + this._hci.on('disconnComplete', this.onDisconnComplete.bind(this)); + this._hci.on('encryptChange', this.onEncryptChange.bind(this)); + this._hci.on('leLtkNegReply', this.onLeLtkNegReply.bind(this)); + this._hci.on('aclDataPkt', this.onAclDataPkt.bind(this)); - this.emit('platform', os.platform()); + this.emit('platform', os.platform()); - this._hci.init(); -}; + this._hci.init(); + } -BlenoBindings.prototype.onStateChange = function(state) { - if (this._state === state) { - return; + onStateChange(state) { + if (this._state === state) { + return; + } + this._state = state; + + if (state === 'unauthorized') { + console.log('Bleno warning: adapter state unauthorized, please run as root or with sudo'); + console.log(' or see README for information on running without root/sudo:'); + console.log(' https://github.com/abandonware/bleno#running-on-linux'); + } else if (state === 'unsupported') { + console.log('Bleno warning: adapter does not support Bluetooth Low Energy (BLE, Bluetooth Smart).'); + console.log(' Try to run with environment variable:'); + console.log(' [sudo] BLENO_HCI_DEVICE_ID=x node ...'); + } + + this.emit('stateChange', state); } - this._state = state; - if (state === 'unauthorized') { - console.log('bleno warning: adapter state unauthorized, please run as root or with sudo'); - console.log(' or see README for information on running without root/sudo:'); - console.log(' https://github.com/abandonware/bleno#running-on-linux'); - } else if (state === 'unsupported') { - console.log('bleno warning: adapter does not support Bluetooth Low Energy (BLE, Bluetooth Smart).'); - console.log(' Try to run with environment variable:'); - console.log(' [sudo] BLENO_HCI_DEVICE_ID=x node ...'); + onAddressChange(address) { + this.emit('addressChange', address); } - this.emit('stateChange', state); -}; + onReadLocalVersion(hciVer, hciRev, lmpVer, manufacturer, lmpSubVer) { + } -BlenoBindings.prototype.onAddressChange = function(address) { - this.emit('addressChange', address); -}; + onAdvertisingStart(error) { + this.emit('advertisingStart', error); + } -BlenoBindings.prototype.onReadLocalVersion = function(hciVer, hciRev, lmpVer, manufacturer, lmpSubVer) { -}; + onAdvertisingStop() { + this.emit('advertisingStop'); + } -BlenoBindings.prototype.onAdvertisingStart = function(error) { - this.emit('advertisingStart', error); -}; + onLeConnComplete(status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy) { + if (role !== 1) { + // not slave, ignore + return; + } -BlenoBindings.prototype.onAdvertisingStop = function() { - this.emit('advertisingStop'); -}; + this._address = address; + this._handle = handle; + this._aclStream = new AclStream(this._hci, handle, this._hci.addressType, this._hci.address, addressType, address); + this._gatt.setAclStream(this._aclStream); -BlenoBindings.prototype.onLeConnComplete = function(status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy) { - if (role !== 1) { - // not slave, ignore - return; + this.emit('accept', address); } - this._address = address; - this._handle = handle; - this._aclStream = new AclStream(this._hci, handle, this._hci.addressType, this._hci.address, addressType, address); - this._gatt.setAclStream(this._aclStream); - - this.emit('accept', address); -}; + onLeConnUpdateComplete(handle, interval, latency, supervisionTimeout) { + // no-op + } -BlenoBindings.prototype.onLeConnUpdateComplete = function(handle, interval, latency, supervisionTimeout) { - // no-op -}; + onDisconnComplete(handle, reason) { + if (this._aclStream) { + this._aclStream.push(null, null); + } -BlenoBindings.prototype.onDisconnComplete = function(handle, reason) { - if (this._aclStream) { - this._aclStream.push(null, null); - } + const address = this._address; - var address = this._address; + this._address = null; + this._handle = null; + this._aclStream = null; - this._address = null; - this._handle = null; - this._aclStream = null; + if (address) { + this.emit('disconnect', address); // TODO: use reason + } - if (address) { - this.emit('disconnect', address); // TODO: use reason + if (this._advertising) { + this._gap.restartAdvertising(); + } } - if (this._advertising) { - this._gap.restartAdvertising(); + onEncryptChange(handle, encrypt) { + if (this._handle === handle && this._aclStream) { + this._aclStream.pushEncrypt(encrypt); + } } -}; -BlenoBindings.prototype.onEncryptChange = function(handle, encrypt) { - if (this._handle === handle && this._aclStream) { - this._aclStream.pushEncrypt(encrypt); + onLeLtkNegReply(handle) { + if (this._handle === handle && this._aclStream) { + this._aclStream.pushLtkNegReply(); + } } -}; -BlenoBindings.prototype.onLeLtkNegReply = function(handle) { - if (this._handle === handle && this._aclStream) { - this._aclStream.pushLtkNegReply(); + onMtuChange(mtu) { + this.emit('mtuChange', mtu); } -}; - -BlenoBindings.prototype.onMtuChange = function(mtu) { - this.emit('mtuChange', mtu); -}; -BlenoBindings.prototype.onRssiRead = function(handle, rssi) { - this.emit('rssiUpdate', rssi); -}; + onRssiRead(handle, rssi) { + this.emit('rssiUpdate', rssi); + } -BlenoBindings.prototype.onAclDataPkt = function(handle, cid, data) { - if (this._handle === handle && this._aclStream) { - this._aclStream.push(cid, data); + onAclDataPkt(handle, cid, data) { + if (this._handle === handle && this._aclStream) { + this._aclStream.push(cid, data); + } } -}; -BlenoBindings.prototype.onSigInt = function() { - var sigIntListeners = process.listeners('SIGINT'); + onSigInt() { + const sigIntListeners = process.listeners('SIGINT'); - if (sigIntListeners[sigIntListeners.length - 1] === this.onSigIntBinded) { - // we are the last listener, so exit - // this will trigger onExit, and clean up - process.exit(1); + if (sigIntListeners[sigIntListeners.length - 1] === this.onSigIntBinded) { + // we are the last listener, so exit + // this will trigger onExit, and clean up + process.exit(1); + } } -}; -BlenoBindings.prototype.onExit = function() { - this._gap.stopAdvertising(); + onExit() { + this._gap.stopAdvertising(); - this.disconnect(); -}; + this.disconnect(); + } +} module.exports = new BlenoBindings(); diff --git a/lib/hci-socket/crypto.js b/lib/hci-socket/crypto.js index b9300804..11f700a4 100644 --- a/lib/hci-socket/crypto.js +++ b/lib/hci-socket/crypto.js @@ -1,24 +1,24 @@ -var crypto = require('crypto'); +const crypto = require('crypto'); function r() { return crypto.randomBytes(16); } function c1(k, r, pres, preq, iat, ia, rat, ra) { - var p1 = Buffer.concat([ + const p1 = Buffer.concat([ iat, rat, preq, pres ]); - var p2 = Buffer.concat([ + const p2 = Buffer.concat([ ra, ia, Buffer.from('00000000', 'hex') ]); - var res = xor(r, p1); + let res = xor(r, p1); res = e(k, res); res = xor(res, p2); res = e(k, res); @@ -37,7 +37,7 @@ function e(key, data) { key = swap(key); data = swap(data); - var cipher = crypto.createCipheriv('aes-128-ecb', key, ''); + const cipher = crypto.createCipheriv('aes-128-ecb', key, ''); cipher.setAutoPadding(false); return swap(Buffer.concat([ @@ -47,9 +47,9 @@ function e(key, data) { } function xor(b1, b2) { - var result = Buffer.alloc(b1.length); + const result = Buffer.alloc(b1.length); - for (var i = 0; i < b1.length; i++) { + for (let i = 0; i < b1.length; i++) { result[i] = b1[i] ^ b2[i]; } @@ -57,9 +57,9 @@ function xor(b1, b2) { } function swap(input) { - var output = Buffer.alloc(input.length); + const output = Buffer.alloc(input.length); - for (var i = 0; i < output.length; i++) { + for (let i = 0; i < output.length; i++) { output[i] = input[input.length - i - 1]; } @@ -67,8 +67,8 @@ function swap(input) { } module.exports = { - r: r, - c1: c1, - s1: s1, - e: e + r, + c1, + s1, + e }; diff --git a/lib/hci-socket/gap.js b/lib/hci-socket/gap.js index 7cf14734..d08c4112 100644 --- a/lib/hci-socket/gap.js +++ b/lib/hci-socket/gap.js @@ -1,212 +1,211 @@ -var debug = require('debug')('gap'); +const debug = require('debug')('gap'); -var events = require('events'); -var os = require('os'); -var util = require('util'); +const { EventEmitter } = require('events'); +const os = require('os'); -var Hci = require('./hci'); +const Hci = require('./hci'); -var isLinux = (os.platform() === 'linux'); -var isIntelEdison = isLinux && (os.release().indexOf('edison') !== -1); -var isYocto = isLinux && (os.release().indexOf('yocto') !== -1); +const isLinux = (os.platform() === 'linux'); +const isIntelEdison = isLinux && (os.release().includes('edison')); +const isYocto = isLinux && (os.release().includes('yocto')); -function Gap(hci) { - this._hci = hci; +class Gap extends EventEmitter { + constructor(hci) { + super(); - this._advertiseState = null; + this._hci = hci; - this._hci.on('error', this.onHciError.bind(this)); + this._advertiseState = null; - this._hci.on('leAdvertisingParametersSet', this.onHciLeAdvertisingParametersSet.bind(this)); - this._hci.on('leAdvertisingDataSet', this.onHciLeAdvertisingDataSet.bind(this)); - this._hci.on('leScanResponseDataSet', this.onHciLeScanResponseDataSet.bind(this)); - this._hci.on('leAdvertiseEnableSet', this.onHciLeAdvertiseEnableSet.bind(this)); -} + this._hci.on('error', this.onHciError.bind(this)); -util.inherits(Gap, events.EventEmitter); + this._hci.on('leAdvertisingParametersSet', this.onHciLeAdvertisingParametersSet.bind(this)); + this._hci.on('leAdvertisingDataSet', this.onHciLeAdvertisingDataSet.bind(this)); + this._hci.on('leScanResponseDataSet', this.onHciLeScanResponseDataSet.bind(this)); + this._hci.on('leAdvertiseEnableSet', this.onHciLeAdvertiseEnableSet.bind(this)); + } -Gap.prototype.startAdvertising = function(name, serviceUuids) { - debug('startAdvertising: name = ' + name + ', serviceUuids = ' + JSON.stringify(serviceUuids, null, 2)); + startAdvertising(name, serviceUuids) { + debug('startAdvertising: name = ' + name + ', serviceUuids = ' + JSON.stringify(serviceUuids, null, 2)); - var advertisementDataLength = 3; - var scanDataLength = 0; + let advertisementDataLength = 3; + let scanDataLength = 0; - var serviceUuids16bit = []; - var serviceUuids128bit = []; - var i = 0; + const serviceUuids16bit = []; + const serviceUuids128bit = []; - if (name && name.length) { - scanDataLength += 2 + name.length; - } + if (name && name.length) { + scanDataLength += 2 + name.length; + } - if (serviceUuids && serviceUuids.length) { - for (i = 0; i < serviceUuids.length; i++) { - var serviceUuid = Buffer.from(serviceUuids[i].match(/.{1,2}/g).reverse().join(''), 'hex'); + if (serviceUuids && serviceUuids.length) { + for (let i = 0; i < serviceUuids.length; i++) { + const serviceUuid = Buffer.from(serviceUuids[i].match(/.{1,2}/g).reverse().join(''), 'hex'); - if (serviceUuid.length === 2) { - serviceUuids16bit.push(serviceUuid); - } else if (serviceUuid.length === 16) { - serviceUuids128bit.push(serviceUuid); + if (serviceUuid.length === 2) { + serviceUuids16bit.push(serviceUuid); + } else if (serviceUuid.length === 16) { + serviceUuids128bit.push(serviceUuid); + } } } - } - if (serviceUuids16bit.length) { - advertisementDataLength += 2 + 2 * serviceUuids16bit.length; - } + if (serviceUuids16bit.length) { + advertisementDataLength += 2 + 2 * serviceUuids16bit.length; + } - if (serviceUuids128bit.length) { - advertisementDataLength += 2 + 16 * serviceUuids128bit.length; - } + if (serviceUuids128bit.length) { + advertisementDataLength += 2 + 16 * serviceUuids128bit.length; + } - var advertisementData = Buffer.alloc(advertisementDataLength); - var scanData = Buffer.alloc(scanDataLength); + const advertisementData = Buffer.alloc(advertisementDataLength); + const scanData = Buffer.alloc(scanDataLength); - // flags - advertisementData.writeUInt8(2, 0); - advertisementData.writeUInt8(0x01, 1); - advertisementData.writeUInt8(0x06, 2); + // flags + advertisementData.writeUInt8(2, 0); + advertisementData.writeUInt8(0x01, 1); + advertisementData.writeUInt8(0x06, 2); - var advertisementDataOffset = 3; + let advertisementDataOffset = 3; - if (serviceUuids16bit.length) { - advertisementData.writeUInt8(1 + 2 * serviceUuids16bit.length, advertisementDataOffset); - advertisementDataOffset++; + if (serviceUuids16bit.length) { + advertisementData.writeUInt8(1 + 2 * serviceUuids16bit.length, advertisementDataOffset); + advertisementDataOffset++; - advertisementData.writeUInt8(0x03, advertisementDataOffset); - advertisementDataOffset++; + advertisementData.writeUInt8(0x03, advertisementDataOffset); + advertisementDataOffset++; - for (i = 0; i < serviceUuids16bit.length; i++) { - serviceUuids16bit[i].copy(advertisementData, advertisementDataOffset); - advertisementDataOffset += serviceUuids16bit[i].length; + for (let i = 0; i < serviceUuids16bit.length; i++) { + serviceUuids16bit[i].copy(advertisementData, advertisementDataOffset); + advertisementDataOffset += serviceUuids16bit[i].length; + } } - } - if (serviceUuids128bit.length) { - advertisementData.writeUInt8(1 + 16 * serviceUuids128bit.length, advertisementDataOffset); - advertisementDataOffset++; + if (serviceUuids128bit.length) { + advertisementData.writeUInt8(1 + 16 * serviceUuids128bit.length, advertisementDataOffset); + advertisementDataOffset++; - advertisementData.writeUInt8(0x06, advertisementDataOffset); - advertisementDataOffset++; + advertisementData.writeUInt8(0x06, advertisementDataOffset); + advertisementDataOffset++; - for (i = 0; i < serviceUuids128bit.length; i++) { - serviceUuids128bit[i].copy(advertisementData, advertisementDataOffset); - advertisementDataOffset += serviceUuids128bit[i].length; + for (let i = 0; i < serviceUuids128bit.length; i++) { + serviceUuids128bit[i].copy(advertisementData, advertisementDataOffset); + advertisementDataOffset += serviceUuids128bit[i].length; + } } - } - // name - if (name && name.length) { - var nameBuffer = Buffer.from(name); - - scanData.writeUInt8(1 + nameBuffer.length, 0); - scanData.writeUInt8(0x08, 1); - nameBuffer.copy(scanData, 2); - } - - this.startAdvertisingWithEIRData(advertisementData, scanData); -}; + // name + if (name && name.length) { + const nameBuffer = Buffer.from(name); + scanData.writeUInt8(1 + nameBuffer.length, 0); + scanData.writeUInt8(0x08, 1); + nameBuffer.copy(scanData, 2); + } -Gap.prototype.startAdvertisingIBeacon = function(data) { - debug('startAdvertisingIBeacon: data = ' + data.toString('hex')); + this.startAdvertisingWithEIRData(advertisementData, scanData); + } - var dataLength = data.length; - var manufacturerDataLength = 4 + dataLength; - var advertisementDataLength = 5 + manufacturerDataLength; - var scanDataLength = 0; + startAdvertisingIBeacon(data) { + debug('startAdvertisingIBeacon: data = ' + data.toString('hex')); - var advertisementData = Buffer.alloc(advertisementDataLength); - var scanData = Buffer.alloc(0); + const dataLength = data.length; + const manufacturerDataLength = 4 + dataLength; + const advertisementDataLength = 5 + manufacturerDataLength; + const scanDataLength = 0; - // flags - advertisementData.writeUInt8(2, 0); - advertisementData.writeUInt8(0x01, 1); - advertisementData.writeUInt8(0x06, 2); + const advertisementData = Buffer.alloc(advertisementDataLength); + const scanData = Buffer.alloc(0); - advertisementData.writeUInt8(manufacturerDataLength + 1, 3); - advertisementData.writeUInt8(0xff, 4); - advertisementData.writeUInt16LE(0x004c, 5); // Apple Company Identifier LE (16 bit) - advertisementData.writeUInt8(0x02, 7); // type, 2 => iBeacon - advertisementData.writeUInt8(dataLength, 8); + // flags + advertisementData.writeUInt8(2, 0); + advertisementData.writeUInt8(0x01, 1); + advertisementData.writeUInt8(0x06, 2); - data.copy(advertisementData, 9); + advertisementData.writeUInt8(manufacturerDataLength + 1, 3); + advertisementData.writeUInt8(0xff, 4); + advertisementData.writeUInt16LE(0x004c, 5); // Apple Company Identifier LE (16 bit) + advertisementData.writeUInt8(0x02, 7); // type, 2 => iBeacon + advertisementData.writeUInt8(dataLength, 8); - this.startAdvertisingWithEIRData(advertisementData, scanData); -}; + data.copy(advertisementData, 9); -Gap.prototype.startAdvertisingWithEIRData = function(advertisementData, scanData) { - advertisementData = advertisementData || Buffer.alloc(0); - scanData = scanData || Buffer.alloc(0); + this.startAdvertisingWithEIRData(advertisementData, scanData); + } - debug('startAdvertisingWithEIRData: advertisement data = ' + advertisementData.toString('hex') + ', scan data = ' + scanData.toString('hex')); + startAdvertisingWithEIRData(advertisementData, scanData) { + advertisementData = advertisementData || Buffer.alloc(0); + scanData = scanData || Buffer.alloc(0); - var error = null; + debug('startAdvertisingWithEIRData: advertisement data = ' + advertisementData.toString('hex') + ', scan data = ' + scanData.toString('hex')); - if (advertisementData.length > 31) { - error = new Error('Advertisement data is over maximum limit of 31 bytes'); - } else if (scanData.length > 31) { - error = new Error('Scan data is over maximum limit of 31 bytes'); - } + let error = null; - if (error) { - this.emit('advertisingStart', error); - } else { - this._advertiseState = 'starting'; + if (advertisementData.length > 31) { + error = new Error('Advertisement data is over maximum limit of 31 bytes'); + } else if (scanData.length > 31) { + error = new Error('Scan data is over maximum limit of 31 bytes'); + } - if (isIntelEdison || isYocto) { - // work around for Intel Edison - debug('skipping first set of scan response and advertisement data'); + if (error) { + this.emit('advertisingStart', error); } else { + this._advertiseState = 'starting'; + + if (isIntelEdison || isYocto) { + // work around for Intel Edison + debug('skipping first set of scan response and advertisement data'); + } else { + this._hci.setScanResponseData(scanData); + this._hci.setAdvertisingData(advertisementData); + } + this._hci.setAdvertiseEnable(true); this._hci.setScanResponseData(scanData); this._hci.setAdvertisingData(advertisementData); } - this._hci.setAdvertiseEnable(true); - this._hci.setScanResponseData(scanData); - this._hci.setAdvertisingData(advertisementData); } -}; -Gap.prototype.restartAdvertising = function() { - this._advertiseState = 'restarting'; + restartAdvertising() { + this._advertiseState = 'restarting'; - this._hci.setAdvertiseEnable(true); -}; + this._hci.setAdvertiseEnable(true); + } -Gap.prototype.stopAdvertising = function() { - this._advertiseState = 'stopping'; + stopAdvertising() { + this._advertiseState = 'stopping'; - this._hci.setAdvertiseEnable(false); -}; + this._hci.setAdvertiseEnable(false); + } -Gap.prototype.onHciError = function(error) { -}; + onHciError(error) { + } -Gap.prototype.onHciLeAdvertisingParametersSet = function(status) { -}; + onHciLeAdvertisingParametersSet(status) { + } -Gap.prototype.onHciLeAdvertisingDataSet = function(status) { -}; + onHciLeAdvertisingDataSet(status) { + } -Gap.prototype.onHciLeScanResponseDataSet = function(status) { -}; + onHciLeScanResponseDataSet(status) { + } -Gap.prototype.onHciLeAdvertiseEnableSet = function(status) { - if (this._advertiseState === 'starting') { - this._advertiseState = 'started'; + onHciLeAdvertiseEnableSet(status) { + if (this._advertiseState === 'starting') { + this._advertiseState = 'started'; - var error = null; + let error = null; - if (status) { - error = new Error(Hci.STATUS_MAPPER[status] || ('Unknown (' + status + ')')); - } + if (status) { + error = new Error(Hci.STATUS_MAPPER[status] || ('Unknown (' + status + ')')); + } - this.emit('advertisingStart', error); - } else if (this._advertiseState === 'stopping') { - this._advertiseState = 'stopped'; + this.emit('advertisingStart', error); + } else if (this._advertiseState === 'stopping') { + this._advertiseState = 'stopped'; - this.emit('advertisingStop'); + this.emit('advertisingStop'); + } } -}; +} module.exports = Gap; diff --git a/lib/hci-socket/gatt.js b/lib/hci-socket/gatt.js index c5b2fe88..ecc67743 100644 --- a/lib/hci-socket/gatt.js +++ b/lib/hci-socket/gatt.js @@ -1,1047 +1,1032 @@ /*jshint loopfunc: true */ -var debug = require('debug')('gatt'); - -var events = require('events'); -var os = require('os'); -var util = require('util'); - -var ATT_OP_ERROR = 0x01; -var ATT_OP_MTU_REQ = 0x02; -var ATT_OP_MTU_RESP = 0x03; -var ATT_OP_FIND_INFO_REQ = 0x04; -var ATT_OP_FIND_INFO_RESP = 0x05; -var ATT_OP_FIND_BY_TYPE_REQ = 0x06; -var ATT_OP_FIND_BY_TYPE_RESP = 0x07; -var ATT_OP_READ_BY_TYPE_REQ = 0x08; -var ATT_OP_READ_BY_TYPE_RESP = 0x09; -var ATT_OP_READ_REQ = 0x0a; -var ATT_OP_READ_RESP = 0x0b; -var ATT_OP_READ_BLOB_REQ = 0x0c; -var ATT_OP_READ_BLOB_RESP = 0x0d; -var ATT_OP_READ_MULTI_REQ = 0x0e; -var ATT_OP_READ_MULTI_RESP = 0x0f; -var ATT_OP_READ_BY_GROUP_REQ = 0x10; -var ATT_OP_READ_BY_GROUP_RESP = 0x11; -var ATT_OP_WRITE_REQ = 0x12; -var ATT_OP_WRITE_RESP = 0x13; -var ATT_OP_WRITE_CMD = 0x52; -var ATT_OP_PREP_WRITE_REQ = 0x16; -var ATT_OP_PREP_WRITE_RESP = 0x17; -var ATT_OP_EXEC_WRITE_REQ = 0x18; -var ATT_OP_EXEC_WRITE_RESP = 0x19; -var ATT_OP_HANDLE_NOTIFY = 0x1b; -var ATT_OP_HANDLE_IND = 0x1d; -var ATT_OP_HANDLE_CNF = 0x1e; -var ATT_OP_SIGNED_WRITE_CMD = 0xd2; - -var GATT_PRIM_SVC_UUID = 0x2800; -var GATT_INCLUDE_UUID = 0x2802; -var GATT_CHARAC_UUID = 0x2803; - -var GATT_CLIENT_CHARAC_CFG_UUID = 0x2902; -var GATT_SERVER_CHARAC_CFG_UUID = 0x2903; - -var ATT_ECODE_SUCCESS = 0x00; -var ATT_ECODE_INVALID_HANDLE = 0x01; -var ATT_ECODE_READ_NOT_PERM = 0x02; -var ATT_ECODE_WRITE_NOT_PERM = 0x03; -var ATT_ECODE_INVALID_PDU = 0x04; -var ATT_ECODE_AUTHENTICATION = 0x05; -var ATT_ECODE_REQ_NOT_SUPP = 0x06; -var ATT_ECODE_INVALID_OFFSET = 0x07; -var ATT_ECODE_AUTHORIZATION = 0x08; -var ATT_ECODE_PREP_QUEUE_FULL = 0x09; -var ATT_ECODE_ATTR_NOT_FOUND = 0x0a; -var ATT_ECODE_ATTR_NOT_LONG = 0x0b; -var ATT_ECODE_INSUFF_ENCR_KEY_SIZE = 0x0c; -var ATT_ECODE_INVAL_ATTR_VALUE_LEN = 0x0d; -var ATT_ECODE_UNLIKELY = 0x0e; -var ATT_ECODE_INSUFF_ENC = 0x0f; -var ATT_ECODE_UNSUPP_GRP_TYPE = 0x10; -var ATT_ECODE_INSUFF_RESOURCES = 0x11; - -var ATT_CID = 0x0004; - -var Gatt = function() { - this.maxMtu = 256; - this._mtu = 23; - this._preparedWriteRequest = null; - - this.setServices([]); - - this.onAclStreamDataBinded = this.onAclStreamData.bind(this); - this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this); -}; - -util.inherits(Gatt, events.EventEmitter); - -Gatt.prototype.setServices = function(services) { - var deviceName = process.env.BLENO_DEVICE_NAME || os.hostname(); - - // base services and characteristics - var allServices = [ - { - uuid: '1800', - characteristics: [ - { - uuid: '2a00', - properties: ['read'], - secure: [], - value: Buffer.from(deviceName), - descriptors: [] - }, - { - uuid: '2a01', - properties: ['read'], - secure: [], - value: Buffer.from([0x80, 0x00]), - descriptors: [] - } - ] - }, - { - uuid: '1801', - characteristics: [ - { - uuid: '2a05', - properties: ['indicate'], - secure: [], - value: Buffer.from([0x00, 0x00, 0x00, 0x00]), - descriptors: [] - } - ] - } - ].concat(services); +const debug = require('debug')('gatt'); + +const { EventEmitter } = require('events'); +const os = require('os'); + +const ATT_OP_ERROR = 0x01; +const ATT_OP_MTU_REQ = 0x02; +const ATT_OP_MTU_RESP = 0x03; +const ATT_OP_FIND_INFO_REQ = 0x04; +const ATT_OP_FIND_INFO_RESP = 0x05; +const ATT_OP_FIND_BY_TYPE_REQ = 0x06; +const ATT_OP_FIND_BY_TYPE_RESP = 0x07; +const ATT_OP_READ_BY_TYPE_REQ = 0x08; +const ATT_OP_READ_BY_TYPE_RESP = 0x09; +const ATT_OP_READ_REQ = 0x0a; +const ATT_OP_READ_RESP = 0x0b; +const ATT_OP_READ_BLOB_REQ = 0x0c; +const ATT_OP_READ_BLOB_RESP = 0x0d; +const ATT_OP_READ_MULTI_REQ = 0x0e; +const ATT_OP_READ_MULTI_RESP = 0x0f; +const ATT_OP_READ_BY_GROUP_REQ = 0x10; +const ATT_OP_READ_BY_GROUP_RESP = 0x11; +const ATT_OP_WRITE_REQ = 0x12; +const ATT_OP_WRITE_RESP = 0x13; +const ATT_OP_WRITE_CMD = 0x52; +const ATT_OP_PREP_WRITE_REQ = 0x16; +const ATT_OP_PREP_WRITE_RESP = 0x17; +const ATT_OP_EXEC_WRITE_REQ = 0x18; +const ATT_OP_EXEC_WRITE_RESP = 0x19; +const ATT_OP_HANDLE_NOTIFY = 0x1b; +const ATT_OP_HANDLE_IND = 0x1d; +const ATT_OP_HANDLE_CNF = 0x1e; +const ATT_OP_SIGNED_WRITE_CMD = 0xd2; + +const GATT_PRIM_SVC_UUID = 0x2800; +const GATT_INCLUDE_UUID = 0x2802; +const GATT_CHARAC_UUID = 0x2803; + +const GATT_CLIENT_CHARAC_CFG_UUID = 0x2902; +const GATT_SERVER_CHARAC_CFG_UUID = 0x2903; + +const ATT_ECODE_SUCCESS = 0x00; +const ATT_ECODE_INVALID_HANDLE = 0x01; +const ATT_ECODE_READ_NOT_PERM = 0x02; +const ATT_ECODE_WRITE_NOT_PERM = 0x03; +const ATT_ECODE_INVALID_PDU = 0x04; +const ATT_ECODE_AUTHENTICATION = 0x05; +const ATT_ECODE_REQ_NOT_SUPP = 0x06; +const ATT_ECODE_INVALID_OFFSET = 0x07; +const ATT_ECODE_AUTHORIZATION = 0x08; +const ATT_ECODE_PREP_QUEUE_FULL = 0x09; +const ATT_ECODE_ATTR_NOT_FOUND = 0x0a; +const ATT_ECODE_ATTR_NOT_LONG = 0x0b; +const ATT_ECODE_INSUFF_ENCR_KEY_SIZE = 0x0c; +const ATT_ECODE_INVAL_ATTR_VALUE_LEN = 0x0d; +const ATT_ECODE_UNLIKELY = 0x0e; +const ATT_ECODE_INSUFF_ENC = 0x0f; +const ATT_ECODE_UNSUPP_GRP_TYPE = 0x10; +const ATT_ECODE_INSUFF_RESOURCES = 0x11; + +const ATT_CID = 0x0004; + +class Gatt extends EventEmitter { + constructor() { + super(); + + this.maxMtu = 256; + this._mtu = 23; + this._preparedWriteRequest = null; - this._handles = []; + this.setServices([]); - var handle = 0; - var i; - var j; + this.onAclStreamDataBinded = this.onAclStreamData.bind(this); + this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this); + } - for (i = 0; i < allServices.length; i++) { - var service = allServices[i]; + setServices(services) { + const deviceName = process.env.BLENO_DEVICE_NAME || os.hostname(); + + // base services and characteristics + const allServices = [ + { + uuid: '1800', + characteristics: [ + { + uuid: '2a00', + properties: ['read'], + secure: [], + value: Buffer.from(deviceName), + descriptors: [] + }, + { + uuid: '2a01', + properties: ['read'], + secure: [], + value: Buffer.from([0x80, 0x00]), + descriptors: [] + } + ] + }, + { + uuid: '1801', + characteristics: [ + { + uuid: '2a05', + properties: ['indicate'], + secure: [], + value: Buffer.from([0x00, 0x00, 0x00, 0x00]), + descriptors: [] + } + ] + } + ].concat(services); - handle++; - var serviceHandle = handle; + this._handles = []; - this._handles[serviceHandle] = { - type: 'service', - uuid: service.uuid, - attribute: service, - startHandle: serviceHandle - // endHandle filled in below - }; + let handle = 0; - for (j = 0; j < service.characteristics.length; j++) { - var characteristic = service.characteristics[j]; + for (let i = 0; i < allServices.length; i++) { + const service = allServices[i]; - var properties = 0; - var secure = 0; + handle++; + const serviceHandle = handle; + + this._handles[serviceHandle] = { + type: 'service', + uuid: service.uuid, + attribute: service, + startHandle: serviceHandle + // endHandle filled in below + }; - if (characteristic.properties.indexOf('read') !== -1) { - properties |= 0x02; + for (let j = 0; j < service.characteristics.length; j++) { + const characteristic = service.characteristics[j]; - if (characteristic.secure.indexOf('read') !== -1) { - secure |= 0x02; - } - } + let properties = 0; + let secure = 0; - if (characteristic.properties.indexOf('writeWithoutResponse') !== -1) { - properties |= 0x04; + if (characteristic.properties.indexOf('read') !== -1) { + properties |= 0x02; - if (characteristic.secure.indexOf('writeWithoutResponse') !== -1) { - secure |= 0x04; + if (characteristic.secure.indexOf('read') !== -1) { + secure |= 0x02; + } } - } - if (characteristic.properties.indexOf('write') !== -1) { - properties |= 0x08; + if (characteristic.properties.indexOf('writeWithoutResponse') !== -1) { + properties |= 0x04; - if (characteristic.secure.indexOf('write') !== -1) { - secure |= 0x08; + if (characteristic.secure.indexOf('writeWithoutResponse') !== -1) { + secure |= 0x04; + } } - } - if (characteristic.properties.indexOf('notify') !== -1) { - properties |= 0x10; + if (characteristic.properties.indexOf('write') !== -1) { + properties |= 0x08; - if (characteristic.secure.indexOf('notify') !== -1) { - secure |= 0x10; + if (characteristic.secure.indexOf('write') !== -1) { + secure |= 0x08; + } } - } - if (characteristic.properties.indexOf('indicate') !== -1) { - properties |= 0x20; + if (characteristic.properties.indexOf('notify') !== -1) { + properties |= 0x10; - if (characteristic.secure.indexOf('indicate') !== -1) { - secure |= 0x20; + if (characteristic.secure.indexOf('notify') !== -1) { + secure |= 0x10; + } } - } - handle++; - var characteristicHandle = handle; - - handle++; - var characteristicValueHandle = handle; - - this._handles[characteristicHandle] = { - type: 'characteristic', - uuid: characteristic.uuid, - properties: properties, - secure: secure, - attribute: characteristic, - startHandle: characteristicHandle, - valueHandle: characteristicValueHandle - }; + if (characteristic.properties.indexOf('indicate') !== -1) { + properties |= 0x20; - this._handles[characteristicValueHandle] = { - type: 'characteristicValue', - handle: characteristicValueHandle, - value: characteristic.value - }; + if (characteristic.secure.indexOf('indicate') !== -1) { + secure |= 0x20; + } + } - if (properties & 0x30) { // notify or indicate - // add client characteristic configuration descriptor + handle++; + const characteristicHandle = handle; handle++; - var clientCharacteristicConfigurationDescriptorHandle = handle; - this._handles[clientCharacteristicConfigurationDescriptorHandle] = { - type: 'descriptor', - handle: clientCharacteristicConfigurationDescriptorHandle, - uuid: '2902', + const characteristicValueHandle = handle; + + this._handles[characteristicHandle] = { + type: 'characteristic', + uuid: characteristic.uuid, + properties, + secure, attribute: characteristic, - properties: (0x02 | 0x04 | 0x08), // read/write - secure: (secure & 0x10) ? (0x02 | 0x04 | 0x08) : 0, - value: Buffer.from([0x00, 0x00]) + startHandle: characteristicHandle, + valueHandle: characteristicValueHandle }; - } - - for (var k = 0; k < characteristic.descriptors.length; k++) { - var descriptor = characteristic.descriptors[k]; - handle++; - var descriptorHandle = handle; - - this._handles[descriptorHandle] = { - type: 'descriptor', - handle: descriptorHandle, - uuid: descriptor.uuid, - attribute: descriptor, - properties: 0x02, // read only - secure: 0x00, - value: descriptor.value + this._handles[characteristicValueHandle] = { + type: 'characteristicValue', + handle: characteristicValueHandle, + value: characteristic.value }; + + if (properties & 0x30) { // notify or indicate + // add client characteristic configuration descriptor + + handle++; + const clientCharacteristicConfigurationDescriptorHandle = handle; + this._handles[clientCharacteristicConfigurationDescriptorHandle] = { + type: 'descriptor', + handle: clientCharacteristicConfigurationDescriptorHandle, + uuid: '2902', + attribute: characteristic, + properties: (0x02 | 0x04 | 0x08), // read/write + secure: (secure & 0x10) ? (0x02 | 0x04 | 0x08) : 0, + value: Buffer.from([0x00, 0x00]) + }; + } + + for (let k = 0; k < characteristic.descriptors.length; k++) { + const descriptor = characteristic.descriptors[k]; + + handle++; + const descriptorHandle = handle; + + this._handles[descriptorHandle] = { + type: 'descriptor', + handle: descriptorHandle, + uuid: descriptor.uuid, + attribute: descriptor, + properties: 0x02, // read only + secure: 0x00, + value: descriptor.value + }; + } } - } - this._handles[serviceHandle].endHandle = handle; - } + this._handles[serviceHandle].endHandle = handle; + } - var debugHandles = []; - for (i = 0; i < this._handles.length; i++) { - handle = this._handles[i]; + const debugHandles = []; + for (let i = 0; i < this._handles.length; i++) { + handle = this._handles[i]; - debugHandles[i] = {}; - for(j in handle) { - if (Buffer.isBuffer(handle[j])) { - debugHandles[i][j] = handle[j] ? 'Buffer(\'' + handle[j].toString('hex') + '\', \'hex\')' : null; - } else if (j !== 'attribute') { - debugHandles[i][j] = handle[j]; + debugHandles[i] = {}; + for (let j in handle) { + if (Buffer.isBuffer(handle[j])) { + debugHandles[i][j] = handle[j] ? 'Buffer(\'' + handle[j].toString('hex') + '\', \'hex\')' : null; + } else if (j !== 'attribute') { + debugHandles[i][j] = handle[j]; + } } } + + debug('handles = ' + JSON.stringify(debugHandles, null, 2)); } - debug('handles = ' + JSON.stringify(debugHandles, null, 2)); -}; + setAclStream(aclStream) { + this._mtu = 23; + this._preparedWriteRequest = null; -Gatt.prototype.setAclStream = function(aclStream) { - this._mtu = 23; - this._preparedWriteRequest = null; + this._aclStream = aclStream; - this._aclStream = aclStream; + this._aclStream.on('data', this.onAclStreamDataBinded); + this._aclStream.on('end', this.onAclStreamEndBinded); + } - this._aclStream.on('data', this.onAclStreamDataBinded); - this._aclStream.on('end', this.onAclStreamEndBinded); -}; + onAclStreamData(cid, data) { + if (cid !== ATT_CID) { + return; + } -Gatt.prototype.onAclStreamData = function(cid, data) { - if (cid !== ATT_CID) { - return; + this.handleRequest(data); } - this.handleRequest(data); -}; - -Gatt.prototype.onAclStreamEnd = function() { - this._aclStream.removeListener('data', this.onAclStreamDataBinded); - this._aclStream.removeListener('end', this.onAclStreamEndBinded); + onAclStreamEnd() { + this._aclStream.removeListener('data', this.onAclStreamDataBinded); + this._aclStream.removeListener('end', this.onAclStreamEndBinded); - for (var i = 0; i < this._handles.length; i++) { - if (this._handles[i] && this._handles[i].type === 'descriptor' && - this._handles[i].uuid === '2902' && this._handles[i].value.readUInt16LE(0) !== 0) { + for (let i = 0; i < this._handles.length; i++) { + if (this._handles[i] && this._handles[i].type === 'descriptor' && + this._handles[i].uuid === '2902' && this._handles[i].value.readUInt16LE(0) !== 0) { - this._handles[i].value = Buffer.from([0x00, 0x00]); + this._handles[i].value = Buffer.from([0x00, 0x00]); - if (this._handles[i].attribute && this._handles[i].attribute.emit) { - this._handles[i].attribute.emit('unsubscribe'); + if (this._handles[i].attribute && this._handles[i].attribute.emit) { + this._handles[i].attribute.emit('unsubscribe'); + } } } } -}; - -Gatt.prototype.send = function(data) { - debug('send: ' + data.toString('hex')); - this._aclStream.write(ATT_CID, data); -}; - -Gatt.prototype.errorResponse = function(opcode, handle, status) { - var buf = Buffer.alloc(5); - - buf.writeUInt8(ATT_OP_ERROR, 0); - buf.writeUInt8(opcode, 1); - buf.writeUInt16LE(handle, 2); - buf.writeUInt8(status, 4); - - return buf; -}; - -Gatt.prototype.handleRequest = function(request) { - debug('handing request: ' + request.toString('hex')); - - var requestType = request[0]; - var response = null; - - switch(requestType) { - case ATT_OP_MTU_REQ: - response = this.handleMtuRequest(request); - break; - - case ATT_OP_FIND_INFO_REQ: - response = this.handleFindInfoRequest(request); - break; - - case ATT_OP_FIND_BY_TYPE_REQ: - response = this.handleFindByTypeRequest(request); - break; - - case ATT_OP_READ_BY_TYPE_REQ: - response = this.handleReadByTypeRequest(request); - break; - - case ATT_OP_READ_REQ: - case ATT_OP_READ_BLOB_REQ: - response = this.handleReadOrReadBlobRequest(request); - break; - - case ATT_OP_READ_BY_GROUP_REQ: - response = this.handleReadByGroupRequest(request); - break; - - case ATT_OP_WRITE_REQ: - case ATT_OP_WRITE_CMD: - response = this.handleWriteRequestOrCommand(request); - break; - - case ATT_OP_PREP_WRITE_REQ: - response = this.handlePrepareWriteRequest(request); - break; - - case ATT_OP_EXEC_WRITE_REQ: - response = this.handleExecuteWriteRequest(request); - break; - - case ATT_OP_HANDLE_CNF: - response = this.handleConfirmation(request); - break; - - default: - case ATT_OP_READ_MULTI_REQ: - case ATT_OP_SIGNED_WRITE_CMD: - response = this.errorResponse(requestType, 0x0000, ATT_ECODE_REQ_NOT_SUPP); - break; + + send(data) { + debug('send: ' + data.toString('hex')); + this._aclStream.write(ATT_CID, data); } - if (response) { - debug('response: ' + response.toString('hex')); + errorResponse(opcode, handle, status) { + const buf = Buffer.alloc(5); - this.send(response); + buf.writeUInt8(ATT_OP_ERROR, 0); + buf.writeUInt8(opcode, 1); + buf.writeUInt16LE(handle, 2); + buf.writeUInt8(status, 4); + + return buf; } -}; -Gatt.prototype.handleMtuRequest = function(request) { - var mtu = request.readUInt16LE(1); + handleRequest(request) { + debug('handing request: ' + request.toString('hex')); - if (mtu < 23) { - mtu = 23; - } else if (mtu > this.maxMtu) { - mtu = this.maxMtu; - } + const requestType = request[0]; + let response = null; - this._mtu = mtu; + switch (requestType) { + case ATT_OP_MTU_REQ: + response = this.handleMtuRequest(request); + break; - this.emit('mtuChange', this._mtu); + case ATT_OP_FIND_INFO_REQ: + response = this.handleFindInfoRequest(request); + break; - var response = Buffer.alloc(3); + case ATT_OP_FIND_BY_TYPE_REQ: + response = this.handleFindByTypeRequest(request); + break; - response.writeUInt8(ATT_OP_MTU_RESP, 0); - response.writeUInt16LE(mtu, 1); + case ATT_OP_READ_BY_TYPE_REQ: + response = this.handleReadByTypeRequest(request); + break; - return response; -}; + case ATT_OP_READ_REQ: + case ATT_OP_READ_BLOB_REQ: + response = this.handleReadOrReadBlobRequest(request); + break; -Gatt.prototype.handleFindInfoRequest = function(request) { - var response = null; + case ATT_OP_READ_BY_GROUP_REQ: + response = this.handleReadByGroupRequest(request); + break; - var startHandle = request.readUInt16LE(1); - var endHandle = request.readUInt16LE(3); + case ATT_OP_WRITE_REQ: + case ATT_OP_WRITE_CMD: + response = this.handleWriteRequestOrCommand(request); + break; - var infos = []; - var uuid = null; - var i; + case ATT_OP_PREP_WRITE_REQ: + response = this.handlePrepareWriteRequest(request); + break; - for (i = startHandle; i <= endHandle; i++) { - var handle = this._handles[i]; + case ATT_OP_EXEC_WRITE_REQ: + response = this.handleExecuteWriteRequest(request); + break; - if (!handle) { - break; - } + case ATT_OP_HANDLE_CNF: + this.handleConfirmation(request); + break; - uuid = null; - - if ('service' === handle.type) { - uuid = '2800'; - } else if ('includedService' === handle.type) { - uuid = '2802'; - } else if ('characteristic' === handle.type) { - uuid = '2803'; - } else if ('characteristicValue' === handle.type) { - uuid = this._handles[i - 1].uuid; - } else if ('descriptor' === handle.type) { - uuid = handle.uuid; + default: + case ATT_OP_READ_MULTI_REQ: + case ATT_OP_SIGNED_WRITE_CMD: + response = this.errorResponse(requestType, 0x0000, ATT_ECODE_REQ_NOT_SUPP); + break; } - if (uuid) { - infos.push({ - handle: i, - uuid: uuid - }); + if (response) { + debug('response: ' + response.toString('hex')); + + this.send(response); } } - if (infos.length) { - var uuidSize = infos[0].uuid.length / 2; - var numInfo = 1; + handleMtuRequest(request) { + let mtu = request.readUInt16LE(1); - for (i = 1; i < infos.length; i++) { - if (infos[0].uuid.length !== infos[i].uuid.length) { - break; - } - numInfo++; + if (mtu < 23) { + mtu = 23; + } else if (mtu > this.maxMtu) { + mtu = this.maxMtu; } - var lengthPerInfo = (uuidSize === 2) ? 4 : 18; - var maxInfo = Math.floor((this._mtu - 2) / lengthPerInfo); - numInfo = Math.min(numInfo, maxInfo); + this._mtu = mtu; - response = Buffer.alloc(2 + numInfo * lengthPerInfo); + this.emit('mtuChange', this._mtu); - response[0] = ATT_OP_FIND_INFO_RESP; - response[1] = (uuidSize === 2) ? 0x01 : 0x2; + const response = Buffer.alloc(3); - for (i = 0; i < numInfo; i++) { - var info = infos[i]; + response.writeUInt8(ATT_OP_MTU_RESP, 0); + response.writeUInt16LE(mtu, 1); - response.writeUInt16LE(info.handle, 2 + i * lengthPerInfo); - - uuid = Buffer.from(info.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); - for (var j = 0; j < uuid.length; j++) { - response[2 + i * lengthPerInfo + 2 + j] = uuid[j]; - } - } - } else { - response = this.errorResponse(ATT_OP_FIND_INFO_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); + return response; } - return response; -}; - -Gatt.prototype.handleFindByTypeRequest = function(request) { - var response = null; + handleFindInfoRequest(request) { + const startHandle = request.readUInt16LE(1); + const endHandle = request.readUInt16LE(3); - var startHandle = request.readUInt16LE(1); - var endHandle = request.readUInt16LE(3); - var uuid = request.slice(5, 7).toString('hex').match(/.{1,2}/g).reverse().join(''); - var value = request.slice(7).toString('hex').match(/.{1,2}/g).reverse().join(''); + const infos = []; + let uuid = null; - var handles = []; - var handle; + for (let i = startHandle; i <= endHandle; i++) { + const handle = this._handles[i]; - for (var i = startHandle; i <= endHandle; i++) { - handle = this._handles[i]; + if (!handle) { + break; + } - if (!handle) { - break; - } + uuid = null; + + if ('service' === handle.type) { + uuid = '2800'; + } else if ('includedService' === handle.type) { + uuid = '2802'; + } else if ('characteristic' === handle.type) { + uuid = '2803'; + } else if ('characteristicValue' === handle.type) { + uuid = this._handles[i - 1].uuid; + } else if ('descriptor' === handle.type) { + uuid = handle.uuid; + } - if ('2800' === uuid && handle.type === 'service' && handle.uuid === value) { - handles.push({ - start: handle.startHandle, - end: handle.endHandle - }); + if (uuid) { + infos.push({ + handle: i, + uuid: uuid + }); + } } - } - if (handles.length) { - var lengthPerHandle = 4; - var numHandles = handles.length; - var maxHandles = Math.floor((this._mtu - 1) / lengthPerHandle); + if (infos.length) { + const uuidSize = infos[0].uuid.length / 2; + let numInfo = 1; - numHandles = Math.min(numHandles, maxHandles); + for (let i = 1; i < infos.length; i++) { + if (infos[0].uuid.length !== infos[i].uuid.length) { + break; + } + numInfo++; + } - response = Buffer.alloc(1 + numHandles * lengthPerHandle); + const lengthPerInfo = (uuidSize === 2) ? 4 : 18; + const maxInfo = Math.floor((this._mtu - 2) / lengthPerInfo); + numInfo = Math.min(numInfo, maxInfo); - response[0] = ATT_OP_FIND_BY_TYPE_RESP; + const response = Buffer.alloc(2 + numInfo * lengthPerInfo); - for (i = 0; i < numHandles; i++) { - handle = handles[i]; + response[0] = ATT_OP_FIND_INFO_RESP; + response[1] = (uuidSize === 2) ? 0x01 : 0x2; - response.writeUInt16LE(handle.start, 1 + i * lengthPerHandle); - response.writeUInt16LE(handle.end, 1 + i * lengthPerHandle + 2); - } - } else { - response = this.errorResponse(ATT_OP_FIND_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); - } + for (let i = 0; i < numInfo; i++) { + const info = infos[i]; - return response; -}; + response.writeUInt16LE(info.handle, 2 + i * lengthPerInfo); -Gatt.prototype.handleReadByGroupRequest = function(request) { - var response = null; + uuid = Buffer.from(info.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); + for (let j = 0; j < uuid.length; j++) { + response[2 + i * lengthPerInfo + 2 + j] = uuid[j]; + } + } + return response; + } - var startHandle = request.readUInt16LE(1); - var endHandle = request.readUInt16LE(3); - var uuid = request.slice(5).toString('hex').match(/.{1,2}/g).reverse().join(''); + return this.errorResponse(ATT_OP_FIND_INFO_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); + } - debug('read by group: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16)); + handleFindByTypeRequest(request) { + const startHandle = request.readUInt16LE(1); + const endHandle = request.readUInt16LE(3); + const uuid = request.slice(5, 7).toString('hex').match(/.{1,2}/g).reverse().join(''); + const value = request.slice(7).toString('hex').match(/.{1,2}/g).reverse().join(''); - if ('2800' === uuid || '2802' === uuid) { - var services = []; - var type = ('2800' === uuid) ? 'service' : 'includedService'; - var i; + const handles = []; + let handle; - for (i = startHandle; i <= endHandle; i++) { - var handle = this._handles[i]; + for (let i = startHandle; i <= endHandle; i++) { + handle = this._handles[i]; if (!handle) { break; } - if (handle.type === type) { - services.push(handle); + if ('2800' === uuid && handle.type === 'service' && handle.uuid === value) { + handles.push({ + start: handle.startHandle, + end: handle.endHandle + }); } } - if (services.length) { - var uuidSize = services[0].uuid.length / 2; - var numServices = 1; + if (handles.length) { + const lengthPerHandle = 4; + let numHandles = handles.length; + const maxHandles = Math.floor((this._mtu - 1) / lengthPerHandle); - for (i = 1; i < services.length; i++) { - if (services[0].uuid.length !== services[i].uuid.length) { - break; - } - numServices++; + numHandles = Math.min(numHandles, maxHandles); + + const response = Buffer.alloc(1 + numHandles * lengthPerHandle); + + response[0] = ATT_OP_FIND_BY_TYPE_RESP; + + for (let i = 0; i < numHandles; i++) { + handle = handles[i]; + + response.writeUInt16LE(handle.start, 1 + i * lengthPerHandle); + response.writeUInt16LE(handle.end, 1 + i * lengthPerHandle + 2); } + return response; + } + + return this.errorResponse(ATT_OP_FIND_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); + } - var lengthPerService = (uuidSize === 2) ? 6 : 20; - var maxServices = Math.floor((this._mtu - 2) / lengthPerService); - numServices = Math.min(numServices, maxServices); + handleReadByGroupRequest(request) { - response = Buffer.alloc(2 + numServices * lengthPerService); + const startHandle = request.readUInt16LE(1); + const endHandle = request.readUInt16LE(3); + const uuid = request.slice(5).toString('hex').match(/.{1,2}/g).reverse().join(''); - response[0] = ATT_OP_READ_BY_GROUP_RESP; - response[1] = lengthPerService; + debug('read by group: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16)); - for (i = 0; i < numServices; i++) { - var service = services[i]; + if ('2800' === uuid || '2802' === uuid) { + const services = []; + const type = ('2800' === uuid) ? 'service' : 'includedService'; - response.writeUInt16LE(service.startHandle, 2 + i * lengthPerService); - response.writeUInt16LE(service.endHandle, 2 + i * lengthPerService + 2); + for (let i = startHandle; i <= endHandle; i++) { + const handle = this._handles[i]; - var serviceUuid = Buffer.from(service.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); - for (var j = 0; j < serviceUuid.length; j++) { - response[2 + i * lengthPerService + 4 + j] = serviceUuid[j]; + if (!handle) { + break; + } + + if (handle.type === type) { + services.push(handle); } } - } else { - response = this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); - } - } else { - response = this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_UNSUPP_GRP_TYPE); - } - return response; -}; + if (services.length) { + const uuidSize = services[0].uuid.length / 2; + let numServices = 1; + + for (let i = 1; i < services.length; i++) { + if (services[0].uuid.length !== services[i].uuid.length) { + break; + } + numServices++; + } -Gatt.prototype.handleReadByTypeRequest = function(request) { - var response = null; - var requestType = request[0]; + const lengthPerService = (uuidSize === 2) ? 6 : 20; + const maxServices = Math.floor((this._mtu - 2) / lengthPerService); + numServices = Math.min(numServices, maxServices); - var startHandle = request.readUInt16LE(1); - var endHandle = request.readUInt16LE(3); - var uuid = request.slice(5).toString('hex').match(/.{1,2}/g).reverse().join(''); - var i; - var handle; + const response = Buffer.alloc(2 + numServices * lengthPerService); - debug('read by type: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16)); + response[0] = ATT_OP_READ_BY_GROUP_RESP; + response[1] = lengthPerService; - if ('2803' === uuid) { - var characteristics = []; + for (let i = 0; i < numServices; i++) { + const service = services[i]; - for (i = startHandle; i <= endHandle; i++) { - handle = this._handles[i]; + response.writeUInt16LE(service.startHandle, 2 + i * lengthPerService); + response.writeUInt16LE(service.endHandle, 2 + i * lengthPerService + 2); - if (!handle) { - break; + const serviceUuid = Buffer.from(service.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); + for (let j = 0; j < serviceUuid.length; j++) { + response[2 + i * lengthPerService + 4 + j] = serviceUuid[j]; + } + } + return response; } - if (handle.type === 'characteristic') { - characteristics.push(handle); - } + return this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); } - if (characteristics.length) { - var uuidSize = characteristics[0].uuid.length / 2; - var numCharacteristics = 1; + return this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_UNSUPP_GRP_TYPE); + } + + handleReadByTypeRequest(request) { + let response = null; + const requestType = request[0]; + + const startHandle = request.readUInt16LE(1); + const endHandle = request.readUInt16LE(3); + const uuid = request.slice(5).toString('hex').match(/.{1,2}/g).reverse().join(''); + + debug('read by type: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16)); - for (i = 1; i < characteristics.length; i++) { - if (characteristics[0].uuid.length !== characteristics[i].uuid.length) { + if ('2803' === uuid) { + const characteristics = []; + + for (let i = startHandle; i <= endHandle; i++) { + const handle = this._handles[i]; + + if (!handle) { break; } - numCharacteristics++; + + if (handle.type === 'characteristic') { + characteristics.push(handle); + } } - var lengthPerCharacteristic = (uuidSize === 2) ? 7 : 21; - var maxCharacteristics = Math.floor((this._mtu - 2) / lengthPerCharacteristic); - numCharacteristics = Math.min(numCharacteristics, maxCharacteristics); + if (characteristics.length) { + const uuidSize = characteristics[0].uuid.length / 2; + let numCharacteristics = 1; + + for (let i = 1; i < characteristics.length; i++) { + if (characteristics[0].uuid.length !== characteristics[i].uuid.length) { + break; + } + numCharacteristics++; + } + + const lengthPerCharacteristic = (uuidSize === 2) ? 7 : 21; + const maxCharacteristics = Math.floor((this._mtu - 2) / lengthPerCharacteristic); + numCharacteristics = Math.min(numCharacteristics, maxCharacteristics); - response = Buffer.alloc(2 + numCharacteristics * lengthPerCharacteristic); + response = Buffer.alloc(2 + numCharacteristics * lengthPerCharacteristic); - response[0] = ATT_OP_READ_BY_TYPE_RESP; - response[1] = lengthPerCharacteristic; + response[0] = ATT_OP_READ_BY_TYPE_RESP; + response[1] = lengthPerCharacteristic; - for (i = 0; i < numCharacteristics; i++) { - var characteristic = characteristics[i]; + for (let i = 0; i < numCharacteristics; i++) { + const characteristic = characteristics[i]; - response.writeUInt16LE(characteristic.startHandle, 2 + i * lengthPerCharacteristic); - response.writeUInt8(characteristic.properties, 2 + i * lengthPerCharacteristic + 2); - response.writeUInt16LE(characteristic.valueHandle, 2 + i * lengthPerCharacteristic + 3); + response.writeUInt16LE(characteristic.startHandle, 2 + i * lengthPerCharacteristic); + response.writeUInt8(characteristic.properties, 2 + i * lengthPerCharacteristic + 2); + response.writeUInt16LE(characteristic.valueHandle, 2 + i * lengthPerCharacteristic + 3); - var characteristicUuid = Buffer.from(characteristic.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); - for (var j = 0; j < characteristicUuid.length; j++) { - response[2 + i * lengthPerCharacteristic + 5 + j] = characteristicUuid[j]; + const characteristicUuid = Buffer.from(characteristic.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); + for (let j = 0; j < characteristicUuid.length; j++) { + response[2 + i * lengthPerCharacteristic + 5 + j] = characteristicUuid[j]; + } } + } else { + response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); } } else { - response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); - } - } else { - var handleAttribute = null; - var valueHandle = null; - var secure = false; + let handleAttribute = null; + let valueHandle = null; + let secure = false; - for (i = startHandle; i <= endHandle; i++) { - handle = this._handles[i]; + for (let i = startHandle; i <= endHandle; i++) { + const handle = this._handles[i]; - if (!handle) { - break; - } + if (!handle) { + break; + } - if (handle.type === 'characteristic' && handle.uuid === uuid) { - handleAttribute = handle.attribute; - valueHandle = handle.valueHandle; - secure = handle.secure & 0x02; - break; - } else if (handle.type === 'descriptor' && handle.uuid === uuid) { - valueHandle = i; - secure = handle.secure & 0x02; - break; + if (handle.type === 'characteristic' && handle.uuid === uuid) { + handleAttribute = handle.attribute; + valueHandle = handle.valueHandle; + secure = handle.secure & 0x02; + break; + } else if (handle.type === 'descriptor' && handle.uuid === uuid) { + valueHandle = i; + secure = handle.secure & 0x02; + break; + } } - } - if (secure && !this._aclStream.encrypted) { - response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_AUTHENTICATION); - } else if (valueHandle) { - var callback = (function(valueHandle) { - return function(result, data) { - var callbackResponse = null; + if (secure && !this._aclStream.encrypted) { + response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_AUTHENTICATION); + } else if (valueHandle) { + const callback = (function (valueHandle) { + return function (result, data) { + let callbackResponse; - if (ATT_ECODE_SUCCESS === result) { - var dataLength = Math.min(data.length, this._mtu - 4); - callbackResponse = Buffer.alloc(4 + dataLength); - - callbackResponse[0] = ATT_OP_READ_BY_TYPE_RESP; - callbackResponse[1] = dataLength + 2; - callbackResponse.writeUInt16LE(valueHandle, 2); - for (i = 0; i < dataLength; i++) { - callbackResponse[4 + i] = data[i]; + if (ATT_ECODE_SUCCESS === result) { + const dataLength = Math.min(data.length, this._mtu - 4); + callbackResponse = Buffer.alloc(4 + dataLength); + + callbackResponse[0] = ATT_OP_READ_BY_TYPE_RESP; + callbackResponse[1] = dataLength + 2; + callbackResponse.writeUInt16LE(valueHandle, 2); + for (let i = 0; i < dataLength; i++) { + callbackResponse[4 + i] = data[i]; + } + } else { + callbackResponse = this.errorResponse(requestType, valueHandle, result); } - } else { - callbackResponse = this.errorResponse(requestType, valueHandle, result); - } - debug('read by type response: ' + callbackResponse.toString('hex')); + debug('read by type response: ' + callbackResponse.toString('hex')); - this.send(callbackResponse); - }.bind(this); - }.bind(this))(valueHandle); + this.send(callbackResponse); + }.bind(this); + }.bind(this))(valueHandle); - var data = this._handles[valueHandle].value; + const data = this._handles[valueHandle].value; - if (data) { - callback(ATT_ECODE_SUCCESS, data); - } else if (handleAttribute) { - handleAttribute.emit('readRequest', 0, callback); + if (data) { + callback(ATT_ECODE_SUCCESS, data); + } else if (handleAttribute) { + handleAttribute.emit('readRequest', 0, callback); + } else { + callback(ATT_ECODE_UNLIKELY); + } } else { - callback(ATT_ECODE_UNLIKELY); + response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); } - } else { - response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND); } - } - return response; -}; + return response; + } -Gatt.prototype.handleReadOrReadBlobRequest = function(request) { - var response = null; + handleReadOrReadBlobRequest(request) { + let response = null; - var requestType = request[0]; - var valueHandle = request.readUInt16LE(1); - var offset = (requestType === ATT_OP_READ_BLOB_REQ) ? request.readUInt16LE(3) : 0; + const requestType = request[0]; + const valueHandle = request.readUInt16LE(1); + const offset = (requestType === ATT_OP_READ_BLOB_REQ) ? request.readUInt16LE(3) : 0; - var handle = this._handles[valueHandle]; - var i; + const handle = this._handles[valueHandle]; - if (handle) { - var result = null; - var data = null; - var handleType = handle.type; + if (handle) { + let result = null; + let data = null; + const handleType = handle.type; - var callback = (function(requestType, valueHandle) { - return function(result, data) { - var callbackResponse = null; + const callback = (function (requestType, valueHandle) { + return function (result, data) { + let callbackResponse; - if (ATT_ECODE_SUCCESS === result) { - var dataLength = Math.min(data.length, this._mtu - 1); - callbackResponse = Buffer.alloc(1 + dataLength); + if (ATT_ECODE_SUCCESS === result) { + const dataLength = Math.min(data.length, this._mtu - 1); + callbackResponse = Buffer.alloc(1 + dataLength); - callbackResponse[0] = (requestType === ATT_OP_READ_BLOB_REQ) ? ATT_OP_READ_BLOB_RESP : ATT_OP_READ_RESP; - for (i = 0; i < dataLength; i++) { - callbackResponse[1 + i] = data[i]; + callbackResponse[0] = (requestType === ATT_OP_READ_BLOB_REQ) ? ATT_OP_READ_BLOB_RESP : ATT_OP_READ_RESP; + for (let i = 0; i < dataLength; i++) { + callbackResponse[1 + i] = data[i]; + } + } else { + callbackResponse = this.errorResponse(requestType, valueHandle, result); } - } else { - callbackResponse = this.errorResponse(requestType, valueHandle, result); - } - - debug('read response: ' + callbackResponse.toString('hex')); - this.send(callbackResponse); - }.bind(this); - }.bind(this))(requestType, valueHandle); + debug('read response: ' + callbackResponse.toString('hex')); - if (handleType === 'service' || handleType === 'includedService') { - result = ATT_ECODE_SUCCESS; - data = Buffer.from(handle.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); - } else if (handleType === 'characteristic') { - var uuid = Buffer.from(handle.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); + this.send(callbackResponse); + }.bind(this); + }.bind(this))(requestType, valueHandle); - result = ATT_ECODE_SUCCESS; - data = Buffer.alloc(3 + uuid.length); - data.writeUInt8(handle.properties, 0); - data.writeUInt16LE(handle.valueHandle, 1); + if (handleType === 'service' || handleType === 'includedService') { + result = ATT_ECODE_SUCCESS; + data = Buffer.from(handle.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); + } else if (handleType === 'characteristic') { + const uuid = Buffer.from(handle.uuid.match(/.{1,2}/g).reverse().join(''), 'hex'); - for (i = 0; i < uuid.length; i++) { - data[i + 3] = uuid[i]; - } - } else if (handleType === 'characteristicValue' || handleType === 'descriptor') { - var handleProperties = handle.properties; - var handleSecure = handle.secure; - var handleAttribute = handle.attribute; - if (handleType === 'characteristicValue') { - handleProperties = this._handles[valueHandle - 1].properties; - handleSecure = this._handles[valueHandle - 1].secure; - handleAttribute = this._handles[valueHandle - 1].attribute; - } + result = ATT_ECODE_SUCCESS; + data = Buffer.alloc(3 + uuid.length); + data.writeUInt8(handle.properties, 0); + data.writeUInt16LE(handle.valueHandle, 1); - if (handleProperties & 0x02) { - if (handleSecure & 0x02 && !this._aclStream.encrypted) { - result = ATT_ECODE_AUTHENTICATION; - } else { - data = handle.value; + for (let i = 0; i < uuid.length; i++) { + data[i + 3] = uuid[i]; + } + } else if (handleType === 'characteristicValue' || handleType === 'descriptor') { + let handleProperties = handle.properties; + let handleSecure = handle.secure; + let handleAttribute = handle.attribute; + if (handleType === 'characteristicValue') { + handleProperties = this._handles[valueHandle - 1].properties; + handleSecure = this._handles[valueHandle - 1].secure; + handleAttribute = this._handles[valueHandle - 1].attribute; + } - if (data) { - result = ATT_ECODE_SUCCESS; + if (handleProperties & 0x02) { + if (handleSecure & 0x02 && !this._aclStream.encrypted) { + result = ATT_ECODE_AUTHENTICATION; } else { - handleAttribute.emit('readRequest', offset, callback); + data = handle.value; + + if (data) { + result = ATT_ECODE_SUCCESS; + } else { + handleAttribute.emit('readRequest', offset, callback); + } } + } else { + result = ATT_ECODE_READ_NOT_PERM; // non-readable } - } else { - result = ATT_ECODE_READ_NOT_PERM; // non-readable } - } - if (data && typeof data === 'string') { - data = Buffer.from(data); - } + if (data && typeof data === 'string') { + data = Buffer.from(data); + } - if (result === ATT_ECODE_SUCCESS && data && offset) { - if (data.length < offset) { - result = ATT_ECODE_INVALID_OFFSET; - data = null; - } else { - data = data.slice(offset); + if (result === ATT_ECODE_SUCCESS && data && offset) { + if (data.length < offset) { + result = ATT_ECODE_INVALID_OFFSET; + data = null; + } else { + data = data.slice(offset); + } } - } - if (result !== null) { - callback(result, data); + if (result !== null) { + callback(result, data); + } + } else { + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE); } - } else { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE); + + return response; } - return response; -}; + handleWriteRequestOrCommand(request) { + let response = null; -Gatt.prototype.handleWriteRequestOrCommand = function(request) { - var response = null; + const requestType = request[0]; + const withoutResponse = (requestType === ATT_OP_WRITE_CMD); + const valueHandle = request.readUInt16LE(1); + const data = request.slice(3); + const offset = 0; - var requestType = request[0]; - var withoutResponse = (requestType === ATT_OP_WRITE_CMD); - var valueHandle = request.readUInt16LE(1); - var data = request.slice(3); - var offset = 0; + let handle = this._handles[valueHandle]; - var handle = this._handles[valueHandle]; + if (handle) { + if (handle.type === 'characteristicValue') { + handle = this._handles[valueHandle - 1]; + } - if (handle) { - if (handle.type === 'characteristicValue') { - handle = this._handles[valueHandle - 1]; - } + const handleProperties = handle.properties; + const handleSecure = handle.secure; - var handleProperties = handle.properties; - var handleSecure = handle.secure; + if (handleProperties && (withoutResponse ? (handleProperties & 0x04) : (handleProperties & 0x08))) { - if (handleProperties && (withoutResponse ? (handleProperties & 0x04) : (handleProperties & 0x08))) { + const callback = (function (requestType, valueHandle, withoutResponse) { + return function (result) { + if (!withoutResponse) { + let callbackResponse; - var callback = (function(requestType, valueHandle, withoutResponse) { - return function(result) { - if (!withoutResponse) { - var callbackResponse = null; + if (ATT_ECODE_SUCCESS === result) { + callbackResponse = Buffer.from([ATT_OP_WRITE_RESP]); + } else { + callbackResponse = this.errorResponse(requestType, valueHandle, result); + } - if (ATT_ECODE_SUCCESS === result) { - callbackResponse = Buffer.from([ATT_OP_WRITE_RESP]); - } else { - callbackResponse = this.errorResponse(requestType, valueHandle, result); + debug('write response: ' + callbackResponse.toString('hex')); + + this.send(callbackResponse); } + }.bind(this); + }.bind(this))(requestType, valueHandle, withoutResponse); - debug('write response: ' + callbackResponse.toString('hex')); + if (handleSecure & (withoutResponse ? 0x04 : 0x08) && !this._aclStream.encrypted) { + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_AUTHENTICATION); + } else if (handle.type === 'descriptor' || handle.uuid === '2902') { + let result; - this.send(callbackResponse); - } - }.bind(this); - }.bind(this))(requestType, valueHandle, withoutResponse); + if (data.length !== 2) { + result = ATT_ECODE_INVAL_ATTR_VALUE_LEN; + } else { + const value = data.readUInt16LE(0); + const handleAttribute = handle.attribute; - if (handleSecure & (withoutResponse ? 0x04 : 0x08) && !this._aclStream.encrypted) { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_AUTHENTICATION); - } else if (handle.type === 'descriptor' || handle.uuid === '2902') { - var result = null; + handle.value = data; - if (data.length !== 2) { - result = ATT_ECODE_INVAL_ATTR_VALUE_LEN; - } else { - var value = data.readUInt16LE(0); - var handleAttribute = handle.attribute; + if (value & 0x0003) { + const updateValueCallback = (function (valueHandle, attribute) { + return function (data) { + const dataLength = Math.min(data.length, this._mtu - 3); + const useNotify = attribute.properties.indexOf('notify') !== -1; + const useIndicate = attribute.properties.indexOf('indicate') !== -1; - handle.value = data; + if (useNotify) { + const notifyMessage = Buffer.alloc(3 + dataLength); - if (value & 0x0003) { - var updateValueCallback = (function(valueHandle, attribute) { - return function(data) { - var dataLength = Math.min(data.length, this._mtu - 3); - var useNotify = attribute.properties.indexOf('notify') !== -1; - var useIndicate = attribute.properties.indexOf('indicate') !== -1; - var i; + notifyMessage.writeUInt8(ATT_OP_HANDLE_NOTIFY, 0); + notifyMessage.writeUInt16LE(valueHandle, 1); - if (useNotify) { - var notifyMessage = Buffer.alloc(3 + dataLength); + for (let i = 0; i < dataLength; i++) { + notifyMessage[3 + i] = data[i]; + } - notifyMessage.writeUInt8(ATT_OP_HANDLE_NOTIFY, 0); - notifyMessage.writeUInt16LE(valueHandle, 1); + debug('notify message: ' + notifyMessage.toString('hex')); + this.send(notifyMessage); - for (i = 0; i < dataLength; i++) { - notifyMessage[3 + i] = data[i]; - } + attribute.emit('notify'); + } else if (useIndicate) { + const indicateMessage = Buffer.alloc(3 + dataLength); - debug('notify message: ' + notifyMessage.toString('hex')); - this.send(notifyMessage); + indicateMessage.writeUInt8(ATT_OP_HANDLE_IND, 0); + indicateMessage.writeUInt16LE(valueHandle, 1); - attribute.emit('notify'); - } else if (useIndicate) { - var indicateMessage = Buffer.alloc(3 + dataLength); + for (let i = 0; i < dataLength; i++) { + indicateMessage[3 + i] = data[i]; + } - indicateMessage.writeUInt8(ATT_OP_HANDLE_IND, 0); - indicateMessage.writeUInt16LE(valueHandle, 1); + this._lastIndicatedAttribute = attribute; - for (i = 0; i < dataLength; i++) { - indicateMessage[3 + i] = data[i]; + debug('indicate message: ' + indicateMessage.toString('hex')); + this.send(indicateMessage); } + }.bind(this); + }.bind(this))(valueHandle - 1, handleAttribute); - this._lastIndicatedAttribute = attribute; - - debug('indicate message: ' + indicateMessage.toString('hex')); - this.send(indicateMessage); - } - }.bind(this); - }.bind(this))(valueHandle - 1, handleAttribute); - - if (handleAttribute.emit) { - handleAttribute.emit('subscribe', this._mtu - 3, updateValueCallback); - } - } else { - if (handleAttribute.emit) { - handleAttribute.emit('unsubscribe'); + if (handleAttribute.emit) { + handleAttribute.emit('subscribe', this._mtu - 3, updateValueCallback); + } + } else { + if (handleAttribute.emit) { + handleAttribute.emit('unsubscribe'); + } } + + result = ATT_ECODE_SUCCESS; } - result = ATT_ECODE_SUCCESS; + callback(result); + } else { + handle.attribute.emit('writeRequest', data, offset, withoutResponse, callback); } - - callback(result); } else { - handle.attribute.emit('writeRequest', data, offset, withoutResponse, callback); + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM); } } else { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM); + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE); } - } else { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE); - } - - return response; -}; -Gatt.prototype.handlePrepareWriteRequest = function(request) { - var response = null; - - var requestType = request[0]; - var valueHandle = request.readUInt16LE(1); - var offset = request.readUInt16LE(3); - var data = request.slice(5); - - var handle = this._handles[valueHandle]; - - if (handle) { - if (handle.type === 'characteristicValue') { - handle = this._handles[valueHandle - 1]; - - var handleProperties = handle.properties; - var handleSecure = handle.secure; + return response; + } - if (handleProperties && (handleProperties & 0x08)) { - if ((handleSecure & 0x08) && !this._aclStream.encrypted) { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_AUTHENTICATION); - } else if (this._preparedWriteRequest) { - if (this._preparedWriteRequest.handle !== handle) { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_UNLIKELY); - } else if (offset === (this._preparedWriteRequest.offset + this._preparedWriteRequest.data.length)) { - this._preparedWriteRequest.data = Buffer.concat([ - this._preparedWriteRequest.data, + handlePrepareWriteRequest(request) { + let response; + + const requestType = request[0]; + const valueHandle = request.readUInt16LE(1); + const offset = request.readUInt16LE(3); + const data = request.slice(5); + + let handle = this._handles[valueHandle]; + + if (handle) { + if (handle.type === 'characteristicValue') { + handle = this._handles[valueHandle - 1]; + + const handleProperties = handle.properties; + const handleSecure = handle.secure; + + if (handleProperties && (handleProperties & 0x08)) { + if ((handleSecure & 0x08) && !this._aclStream.encrypted) { + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_AUTHENTICATION); + } else if (this._preparedWriteRequest) { + if (this._preparedWriteRequest.handle !== handle) { + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_UNLIKELY); + } else if (offset === (this._preparedWriteRequest.offset + this._preparedWriteRequest.data.length)) { + this._preparedWriteRequest.data = Buffer.concat([ + this._preparedWriteRequest.data, + data + ]); + + response = Buffer.alloc(request.length); + request.copy(response); + response[0] = ATT_OP_PREP_WRITE_RESP; + } else { + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_OFFSET); + } + } else { + this._preparedWriteRequest = { + handle, + valueHandle, + offset, data - ]); + }; response = Buffer.alloc(request.length); request.copy(response); response[0] = ATT_OP_PREP_WRITE_RESP; - } else { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_OFFSET); } } else { - this._preparedWriteRequest = { - handle: handle, - valueHandle: valueHandle, - offset: offset, - data: data - }; - - response = Buffer.alloc(request.length); - request.copy(response); - response[0] = ATT_OP_PREP_WRITE_RESP; + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM); } } else { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM); + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_ATTR_NOT_LONG); } } else { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_ATTR_NOT_LONG); + response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE); } - } else { - response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE); + + return response; } - return response; -}; + handleExecuteWriteRequest(request) { + let response = null; -Gatt.prototype.handleExecuteWriteRequest = function(request) { - var response = null; + const requestType = request[0]; + const flag = request[1]; - var requestType = request[0]; - var flag = request[1]; + if (this._preparedWriteRequest) { + const valueHandle = this._preparedWriteRequest.valueHandle; - if (this._preparedWriteRequest) { - var valueHandle = this._preparedWriteRequest.valueHandle; + if (flag === 0x00) { + response = Buffer.from([ATT_OP_EXEC_WRITE_RESP]); + } else if (flag === 0x01) { + const callback = (function (requestType, valueHandle) { + return function (result) { + let callbackResponse; - if (flag === 0x00) { - response = Buffer.from([ATT_OP_EXEC_WRITE_RESP]); - } else if (flag === 0x01) { - var callback = (function(requestType, valueHandle) { - return function(result) { - var callbackResponse = null; + if (ATT_ECODE_SUCCESS === result) { + callbackResponse = Buffer.from([ATT_OP_EXEC_WRITE_RESP]); + } else { + callbackResponse = this.errorResponse(requestType, valueHandle, result); + } - if (ATT_ECODE_SUCCESS === result) { - callbackResponse = Buffer.from([ATT_OP_EXEC_WRITE_RESP]); - } else { - callbackResponse = this.errorResponse(requestType, valueHandle, result); - } + debug('execute write response: ' + callbackResponse.toString('hex')); - debug('execute write response: ' + callbackResponse.toString('hex')); + this.send(callbackResponse); + }.bind(this); + }.bind(this))(requestType, this._preparedWriteRequest.valueHandle); - this.send(callbackResponse); - }.bind(this); - }.bind(this))(requestType, this._preparedWriteRequest.valueHandle); + this._preparedWriteRequest.handle.attribute.emit('writeRequest', this._preparedWriteRequest.data, this._preparedWriteRequest.offset, false, callback); + } else { + response = this.errorResponse(requestType, 0x0000, ATT_ECODE_UNLIKELY); + } - this._preparedWriteRequest.handle.attribute.emit('writeRequest', this._preparedWriteRequest.data, this._preparedWriteRequest.offset, false, callback); + this._preparedWriteRequest = null; } else { response = this.errorResponse(requestType, 0x0000, ATT_ECODE_UNLIKELY); } - this._preparedWriteRequest = null; - } else { - response = this.errorResponse(requestType, 0x0000, ATT_ECODE_UNLIKELY); + return response; } - return response; -}; + handleConfirmation(request) { + if (this._lastIndicatedAttribute) { + if (this._lastIndicatedAttribute.emit) { + this._lastIndicatedAttribute.emit('indicate'); + } -Gatt.prototype.handleConfirmation = function(request) { - if (this._lastIndicatedAttribute) { - if (this._lastIndicatedAttribute.emit) { - this._lastIndicatedAttribute.emit('indicate'); + this._lastIndicatedAttribute = null; } - - this._lastIndicatedAttribute = null; } -}; +} module.exports = Gatt; diff --git a/lib/hci-socket/hci.js b/lib/hci-socket/hci.js index df55f109..fb50048e 100644 --- a/lib/hci-socket/hci.js +++ b/lib/hci-socket/hci.js @@ -1,775 +1,773 @@ -var debug = require('debug')('hci'); +const debug = require('debug')('hci'); -var events = require('events'); -var util = require('util'); +const {EventEmitter} = require('events'); -var BluetoothHciSocket = require('@abandonware/bluetooth-hci-socket'); +const BluetoothHciSocket = require('@abandonware/bluetooth-hci-socket'); -var HCI_COMMAND_PKT = 0x01; -var HCI_ACLDATA_PKT = 0x02; -var HCI_EVENT_PKT = 0x04; +const HCI_COMMAND_PKT = 0x01; +const HCI_ACLDATA_PKT = 0x02; +const HCI_EVENT_PKT = 0x04; -var ACL_START_NO_FLUSH = 0x00; -var ACL_CONT = 0x01; -var ACL_START = 0x02; +const ACL_START_NO_FLUSH = 0x00; +const ACL_CONT = 0x01; +const ACL_START = 0x02; -var EVT_DISCONN_COMPLETE = 0x05; -var EVT_ENCRYPT_CHANGE = 0x08; -var EVT_CMD_COMPLETE = 0x0e; -var EVT_CMD_STATUS = 0x0f; -var EVT_NUMBER_OF_COMPLETED_PACKETS = 0x13; -var EVT_LE_META_EVENT = 0x3e; +const EVT_DISCONN_COMPLETE = 0x05; +const EVT_ENCRYPT_CHANGE = 0x08; +const EVT_CMD_COMPLETE = 0x0e; +const EVT_CMD_STATUS = 0x0f; +const EVT_NUMBER_OF_COMPLETED_PACKETS = 0x13; +const EVT_LE_META_EVENT = 0x3e; -var EVT_LE_CONN_COMPLETE = 0x01; -var EVT_LE_CONN_UPDATE_COMPLETE = 0x03; +const EVT_LE_CONN_COMPLETE = 0x01; +const EVT_LE_CONN_UPDATE_COMPLETE = 0x03; -var OGF_LINK_CTL = 0x01; -var OCF_DISCONNECT = 0x0006; +const OGF_LINK_CTL = 0x01; +const OCF_DISCONNECT = 0x0006; -var OGF_HOST_CTL = 0x03; -var OCF_SET_EVENT_MASK = 0x0001; -var OCF_RESET = 0x0003; -var OCF_READ_LE_HOST_SUPPORTED = 0x006c; -var OCF_WRITE_LE_HOST_SUPPORTED = 0x006d; +const OGF_HOST_CTL = 0x03; +const OCF_SET_EVENT_MASK = 0x0001; +const OCF_RESET = 0x0003; +const OCF_READ_LE_HOST_SUPPORTED = 0x006c; +const OCF_WRITE_LE_HOST_SUPPORTED = 0x006d; -var OGF_INFO_PARAM = 0x04; -var OCF_READ_LOCAL_VERSION = 0x0001; -var OCF_READ_BUFFER_SIZE = 0x0005; -var OCF_READ_BD_ADDR = 0x0009; +const OGF_INFO_PARAM = 0x04; +const OCF_READ_LOCAL_VERSION = 0x0001; +const OCF_READ_BUFFER_SIZE = 0x0005; +const OCF_READ_BD_ADDR = 0x0009; -var OGF_STATUS_PARAM = 0x05; -var OCF_READ_RSSI = 0x0005; +const OGF_STATUS_PARAM = 0x05; +const OCF_READ_RSSI = 0x0005; -var OGF_LE_CTL = 0x08; -var OCF_LE_SET_EVENT_MASK = 0x0001; -var OCF_LE_READ_BUFFER_SIZE = 0x0002; -var OCF_LE_SET_ADVERTISING_PARAMETERS = 0x0006; -var OCF_LE_SET_ADVERTISING_DATA = 0x0008; -var OCF_LE_SET_SCAN_RESPONSE_DATA = 0x0009; -var OCF_LE_SET_ADVERTISE_ENABLE = 0x000a; -var OCF_LE_LTK_NEG_REPLY = 0x001B; +const OGF_LE_CTL = 0x08; +const OCF_LE_SET_EVENT_MASK = 0x0001; +const OCF_LE_READ_BUFFER_SIZE = 0x0002; +const OCF_LE_SET_ADVERTISING_PARAMETERS = 0x0006; +const OCF_LE_SET_ADVERTISING_DATA = 0x0008; +const OCF_LE_SET_SCAN_RESPONSE_DATA = 0x0009; +const OCF_LE_SET_ADVERTISE_ENABLE = 0x000a; +const OCF_LE_LTK_NEG_REPLY = 0x001B; -var DISCONNECT_CMD = OCF_DISCONNECT | OGF_LINK_CTL << 10; +const DISCONNECT_CMD = OCF_DISCONNECT | OGF_LINK_CTL << 10; -var SET_EVENT_MASK_CMD = OCF_SET_EVENT_MASK | OGF_HOST_CTL << 10; -var RESET_CMD = OCF_RESET | OGF_HOST_CTL << 10; -var READ_LE_HOST_SUPPORTED_CMD = OCF_READ_LE_HOST_SUPPORTED | OGF_HOST_CTL << 10; -var WRITE_LE_HOST_SUPPORTED_CMD = OCF_WRITE_LE_HOST_SUPPORTED | OGF_HOST_CTL << 10; +const SET_EVENT_MASK_CMD = OCF_SET_EVENT_MASK | OGF_HOST_CTL << 10; +const RESET_CMD = OCF_RESET | OGF_HOST_CTL << 10; +const READ_LE_HOST_SUPPORTED_CMD = OCF_READ_LE_HOST_SUPPORTED | OGF_HOST_CTL << 10; +const WRITE_LE_HOST_SUPPORTED_CMD = OCF_WRITE_LE_HOST_SUPPORTED | OGF_HOST_CTL << 10; -var READ_LOCAL_VERSION_CMD = OCF_READ_LOCAL_VERSION | (OGF_INFO_PARAM << 10); -var READ_BUFFER_SIZE_CMD = OCF_READ_BUFFER_SIZE | (OGF_INFO_PARAM << 10); -var READ_BD_ADDR_CMD = OCF_READ_BD_ADDR | (OGF_INFO_PARAM << 10); +const READ_LOCAL_VERSION_CMD = OCF_READ_LOCAL_VERSION | (OGF_INFO_PARAM << 10); +const READ_BUFFER_SIZE_CMD = OCF_READ_BUFFER_SIZE | (OGF_INFO_PARAM << 10); +const READ_BD_ADDR_CMD = OCF_READ_BD_ADDR | (OGF_INFO_PARAM << 10); -var READ_RSSI_CMD = OCF_READ_RSSI | OGF_STATUS_PARAM << 10; +const READ_RSSI_CMD = OCF_READ_RSSI | OGF_STATUS_PARAM << 10; -var LE_SET_EVENT_MASK_CMD = OCF_LE_SET_EVENT_MASK | OGF_LE_CTL << 10; -var LE_READ_BUFFER_SIZE_CMD = OCF_LE_READ_BUFFER_SIZE | OGF_LE_CTL << 10; -var LE_SET_ADVERTISING_PARAMETERS_CMD = OCF_LE_SET_ADVERTISING_PARAMETERS | OGF_LE_CTL << 10; -var LE_SET_ADVERTISING_DATA_CMD = OCF_LE_SET_ADVERTISING_DATA | OGF_LE_CTL << 10; -var LE_SET_SCAN_RESPONSE_DATA_CMD = OCF_LE_SET_SCAN_RESPONSE_DATA | OGF_LE_CTL << 10; -var LE_SET_ADVERTISE_ENABLE_CMD = OCF_LE_SET_ADVERTISE_ENABLE | OGF_LE_CTL << 10; -var LE_LTK_NEG_REPLY_CMD = OCF_LE_LTK_NEG_REPLY | OGF_LE_CTL << 10; +const LE_SET_EVENT_MASK_CMD = OCF_LE_SET_EVENT_MASK | OGF_LE_CTL << 10; +const LE_READ_BUFFER_SIZE_CMD = OCF_LE_READ_BUFFER_SIZE | OGF_LE_CTL << 10; +const LE_SET_ADVERTISING_PARAMETERS_CMD = OCF_LE_SET_ADVERTISING_PARAMETERS | OGF_LE_CTL << 10; +const LE_SET_ADVERTISING_DATA_CMD = OCF_LE_SET_ADVERTISING_DATA | OGF_LE_CTL << 10; +const LE_SET_SCAN_RESPONSE_DATA_CMD = OCF_LE_SET_SCAN_RESPONSE_DATA | OGF_LE_CTL << 10; +const LE_SET_ADVERTISE_ENABLE_CMD = OCF_LE_SET_ADVERTISE_ENABLE | OGF_LE_CTL << 10; +const LE_LTK_NEG_REPLY_CMD = OCF_LE_LTK_NEG_REPLY | OGF_LE_CTL << 10; -var HCI_OE_USER_ENDED_CONNECTION = 0x13; +const HCI_OE_USER_ENDED_CONNECTION = 0x13; -var STATUS_MAPPER = require('./hci-status'); +const STATUS_MAPPER = require('./hci-status'); -var Hci = function() { - this._socket = new BluetoothHciSocket(); - this._isDevUp = null; - this._state = null; - this._deviceId = null; - // le-u min payload size + l2cap header size - // see Bluetooth spec 4.2 [Vol 3, Part A, Chapter 4] - this._aclMtu = 23 + 4; - this._aclMaxInProgress = 1; +class Hci extends EventEmitter { + constructor() { + super(); - this.resetBuffers(); + this._socket = new BluetoothHciSocket(); + this._isDevUp = null; + this._state = null; + this._deviceId = null; + // le-u min payload size + l2cap header size + // see Bluetooth spec 4.2 [Vol 3, Part A, Chapter 4] + this._aclMtu = 23 + 4; + this._aclMaxInProgress = 1; - this.on('stateChange', this.onStateChange.bind(this)); -}; + this.resetBuffers(); -util.inherits(Hci, events.EventEmitter); + this.on('stateChange', this.onStateChange.bind(this)); -Hci.STATUS_MAPPER = STATUS_MAPPER; + this.STATUS_MAPPER = STATUS_MAPPER; + } -Hci.prototype.init = function() { - this._socket.on('data', this.onSocketData.bind(this)); - this._socket.on('error', this.onSocketError.bind(this)); + init() { + this._socket.on('data', this.onSocketData.bind(this)); + this._socket.on('error', this.onSocketError.bind(this)); - var deviceId = process.env.BLENO_HCI_DEVICE_ID ? parseInt(process.env.BLENO_HCI_DEVICE_ID) : undefined; + const deviceId = process.env.BLENO_HCI_DEVICE_ID ? parseInt(process.env.BLENO_HCI_DEVICE_ID) : undefined; + if (process.env.HCI_CHANNEL_USER) { + this._deviceId = this._socket.bindUser(deviceId); - if (process.env.HCI_CHANNEL_USER) { - this._deviceId = this._socket.bindUser(deviceId); + this._socket.start(); - this._socket.start(); + this.reset(); + } else { + this._deviceId = this._socket.bindRaw(deviceId); + this._socket.start(); - this.reset(); - } else { - this._deviceId = this._socket.bindRaw(deviceId); - this._socket.start(); + this.pollIsDevUp(); + } + } - this.pollIsDevUp(); + resetBuffers() { + this._mainHandle = null; + this._handleAclsInProgress = {}; + this._handleBuffers = {}; + this._aclOutQueue = []; } -}; -Hci.prototype.resetBuffers = function() { - this._mainHandle = null; - this._handleAclsInProgress = {}; - this._handleBuffers = {}; - this._aclOutQueue = []; -}; + pollIsDevUp() { + const isDevUp = this._socket.isDevUp(); -Hci.prototype.pollIsDevUp = function() { - var isDevUp = this._socket.isDevUp(); + if (this._isDevUp !== isDevUp) { + if (isDevUp) { + this.setSocketFilter(); + this.initDev(); + } else { + this.emit('stateChange', 'poweredOff'); + } - if (this._isDevUp !== isDevUp) { - if (isDevUp) { - this.setSocketFilter(); - this.initDev(); - } else { - this.emit('stateChange', 'poweredOff'); + this._isDevUp = isDevUp; } - this._isDevUp = isDevUp; + setTimeout(this.pollIsDevUp.bind(this), 1000); } - setTimeout(this.pollIsDevUp.bind(this), 1000); -}; - -Hci.prototype.initDev = function() { - this.resetBuffers(); - this.setEventMask(); - this.setLeEventMask(); - this.readLocalVersion(); - this.writeLeHostSupported(); - this.readLeHostSupported(); - this.readBdAddr(); - this.leReadBufferSize(); -}; - -Hci.prototype.setSocketFilter = function() { - var filter = Buffer.alloc(14); - var typeMask = (1 << HCI_EVENT_PKT)| (1 << HCI_ACLDATA_PKT); - var eventMask1 = (1 << EVT_DISCONN_COMPLETE) | (1 << EVT_ENCRYPT_CHANGE) | (1 << EVT_CMD_COMPLETE) | (1 << EVT_CMD_STATUS) | ( 1 << EVT_NUMBER_OF_COMPLETED_PACKETS); - var eventMask2 = (1 << (EVT_LE_META_EVENT - 32)); - var opcode = 0; - - filter.writeUInt32LE(typeMask, 0); - filter.writeUInt32LE(eventMask1, 4); - filter.writeUInt32LE(eventMask2, 8); - filter.writeUInt16LE(opcode, 12); - - debug('setting filter to: ' + filter.toString('hex')); - this._socket.setFilter(filter); -}; + initDev() { + this.resetBuffers(); + this.setEventMask(); + this.setLeEventMask(); + this.readLocalVersion(); + this.writeLeHostSupported(); + this.readLeHostSupported(); + this.readBdAddr(); + this.leReadBufferSize(); + } -Hci.prototype.setEventMask = function() { - var cmd = Buffer.alloc(12); - var eventMask = Buffer.from('fffffbff07f8bf3d', 'hex'); + setSocketFilter() { + const filter = Buffer.alloc(14); + const typeMask = (1 << HCI_EVENT_PKT) | (1 << HCI_ACLDATA_PKT); + const eventMask1 = (1 << EVT_DISCONN_COMPLETE) | (1 << EVT_ENCRYPT_CHANGE) | (1 << EVT_CMD_COMPLETE) | (1 << EVT_CMD_STATUS) | (1 << EVT_NUMBER_OF_COMPLETED_PACKETS); + const eventMask2 = (1 << (EVT_LE_META_EVENT - 32)); + const opcode = 0; - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(SET_EVENT_MASK_CMD, 1); + filter.writeUInt32LE(typeMask, 0); + filter.writeUInt32LE(eventMask1, 4); + filter.writeUInt32LE(eventMask2, 8); + filter.writeUInt16LE(opcode, 12); - // length - cmd.writeUInt8(eventMask.length, 3); + debug('setting filter to: ' + filter.toString('hex')); + this._socket.setFilter(filter); + } - eventMask.copy(cmd, 4); + setEventMask() { + const cmd = Buffer.alloc(12); + const eventMask = Buffer.from('fffffbff07f8bf3d', 'hex'); - debug('set event mask - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(SET_EVENT_MASK_CMD, 1); -Hci.prototype.reset = function() { - var cmd = Buffer.alloc(4); + // length + cmd.writeUInt8(eventMask.length, 3); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(OCF_RESET | OGF_HOST_CTL << 10, 1); + eventMask.copy(cmd, 4); - // length - cmd.writeUInt8(0x00, 3); + debug('set event mask - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - debug('reset - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + reset() { + const cmd = Buffer.alloc(4); -Hci.prototype.readLeHostSupported = function() { - var cmd = Buffer.alloc(4); + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(OCF_RESET | OGF_HOST_CTL << 10, 1); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(READ_LE_HOST_SUPPORTED_CMD, 1); + // length + cmd.writeUInt8(0x00, 3); - // length - cmd.writeUInt8(0x00, 3); + debug('reset - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - debug('read LE host supported - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + readLeHostSupported() { + const cmd = Buffer.alloc(4); -Hci.prototype.writeLeHostSupported = function() { - var cmd = Buffer.alloc(6); + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(READ_LE_HOST_SUPPORTED_CMD, 1); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(WRITE_LE_HOST_SUPPORTED_CMD, 1); + // length + cmd.writeUInt8(0x00, 3); - // length - cmd.writeUInt8(0x02, 3); + debug('read LE host supported - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - // data - cmd.writeUInt8(0x01, 4); // le - cmd.writeUInt8(0x00, 5); // simul + writeLeHostSupported() { + const cmd = Buffer.alloc(6); - debug('write LE host supported - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(WRITE_LE_HOST_SUPPORTED_CMD, 1); -Hci.prototype.readLocalVersion = function() { - var cmd = Buffer.alloc(4); + // length + cmd.writeUInt8(0x02, 3); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(READ_LOCAL_VERSION_CMD, 1); + // data + cmd.writeUInt8(0x01, 4); // le + cmd.writeUInt8(0x00, 5); // simul - // length - cmd.writeUInt8(0x0, 3); + debug('write LE host supported - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - debug('read local version - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + readLocalVersion() { + const cmd = Buffer.alloc(4); -Hci.prototype.readBdAddr = function() { - var cmd = Buffer.alloc(4); + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(READ_LOCAL_VERSION_CMD, 1); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(READ_BD_ADDR_CMD, 1); + // length + cmd.writeUInt8(0x0, 3); - // length - cmd.writeUInt8(0x0, 3); + debug('read local version - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - debug('read bd addr - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + readBdAddr() { + const cmd = Buffer.alloc(4); -Hci.prototype.setLeEventMask = function() { - var cmd = Buffer.alloc(12); - var leEventMask = Buffer.from('1f00000000000000', 'hex'); + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(READ_BD_ADDR_CMD, 1); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(LE_SET_EVENT_MASK_CMD, 1); + // length + cmd.writeUInt8(0x0, 3); - // length - cmd.writeUInt8(leEventMask.length, 3); + debug('read bd addr - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - leEventMask.copy(cmd, 4); + setLeEventMask() { + const cmd = Buffer.alloc(12); + const leEventMask = Buffer.from('1f00000000000000', 'hex'); - debug('set le event mask - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(LE_SET_EVENT_MASK_CMD, 1); -Hci.prototype.setAdvertisingParameters = function() { - var cmd = Buffer.alloc(19); + // length + cmd.writeUInt8(leEventMask.length, 3); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(LE_SET_ADVERTISING_PARAMETERS_CMD, 1); + leEventMask.copy(cmd, 4); - // length - cmd.writeUInt8(15, 3); + debug('set le event mask - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - var advertisementInterval = Math.floor((process.env.BLENO_ADVERTISING_INTERVAL ? parseFloat(process.env.BLENO_ADVERTISING_INTERVAL) : 100) * 1.6); + setAdvertisingParameters() { + const cmd = Buffer.alloc(19); - // data - cmd.writeUInt16LE(advertisementInterval, 4); // min interval - cmd.writeUInt16LE(advertisementInterval, 6); // max interval - cmd.writeUInt8(0x00, 8); // adv type - cmd.writeUInt8(0x00, 9); // own addr typ - cmd.writeUInt8(0x00, 10); // direct addr type - (Buffer.from('000000000000', 'hex')).copy(cmd, 11); // direct addr - cmd.writeUInt8(0x07, 17); - cmd.writeUInt8(0x00, 18); + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(LE_SET_ADVERTISING_PARAMETERS_CMD, 1); - debug('set advertisement parameters - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + // length + cmd.writeUInt8(15, 3); -Hci.prototype.setAdvertisingData = function(data) { - var cmd = Buffer.alloc(36); + const advertisementInterval = Math.floor((process.env.BLENO_ADVERTISING_INTERVAL ? parseFloat(process.env.BLENO_ADVERTISING_INTERVAL) : 100) * 1.6); - cmd.fill(0x00); + // data + cmd.writeUInt16LE(advertisementInterval, 4); // min interval + cmd.writeUInt16LE(advertisementInterval, 6); // max interval + cmd.writeUInt8(0x00, 8); // adv type + cmd.writeUInt8(0x00, 9); // own addr typ + cmd.writeUInt8(0x00, 10); // direct addr type + (Buffer.from('000000000000', 'hex')).copy(cmd, 11); // direct addr + cmd.writeUInt8(0x07, 17); + cmd.writeUInt8(0x00, 18); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(LE_SET_ADVERTISING_DATA_CMD, 1); + debug('set advertisement parameters - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - // length - cmd.writeUInt8(32, 3); + setAdvertisingData(data) { + const cmd = Buffer.alloc(36); - // data - cmd.writeUInt8(data.length, 4); - data.copy(cmd, 5); + cmd.fill(0x00); - debug('set advertisement data - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(LE_SET_ADVERTISING_DATA_CMD, 1); -Hci.prototype.setScanResponseData = function(data) { - var cmd = Buffer.alloc(36); + // length + cmd.writeUInt8(32, 3); - cmd.fill(0x00); + // data + cmd.writeUInt8(data.length, 4); + data.copy(cmd, 5); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(LE_SET_SCAN_RESPONSE_DATA_CMD, 1); + debug('set advertisement data - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - // length - cmd.writeUInt8(32, 3); + setScanResponseData(data) { + const cmd = Buffer.alloc(36); - // data - cmd.writeUInt8(data.length, 4); - data.copy(cmd, 5); + cmd.fill(0x00); - debug('set scan response data - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(LE_SET_SCAN_RESPONSE_DATA_CMD, 1); -Hci.prototype.setAdvertiseEnable = function(enabled) { - var cmd = Buffer.alloc(5); + // length + cmd.writeUInt8(32, 3); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(LE_SET_ADVERTISE_ENABLE_CMD, 1); + // data + cmd.writeUInt8(data.length, 4); + data.copy(cmd, 5); - // length - cmd.writeUInt8(0x01, 3); + debug('set scan response data - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - // data - cmd.writeUInt8(enabled ? 0x01 : 0x00, 4); // enable: 0 -> disabled, 1 -> enabled + setAdvertiseEnable(enabled) { + const cmd = Buffer.alloc(5); - debug('set advertise enable - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(LE_SET_ADVERTISE_ENABLE_CMD, 1); -Hci.prototype.disconnect = function(handle, reason) { - var cmd = Buffer.alloc(7); + // length + cmd.writeUInt8(0x01, 3); - reason = reason || HCI_OE_USER_ENDED_CONNECTION; + // data + cmd.writeUInt8(enabled ? 0x01 : 0x00, 4); // enable: 0 -> disabled, 1 -> enabled - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(DISCONNECT_CMD, 1); + debug('set advertise enable - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - // length - cmd.writeUInt8(0x03, 3); + disconnect(handle, reason) { + const cmd = Buffer.alloc(7); - // data - cmd.writeUInt16LE(handle, 4); // handle - cmd.writeUInt8(reason, 6); // reason + reason = reason || HCI_OE_USER_ENDED_CONNECTION; - debug('disconnect - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(DISCONNECT_CMD, 1); -Hci.prototype.readRssi = function(handle) { - var cmd = Buffer.alloc(6); + // length + cmd.writeUInt8(0x03, 3); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(READ_RSSI_CMD, 1); + // data + cmd.writeUInt16LE(handle, 4); // handle + cmd.writeUInt8(reason, 6); // reason - // length - cmd.writeUInt8(0x02, 3); + debug('disconnect - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - // data - cmd.writeUInt16LE(handle, 4); // handle + readRssi(handle) { + const cmd = Buffer.alloc(6); - debug('read rssi - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(READ_RSSI_CMD, 1); -Hci.prototype.leReadBufferSize = function() { - var cmd = Buffer.alloc(4); + // length + cmd.writeUInt8(0x02, 3); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(LE_READ_BUFFER_SIZE_CMD, 1); + // data + cmd.writeUInt16LE(handle, 4); // handle - // length - cmd.writeUInt8(0x0, 3); + debug('read rssi - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - debug('le read buffer size - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + leReadBufferSize() { + const cmd = Buffer.alloc(4); -Hci.prototype.readBufferSize = function() { - var cmd = Buffer.alloc(4); + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(LE_READ_BUFFER_SIZE_CMD, 1); - // header - cmd.writeUInt8(HCI_COMMAND_PKT, 0); - cmd.writeUInt16LE(READ_BUFFER_SIZE_CMD, 1); + // length + cmd.writeUInt8(0x0, 3); - // length - cmd.writeUInt8(0x0, 3); + debug('le read buffer size - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - debug('read buffer size - writing: ' + cmd.toString('hex')); - this._socket.write(cmd); -}; + readBufferSize() { + const cmd = Buffer.alloc(4); -Hci.prototype.queueAclDataPkt = function(handle, cid, data) { - var hf = handle | ACL_START_NO_FLUSH << 12; - // l2cap pdu may be fragmented on hci level - var l2capPdu = Buffer.alloc(4 + data.length); - l2capPdu.writeUInt16LE(data.length, 0); - l2capPdu.writeUInt16LE(cid, 2); - data.copy(l2capPdu, 4); - var fragId = 0; + // header + cmd.writeUInt8(HCI_COMMAND_PKT, 0); + cmd.writeUInt16LE(READ_BUFFER_SIZE_CMD, 1); - while (l2capPdu.length) { - var frag = l2capPdu.slice(0, this._aclMtu); - l2capPdu = l2capPdu.slice(frag.length); - var pkt = Buffer.alloc(5 + frag.length); + // length + cmd.writeUInt8(0x0, 3); - // hci header - pkt.writeUInt8(HCI_ACLDATA_PKT, 0); - pkt.writeUInt16LE(hf, 1); - hf |= ACL_CONT << 12; - pkt.writeUInt16LE(frag.length, 3); // hci pdu length + debug('read buffer size - writing: ' + cmd.toString('hex')); + this._socket.write(cmd); + } - frag.copy(pkt, 5); + queueAclDataPkt(handle, cid, data) { + let hf = handle | ACL_START_NO_FLUSH << 12; + // l2cap pdu may be fragmented on hci level + let l2capPdu = Buffer.alloc(4 + data.length); + l2capPdu.writeUInt16LE(data.length, 0); + l2capPdu.writeUInt16LE(cid, 2); + data.copy(l2capPdu, 4); + let fragId = 0; + + while (l2capPdu.length) { + const frag = l2capPdu.slice(0, this._aclMtu); + l2capPdu = l2capPdu.slice(frag.length); + const pkt = Buffer.alloc(5 + frag.length); + + // hci header + pkt.writeUInt8(HCI_ACLDATA_PKT, 0); + pkt.writeUInt16LE(hf, 1); + hf |= ACL_CONT << 12; + pkt.writeUInt16LE(frag.length, 3); // hci pdu length + + frag.copy(pkt, 5); + + this._aclOutQueue.push({ + handle: handle, + pkt: pkt, + fragId: fragId++ + }); + } - this._aclOutQueue.push({ - handle: handle, - pkt: pkt, - fragId: fragId++ - }); + this.pushAclOutQueue(); } - this.pushAclOutQueue(); -}; + pushAclOutQueue() { + let inProgress = 0; + for (const handle in this._handleAclsInProgress) { + inProgress += this._handleAclsInProgress[handle]; + } + while (inProgress < this._aclMaxInProgress && this._aclOutQueue.length) { + inProgress++; + this.writeOneAclDataPkt(); + } -Hci.prototype.pushAclOutQueue = function() { - var inProgress = 0; - for (var handle in this._handleAclsInProgress) { - inProgress += this._handleAclsInProgress[handle]; - } - while (inProgress < this._aclMaxInProgress && this._aclOutQueue.length) { - inProgress++; - this.writeOneAclDataPkt(); + if (inProgress >= this._aclMaxInProgress && this._aclOutQueue.length) { + debug("acl out queue congested"); + debug("\tin progress = " + inProgress); + debug("\twaiting = " + this._aclOutQueue.length); + } } - if (inProgress >= this._aclMaxInProgress && this._aclOutQueue.length) { - debug("acl out queue congested"); - debug("\tin progress = " + inProgress); - debug("\twaiting = " + this._aclOutQueue.length); + writeOneAclDataPkt() { + const pkt = this._aclOutQueue.shift(); + this._handleAclsInProgress[pkt.handle]++; + debug('write acl data pkt frag ' + pkt.fragId + ' handle ' + pkt.handle + ' - writing: ' + pkt.pkt.toString('hex')); + this._socket.write(pkt.pkt); } -}; - -Hci.prototype.writeOneAclDataPkt = function() { - var pkt = this._aclOutQueue.shift(); - this._handleAclsInProgress[pkt.handle]++; - debug('write acl data pkt frag ' + pkt.fragId + ' handle ' + pkt.handle + ' - writing: ' + pkt.pkt.toString('hex')); - this._socket.write(pkt.pkt); -}; -Hci.prototype.onSocketData = function(data) { - debug('onSocketData: ' + data.toString('hex')); + onSocketData(data) { + debug('onSocketData: ' + data.toString('hex')); - var eventType = data.readUInt8(0); - var handle; + const eventType = data.readUInt8(0); + let handle; - debug('\tevent type = ' + eventType); + debug('\tevent type = ' + eventType); - if (HCI_EVENT_PKT === eventType) { - var subEventType = data.readUInt8(1); + if (HCI_EVENT_PKT === eventType) { + const subEventType = data.readUInt8(1); - debug('\tsub event type = ' + subEventType); + debug('\tsub event type = ' + subEventType); - if (subEventType === EVT_DISCONN_COMPLETE) { - handle = data.readUInt16LE(4); - debug('\t\thandle = ' + handle); - if (handle !== this._mainHandle) { + if (subEventType === EVT_DISCONN_COMPLETE) { + handle = data.readUInt16LE(4); + debug('\t\thandle = ' + handle); + if (handle !== this._mainHandle) { debug('\tignoring event because handle is unknown to bleno.'); debug('\This might be OK in a multi role scenario in which the handle is part of a noble connection.'); return; - } + } - var reason = data.readUInt8(6); - debug('\t\treason = ' + reason); - - /* As per Bluetooth Core specs: - When the Host receives a Disconnection Complete, Disconnection Physical - Link Complete or Disconnection Logical Link Complete event, the Host shall - assume that all unacknowledged HCI Data Packets that have been sent to the - Controller for the returned Handle have been flushed, and that the - corresponding data buffers have been freed. */ - delete this._handleAclsInProgress[handle]; - this._mainHandle = null; - var aclOutQueue = []; - var discarded = 0; - for (var i in this._aclOutQueue) { - if (this._aclOutQueue[i].handle != handle) { - aclOutQueue.push(this._aclOutQueue[i]); - } else { - discarded++; + const reason = data.readUInt8(6); + debug('\t\treason = ' + reason); + + /* As per Bluetooth Core specs: + When the Host receives a Disconnection Complete, Disconnection Physical + Link Complete or Disconnection Logical Link Complete event, the Host shall + assume that all unacknowledged HCI Data Packets that have been sent to the + Controller for the returned Handle have been flushed, and that the + corresponding data buffers have been freed. */ + delete this._handleAclsInProgress[handle]; + this._mainHandle = null; + const aclOutQueue = []; + let discarded = 0; + for (const i in this._aclOutQueue) { + if (this._aclOutQueue[i].handle !== handle) { + aclOutQueue.push(this._aclOutQueue[i]); + } else { + discarded++; + } } - } - if (discarded) { - debug('\t\tacls discarded = ' + discarded); - } - this._aclOutQueue = aclOutQueue; - this.pushAclOutQueue(); - this.emit('disconnComplete', handle, reason); - } else if (subEventType === EVT_ENCRYPT_CHANGE) { - handle = data.readUInt16LE(4); - var encrypt = data.readUInt8(6); - - debug('\t\thandle = ' + handle); - debug('\t\tencrypt = ' + encrypt); - - this.emit('encryptChange', handle, encrypt); - } else if (subEventType === EVT_CMD_COMPLETE) { - var ncmd = data.readUInt8(3); - var cmd = data.readUInt16LE(4); - var status = data.readUInt8(6); - var result = data.slice(7); - - debug('\t\tncmd = ' + ncmd); - debug('\t\tcmd = ' + cmd); - debug('\t\tstatus = ' + status); - debug('\t\tresult = ' + result.toString('hex')); - - this.processCmdCompleteEvent(cmd, status, result); - } else if (subEventType === EVT_LE_META_EVENT) { - var leMetaEventType = data.readUInt8(3); - var leMetaEventStatus = data.readUInt8(4); - var leMetaEventData = data.slice(5); - - debug('\t\tLE meta event type = ' + leMetaEventType); - debug('\t\tLE meta event status = ' + leMetaEventStatus); - debug('\t\tLE meta event data = ' + leMetaEventData.toString('hex')); - - this.processLeMetaEvent(leMetaEventType, leMetaEventStatus, leMetaEventData); - } else if (subEventType === EVT_NUMBER_OF_COMPLETED_PACKETS) { - var handles = data.readUInt8(3); - for (var j = 0; j < handles; j++) { - var handle_ = data.readUInt16LE(4 + j * 4); - var pkts = data.readUInt16LE(6 + j * 4); - debug("\thandle = " + handle_); - debug("\t\tcompleted = " + pkts); - if (this._handleAclsInProgress[handle_] === undefined) { - debug("\t\talready closed"); - continue; + if (discarded) { + debug('\t\tacls discarded = ' + discarded); } - if (pkts > this._handleAclsInProgress[handle_]) { - // Linux kernel may send acl packets by itself, so be ready for underflow - this._handleAclsInProgress[handle_] = 0; - } else { - this._handleAclsInProgress[handle_] -= pkts; + this._aclOutQueue = aclOutQueue; + this.pushAclOutQueue(); + this.emit('disconnComplete', handle, reason); + } else if (subEventType === EVT_ENCRYPT_CHANGE) { + handle = data.readUInt16LE(4); + const encrypt = data.readUInt8(6); + + debug('\t\thandle = ' + handle); + debug('\t\tencrypt = ' + encrypt); + + this.emit('encryptChange', handle, encrypt); + } else if (subEventType === EVT_CMD_COMPLETE) { + const ncmd = data.readUInt8(3); + const cmd = data.readUInt16LE(4); + const status = data.readUInt8(6); + const result = data.slice(7); + + debug('\t\tncmd = ' + ncmd); + debug('\t\tcmd = ' + cmd); + debug('\t\tstatus = ' + status); + debug('\t\tresult = ' + result.toString('hex')); + + this.processCmdCompleteEvent(cmd, status, result); + } else if (subEventType === EVT_LE_META_EVENT) { + const leMetaEventType = data.readUInt8(3); + const leMetaEventStatus = data.readUInt8(4); + const leMetaEventData = data.slice(5); + + debug('\t\tLE meta event type = ' + leMetaEventType); + debug('\t\tLE meta event status = ' + leMetaEventStatus); + debug('\t\tLE meta event data = ' + leMetaEventData.toString('hex')); + + this.processLeMetaEvent(leMetaEventType, leMetaEventStatus, leMetaEventData); + } else if (subEventType === EVT_NUMBER_OF_COMPLETED_PACKETS) { + const handles = data.readUInt8(3); + for (let j = 0; j < handles; j++) { + const handle_ = data.readUInt16LE(4 + j * 4); + const pkts = data.readUInt16LE(6 + j * 4); + debug("\thandle = " + handle_); + debug("\t\tcompleted = " + pkts); + if (this._handleAclsInProgress[handle_] === undefined) { + debug("\t\talready closed"); + continue; + } + if (pkts > this._handleAclsInProgress[handle_]) { + // Linux kernel may send acl packets by itself, so be ready for underflow + this._handleAclsInProgress[handle_] = 0; + } else { + this._handleAclsInProgress[handle_] -= pkts; + } + debug("\t\tin progress = " + this._handleAclsInProgress[handle_]); } - debug("\t\tin progress = " + this._handleAclsInProgress[handle_]); + this.pushAclOutQueue(); } - this.pushAclOutQueue(); - } - } else if (HCI_ACLDATA_PKT === eventType) { - var flags = data.readUInt16LE(1) >> 12; - handle = data.readUInt16LE(1) & 0x0fff; + } else if (HCI_ACLDATA_PKT === eventType) { + const flags = data.readUInt16LE(1) >> 12; + handle = data.readUInt16LE(1) & 0x0fff; - if (ACL_START === flags) { - var cid = data.readUInt16LE(7); + if (ACL_START === flags) { + const cid = data.readUInt16LE(7); - var length = data.readUInt16LE(5); - var pktData = data.slice(9); + const length = data.readUInt16LE(5); + const pktData = data.slice(9); - debug('\t\tcid = ' + cid); + debug('\t\tcid = ' + cid); - if (length === pktData.length) { - debug('\t\thandle = ' + handle); - debug('\t\tdata = ' + pktData.toString('hex')); + if (length === pktData.length) { + debug('\t\thandle = ' + handle); + debug('\t\tdata = ' + pktData.toString('hex')); - this.emit('aclDataPkt', handle, cid, pktData); - } else { - this._handleBuffers[handle] = { - length: length, - cid: cid, - data: pktData - }; - } - } else if (ACL_CONT === flags) { - if (!this._handleBuffers[handle] || !this._handleBuffers[handle].data) { - return; - } + this.emit('aclDataPkt', handle, cid, pktData); + } else { + this._handleBuffers[handle] = { + length: length, + cid: cid, + data: pktData + }; + } + } else if (ACL_CONT === flags) { + if (!this._handleBuffers[handle] || !this._handleBuffers[handle].data) { + return; + } - this._handleBuffers[handle].data = Buffer.concat([ - this._handleBuffers[handle].data, - data.slice(5) - ]); + this._handleBuffers[handle].data = Buffer.concat([ + this._handleBuffers[handle].data, + data.slice(5) + ]); - if (this._handleBuffers[handle].data.length === this._handleBuffers[handle].length) { - this.emit('aclDataPkt', handle, this._handleBuffers[handle].cid, this._handleBuffers[handle].data); + if (this._handleBuffers[handle].data.length === this._handleBuffers[handle].length) { + this.emit('aclDataPkt', handle, this._handleBuffers[handle].cid, this._handleBuffers[handle].data); - delete this._handleBuffers[handle]; + delete this._handleBuffers[handle]; + } } } } -}; -Hci.prototype.onSocketError = function(error) { - debug('onSocketError: ' + error.message); + onSocketError(error) { + debug('onSocketError: ' + error.message); - if (error.code === 'EPERM') { - this.emit('stateChange', 'unauthorized'); - } else if (error.message === 'Network is down') { - // no-op + if (error.code === 'EPERM') { + this.emit('stateChange', 'unauthorized'); + } else if (error.message === 'Network is down') { + // no-op + } } -}; -Hci.prototype.processCmdCompleteEvent = function(cmd, status, result) { - var handle; + processCmdCompleteEvent(cmd, status, result) { + if (cmd === RESET_CMD) { + this.initDev(); + } else if (cmd === READ_LE_HOST_SUPPORTED_CMD) { + if (status === 0) { + const le = result.readUInt8(0); + const simul = result.readUInt8(1); - if (cmd === RESET_CMD) { - this.initDev(); - } else if (cmd === READ_LE_HOST_SUPPORTED_CMD) { - if (status === 0) { - var le = result.readUInt8(0); - var simul = result.readUInt8(1); + debug('\t\t\tle = ' + le); + debug('\t\t\tsimul = ' + simul); + } + } else if (cmd === READ_LOCAL_VERSION_CMD) { + const hciVer = result.readUInt8(0); + const hciRev = result.readUInt16LE(1); + const lmpVer = result.readInt8(3); + const manufacturer = result.readUInt16LE(4); + const lmpSubVer = result.readUInt16LE(6); + + if (hciVer < 0x06) { + this.emit('stateChange', 'unsupported'); + } else if (this._state !== 'poweredOn') { + this.setAdvertiseEnable(false); + this.setAdvertisingParameters(); + } - debug('\t\t\tle = ' + le); - debug('\t\t\tsimul = ' + simul); + this.emit('readLocalVersion', hciVer, hciRev, lmpVer, manufacturer, lmpSubVer); + } else if (cmd === READ_BD_ADDR_CMD) { + this.addressType = 'public'; + this.address = result.toString('hex').match(/.{1,2}/g).reverse().join(':'); + + debug('address = ' + this.address); + + this.emit('addressChange', this.address); + } else if (cmd === LE_SET_ADVERTISING_PARAMETERS_CMD) { + this.emit('stateChange', 'poweredOn'); + + this.emit('leAdvertisingParametersSet', status); + } else if (cmd === LE_SET_ADVERTISING_DATA_CMD) { + this.emit('leAdvertisingDataSet', status); + } else if (cmd === LE_SET_SCAN_RESPONSE_DATA_CMD) { + this.emit('leScanResponseDataSet', status); + } else if (cmd === LE_SET_ADVERTISE_ENABLE_CMD) { + this.emit('leAdvertiseEnableSet', status); + } else if (cmd === READ_RSSI_CMD) { + const handle = result.readUInt16LE(0); + const rssi = result.readInt8(2); + + debug('\t\t\thandle = ' + handle); + debug('\t\t\trssi = ' + rssi); + + this.emit('rssiRead', handle, rssi); + } else if (cmd === LE_LTK_NEG_REPLY_CMD) { + const handle = result.readUInt16LE(0); + + debug('\t\t\thandle = ' + handle); + this.emit('leLtkNegReply', handle); + } else if (cmd === LE_READ_BUFFER_SIZE_CMD) { + if (!status) { + this.processLeReadBufferSize(result); + } + } else if (cmd === READ_BUFFER_SIZE_CMD) { + if (!status) { + const aclMtu = result.readUInt16LE(0); + const aclMaxInProgress = result.readUInt16LE(3); + // sanity + if (aclMtu && aclMaxInProgress) { + debug('br/edr acl mtu = ' + aclMtu); + debug('br/edr acl max pkts = ' + aclMaxInProgress); + this._aclMtu = aclMtu; + this._aclMaxInProgress = aclMaxInProgress; + } + } } - } else if (cmd === READ_LOCAL_VERSION_CMD) { - var hciVer = result.readUInt8(0); - var hciRev = result.readUInt16LE(1); - var lmpVer = result.readInt8(3); - var manufacturer = result.readUInt16LE(4); - var lmpSubVer = result.readUInt16LE(6); - - if (hciVer < 0x06) { - this.emit('stateChange', 'unsupported'); - } else if (this._state !== 'poweredOn') { - this.setAdvertiseEnable(false); - this.setAdvertisingParameters(); + } + + processLeReadBufferSize(result) { + const aclMtu = result.readUInt16LE(0); + const aclMaxInProgress = result.readUInt8(2); + if (!aclMtu) { + // as per Bluetooth specs + debug('falling back to br/edr buffer size'); + this.readBufferSize(); + } else { + debug('le acl mtu = ' + aclMtu); + debug('le acl max in progress = ' + aclMaxInProgress); + this._aclMtu = aclMtu; + this._aclMaxInProgress = aclMaxInProgress; } + } - this.emit('readLocalVersion', hciVer, hciRev, lmpVer, manufacturer, lmpSubVer); - } else if (cmd === READ_BD_ADDR_CMD) { - this.addressType = 'public'; - this.address = result.toString('hex').match(/.{1,2}/g).reverse().join(':'); + processLeMetaEvent(eventType, status, data) { + if (eventType === EVT_LE_CONN_COMPLETE) { + this.processLeConnComplete(status, data); + } else if (eventType === EVT_LE_CONN_UPDATE_COMPLETE) { + this.processLeConnUpdateComplete(status, data); + } + } - debug('address = ' + this.address); + processLeConnComplete(status, data) { + const handle = data.readUInt16LE(0); + const role = data.readUInt8(2); + const addressType = data.readUInt8(3) === 0x01 ? 'random' : 'public'; + const address = data.slice(4, 10).toString('hex').match(/.{1,2}/g).reverse().join(':'); + const interval = data.readUInt16LE(10) * 1.25; + const latency = data.readUInt16LE(12); // TODO: multiplier? + const supervisionTimeout = data.readUInt16LE(14) * 10; + const masterClockAccuracy = data.readUInt8(16); // TODO: multiplier? - this.emit('addressChange', this.address); - } else if (cmd === LE_SET_ADVERTISING_PARAMETERS_CMD) { - this.emit('stateChange', 'poweredOn'); + debug('\t\t\thandle = ' + handle); + debug('\t\t\trole = ' + role); + debug('\t\t\taddress type = ' + addressType); + debug('\t\t\taddress = ' + address); + debug('\t\t\tinterval = ' + interval); + debug('\t\t\tlatency = ' + latency); + debug('\t\t\tsupervision timeout = ' + supervisionTimeout); + debug('\t\t\tmaster clock accuracy = ' + masterClockAccuracy); + + this._mainHandle = handle; + this._handleAclsInProgress[handle] = 0; + + this.emit('leConnComplete', status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy); + } - this.emit('leAdvertisingParametersSet', status); - } else if (cmd === LE_SET_ADVERTISING_DATA_CMD) { - this.emit('leAdvertisingDataSet', status); - } else if (cmd === LE_SET_SCAN_RESPONSE_DATA_CMD) { - this.emit('leScanResponseDataSet', status); - } else if (cmd === LE_SET_ADVERTISE_ENABLE_CMD) { - this.emit('leAdvertiseEnableSet', status); - } else if (cmd === READ_RSSI_CMD) { - handle = result.readUInt16LE(0); - var rssi = result.readInt8(2); + processLeConnUpdateComplete(status, data) { + const handle = data.readUInt16LE(0); + const interval = data.readUInt16LE(2) * 1.25; + const latency = data.readUInt16LE(4); // TODO: multiplier? + const supervisionTimeout = data.readUInt16LE(6) * 10; debug('\t\t\thandle = ' + handle); - debug('\t\t\trssi = ' + rssi); + debug('\t\t\tinterval = ' + interval); + debug('\t\t\tlatency = ' + latency); + debug('\t\t\tsupervision timeout = ' + supervisionTimeout); - this.emit('rssiRead', handle, rssi); - } else if (cmd === LE_LTK_NEG_REPLY_CMD) { - handle = result.readUInt16LE(0); + this.emit('leConnUpdateComplete', status, handle, interval, latency, supervisionTimeout); + } - debug('\t\t\thandle = ' + handle); - this.emit('leLtkNegReply', handle); - } else if (cmd === LE_READ_BUFFER_SIZE_CMD) { - if (!status) { - this.processLeReadBufferSize(result); - } - } else if (cmd === READ_BUFFER_SIZE_CMD) { - if (!status) { - var aclMtu = result.readUInt16LE(0); - var aclMaxInProgress = result.readUInt16LE(3); - // sanity - if (aclMtu && aclMaxInProgress) { - debug('br/edr acl mtu = ' + aclMtu); - debug('br/edr acl max pkts = ' + aclMaxInProgress); - this._aclMtu = aclMtu; - this._aclMaxInProgress = aclMaxInProgress; - } - } + onStateChange(state) { + this._state = state; } -}; - -Hci.prototype.processLeReadBufferSize = function(result) { - var aclMtu = result.readUInt16LE(0); - var aclMaxInProgress = result.readUInt8(2); - if (!aclMtu) { - // as per Bluetooth specs - debug('falling back to br/edr buffer size'); - this.readBufferSize(); - } else { - debug('le acl mtu = ' + aclMtu); - debug('le acl max in progress = ' + aclMaxInProgress); - this._aclMtu = aclMtu; - this._aclMaxInProgress = aclMaxInProgress; - } -}; - -Hci.prototype.processLeMetaEvent = function(eventType, status, data) { - if (eventType === EVT_LE_CONN_COMPLETE) { - this.processLeConnComplete(status, data); - } else if (eventType === EVT_LE_CONN_UPDATE_COMPLETE) { - this.processLeConnUpdateComplete(status, data); - } -}; - -Hci.prototype.processLeConnComplete = function(status, data) { - var handle = data.readUInt16LE(0); - var role = data.readUInt8(2); - var addressType = data.readUInt8(3) === 0x01 ? 'random': 'public'; - var address = data.slice(4, 10).toString('hex').match(/.{1,2}/g).reverse().join(':'); - var interval = data.readUInt16LE(10) * 1.25; - var latency = data.readUInt16LE(12); // TODO: multiplier? - var supervisionTimeout = data.readUInt16LE(14) * 10; - var masterClockAccuracy = data.readUInt8(16); // TODO: multiplier? - - debug('\t\t\thandle = ' + handle); - debug('\t\t\trole = ' + role); - debug('\t\t\taddress type = ' + addressType); - debug('\t\t\taddress = ' + address); - debug('\t\t\tinterval = ' + interval); - debug('\t\t\tlatency = ' + latency); - debug('\t\t\tsupervision timeout = ' + supervisionTimeout); - debug('\t\t\tmaster clock accuracy = ' + masterClockAccuracy); - - this._mainHandle = handle; - this._handleAclsInProgress[handle] = 0; - - this.emit('leConnComplete', status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy); -}; - -Hci.prototype.processLeConnUpdateComplete = function(status, data) { - var handle = data.readUInt16LE(0); - var interval = data.readUInt16LE(2) * 1.25; - var latency = data.readUInt16LE(4); // TODO: multiplier? - var supervisionTimeout = data.readUInt16LE(6) * 10; - - debug('\t\t\thandle = ' + handle); - debug('\t\t\tinterval = ' + interval); - debug('\t\t\tlatency = ' + latency); - debug('\t\t\tsupervision timeout = ' + supervisionTimeout); - - this.emit('leConnUpdateComplete', status, handle, interval, latency, supervisionTimeout); -}; - -Hci.prototype.onStateChange = function(state) { - this._state = state; -}; +} module.exports = Hci; diff --git a/lib/hci-socket/mgmt.js b/lib/hci-socket/mgmt.js index 5bfe7c87..1a035ca5 100644 --- a/lib/hci-socket/mgmt.js +++ b/lib/hci-socket/mgmt.js @@ -1,90 +1,89 @@ -var debug = require('debug')('mgmt'); +const debug = require('debug')('mgmt'); -var events = require('events'); -var util = require('util'); +const BluetoothHciSocket = require('@abandonware/bluetooth-hci-socket'); -var BluetoothHciSocket = require('@abandonware/bluetooth-hci-socket'); +const LTK_INFO_SIZE = 36; -var LTK_INFO_SIZE = 36; +const MGMT_OP_LOAD_LONG_TERM_KEYS = 0x0013; -var MGMT_OP_LOAD_LONG_TERM_KEYS = 0x0013; +class Mgmt { + constructor() { + this._socket = new BluetoothHciSocket(); + this._ltkInfos = []; -function Mgmt() { - this._socket = new BluetoothHciSocket(); - this._ltkInfos = []; + this._socket.on('data', this.onSocketData.bind(this)); + this._socket.on('error', this.onSocketError.bind(this)); - this._socket.on('data', this.onSocketData.bind(this)); - this._socket.on('error', this.onSocketError.bind(this)); + this._socket.bindControl(); + this._socket.start(); + } - this._socket.bindControl(); - this._socket.start(); -} + onSocketData(data) { + debug('on data ->' + data.toString('hex')); + } -Mgmt.prototype.onSocketData = function(data) { - debug('on data ->' + data.toString('hex')); -}; + onSocketError(error) { + debug('on error ->' + error.message); + } -Mgmt.prototype.onSocketError = function(error) { - debug('on error ->' + error.message); -}; + addLongTermKey(address, addressType, authenticated, master, ediv, rand, key) { + const ltkInfo = Buffer.alloc(LTK_INFO_SIZE); -Mgmt.prototype.addLongTermKey = function(address, addressType, authenticated, master, ediv, rand, key) { - var ltkInfo = Buffer.alloc(LTK_INFO_SIZE); + address.copy(ltkInfo, 0); + ltkInfo.writeUInt8(addressType.readUInt8(0) + 1, 6); // BDADDR_LE_PUBLIC = 0x01, BDADDR_LE_RANDOM 0x02, so add one - address.copy(ltkInfo, 0); - ltkInfo.writeUInt8(addressType.readUInt8(0) + 1, 6); // BDADDR_LE_PUBLIC = 0x01, BDADDR_LE_RANDOM 0x02, so add one + ltkInfo.writeUInt8(authenticated, 7); + ltkInfo.writeUInt8(master, 8); + ltkInfo.writeUInt8(key.length, 9); - ltkInfo.writeUInt8(authenticated, 7); - ltkInfo.writeUInt8(master, 8); - ltkInfo.writeUInt8(key.length, 9); + ediv.copy(ltkInfo, 10); + rand.copy(ltkInfo, 12); + key.copy(ltkInfo, 20); - ediv.copy(ltkInfo, 10); - rand.copy(ltkInfo, 12); - key.copy(ltkInfo, 20); + this._ltkInfos.push(ltkInfo); - this._ltkInfos.push(ltkInfo); + this.loadLongTermKeys(); + } - this.loadLongTermKeys(); -}; + clearLongTermKeys() { + this._ltkInfos = []; -Mgmt.prototype.clearLongTermKeys = function() { - this._ltkInfos = []; + this.loadLongTermKeys(); + } - this.loadLongTermKeys(); -}; + loadLongTermKeys() { + const numLongTermKeys = this._ltkInfos.length; + const op = Buffer.alloc(2 + numLongTermKeys * LTK_INFO_SIZE); -Mgmt.prototype.loadLongTermKeys = function() { - var numLongTermKeys = this._ltkInfos.length; - var op = Buffer.alloc(2 + numLongTermKeys * LTK_INFO_SIZE); + op.writeUInt16LE(numLongTermKeys, 0); - op.writeUInt16LE(numLongTermKeys, 0); + for (let i = 0; i < numLongTermKeys; i++) { + this._ltkInfos[i].copy(op, 2 + i * LTK_INFO_SIZE); + } - for (var i = 0; i < numLongTermKeys; i++) { - this._ltkInfos[i].copy(op, 2 + i * LTK_INFO_SIZE); + this.write(MGMT_OP_LOAD_LONG_TERM_KEYS, 0, op); } - this.write(MGMT_OP_LOAD_LONG_TERM_KEYS, 0, op); -}; + write(opcode, index, data) { + let length = 0; -Mgmt.prototype.write = function(opcode, index, data) { - var length = 0; + if (data) { + length = data.length; + } - if (data) { - length = data.length; - } + const pkt = Buffer.alloc(6 + length); - var pkt = Buffer.alloc(6 + length); + pkt.writeUInt16LE(opcode, 0); + pkt.writeUInt16LE(index, 2); + pkt.writeUInt16LE(length, 4); - pkt.writeUInt16LE(opcode, 0); - pkt.writeUInt16LE(index, 2); - pkt.writeUInt16LE(length, 4); + if (length) { + data.copy(pkt, 6); + } - if (length) { - data.copy(pkt, 6); + debug('writing -> ' + pkt.toString('hex')); + this._socket.write(pkt); } - - debug('writing -> ' + pkt.toString('hex')); - this._socket.write(pkt); -}; +} module.exports = new Mgmt(); diff --git a/lib/hci-socket/smp.js b/lib/hci-socket/smp.js index 49de2cc1..f5d53465 100644 --- a/lib/hci-socket/smp.js +++ b/lib/hci-socket/smp.js @@ -1,162 +1,163 @@ -var debug = require('debug')('smp'); +const debug = require('debug')('smp'); -var events = require('events'); -var util = require('util'); +const { EventEmitter } = require('events'); -var crypto = require('./crypto'); -var mgmt = require('./mgmt'); +const crypto = require('./crypto'); +const mgmt = require('./mgmt'); -var SMP_CID = 0x0006; +const SMP_CID = 0x0006; -var SMP_PAIRING_REQUEST = 0x01; -var SMP_PAIRING_RESPONSE = 0x02; -var SMP_PAIRING_CONFIRM = 0x03; -var SMP_PAIRING_RANDOM = 0x04; -var SMP_PAIRING_FAILED = 0x05; -var SMP_ENCRYPT_INFO = 0x06; -var SMP_MASTER_IDENT = 0x07; +const SMP_PAIRING_REQUEST = 0x01; +const SMP_PAIRING_RESPONSE = 0x02; +const SMP_PAIRING_CONFIRM = 0x03; +const SMP_PAIRING_RANDOM = 0x04; +const SMP_PAIRING_FAILED = 0x05; +const SMP_ENCRYPT_INFO = 0x06; +const SMP_MASTER_IDENT = 0x07; -var SMP_UNSPECIFIED = 0x08; +const SMP_UNSPECIFIED = 0x08; -var Smp = function(aclStream, localAddressType, localAddress, remoteAddressType, remoteAddress) { - this._aclStream = aclStream; +class Smp extends EventEmitter { + constructor(aclStream, localAddressType, localAddress, remoteAddressType, remoteAddress) { + super(); - this._iat = Buffer.from([(remoteAddressType === 'random') ? 0x01 : 0x00]); - this._ia = Buffer.from(remoteAddress.split(':').reverse().join(''), 'hex'); - this._rat = Buffer.from([(localAddressType === 'random') ? 0x01 : 0x00]); - this._ra = Buffer.from(localAddress.split(':').reverse().join(''), 'hex'); + this._aclStream = aclStream; - this._stk = null; - this._random = null; - this._diversifier = null; + this._iat = Buffer.from([(remoteAddressType === 'random') ? 0x01 : 0x00]); + this._ia = Buffer.from(remoteAddress.split(':').reverse().join(''), 'hex'); + this._rat = Buffer.from([(localAddressType === 'random') ? 0x01 : 0x00]); + this._ra = Buffer.from(localAddress.split(':').reverse().join(''), 'hex'); - this.onAclStreamDataBinded = this.onAclStreamData.bind(this); - this.onAclStreamEncryptChangeBinded = this.onAclStreamEncryptChange.bind(this); - this.onAclStreamLtkNegReplyBinded = this.onAclStreamLtkNegReply.bind(this); - this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this); + this._stk = null; + this._random = null; + this._diversifier = null; - this._aclStream.on('data', this.onAclStreamDataBinded); - this._aclStream.on('encryptChange', this.onAclStreamEncryptChangeBinded); - this._aclStream.on('ltkNegReply', this.onAclStreamLtkNegReplyBinded); - this._aclStream.on('end', this.onAclStreamEndBinded); -}; + this.onAclStreamDataBinded = this.onAclStreamData.bind(this); + this.onAclStreamEncryptChangeBinded = this.onAclStreamEncryptChange.bind(this); + this.onAclStreamLtkNegReplyBinded = this.onAclStreamLtkNegReply.bind(this); + this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this); -util.inherits(Smp, events.EventEmitter); - -Smp.prototype.onAclStreamData = function(cid, data) { - if (cid !== SMP_CID) { - return; + this._aclStream.on('data', this.onAclStreamDataBinded); + this._aclStream.on('encryptChange', this.onAclStreamEncryptChangeBinded); + this._aclStream.on('ltkNegReply', this.onAclStreamLtkNegReplyBinded); + this._aclStream.on('end', this.onAclStreamEndBinded); } - var code = data.readUInt8(0); + onAclStreamData(cid, data) { + if (cid !== SMP_CID) { + return; + } - if (SMP_PAIRING_REQUEST === code) { - this.handlePairingRequest(data); - } else if (SMP_PAIRING_CONFIRM === code) { - this.handlePairingConfirm(data); - } else if (SMP_PAIRING_RANDOM === code) { - this.handlePairingRandom(data); - } else if (SMP_PAIRING_FAILED === code) { - this.handlePairingFailed(data); - } -}; + const code = data.readUInt8(0); -Smp.prototype.onAclStreamEncryptChange = function(encrypted) { - if (encrypted) { - if (this._stk && this._diversifier && this._random) { - this.write(Buffer.concat([ - Buffer.from([SMP_ENCRYPT_INFO]), - this._stk - ])); + if (SMP_PAIRING_REQUEST === code) { + this.handlePairingRequest(data); + } else if (SMP_PAIRING_CONFIRM === code) { + this.handlePairingConfirm(data); + } else if (SMP_PAIRING_RANDOM === code) { + this.handlePairingRandom(data); + } else if (SMP_PAIRING_FAILED === code) { + this.handlePairingFailed(data); + } + } - this.write(Buffer.concat([ - Buffer.from([SMP_MASTER_IDENT]), - this._diversifier, - this._random - ])); + onAclStreamEncryptChange(encrypted) { + if (encrypted) { + if (this._stk && this._diversifier && this._random) { + this.write(Buffer.concat([ + Buffer.from([SMP_ENCRYPT_INFO]), + this._stk + ])); + + this.write(Buffer.concat([ + Buffer.from([SMP_MASTER_IDENT]), + this._diversifier, + this._random + ])); + } } } -}; -Smp.prototype.onAclStreamLtkNegReply = function() { + onAclStreamLtkNegReply() { this.write(Buffer.from([ SMP_PAIRING_FAILED, SMP_UNSPECIFIED ])); this.emit('fail'); -}; - -Smp.prototype.onAclStreamEnd = function() { - this._aclStream.removeListener('data', this.onAclStreamDataBinded); - this._aclStream.removeListener('encryptChange', this.onAclStreamEncryptChangeBinded); - this._aclStream.removeListener('ltkNegReply', this.onAclStreamLtkNegReplyBinded); - this._aclStream.removeListener('end', this.onAclStreamEndBinded); -}; - -Smp.prototype.handlePairingRequest = function(data) { - this._preq = data; - - this._pres = Buffer.from([ - SMP_PAIRING_RESPONSE, - 0x03, // IO capability: NoInputNoOutput - 0x00, // OOB data: Authentication data not present - 0x01, // Authentication requirement: Bonding - No MITM - 0x10, // Max encryption key size - 0x00, // Initiator key distribution: - 0x01 // Responder key distribution: EncKey - ]); - - this.write(this._pres); -}; - -Smp.prototype.handlePairingConfirm = function(data) { - this._pcnf = data; - - this._tk = Buffer.from('00000000000000000000000000000000', 'hex'); - this._r = crypto.r(); - - this.write(Buffer.concat([ - Buffer.from([SMP_PAIRING_CONFIRM]), - crypto.c1(this._tk, this._r, this._pres, this._preq, this._iat, this._ia, this._rat, this._ra) - ])); -}; - -Smp.prototype.handlePairingRandom = function(data) { - var r = data.slice(1); - - var pcnf = Buffer.concat([ - Buffer.from([SMP_PAIRING_CONFIRM]), - crypto.c1(this._tk, r, this._pres, this._preq, this._iat, this._ia, this._rat, this._ra) - ]); - - if (this._pcnf.toString('hex') === pcnf.toString('hex')) { - this._diversifier = Buffer.from('0000', 'hex'); - this._random = Buffer.from('0000000000000000', 'hex'); - this._stk = crypto.s1(this._tk, this._r, r); - - mgmt.addLongTermKey(this._ia, this._iat, 0, 0, this._diversifier, this._random, this._stk); + } + + onAclStreamEnd() { + this._aclStream.removeListener('data', this.onAclStreamDataBinded); + this._aclStream.removeListener('encryptChange', this.onAclStreamEncryptChangeBinded); + this._aclStream.removeListener('ltkNegReply', this.onAclStreamLtkNegReplyBinded); + this._aclStream.removeListener('end', this.onAclStreamEndBinded); + } + + handlePairingRequest(data) { + this._preq = data; + + this._pres = Buffer.from([ + SMP_PAIRING_RESPONSE, + 0x03, // IO capability: NoInputNoOutput + 0x00, // OOB data: Authentication data not present + 0x01, // Authentication requirement: Bonding - No MITM + 0x10, // Max encryption key size + 0x00, // Initiator key distribution: + 0x01 // Responder key distribution: EncKey + ]); + + this.write(this._pres); + } + + handlePairingConfirm(data) { + this._pcnf = data; + + this._tk = Buffer.from('00000000000000000000000000000000', 'hex'); + this._r = crypto.r(); this.write(Buffer.concat([ - Buffer.from([SMP_PAIRING_RANDOM]), - this._r - ])); - } else { - this.write(Buffer.from([ - SMP_PAIRING_FAILED, - SMP_PAIRING_CONFIRM + Buffer.from([SMP_PAIRING_CONFIRM]), + crypto.c1(this._tk, this._r, this._pres, this._preq, this._iat, this._ia, this._rat, this._ra) ])); + } - this.emit('fail'); + handlePairingRandom(data) { + const r = data.slice(1); + + const pcnf = Buffer.concat([ + Buffer.from([SMP_PAIRING_CONFIRM]), + crypto.c1(this._tk, r, this._pres, this._preq, this._iat, this._ia, this._rat, this._ra) + ]); + + if (this._pcnf.toString('hex') === pcnf.toString('hex')) { + this._diversifier = Buffer.from('0000', 'hex'); + this._random = Buffer.from('0000000000000000', 'hex'); + this._stk = crypto.s1(this._tk, this._r, r); + + mgmt.addLongTermKey(this._ia, this._iat, 0, 0, this._diversifier, this._random, this._stk); + + this.write(Buffer.concat([ + Buffer.from([SMP_PAIRING_RANDOM]), + this._r + ])); + } else { + this.write(Buffer.from([ + SMP_PAIRING_FAILED, + SMP_PAIRING_CONFIRM + ])); + + this.emit('fail'); + } } -}; -Smp.prototype.handlePairingFailed = function(data) { - this.emit('fail'); -}; + handlePairingFailed(data) { + this.emit('fail'); + } -Smp.prototype.write = function(data) { - this._aclStream.write(SMP_CID, data); -}; + write(data) { + this._aclStream.write(SMP_CID, data); + } +} module.exports = Smp; diff --git a/lib/primary-service.js b/lib/primary-service.js index 6bc3b6e5..b4cf8c60 100644 --- a/lib/primary-service.js +++ b/lib/primary-service.js @@ -1,22 +1,22 @@ -var events = require('events'); -var util = require('util'); +const { EventEmitter } = require('events'); -var debug = require('debug')('primary-service'); +const debug = require('debug')('primary-service'); -var UuidUtil = require('./uuid-util'); +const UuidUtil = require('./uuid-util'); -function PrimaryService(options) { - this.uuid = UuidUtil.removeDashes(options.uuid); - this.characteristics = options.characteristics || []; -} - -util.inherits(PrimaryService, events.EventEmitter); +class PrimaryService extends EventEmitter { + constructor(options) { + super(); + this.uuid = UuidUtil.removeDashes(options.uuid); + this.characteristics = options.characteristics || []; + } -PrimaryService.prototype.toString = function() { - return JSON.stringify({ - uuid: this.uuid, - characteristics: this.characteristics - }); -}; + toString() { + return JSON.stringify({ + uuid: this.uuid, + characteristics: this.characteristics + }); + } +} module.exports = PrimaryService; diff --git a/test-ibeacon.js b/test-ibeacon.js index 391b93a0..b56767e7 100644 --- a/test-ibeacon.js +++ b/test-ibeacon.js @@ -1,4 +1,4 @@ -var bleno = require('./index'); +const bleno = require('./index'); console.log('bleno - iBeacon'); diff --git a/test-matter.js b/test-matter.js new file mode 100644 index 00000000..b8001736 --- /dev/null +++ b/test-matter.js @@ -0,0 +1,162 @@ +const bleno = require('./index'); + +let handshakeDone = false; +let C2Callback = null; + +console.log('bleno-matter'); + +class C3DynamicReadOnlyCharacteristic extends bleno.Characteristic { + constructor() { + super({ + uuid: '64630238-8772-45F2-B87D-748A83218F04', + properties: ['read'] + }); + } + + onReadRequest(offset, callback) { + let result = this.RESULT_SUCCESS; + let data = Buffer.from('dynamic value'); + + console.log('C3DynamicReadOnlyCharacteristic read request: ' + data.toString('hex') + ' ' + offset); + + if (offset > data.length) { + result = this.RESULT_INVALID_OFFSET; + data = null; + } else { + data = data.slice(offset); + } + + callback(result, data); + } +} + +class C1WriteOnlyCharacteristic extends bleno.Characteristic { + constructor() { + super({ + uuid: '18EE2EF5-263D-4559-959F-4F9C429F9D11', + properties: ['write'] + }); + } + + onWriteRequest(data, offset, withoutResponse, callback) { + console.log('C1WriteOnlyCharacteristic write request: ' + data.toString('hex') + ' ' + offset + ' ' + withoutResponse); + + if (data[0] === 0x65 && data[1] === 0x6c) { + handshakeDone = true; + callback(); + return; + } + + callback(this.RESULT_SUCCESS); + } +} + +class C2IndicateOnlyCharacteristic extends bleno.Characteristic { + constructor() { + super({ + uuid: '18EE2EF5-263D-4559-959F-4F9C429F9D12', + properties: ['indicate'] + }); + } + + onSubscribe(maxValueSize, updateValueCallback) { + console.log('C2IndicateOnlyCharacteristic subscribe ' + maxValueSize); + + if (handshakeDone) { + C2Callback = updateValueCallback; + console.log('C2IndicateOnlyCharacteristic handshake response'); + updateValueCallback(Buffer.from("656c04000106", "hex")); + } + } + + onUnsubscribe() { + console.log('C2IndicateOnlyCharacteristic unsubscribe'); + + if (this.changeInterval) { + clearInterval(this.changeInterval); + this.changeInterval = null; + } + } + + onIndicate() { + console.log('C2IndicateOnlyCharacteristic on indicate'); + } +} + +class MatterService extends bleno.PrimaryService { + constructor() { + super({ + uuid: 'fff6', // Matter + characteristics: [ + new C3DynamicReadOnlyCharacteristic(), + new C1WriteOnlyCharacteristic(), + new C2IndicateOnlyCharacteristic() + ] + }); + } +} + +bleno.on('stateChange', function(state) { + console.log('on -> stateChange: ' + state + ', address = ' + bleno.address); + + if (state === 'poweredOn') { + //bleno.startAdvertising('test', ['fffffffffffffffffffffffffffffff0']); + + // Data as defined 5.4.2.5.6 Matter Spec for a hardcoded product and vendor Id + // Theoretically use this QR Code https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT:86PS0KQS02-10648G00 + + const advertiseBuffer = Buffer.alloc(15); + advertiseBuffer.writeUInt8(0x02, 0); + advertiseBuffer.writeUInt8(0x01, 1); + advertiseBuffer.writeUInt8(0x06, 2); + advertiseBuffer.writeUInt8(0x0B, 3); + advertiseBuffer.writeUInt8(0x16, 4); + advertiseBuffer.writeUInt16LE(0xFFF6, 5); + advertiseBuffer.writeUInt8(0x00, 7); + advertiseBuffer.writeUInt16LE(1234, 8); + advertiseBuffer.writeUInt16LE(65522, 10); + advertiseBuffer.writeUInt16LE(32770, 12); + advertiseBuffer.writeUInt8(0x00, 14); + bleno.startAdvertisingWithEIRData(advertiseBuffer); + } else { + bleno.stopAdvertising(); + } +}); + +// Linux only events ///////////////// +bleno.on('accept', function(clientAddress) { + console.log('on -> accept, client: ' + clientAddress); + + bleno.updateRssi(); +}); + +bleno.on('disconnect', function(clientAddress) { + console.log('on -> disconnect, client: ' + clientAddress); +}); + +bleno.on('rssiUpdate', function(rssi) { + console.log('on -> rssiUpdate: ' + rssi); +}); +////////////////////////////////////// + +bleno.on('mtuChange', function(mtu) { + console.log('on -> mtuChange: ' + mtu); +}); + +bleno.on('advertisingStart', function(error) { + console.log('on -> advertisingStart: ' + (error ? 'error ' + error : 'success')); + + if (!error) { + bleno.setServices([ + new MatterService() + ]); + } +}); + +bleno.on('advertisingStop', function() { + console.log('on -> advertisingStop'); +}); + +bleno.on('servicesSet', function(error) { + console.log('on -> servicesSet: ' + (error ? 'error ' + error : 'success')); +}); diff --git a/test.js b/test.js index 1233ffb4..94c6b10e 100644 --- a/test.js +++ b/test.js @@ -1,181 +1,181 @@ -var util = require('util'); +const bleno = require('./index'); -var bleno = require('./index'); +const BlenoPrimaryService = bleno.PrimaryService; +const BlenoCharacteristic = bleno.Characteristic; +const BlenoDescriptor = bleno.Descriptor; +console.log('bleno'); -var BlenoPrimaryService = bleno.PrimaryService; -var BlenoCharacteristic = bleno.Characteristic; -var BlenoDescriptor = bleno.Descriptor; +console.log(bleno.PrimaryService); + +class StaticReadOnlyCharacteristic extends BlenoCharacteristic { + constructor() { + super({ + uuid: 'fffffffffffffffffffffffffffffff1', + properties: ['read'], + value: Buffer.from('value'), + descriptors: [ + new BlenoDescriptor({ + uuid: '2901', + value: 'user description' + }) + ] + }); + } +} -console.log('bleno'); +class DynamicReadOnlyCharacteristic extends BlenoCharacteristic { + constructor() { + super({ + uuid: 'fffffffffffffffffffffffffffffff2', + properties: ['read'] + }); + } -var StaticReadOnlyCharacteristic = function() { - StaticReadOnlyCharacteristic.super_.call(this, { - uuid: 'fffffffffffffffffffffffffffffff1', - properties: ['read'], - value: Buffer.from('value'), - descriptors: [ - new BlenoDescriptor({ - uuid: '2901', - value: 'user description' - }) - ] - }); -}; -util.inherits(StaticReadOnlyCharacteristic, BlenoCharacteristic); - -var DynamicReadOnlyCharacteristic = function() { - DynamicReadOnlyCharacteristic.super_.call(this, { - uuid: 'fffffffffffffffffffffffffffffff2', - properties: ['read'] - }); -}; - -util.inherits(DynamicReadOnlyCharacteristic, BlenoCharacteristic); - -DynamicReadOnlyCharacteristic.prototype.onReadRequest = function(offset, callback) { - var result = this.RESULT_SUCCESS; - var data = Buffer.from('dynamic value'); - - if (offset > data.length) { - result = this.RESULT_INVALID_OFFSET; - data = null; - } else { - data = data.slice(offset); + onReadRequest(offset, callback) { + let result = this.RESULT_SUCCESS; + let data = Buffer.from('dynamic value'); + + if (offset > data.length) { + result = this.RESULT_INVALID_OFFSET; + data = null; + } else { + data = data.slice(offset); + } + + callback(result, data); + } +} + +class LongDynamicReadOnlyCharacteristic extends BlenoCharacteristic { + constructor() { + super({ + uuid: 'fffffffffffffffffffffffffffffff3', + properties: ['read'] + }); } - callback(result, data); -}; + onReadRequest(offset, callback) { + let result = this.RESULT_SUCCESS; + let data = Buffer.alloc(512); -var LongDynamicReadOnlyCharacteristic = function() { - LongDynamicReadOnlyCharacteristic.super_.call(this, { - uuid: 'fffffffffffffffffffffffffffffff3', - properties: ['read'] - }); -}; + for (let i = 0; i < data.length; i++) { + data[i] = i % 256; + } -util.inherits(LongDynamicReadOnlyCharacteristic, BlenoCharacteristic); + if (offset > data.length) { + result = this.RESULT_INVALID_OFFSET; + data = null; + } else { + data = data.slice(offset); + } -LongDynamicReadOnlyCharacteristic.prototype.onReadRequest = function(offset, callback) { - var result = this.RESULT_SUCCESS; - var data = Buffer.alloc(512); + callback(result, data); + } +} - for (var i = 0; i < data.length; i++) { - data[i] = i % 256; +class WriteOnlyCharacteristic extends BlenoCharacteristic { + constructor() { + super({ + uuid: 'fffffffffffffffffffffffffffffff4', + properties: ['write', 'writeWithoutResponse'] + }); } - if (offset > data.length) { - result = this.RESULT_INVALID_OFFSET; - data = null; - } else { - data = data.slice(offset); + onWriteRequest(data, offset, withoutResponse, callback) { + console.log('WriteOnlyCharacteristic write request: ' + data.toString('hex') + ' ' + offset + ' ' + withoutResponse); + + callback(this.RESULT_SUCCESS); + } +} + +class NotifyOnlyCharacteristic extends BlenoCharacteristic { + constructor() { + super({ + uuid: 'fffffffffffffffffffffffffffffff5', + properties: ['notify'] + }); + } + + onSubscribe(maxValueSize, updateValueCallback) { + console.log('NotifyOnlyCharacteristic subscribe'); + + this.counter = 0; + this.changeInterval = setInterval(function() { + const data = Buffer.alloc(4); + data.writeUInt32LE(this.counter, 0); + + console.log('NotifyOnlyCharacteristic update value: ' + this.counter); + updateValueCallback(data); + this.counter++; + }.bind(this), 5000); + } + + onUnsubscribe() { + console.log('NotifyOnlyCharacteristic unsubscribe'); + + if (this.changeInterval) { + clearInterval(this.changeInterval); + this.changeInterval = null; + } + } + + onNotify() { + console.log('NotifyOnlyCharacteristic on notify'); + } +} + +class IndicateOnlyCharacteristic extends BlenoCharacteristic { + constructor() { + super({ + uuid: 'fffffffffffffffffffffffffffffff6', + properties: ['indicate'] + }); } - callback(result, data); -}; - -var WriteOnlyCharacteristic = function() { - WriteOnlyCharacteristic.super_.call(this, { - uuid: 'fffffffffffffffffffffffffffffff4', - properties: ['write', 'writeWithoutResponse'] - }); -}; - -util.inherits(WriteOnlyCharacteristic, BlenoCharacteristic); - -WriteOnlyCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) { - console.log('WriteOnlyCharacteristic write request: ' + data.toString('hex') + ' ' + offset + ' ' + withoutResponse); - - callback(this.RESULT_SUCCESS); -}; - -var NotifyOnlyCharacteristic = function() { - NotifyOnlyCharacteristic.super_.call(this, { - uuid: 'fffffffffffffffffffffffffffffff5', - properties: ['notify'] - }); -}; - -util.inherits(NotifyOnlyCharacteristic, BlenoCharacteristic); - -NotifyOnlyCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) { - console.log('NotifyOnlyCharacteristic subscribe'); - - this.counter = 0; - this.changeInterval = setInterval(function() { - var data = Buffer.alloc(4); - data.writeUInt32LE(this.counter, 0); - - console.log('NotifyOnlyCharacteristic update value: ' + this.counter); - updateValueCallback(data); - this.counter++; - }.bind(this), 5000); -}; - -NotifyOnlyCharacteristic.prototype.onUnsubscribe = function() { - console.log('NotifyOnlyCharacteristic unsubscribe'); - - if (this.changeInterval) { - clearInterval(this.changeInterval); - this.changeInterval = null; - } -}; - -NotifyOnlyCharacteristic.prototype.onNotify = function() { - console.log('NotifyOnlyCharacteristic on notify'); -}; - -var IndicateOnlyCharacteristic = function() { - IndicateOnlyCharacteristic.super_.call(this, { - uuid: 'fffffffffffffffffffffffffffffff6', - properties: ['indicate'] - }); -}; - -util.inherits(IndicateOnlyCharacteristic, BlenoCharacteristic); - -IndicateOnlyCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) { - console.log('IndicateOnlyCharacteristic subscribe'); - - this.counter = 0; - this.changeInterval = setInterval(function() { - var data = Buffer.alloc(4); - data.writeUInt32LE(this.counter, 0); - - console.log('IndicateOnlyCharacteristic update value: ' + this.counter); - updateValueCallback(data); - this.counter++; - }.bind(this), 1000); -}; - -IndicateOnlyCharacteristic.prototype.onUnsubscribe = function() { - console.log('IndicateOnlyCharacteristic unsubscribe'); - - if (this.changeInterval) { - clearInterval(this.changeInterval); - this.changeInterval = null; - } -}; - -IndicateOnlyCharacteristic.prototype.onIndicate = function() { - console.log('IndicateOnlyCharacteristic on indicate'); -}; - -function SampleService() { - SampleService.super_.call(this, { - uuid: 'fffffffffffffffffffffffffffffff0', - characteristics: [ - new StaticReadOnlyCharacteristic(), - new DynamicReadOnlyCharacteristic(), - new LongDynamicReadOnlyCharacteristic(), - new WriteOnlyCharacteristic(), - new NotifyOnlyCharacteristic(), - new IndicateOnlyCharacteristic() - ] - }); + onSubscribe(maxValueSize, updateValueCallback) { + console.log('IndicateOnlyCharacteristic subscribe'); + + this.counter = 0; + this.changeInterval = setInterval(function () { + const data = Buffer.alloc(4); + data.writeUInt32LE(this.counter, 0); + + console.log('IndicateOnlyCharacteristic update value: ' + this.counter); + updateValueCallback(data); + this.counter++; + }.bind(this), 1000); + } + + onUnsubscribe() { + console.log('IndicateOnlyCharacteristic unsubscribe'); + + if (this.changeInterval) { + clearInterval(this.changeInterval); + this.changeInterval = null; + } + } + + onIndicate() { + console.log('IndicateOnlyCharacteristic on indicate'); + } } -util.inherits(SampleService, BlenoPrimaryService); +class SampleService extends BlenoPrimaryService { + constructor() { + super({ + uuid: 'fffffffffffffffffffffffffffffff0', + characteristics: [ + new StaticReadOnlyCharacteristic(), + new DynamicReadOnlyCharacteristic(), + new LongDynamicReadOnlyCharacteristic(), + new WriteOnlyCharacteristic(), + new NotifyOnlyCharacteristic(), + new IndicateOnlyCharacteristic() + ] + }); + } +} bleno.on('stateChange', function(state) { console.log('on -> stateChange: ' + state + ', address = ' + bleno.address); diff --git a/test/test-characteristic.js b/test/test-characteristic.js index e49040bb..94605e08 100644 --- a/test/test-characteristic.js +++ b/test/test-characteristic.js @@ -1,28 +1,28 @@ /* jshint mocha: true */ -var should = require('should'); +const should = require('should'); -var Characteristic = require('../lib/characteristic'); +const Characteristic = require('../lib/characteristic'); describe('Characteristic', function() { - var mockUuid = 'mockuuid'; - var mockProperties = ['property1', 'property2', 'property3']; - var mockSecure = ['secure1', 'secure2', 'secure3']; - var mockValue = Buffer.from('mock value'); - var mockDescriptors = [{}, {}, {}]; - - var mockOnReadRequest = function() {}; - var mockOnWriteRequest = function() {}; - var mockOnSubscribe = function() {}; - var mockOnUnsubscribe = function() {}; - var mockOnNotify = function() {}; - var mockOnIndicate = function() {}; - - var mockMaxValueSize = 20; - var mockUpdateValueCallback = function() {}; + const mockUuid = 'mockuuid'; + const mockProperties = ['property1', 'property2', 'property3']; + const mockSecure = ['secure1', 'secure2', 'secure3']; + const mockValue = Buffer.from('mock value'); + const mockDescriptors = [{}, {}, {}]; + + const mockOnReadRequest = function() {}; + const mockOnWriteRequest = function() {}; + const mockOnSubscribe = function() {}; + const mockOnUnsubscribe = function() {}; + const mockOnNotify = function() {}; + const mockOnIndicate = function() {}; + + const mockMaxValueSize = 20; + const mockUpdateValueCallback = function() {}; it('should create with uuid option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ uuid: mockUuid }); @@ -41,7 +41,7 @@ describe('Characteristic', function() { }); it('should create with properties option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ properties: mockProperties }); @@ -49,7 +49,7 @@ describe('Characteristic', function() { }); it('should create with secure option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ secure: mockSecure }); @@ -57,7 +57,7 @@ describe('Characteristic', function() { }); it('should create with value option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ properties: ['read'], value: mockValue }); @@ -67,7 +67,7 @@ describe('Characteristic', function() { it('should not create with value option and non-read properties', function() { (function(){ - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ properties: ['write'], value: mockValue }); @@ -75,7 +75,7 @@ describe('Characteristic', function() { }); it('should create with descriptors option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ descriptors: mockDescriptors }); @@ -83,7 +83,7 @@ describe('Characteristic', function() { }); it('should create with onReadRequest option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ onReadRequest: mockOnReadRequest }); @@ -91,7 +91,7 @@ describe('Characteristic', function() { }); it('should create with onWriteRequest option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ onWriteRequest: mockOnWriteRequest }); @@ -99,7 +99,7 @@ describe('Characteristic', function() { }); it('should create with onSubscribe option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ onSubscribe: mockOnSubscribe }); @@ -107,7 +107,7 @@ describe('Characteristic', function() { }); it('should create with onUnsubscribe option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ onUnsubscribe: mockOnUnsubscribe }); @@ -115,23 +115,23 @@ describe('Characteristic', function() { }); it('should create with onNotify option', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ onNotify: mockOnNotify }); characteristic.onNotify.should.equal(mockOnNotify); }); - it('should create with onIndicate option', function() { - var characteristic = new Characteristic({ - onIndicate: mockOnIndicate - }); + it('should create with onIndicate option', function() { + const characteristic = new Characteristic({ + onIndicate: mockOnIndicate + }); characteristic.onIndicate.should.equal(mockOnIndicate); }); it('should toString', function() { - var characteristic = new Characteristic({ + const characteristic = new Characteristic({ uuid: mockUuid }); @@ -139,7 +139,7 @@ describe('Characteristic', function() { }); it('should handle read request', function(done) { - var characteristic = new Characteristic({}); + const characteristic = new Characteristic({}); characteristic.emit('readRequest', 0, function(result, data) { result.should.equal(0x0e); @@ -150,7 +150,7 @@ describe('Characteristic', function() { }); it('should handle write request', function(done) { - var characteristic = new Characteristic({}); + const characteristic = new Characteristic({}); characteristic.emit('writeRequest', Buffer.alloc(0), 0, false, function(result) { result.should.equal(0x0e); @@ -160,7 +160,7 @@ describe('Characteristic', function() { }); it('should handle unsubscribe', function() { - var characteristic = new Characteristic({}); + const characteristic = new Characteristic({}); characteristic.maxValueSize = mockMaxValueSize; characteristic.updateValueCallback = mockUpdateValueCallback; diff --git a/test/test-descriptor.js b/test/test-descriptor.js index 52d00eb5..0212e166 100644 --- a/test/test-descriptor.js +++ b/test/test-descriptor.js @@ -1,15 +1,15 @@ /* jshint mocha: true */ -var should = require('should'); +const should = require('should'); -var Descriptor = require('../lib/descriptor'); +const Descriptor = require('../lib/descriptor'); describe('Descriptor', function() { - var mockUuid = 'mockuuid'; - var mockValue = Buffer.from('mock value'); + const mockUuid = 'mockuuid'; + const mockValue = Buffer.from('mock value'); it('should create with uuid option', function() { - var descriptor = new Descriptor({ + const descriptor = new Descriptor({ uuid: mockUuid }); @@ -20,7 +20,7 @@ describe('Descriptor', function() { }); it('should create with value option', function() { - var descriptor = new Descriptor({ + const descriptor = new Descriptor({ value: mockValue }); @@ -29,7 +29,7 @@ describe('Descriptor', function() { describe('toString', function() { it('should hex buffer value', function() { - var descriptor = new Descriptor({ + const descriptor = new Descriptor({ uuid: mockUuid, value: mockValue }); @@ -38,7 +38,7 @@ describe('Descriptor', function() { }); it('should leave non-buffer value alone', function() { - var descriptor = new Descriptor({ + const descriptor = new Descriptor({ uuid: mockUuid, value: 'mock value' }); diff --git a/test/test-primary-service.js b/test/test-primary-service.js index 720e75c7..ca3df2fa 100644 --- a/test/test-primary-service.js +++ b/test/test-primary-service.js @@ -5,11 +5,11 @@ var should = require('should'); var PrimaryService = require('../lib/primary-service'); describe('PrimaryService', function() { - var mockUuid = 'mockuuid'; - var mockCharacteristics = [{}, {}, {}]; + const mockUuid = 'mockuuid'; + const mockCharacteristics = [{}, {}, {}]; it('should create with uuid option', function() { - var service = new PrimaryService({ + const service = new PrimaryService({ uuid: mockUuid }); @@ -20,7 +20,7 @@ describe('PrimaryService', function() { }); it('should create with characteristics option', function() { - var service = new PrimaryService({ + const service = new PrimaryService({ characteristics: mockCharacteristics }); @@ -28,10 +28,10 @@ describe('PrimaryService', function() { }); it('should toString', function() { - var service = new PrimaryService({ + const service = new PrimaryService({ uuid: mockUuid }); service.toString().should.equal('{"uuid":"mockuuid","characteristics":[]}'); }); -}); \ No newline at end of file +});