From be34355659b42f667cdfefec426945dfd8d6c289 Mon Sep 17 00:00:00 2001 From: Bryce Jacobs Date: Sun, 20 Sep 2015 18:42:25 -0500 Subject: [PATCH] linux - add strategy for handle management --- lib/hci-socket/gatt.js | 315 +++++++++++++++++++++-------------------- 1 file changed, 160 insertions(+), 155 deletions(-) diff --git a/lib/hci-socket/gatt.js b/lib/hci-socket/gatt.js index d809c3f0..51d809f4 100644 --- a/lib/hci-socket/gatt.js +++ b/lib/hci-socket/gatt.js @@ -64,39 +64,6 @@ var ATT_ECODE_INSUFF_RESOURCES = 0x11; var ATT_CID = 0x0004; var Gatt = function() { - this._services = [ - { - uuid: '1800', - characteristics: [ - { - uuid: '2a00', - properties: ['read'], - secure: [], - value: null, - descriptors: [] - }, - { - uuid: '2a01', - properties: ['read'], - secure: [], - value: new Buffer([0x80, 0x00]), - descriptors: [] - } - ] - }, - { - uuid: '1801', - characteristics: [ - { - uuid: '2a05', - properties: ['indicate'], - secure: [], - value: new Buffer([0x00, 0x00, 0x00, 0x00]), - descriptors: [] - } - ] - } - ]; this._mtu = 23; this.onAclStreamDataBinded = this.onAclStreamData.bind(this); @@ -105,35 +72,19 @@ var Gatt = function() { util.inherits(Gatt, events.EventEmitter); -function createATTMessage(op, mtu, valueHandle, data){ - var dataLength = Math.min(data.length, mtu - 3); - var message = new Buffer(3 + dataLength); - - message.writeUInt8(parseInt(op, 16), 0); - message.writeUInt16LE(parseInt(valueHandle, 16), 1); - - for (i = 0; i < dataLength; i++) { - message[3 + i] = data[i]; - } - - return message; -} - -function createServiceHandles(service, start){ - var handle = start; - var handles = []; +Gatt.prototype.addService = function AddGATTService(service, indicate){ + var handles = this._handles; + indicate = indicate === undefined ? true : indicate; - handle++; - var serviceHandle = handle; - - handles.push({ + var serviceHandles = []; + serviceHandles.push({ type: 'service', uuid: service.uuid, attribute: service, - startHandle: serviceHandle + startHandle: null // endHandle filled in below }); - service.handle = serviceHandle; + for (var j = 0; j < service.characteristics.length; j++) { var characteristic = service.characteristics[j]; @@ -180,54 +131,41 @@ function createServiceHandles(service, start){ } } - handle++; - var characteristicHandle = handle; - - handle++; - var characteristicValueHandle = handle; - - handles.push({ + serviceHandles.push({ type: 'characteristic', uuid: characteristic.uuid, properties: properties, secure: secure, attribute: characteristic, - startHandle: characteristicHandle, - valueHandle: characteristicValueHandle + startHandle: null, + valueHandle: null }); - handles.push({ + serviceHandles.push({ type: 'characteristicValue', - handle: characteristicValueHandle, + handle: null, value: characteristic.value }); if (properties & 0x30) { // notify or indicate // add client characteristic configuration descriptor - - handle++; - var clientCharacteristicConfigurationDescriptorHandle = handle; - handles.push({ + serviceHandles.push({ type: 'descriptor', - handle: clientCharacteristicConfigurationDescriptorHandle, + handle: null, uuid: '2902', attribute: characteristic, properties: (0x02 | 0x04 | 0x08), // read/write secure: (secure & 0x10) ? (0x02 | 0x04 | 0x08) : 0, value: new Buffer([0x00, 0x00]) - }); + }); } var descriptors = characteristic.descriptors; for (var k = 0, kk = descriptors.length; k < kk; k++) { var descriptor = descriptors[k]; - - handle++; - var descriptorHandle = handle; - - handles.push({ + serviceHandles.push({ type: 'descriptor', - handle: descriptorHandle, + handle: null, uuid: descriptor.uuid, attribute: descriptor, properties: 0x02, // read only @@ -237,83 +175,99 @@ function createServiceHandles(service, start){ } } - handles[0].endHandle = handle; - - return handles; -} + var attHandle; + if(!handles.length){ + attHandle = 1; + } else if(parseInt((serviceHandles.length + handles.length), 16) >= 0xFFFF){ + var consecutiveCount = 0; + var groupedOpenings = {}; + for(var i = 0, ii = handles.length; i < ii; i++){ + var handle = handles[i]; + + if(!handle.uuid){ + consecutiveCount++; + } else if(handle.uuid && consecutiveCount) { + var previousIndex = i - 1; + + var start = previousIndex - consecutiveCount; + var end = previousIndex; + groupedOpenings[consecutiveCount] = { start: start, end: end }; + consecutiveCount = 0; + } + } -Gatt.prototype.addService = function AddGATTService(service){ - this._services.push(service); - var handles = createServiceHandles(service, this._handles.length - 1); - for(var i = 0, ii = handles.length; i < ii; i++){ - var handle = handles[i].startHandle || handles[i].handle; - this._handles[handle] = handles[i]; + var attributeCount = serviceHandles.length; + if(groupedOpenings[attributeCount]){ + attHandle = groupedOpenings[attributeCount].start; + } else { + for(var openingCount in groupedOpenings){ + if(parseInt(openingCount, 10) >= attributeCount){ + attHandle = groupedOpenings[openingCount].start; + } + } + } + } else { + attHandle = handles.length; } - // Indicate that services changed. -var serviceHandleObject = handles[0]; + var startHandle = attHandle; + for(var l = 0, ll = serviceHandles.length; l < ll; l++){ + var serviceHandle = serviceHandles[l]; - var serviceChangedValue = new Buffer(4); - serviceChangedValue.writeUInt16LE(parseInt(serviceHandleObject.startHandle, 16), 0); - serviceChangedValue.writeUInt16LE(parseInt(serviceHandleObject.endHandle, 16), 2); - this._handles[this._serviceChangedValueHandle].value = serviceChangedValue; + if (serviceHandle.type === 'service'){ + serviceHandle.startHandle = attHandle; + serviceHandle.endHandle = (attHandle + serviceHandles.length); + } else if(serviceHandle.type === 'characteristic'){ + if (serviceHandle.uuid === '2a05'){ + this._serviceChangedValueHandle = attHandle - 1; + } + serviceHandle.startHandle = attHandle; + serviceHandle.valueHandle = (attHandle + 1) + } else if(serviceHandle.type === 'characteristicValue' || characteristic.type === 'descriptor'){ + serviceHandle.handle = attHandle; + } + handles[attHandle] = serviceHandle; + attHandle++; + } - var indicateMessage = createATTMessage(ATT_OP_HANDLE_IND, this._mtu, this._serviceChangedValueHandle, serviceChangedValue); - this._lastIndicatedAttribute = this._handles[this._serviceChangedValueHandle - 1].attribute; + var endHandle = startHandle + serviceHandles.length; + if(indicate){ + this.serviceChanged(startHandle, endHandle); + } + service.handle = startHandle; - debug('addService: indicate message: ' + indicateMessage.toString('hex')); - this.send(indicateMessage); + debug('addService: added handles ' + startHandle + '-' + endHandle); }; Gatt.prototype.removeAllServices = function RemoveAllGATTServices(){ - var startHandle = null; - var endHandle = null; - for (var i = 0; i < this._handles.length; i++) { - var handle = this._handles[i]; + var handles = this._handles; + for (var i = 0; i < handles.length; i++) { + var handle = handles[i]; if(handle.uuid === '1800' || handle.uuid === '1801' || handle.uuid === '2a00' || handle.uuid === '2a01' || handle.uuid === '2a05'){ continue; } else { - if (!startHandle && handle.uuid) { - startHandle = i; - } - - if(handle.uuid){ - endHandle = i; - } + handle = {}; } } - var serviceChangedValue = new Buffer(4); - serviceChangedValue.writeUInt16LE(parseInt(startHandle, 16), 0); - serviceChangedValue.writeUInt16LE(parseInt(endHandle, 16), 2); - this._handles[this._serviceChangedValueHandle].value = serviceChangedValue; - - var indicateMessage = createATTMessage(ATT_OP_HANDLE_IND, this._mtu, this._serviceChangedValueHandle, - serviceChangedValue); + this.serviceChanged(0x0001, 0xFFFF); + debug('removedAllServices'); }; Gatt.prototype.removeService = function RemoveGattService(service){ var handle = service.handle; + var handles = this._handles; - var startHandle = this._handles[handle].startHandle; - var endHandle = this._handles[handle].endHandle; + var startHandle = handles[handle].startHandle; + var endHandle = handles[handle].endHandle; for(var handle = startHandle; handle <= endHandle; handle++){ // Don't slice, indexing will be corrupted. - this._handles[handle] = {}; + handles[handle] = {}; } - debug('removeService - removed handles: ' + startHandle + '-' + endHandle); - - var serviceChangedValue = new Buffer(4); - serviceChangedValue.writeUInt16LE(parseInt(startHandle, 16), 0); - serviceChangedValue.writeUInt16LE(parseInt(endHandle, 16), 2); - this._handles[this._serviceChangedValueHandle].value = serviceChangedValue; + this.serviceChanged(startHandle, endHandle); - var indicateMessage = createATTMessage(ATT_OP_HANDLE_IND, this._mtu, this._serviceChangedValueHandle, serviceChangedValue); - this._lastIndicatedAttribute = this._handles[this._serviceChangedValueHandle - 1].attribute; - - debug('removeService: indicate message: ' + indicateMessage.toString('hex')); - this.send(indicateMessage); + debug('removeService - removed handles: ' + startHandle + '-' + endHandle); }; /** @@ -324,27 +278,48 @@ Gatt.prototype.removeService = function RemoveGattService(service){ */ Gatt.prototype.setServices = function SetGATTServices(services) { var deviceName = process.env.BLENO_DEVICE_NAME || os.hostname(); - this._services[0].characteristics[0].value = new Buffer(deviceName); services = services || []; // base services and characteristics - this._services = this._services.concat(services); - + var baseServices = [ + { + uuid: '1800', + characteristics: [ + { + uuid: '2a00', + properties: ['read'], + secure: [], + value: new Buffer(deviceName), + descriptors: [] + }, + { + uuid: '2a01', + properties: ['read'], + secure: [], + value: new Buffer([0x80, 0x00]), + descriptors: [] + } + ] + }, + { + uuid: '1801', + characteristics: [ + { + uuid: '2a05', + properties: ['indicate'], + secure: [], + value: new Buffer([0x00, 0x00, 0x00, 0x00]), + descriptors: [] + } + ] + } + ]; + services = baseServices.concat(services); this._handles = []; - for (var i = 0, ii = this._services.length; i < ii; i++) { - var service = this._services[i]; - - // A bit hacky, but keeps indexing proper so access can be this._handles[index], concat will leave empty properties - var serviceHandles = createServiceHandles(service, this._handles.length - 1); - for(var j = 0, jj = serviceHandles.length; j < jj; j++){ - var handle = serviceHandles[j].startHandle || serviceHandles[j].handle; - this._handles[handle] = serviceHandles[j]; - - if (this._handles[handle].uuid === '2a05'){ - this._serviceChangedValueHandle = handle; - } - } + for (var i = 0, ii = services.length; i < ii; i++) { + var service = services[i]; + this.addService(service, false); } var debugHandles = []; @@ -364,6 +339,16 @@ Gatt.prototype.setServices = function SetGATTServices(services) { debug('handles = ' + JSON.stringify(debugHandles, null, 2)); }; +Gatt.prototype.serviceChanged = function ServiceChanged(startHandle, endHandle){ + var serviceChangedValue = new Buffer(4); + serviceChangedValue.writeUInt16LE(parseInt(startHandle, 16), 0); + serviceChangedValue.writeUInt16LE(parseInt(endHandle, 16), 2); + this._handles[this._serviceChangedValueHandle].value = serviceChangedValue; + + this.sendATTMessage(ATT_OP_HANDLE_IND, this._serviceChangedValueHandle, serviceChangedValue); + this._lastIndicatedAttribute = this._handles[this._serviceChangedValueHandle - 1].attribute; +}; + Gatt.prototype.setAclStream = function(aclStream) { this._aclStream = aclStream; @@ -391,6 +376,32 @@ Gatt.prototype.send = function(data) { } }; +Gatt.prototype.sendATTMessage = function SendATTMessage(op, valueHandle, data){ + var mtu = this._mtu; + var dataLength = Math.min(data.length, mtu - 3); + var message = new Buffer(3 + dataLength); + + message.writeUInt8(parseInt(op, 16), 0); + message.writeUInt16LE(parseInt(valueHandle, 16), 1); + + for (i = 0; i < dataLength; i++) { + message[3 + i] = data[i]; + } + this.send(message); + + switch(op){ + case ATT_OP_HANDLE_IND: + msg = 'indicate'; + break; + case ATT_OP_HANDLE_NOTIFY: + msg = 'notify'; + break; + default: + msg = ' '; + } + debug('sendATTMessage: ' + msg + '-' + message.toString('hex')); +} + Gatt.prototype.errorResponse = function(opcode, handle, status) { var buf = new Buffer(5); @@ -938,19 +949,13 @@ Gatt.prototype.handleWriteRequestOrCommand = function handleWriteRequestOrComman var i; if (useNotify) { - var notifyMessage = createATTMessage(ATT_OP_HANDLE_NOTIFY, this._mtu, valueHandle, data); - - debug('notify message: ' + notifyMessage.toString('hex')); - this.send(notifyMessage); + this.sendATTMessage(ATT_OP_HANDLE_NOTIFY, valueHandle, data); attribute.emit('notify'); } else if (useIndicate) { - var indicateMessage = createATTMessage(ATT_OP_HANDLE_IND, this._mtu, valueHandle, data); + this.sendATTMessage(ATT_OP_HANDLE_IND, valueHandle, data); this._lastIndicatedAttribute = attribute; - - debug('indicate message: ' + indicateMessage.toString('hex')); - this.send(indicateMessage); } }.bind(this); }.bind(this))(valueHandle - 1, handleAttribute);