1 /* Copyright 2016-present Samsung Electronics Co., Ltd. and other contributors
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 /* Copyright (C) 2015 Sandeep Mistry sandeep.mistry@gmail.com
18 * Permission is hereby granted, free of charge, to any person obtaining a copy
19 * of this software and associated documentation files (the "Software"), to deal
20 * in the Software without restriction, including without limitation the rights
21 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22 * copies of the Software, and to permit persons to whom the Software is
23 * furnished to do so, subject to the following conditions:
25 * The above copyright notice and this permission notice shall be included in
26 * all copies or substantial portions of the Software.
28 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37 /*jshint loopfunc: true */
39 var debug = console.log; //requir('debug')('ble_hci_socket_gatt');
41 var events = require('events');
42 var util = require('util');
44 var ATT_OP_ERROR = 0x01;
45 var ATT_OP_MTU_REQ = 0x02;
46 var ATT_OP_MTU_RESP = 0x03;
47 var ATT_OP_FIND_INFO_REQ = 0x04;
48 var ATT_OP_FIND_INFO_RESP = 0x05;
49 var ATT_OP_FIND_BY_TYPE_REQ = 0x06;
50 var ATT_OP_FIND_BY_TYPE_RESP = 0x07;
51 var ATT_OP_READ_BY_TYPE_REQ = 0x08;
52 var ATT_OP_READ_BY_TYPE_RESP = 0x09;
53 var ATT_OP_READ_REQ = 0x0a;
54 var ATT_OP_READ_RESP = 0x0b;
55 var ATT_OP_READ_BLOB_REQ = 0x0c;
56 var ATT_OP_READ_BLOB_RESP = 0x0d;
57 var ATT_OP_READ_MULTI_REQ = 0x0e;
58 var ATT_OP_READ_MULTI_RESP = 0x0f;
59 var ATT_OP_READ_BY_GROUP_REQ = 0x10;
60 var ATT_OP_READ_BY_GROUP_RESP = 0x11;
61 var ATT_OP_WRITE_REQ = 0x12;
62 var ATT_OP_WRITE_RESP = 0x13;
63 var ATT_OP_WRITE_CMD = 0x52;
64 var ATT_OP_PREP_WRITE_REQ = 0x16;
65 var ATT_OP_PREP_WRITE_RESP = 0x17;
66 var ATT_OP_EXEC_WRITE_REQ = 0x18;
67 var ATT_OP_EXEC_WRITE_RESP = 0x19;
68 var ATT_OP_HANDLE_NOTIFY = 0x1b;
69 var ATT_OP_HANDLE_IND = 0x1d;
70 var ATT_OP_HANDLE_CNF = 0x1e;
71 var ATT_OP_SIGNED_WRITE_CMD = 0xd2;
73 var GATT_PRIM_SVC_UUID = 0x2800;
74 var GATT_INCLUDE_UUID = 0x2802;
75 var GATT_CHARAC_UUID = 0x2803;
77 var GATT_CLIENT_CHARAC_CFG_UUID = 0x2902;
78 var GATT_SERVER_CHARAC_CFG_UUID = 0x2903;
80 var ATT_ECODE_SUCCESS = 0x00;
81 var ATT_ECODE_INVALID_HANDLE = 0x01;
82 var ATT_ECODE_READ_NOT_PERM = 0x02;
83 var ATT_ECODE_WRITE_NOT_PERM = 0x03;
84 var ATT_ECODE_INVALID_PDU = 0x04;
85 var ATT_ECODE_AUTHENTICATION = 0x05;
86 var ATT_ECODE_REQ_NOT_SUPP = 0x06;
87 var ATT_ECODE_INVALID_OFFSET = 0x07;
88 var ATT_ECODE_AUTHORIZATION = 0x08;
89 var ATT_ECODE_PREP_QUEUE_FULL = 0x09;
90 var ATT_ECODE_ATTR_NOT_FOUND = 0x0a;
91 var ATT_ECODE_ATTR_NOT_LONG = 0x0b;
92 var ATT_ECODE_INSUFF_ENCR_KEY_SIZE = 0x0c;
93 var ATT_ECODE_INVAL_ATTR_VALUE_LEN = 0x0d;
94 var ATT_ECODE_UNLIKELY = 0x0e;
95 var ATT_ECODE_INSUFF_ENC = 0x0f;
96 var ATT_ECODE_UNSUPP_GRP_TYPE = 0x10;
97 var ATT_ECODE_INSUFF_RESOURCES = 0x11;
101 var Gatt = function() {
104 this._preparedWriteRequest = null;
106 this.setServices([]);
108 this.onAclStreamDataBinded = this.onAclStreamData.bind(this);
109 this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this);
112 util.inherits(Gatt, events.EventEmitter);
114 Gatt.prototype.setServices = function(services) {
115 var deviceName = process.env.BLENO_DEVICE_NAME || process.platform;
117 // base services and characteristics
124 properties: ['read'],
126 value: new Buffer(deviceName),
131 properties: ['read'],
133 value: new Buffer([0x80, 0x00]),
143 properties: ['indicate'],
145 value: new Buffer([0x00, 0x00, 0x00, 0x00]),
158 for (i = 0; i < allServices.length; i++) {
159 var service = allServices[i];
162 var serviceHandle = handle;
164 this._handles[serviceHandle] = {
168 startHandle: serviceHandle
169 // endHandle filled in below
172 for (j = 0; j < service.characteristics.length; j++) {
173 var characteristic = service.characteristics[j];
178 if (characteristic.properties.indexOf('read') !== -1) {
181 if (characteristic.secure.indexOf('read') !== -1) {
186 if (characteristic.properties.indexOf('writeWithoutResponse') !== -1) {
189 if (characteristic.secure.indexOf('writeWithoutResponse') !== -1) {
194 if (characteristic.properties.indexOf('write') !== -1) {
197 if (characteristic.secure.indexOf('write') !== -1) {
202 if (characteristic.properties.indexOf('notify') !== -1) {
205 if (characteristic.secure.indexOf('notify') !== -1) {
210 if (characteristic.properties.indexOf('indicate') !== -1) {
213 if (characteristic.secure.indexOf('indicate') !== -1) {
219 var characteristicHandle = handle;
222 var characteristicValueHandle = handle;
224 this._handles[characteristicHandle] = {
225 type: 'characteristic',
226 uuid: characteristic.uuid,
227 properties: properties,
229 attribute: characteristic,
230 startHandle: characteristicHandle,
231 valueHandle: characteristicValueHandle
234 this._handles[characteristicValueHandle] = {
235 type: 'characteristicValue',
236 handle: characteristicValueHandle,
237 value: characteristic.value
240 if (properties & 0x30) { // notify or indicate
241 // add client characteristic configuration descriptor
244 var clientCharacteristicConfigurationDescriptorHandle = handle;
245 this._handles[clientCharacteristicConfigurationDescriptorHandle] = {
247 handle: clientCharacteristicConfigurationDescriptorHandle,
249 attribute: characteristic,
250 properties: (0x02 | 0x04 | 0x08), // read/write
251 secure: (secure & 0x10) ? (0x02 | 0x04 | 0x08) : 0,
252 value: new Buffer([0x00, 0x00])
256 for (var k = 0; k < characteristic.descriptors.length; k++) {
257 var descriptor = characteristic.descriptors[k];
260 var descriptorHandle = handle;
262 this._handles[descriptorHandle] = {
264 handle: descriptorHandle,
265 uuid: descriptor.uuid,
266 attribute: descriptor,
267 properties: 0x02, // read only
269 value: descriptor.value
274 this._handles[serviceHandle].endHandle = handle;
277 var debugHandles = [];
278 for (i = 0; i < this._handles.length; i++) {
279 handle = this._handles[i];
281 debugHandles[i] = {};
283 if (Buffer.isBuffer(handle[j])) {
284 debugHandles[i][j] = handle[j] ? 'Buffer(\'' + handle[j].toString('hex') + '\', \'hex\')' : null;
285 } else if (j !== 'attribute') {
286 debugHandles[i][j] = handle[j];
291 debug('handles = ' + JSON.stringify(debugHandles, null, 2));
294 Gatt.prototype.setAclStream = function(aclStream) {
296 this._preparedWriteRequest = null;
298 this._aclStream = aclStream;
300 this._aclStream.on('data', this.onAclStreamDataBinded);
301 this._aclStream.on('end', this.onAclStreamEndBinded);
304 Gatt.prototype.onAclStreamData = function(cid, data) {
305 if (cid !== ATT_CID) {
309 this.handleRequest(data);
312 Gatt.prototype.onAclStreamEnd = function() {
313 this._aclStream.removeListener('data', this.onAclStreamDataBinded);
314 this._aclStream.removeListener('end', this.onAclStreamEndBinded);
317 Gatt.prototype.send = function(data) {
318 debug('send: ' + data.toString('hex'));
319 this._aclStream.write(ATT_CID, data);
322 Gatt.prototype.errorResponse = function(opcode, handle, status) {
323 var buf = new Buffer(5);
325 buf.writeUInt8(ATT_OP_ERROR, 0);
326 buf.writeUInt8(opcode, 1);
327 buf.writeUInt16LE(handle, 2);
328 buf.writeUInt8(status, 4);
333 Gatt.prototype.handleRequest = function(request) {
334 debug('handing request: ' + request.toString('hex'));
336 var requestType = request.readUInt8(0); //buf[0];
339 switch(requestType) {
341 response = this.handleMtuRequest(request);
344 case ATT_OP_FIND_INFO_REQ:
345 response = this.handleFindInfoRequest(request);
348 case ATT_OP_FIND_BY_TYPE_REQ:
349 response = this.handleFindByTypeRequest(request);
352 case ATT_OP_READ_BY_TYPE_REQ:
353 response = this.handleReadByTypeRequest(request);
356 case ATT_OP_READ_REQ:
357 case ATT_OP_READ_BLOB_REQ:
358 response = this.handleReadOrReadBlobRequest(request);
361 case ATT_OP_READ_BY_GROUP_REQ:
362 response = this.handleReadByGroupRequest(request);
365 case ATT_OP_WRITE_REQ:
366 case ATT_OP_WRITE_CMD:
367 response = this.handleWriteRequestOrCommand(request);
370 case ATT_OP_PREP_WRITE_REQ:
371 response = this.handlePrepareWriteRequest(request);
374 case ATT_OP_EXEC_WRITE_REQ:
375 response = this.handleExecuteWriteRequest(request);
378 case ATT_OP_HANDLE_CNF:
379 response = this.handleConfirmation(request);
383 case ATT_OP_READ_MULTI_REQ:
384 case ATT_OP_SIGNED_WRITE_CMD:
385 response = this.errorResponse(requestType, 0x0000, ATT_ECODE_REQ_NOT_SUPP);
390 debug('response: ' + response.toString('hex'));
396 Gatt.prototype.handleMtuRequest = function(request) {
397 var mtu = request.readUInt16LE(1);
401 } else if (mtu > this.maxMtu) {
407 this.emit('mtuChange', this._mtu);
409 var response = new Buffer(3);
411 response.writeUInt8(ATT_OP_MTU_RESP, 0);
412 response.writeUInt16LE(mtu, 1);
417 Gatt.prototype.handleFindInfoRequest = function(request) {
420 var startHandle = request.readUInt16LE(1);
421 var endHandle = request.readUInt16LE(3);
426 for (i = startHandle; i <= endHandle; i++) {
427 var handle = this._handles[i];
435 if ('service' === handle.type) {
437 } else if ('includedService' === handle.type) {
439 } else if ('characteristic' === handle.type) {
441 } else if ('characteristicValue' === handle.type) {
442 uuid = this._handles[i - 1].uuid;
443 } else if ('descriptor' === handle.type) {
456 var uuidSize = infos[0].uuid.length / 2;
459 for (i = 1; i < infos.length; i++) {
460 if (infos[0].uuid.length !== infos[i].uuid.length) {
466 var lengthPerInfo = (uuidSize === 2) ? 4 : 18;
467 var maxInfo = Math.floor((this._mtu - 2) / lengthPerInfo);
468 numInfo = Math.min(numInfo, maxInfo);
470 response = new Buffer(2 + numInfo * lengthPerInfo);
472 //response[0] = ATT_OP_FIND_INFO_RESP;
473 //response[1] = (uuidSize === 2) ? 0x01 : 0x2;
474 response.writeUInt8(ATT_OP_FIND_INFO_RESP, 0);
475 response.writeUInt8((uuidSize === 2) ? 0x01 : 0x2, 1);
477 for (i = 0; i < numInfo; i++) {
480 response.writeUInt16LE(info.handle, 2 + i * lengthPerInfo);
482 uuid = new Buffer(info.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
483 for (var j = 0; j < uuid.length; j++) {
484 //response[2 + i * lengthPerInfo + 2 + j] = uuid[j];
485 response.writeUInt8(uuid[j], 2 + i * lengthPerInfo + 2 + j);
489 response = this.errorResponse(ATT_OP_FIND_INFO_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
495 Gatt.prototype.handleFindByTypeRequest = function(request) {
498 var startHandle = request.readUInt16LE(1);
499 var endHandle = request.readUInt16LE(3);
500 var uuid = request.slice(5, 7).toString('hex').match(/.{1,2}/g).reverse().join('');
501 var value = request.slice(7).toString('hex').match(/.{1,2}/g).reverse().join('');
506 for (var i = startHandle; i <= endHandle; i++) {
507 handle = this._handles[i];
513 if ('2800' === uuid && handle.type === 'service' && handle.uuid === value) {
515 start: handle.startHandle,
516 end: handle.endHandle
521 if (handles.length) {
522 var lengthPerHandle = 4;
523 var numHandles = handles.length;
524 var maxHandles = Math.floor((this._mtu - 1) / lengthPerHandle);
526 numHandles = Math.min(numHandles, maxHandles);
528 response = new Buffer(1 + numHandles * lengthPerHandle);
530 //response[0] = ATT_OP_FIND_BY_TYPE_RESP;
531 response.writeUInt8(ATT_OP_FIND_BY_TYPE_RESP, 0);
533 for (i = 0; i < numHandles; i++) {
536 response.writeUInt16LE(handle.start, 1 + i * lengthPerHandle);
537 response.writeUInt16LE(handle.end, 1 + i * lengthPerHandle + 2);
540 response = this.errorResponse(ATT_OP_FIND_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
546 Gatt.prototype.handleReadByGroupRequest = function(request) {
549 var startHandle = request.readUInt16LE(1);
550 var endHandle = request.readUInt16LE(3);
551 var uuid = request.slice(5).toString('hex').match(/.{1,2}/g).reverse().join('');
553 debug('read by group: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16));
555 if ('2800' === uuid || '2802' === uuid) {
557 var type = ('2800' === uuid) ? 'service' : 'includedService';
560 for (i = startHandle; i <= endHandle; i++) {
561 var handle = this._handles[i];
567 if (handle.type === type) {
568 services.push(handle);
572 if (services.length) {
573 var uuidSize = services[0].uuid.length / 2;
576 for (i = 1; i < services.length; i++) {
577 if (services[0].uuid.length !== services[i].uuid.length) {
583 var lengthPerService = (uuidSize === 2) ? 6 : 20;
584 var maxServices = Math.floor((this._mtu - 2) / lengthPerService);
585 numServices = Math.min(numServices, maxServices);
587 response = new Buffer(2 + numServices * lengthPerService);
589 //response[0] = ATT_OP_READ_BY_GROUP_RESP;
590 //response[1] = lengthPerService;
591 response.writeUInt8(ATT_OP_READ_BY_GROUP_RESP, 0);
592 response.writeUInt8(lengthPerService, 1);
594 for (i = 0; i < numServices; i++) {
595 var service = services[i];
597 response.writeUInt16LE(service.startHandle, 2 + i * lengthPerService);
598 response.writeUInt16LE(service.endHandle, 2 + i * lengthPerService + 2);
600 var serviceUuid = new Buffer(service.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
601 for (var j = 0; j < serviceUuid.length; j++) {
602 //response[2 + i * lengthPerService + 4 + j] = serviceUuid[j];
603 response.writeUInt8(serviceUuid.readUInt8(j), 2 + i * lengthPerService + 4 + j);
607 response = this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
610 response = this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_UNSUPP_GRP_TYPE);
616 Gatt.prototype.handleReadByTypeRequest = function(request) {
619 var startHandle = request.readUInt16LE(1);
620 var endHandle = request.readUInt16LE(3);
621 var uuid = request.slice(5).toString('hex').match(/.{1,2}/g).reverse().join('');
625 debug('read by type: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16));
627 if ('2803' === uuid) {
628 var characteristics = [];
630 for (i = startHandle; i <= endHandle; i++) {
631 handle = this._handles[i];
637 if (handle.type === 'characteristic') {
638 characteristics.push(handle);
642 if (characteristics.length) {
643 var uuidSize = characteristics[0].uuid.length / 2;
644 var numCharacteristics = 1;
646 for (i = 1; i < characteristics.length; i++) {
647 if (characteristics[0].uuid.length !== characteristics[i].uuid.length) {
650 numCharacteristics++;
653 var lengthPerCharacteristic = (uuidSize === 2) ? 7 : 21;
654 var maxCharacteristics = Math.floor((this._mtu - 2) / lengthPerCharacteristic);
655 numCharacteristics = Math.min(numCharacteristics, maxCharacteristics);
657 response = new Buffer(2 + numCharacteristics * lengthPerCharacteristic);
659 //response[0] = ATT_OP_READ_BY_TYPE_RESP;
660 //response[1] = lengthPerCharacteristic;
661 response.writeUInt8(ATT_OP_READ_BY_TYPE_RESP, 0);
662 response.writeUInt8(lengthPerCharacteristic, 1);
664 for (i = 0; i < numCharacteristics; i++) {
665 var characteristic = characteristics[i];
667 response.writeUInt16LE(characteristic.startHandle, 2 + i * lengthPerCharacteristic);
668 response.writeUInt8(characteristic.properties, 2 + i * lengthPerCharacteristic + 2);
669 response.writeUInt16LE(characteristic.valueHandle, 2 + i * lengthPerCharacteristic + 3);
671 var characteristicUuid = new Buffer(characteristic.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
672 for (var j = 0; j < characteristicUuid.length; j++) {
673 //response[2 + i * lengthPerCharacteristic + 5 + j] = characteristicUuid[j];
674 response.writeUInt8(characteristicUuid.readUInt8(j), 2 + i * lengthPerCharacteristic + 5 + j);
678 response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
681 var handleAttribute = null;
682 var valueHandle = null;
685 for (i = startHandle; i <= endHandle; i++) {
686 handle = this._handles[i];
692 if (handle.type === 'characteristic' && handle.uuid === uuid) {
693 handleAttribute = handle.attribute;
694 valueHandle = handle.valueHandle;
695 secure = handle.secure & 0x02;
697 } else if (handle.type === 'descriptor' && handle.uuid === uuid) {
699 secure = handle.secure & 0x02;
704 if (secure && !this._aclStream.encrypted) {
705 response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_AUTHENTICATION);
706 } else if (valueHandle) {
707 var callback = (function(valueHandle) {
708 return function(result, data) {
709 var callbackResponse = null;
711 if (ATT_ECODE_SUCCESS === result) {
712 var dataLength = Math.min(data.length, this._mtu - 4);
713 callbackResponse = new Buffer(4 + dataLength);
715 //callbackResponse[0] = ATT_OP_READ_BY_TYPE_RESP;
716 //callbackResponse[1] = dataLength + 2;
717 callbackResponse.writeUInt8(ATT_OP_READ_BY_TYPE_RESP, 0);
718 callbackResponse.writeUInt8(dataLength + 2, 1);
719 callbackResponse.writeUInt16LE(valueHandle, 2);
720 for (i = 0; i < dataLength; i++) {
721 //callbackResponse[4 + i] = data[i];
722 callbackResponse.writeUInt8(data.readUInt8(i), 4 + i);
725 callbackResponse = this.errorResponse(requestType, valueHandle, result);
728 debug('read by type response: ' + callbackResponse.toString('hex'));
730 this.send(callbackResponse);
732 }.bind(this))(valueHandle);
734 var data = this._handles[valueHandle].value;
737 callback(ATT_ECODE_SUCCESS, data);
738 } else if (handleAttribute) {
739 handleAttribute.emit('readRequest', 0, callback);
741 callback(ATT_ECODE_UNLIKELY);
744 response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
751 Gatt.prototype.handleReadOrReadBlobRequest = function(request) {
754 //var requestType = request[0];
755 var requestType = request.readUInt8(0);
756 var valueHandle = request.readUInt16LE(1);
757 var offset = (requestType === ATT_OP_READ_BLOB_REQ) ? request.readUInt16LE(3) : 0;
759 var handle = this._handles[valueHandle];
764 var handleType = handle.type;
766 var callback = (function(requestType, valueHandle) {
767 return function(result, data) {
768 var callbackResponse = null;
770 if (ATT_ECODE_SUCCESS === result) {
771 var dataLength = Math.min(data.length, this._mtu - 1);
772 callbackResponse = new Buffer(1 + dataLength);
774 //callbackResponse[0] = (requestType === ATT_OP_READ_BLOB_REQ) ? ATT_OP_READ_BLOB_RESP : ATT_OP_READ_RESP;
775 callbackResponse.writeUInt8((requestType === ATT_OP_READ_BLOB_REQ) ? ATT_OP_READ_BLOB_RESP : ATT_OP_READ_RESP, 0);
776 for (i = 0; i < dataLength; i++) {
777 //callbackResponse[1 + i] = data[i];
778 callbackResponse.writeUInt8(data.readUInt8(i), 1 + i);
781 callbackResponse = this.errorResponse(requestType, valueHandle, result);
784 debug('read response: ' + callbackResponse.toString('hex'));
786 this.send(callbackResponse);
788 }.bind(this))(requestType, valueHandle);
790 if (handleType === 'service' || handleType === 'includedService') {
791 result = ATT_ECODE_SUCCESS;
792 data = new Buffer(handle.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
793 } else if (handleType === 'characteristic') {
794 var uuid = new Buffer(handle.uuid.match(/.{1,2}/g).reverse().join(''), 'hex');
796 result = ATT_ECODE_SUCCESS;
797 data = new Buffer(3 + uuid.length);
798 data.writeUInt8(handle.properties, 0);
799 data.writeUInt16LE(handle.valueHandle, 1);
801 for (i = 0; i < uuid.length; i++) {
802 //data[i + 3] = uuid[i];
803 data.writeUInt8(uuid.readUInt8(i), i + 3);
805 } else if (handleType === 'characteristicValue' || handleType === 'descriptor') {
806 var handleProperties = handle.properties;
807 var handleSecure = handle.secure;
808 var handleAttribute = handle.attribute;
809 if (handleType === 'characteristicValue') {
810 handleProperties = this._handles[valueHandle - 1].properties;
811 handleSecure = this._handles[valueHandle - 1].secure;
812 handleAttribute = this._handles[valueHandle - 1].attribute;
815 if (handleProperties & 0x02) {
816 if (handleSecure & 0x02 && !this._aclStream.encrypted) {
817 result = ATT_ECODE_AUTHENTICATION;
822 result = ATT_ECODE_SUCCESS;
824 handleAttribute.emit('readRequest', offset, callback);
828 result = ATT_ECODE_READ_NOT_PERM; // non-readable
832 if (data && typeof data === 'string') {
833 data = new Buffer(data);
836 if (result === ATT_ECODE_SUCCESS && data && offset) {
837 if (data.length < offset) {
838 errorCode = ATT_ECODE_INVALID_OFFSET;
841 data = data.slice(offset);
845 if (result !== null) {
846 callback(result, data);
849 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE);
855 Gatt.prototype.handleWriteRequestOrCommand = function(request) {
858 //var requestType = request[0];
859 var requestType = request.readUInt8(0);
860 var withoutResponse = (requestType === ATT_OP_WRITE_CMD);
861 var valueHandle = request.readUInt16LE(1);
862 var data = request.slice(3);
865 var handle = this._handles[valueHandle];
868 if (handle.type === 'characteristicValue') {
869 handle = this._handles[valueHandle - 1];
872 var handleProperties = handle.properties;
873 var handleSecure = handle.secure;
875 if (handleProperties && (withoutResponse ? (handleProperties & 0x04) : (handleProperties & 0x08))) {
877 var callback = (function(requestType, valueHandle, withoutResponse) {
878 return function(result) {
879 if (!withoutResponse) {
880 var callbackResponse = null;
882 if (ATT_ECODE_SUCCESS === result) {
883 callbackResponse = new Buffer([ATT_OP_WRITE_RESP]);
885 callbackResponse = this.errorResponse(requestType, valueHandle, result);
888 debug('write response: ' + callbackResponse.toString('hex'));
890 this.send(callbackResponse);
893 }.bind(this))(requestType, valueHandle, withoutResponse);
895 if (handleSecure & (withoutResponse ? 0x04 : 0x08) && !this._aclStream.encrypted) {
896 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_AUTHENTICATION);
897 } else if (handle.type === 'descriptor' || handle.uuid === '2902') {
900 if (data.length !== 2) {
901 result = ATT_ECODE_INVAL_ATTR_VALUE_LEN;
903 var value = data.readUInt16LE(0);
904 var handleAttribute = handle.attribute;
908 if (value & 0x0003) {
909 var updateValueCallback = (function(valueHandle, attribute) {
910 return function(data) {
911 var dataLength = Math.min(data.length, this._mtu - 3);
912 var useNotify = attribute.properties.indexOf('notify') !== -1;
913 var useIndicate = attribute.properties.indexOf('indicate') !== -1;
917 var notifyMessage = new Buffer(3 + dataLength);
919 notifyMessage.writeUInt8(ATT_OP_HANDLE_NOTIFY, 0);
920 notifyMessage.writeUInt16LE(valueHandle, 1);
922 for (i = 0; i < dataLength; i++) {
923 //notifyMessage[3 + i] = data[i];
924 notifyMessage.writeUInt8(data.readUInt8(i), 3 + i);
927 debug('notify message: ' + notifyMessage.toString('hex'));
928 this.send(notifyMessage);
930 attribute.emit('notify');
931 } else if (useIndicate) {
932 var indicateMessage = new Buffer(3 + dataLength);
934 indicateMessage.writeUInt8(ATT_OP_HANDLE_IND, 0);
935 indicateMessage.writeUInt16LE(valueHandle, 1);
937 for (i = 0; i < dataLength; i++) {
938 //indicateMessage[3 + i] = data[i];
939 indicateMessage.writeUInt8(data.readUInt8(i), 3 + i);
942 this._lastIndicatedAttribute = attribute;
944 debug('indicate message: ' + indicateMessage.toString('hex'));
945 this.send(indicateMessage);
948 }.bind(this))(valueHandle - 1, handleAttribute);
950 if (handleAttribute.emit) {
951 handleAttribute.emit('subscribe', this._mtu - 3, updateValueCallback);
954 handleAttribute.emit('unsubscribe');
957 result = ATT_ECODE_SUCCESS;
962 handle.attribute.emit('writeRequest', data, offset, withoutResponse, callback);
965 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM);
968 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE);
974 Gatt.prototype.handlePrepareWriteRequest = function(request) {
977 //var requestType = request[0];
978 var requestType = request.readUInt8(0);
979 var valueHandle = request.readUInt16LE(1);
980 var offset = request.readUInt16LE(3);
981 var data = request.slice(5);
983 var handle = this._handles[valueHandle];
986 if (handle.type === 'characteristicValue') {
987 handle = this._handles[valueHandle - 1];
989 var handleProperties = handle.properties;
990 var handleSecure = handle.secure;
992 if (handleProperties && (handleProperties & 0x08)) {
993 if ((handleSecure & 0x08) && !this._aclStream.encrypted) {
994 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_AUTHENTICATION);
995 } else if (this._preparedWriteRequest) {
996 if (this._preparedWriteRequest.handle !== handle) {
997 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_UNLIKELY);
998 } else if (offset === (this._preparedWriteRequest.offset + this._preparedWriteRequest.data.length)) {
999 this._preparedWriteRequest.data = Buffer.concat([
1000 this._preparedWriteRequest.data,
1004 response = new Buffer(request.length);
1005 request.copy(response);
1006 //response[0] = ATT_OP_PREP_WRITE_RESP;
1007 response.writeUInt8(ATT_OP_PREP_WRITE_RESP, 0);
1009 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_OFFSET);
1012 this._preparedWriteRequest = {
1014 valueHandle: valueHandle,
1019 response = new Buffer(request.length);
1020 request.copy(response);
1021 // response[0] = ATT_OP_PREP_WRITE_RESP;
1022 response.writeUInt8(ATT_OP_PREP_WRITE_RESP, 0);
1025 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM);
1028 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_ATTR_NOT_LONG);
1031 response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE);
1037 Gatt.prototype.handleExecuteWriteRequest = function(request) {
1038 var response = null;
1040 //var requestType = request[0];
1041 //var flag = request[1];
1042 var requestType = request.readUInt8(0);
1043 var flag = request.readUInt8(1);
1045 if (this._preparedWriteRequest) {
1046 var valueHandle = this._preparedWriteRequest.valueHandle;
1048 if (flag === 0x00) {
1049 response = new Buffer([ATT_OP_EXEC_WRITE_RESP]);
1050 } else if (flag === 0x01) {
1051 var callback = (function(requestType, valueHandle) {
1052 return function(result) {
1053 var callbackResponse = null;
1055 if (ATT_ECODE_SUCCESS === result) {
1056 callbackResponse = new Buffer([ATT_OP_EXEC_WRITE_RESP]);
1058 callbackResponse = this.errorResponse(requestType, valueHandle, result);
1061 debug('execute write response: ' + callbackResponse.toString('hex'));
1063 this.send(callbackResponse);
1065 }.bind(this))(requestType, this._preparedWriteRequest.valueHandle);
1067 this._preparedWriteRequest.handle.attribute.emit('writeRequest', this._preparedWriteRequest.data, this._preparedWriteRequest.offset, false, callback);
1069 response = this.errorResponse(requestType, 0x0000, ATT_ECODE_UNLIKELY);
1072 this._preparedWriteRequest = null;
1074 response = this.errorResponse(requestType, 0x0000, ATT_ECODE_UNLIKELY);
1080 Gatt.prototype.handleConfirmation = function(request) {
1081 if (this._lastIndicatedAttribute) {
1082 if (this._lastIndicatedAttribute.emit) {
1083 this._lastIndicatedAttribute.emit('indicate');
1086 this._lastIndicatedAttribute = null;
1090 module.exports = Gatt;