Upload packaging folder
[platform/upstream/iotjs.git] / tools / src / js / ble_hci_socket_gatt.js
1 /* Copyright 2016-present Samsung Electronics Co., Ltd. and other contributors
2  *
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
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
14  */
15
16 /* Copyright (C) 2015 Sandeep Mistry sandeep.mistry@gmail.com
17  *
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:
24  *
25  * The above copyright notice and this permission notice shall be included in
26  * all copies or substantial portions of the Software.
27  *
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
34  * SOFTWARE.
35  */
36
37 /*jshint loopfunc: true */
38
39 var debug = console.log; //requir('debug')('ble_hci_socket_gatt');
40
41 var events = require('events');
42 var util = require('util');
43
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;
72
73 var GATT_PRIM_SVC_UUID              = 0x2800;
74 var GATT_INCLUDE_UUID               = 0x2802;
75 var GATT_CHARAC_UUID                = 0x2803;
76
77 var GATT_CLIENT_CHARAC_CFG_UUID     = 0x2902;
78 var GATT_SERVER_CHARAC_CFG_UUID     = 0x2903;
79
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;
98
99 var ATT_CID = 0x0004;
100
101 var Gatt = function() {
102   this.maxMtu = 256;
103   this._mtu = 23;
104   this._preparedWriteRequest = null;
105
106   this.setServices([]);
107
108   this.onAclStreamDataBinded = this.onAclStreamData.bind(this);
109   this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this);
110 };
111
112 util.inherits(Gatt, events.EventEmitter);
113
114 Gatt.prototype.setServices = function(services) {
115   var deviceName = process.env.BLENO_DEVICE_NAME || process.platform;
116
117   // base services and characteristics
118   var allServices = [
119     {
120       uuid: '1800',
121       characteristics: [
122         {
123           uuid: '2a00',
124           properties: ['read'],
125           secure: [],
126           value: new Buffer(deviceName),
127           descriptors: []
128         },
129         {
130           uuid: '2a01',
131           properties: ['read'],
132           secure: [],
133           value: new Buffer([0x80, 0x00]),
134           descriptors: []
135         }
136       ]
137     },
138     {
139       uuid: '1801',
140       characteristics: [
141         {
142           uuid: '2a05',
143           properties: ['indicate'],
144           secure: [],
145           value: new Buffer([0x00, 0x00, 0x00, 0x00]),
146           descriptors: []
147         }
148       ]
149     }
150   ].concat(services);
151
152   this._handles = [];
153
154   var handle = 0;
155   var i;
156   var j;
157
158   for (i = 0; i < allServices.length; i++) {
159     var service = allServices[i];
160
161     handle++;
162     var serviceHandle = handle;
163
164     this._handles[serviceHandle] = {
165       type: 'service',
166       uuid: service.uuid,
167       attribute: service,
168       startHandle: serviceHandle
169       // endHandle filled in below
170     };
171
172     for (j = 0; j < service.characteristics.length; j++) {
173       var characteristic = service.characteristics[j];
174
175       var properties = 0;
176       var secure = 0;
177
178       if (characteristic.properties.indexOf('read') !== -1) {
179         properties |= 0x02;
180
181         if (characteristic.secure.indexOf('read') !== -1) {
182           secure |= 0x02;
183         }
184       }
185
186       if (characteristic.properties.indexOf('writeWithoutResponse') !== -1) {
187         properties |= 0x04;
188
189         if (characteristic.secure.indexOf('writeWithoutResponse') !== -1) {
190           secure |= 0x04;
191         }
192       }
193
194       if (characteristic.properties.indexOf('write') !== -1) {
195         properties |= 0x08;
196
197         if (characteristic.secure.indexOf('write') !== -1) {
198           secure |= 0x08;
199         }
200       }
201
202       if (characteristic.properties.indexOf('notify') !== -1) {
203         properties |= 0x10;
204
205         if (characteristic.secure.indexOf('notify') !== -1) {
206           secure |= 0x10;
207         }
208       }
209
210       if (characteristic.properties.indexOf('indicate') !== -1) {
211         properties |= 0x20;
212
213         if (characteristic.secure.indexOf('indicate') !== -1) {
214           secure |= 0x20;
215         }
216       }
217
218       handle++;
219       var characteristicHandle = handle;
220
221       handle++;
222       var characteristicValueHandle = handle;
223
224       this._handles[characteristicHandle] = {
225         type: 'characteristic',
226         uuid: characteristic.uuid,
227         properties: properties,
228         secure: secure,
229         attribute: characteristic,
230         startHandle: characteristicHandle,
231         valueHandle: characteristicValueHandle
232       };
233
234       this._handles[characteristicValueHandle] = {
235         type: 'characteristicValue',
236         handle: characteristicValueHandle,
237         value: characteristic.value
238       };
239
240       if (properties & 0x30) { // notify or indicate
241         // add client characteristic configuration descriptor
242
243         handle++;
244         var clientCharacteristicConfigurationDescriptorHandle = handle;
245         this._handles[clientCharacteristicConfigurationDescriptorHandle] = {
246           type: 'descriptor',
247           handle: clientCharacteristicConfigurationDescriptorHandle,
248           uuid: '2902',
249           attribute: characteristic,
250           properties: (0x02 | 0x04 | 0x08), // read/write
251           secure: (secure & 0x10) ? (0x02 | 0x04 | 0x08) : 0,
252           value: new Buffer([0x00, 0x00])
253         };
254       }
255
256       for (var k = 0; k < characteristic.descriptors.length; k++) {
257         var descriptor = characteristic.descriptors[k];
258
259         handle++;
260         var descriptorHandle = handle;
261
262         this._handles[descriptorHandle] = {
263           type: 'descriptor',
264           handle: descriptorHandle,
265           uuid: descriptor.uuid,
266           attribute: descriptor,
267           properties: 0x02, // read only
268           secure: 0x00,
269           value: descriptor.value
270         };
271       }
272     }
273
274     this._handles[serviceHandle].endHandle = handle;
275   }
276
277   var debugHandles = [];
278   for (i = 0; i < this._handles.length; i++) {
279     handle = this._handles[i];
280
281     debugHandles[i] = {};
282     for(j in handle) {
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];
287       }
288     }
289   }
290
291   debug('handles = ' + JSON.stringify(debugHandles, null, 2));
292 };
293
294 Gatt.prototype.setAclStream = function(aclStream) {
295   this._mtu = 23;
296   this._preparedWriteRequest = null;
297
298   this._aclStream = aclStream;
299
300   this._aclStream.on('data', this.onAclStreamDataBinded);
301   this._aclStream.on('end', this.onAclStreamEndBinded);
302 };
303
304 Gatt.prototype.onAclStreamData = function(cid, data) {
305   if (cid !== ATT_CID) {
306     return;
307   }
308
309   this.handleRequest(data);
310 };
311
312 Gatt.prototype.onAclStreamEnd = function() {
313   this._aclStream.removeListener('data', this.onAclStreamDataBinded);
314   this._aclStream.removeListener('end', this.onAclStreamEndBinded);
315 };
316
317 Gatt.prototype.send = function(data) {
318   debug('send: ' + data.toString('hex'));
319   this._aclStream.write(ATT_CID, data);
320 };
321
322 Gatt.prototype.errorResponse = function(opcode, handle, status) {
323   var buf = new Buffer(5);
324
325   buf.writeUInt8(ATT_OP_ERROR, 0);
326   buf.writeUInt8(opcode, 1);
327   buf.writeUInt16LE(handle, 2);
328   buf.writeUInt8(status, 4);
329
330   return buf;
331 };
332
333 Gatt.prototype.handleRequest = function(request) {
334   debug('handing request: ' + request.toString('hex'));
335
336   var requestType = request.readUInt8(0); //buf[0];
337   var response = null;
338
339   switch(requestType) {
340     case ATT_OP_MTU_REQ:
341       response = this.handleMtuRequest(request);
342       break;
343
344     case ATT_OP_FIND_INFO_REQ:
345       response = this.handleFindInfoRequest(request);
346       break;
347
348     case ATT_OP_FIND_BY_TYPE_REQ:
349       response = this.handleFindByTypeRequest(request);
350       break;
351
352     case ATT_OP_READ_BY_TYPE_REQ:
353       response = this.handleReadByTypeRequest(request);
354       break;
355
356     case ATT_OP_READ_REQ:
357     case ATT_OP_READ_BLOB_REQ:
358       response = this.handleReadOrReadBlobRequest(request);
359       break;
360
361     case ATT_OP_READ_BY_GROUP_REQ:
362       response = this.handleReadByGroupRequest(request);
363       break;
364
365     case ATT_OP_WRITE_REQ:
366     case ATT_OP_WRITE_CMD:
367       response = this.handleWriteRequestOrCommand(request);
368       break;
369
370     case ATT_OP_PREP_WRITE_REQ:
371       response = this.handlePrepareWriteRequest(request);
372       break;
373
374     case ATT_OP_EXEC_WRITE_REQ:
375       response = this.handleExecuteWriteRequest(request);
376       break;
377
378     case ATT_OP_HANDLE_CNF:
379       response = this.handleConfirmation(request);
380       break;
381
382     default:
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);
386       break;
387   }
388
389   if (response) {
390     debug('response: ' + response.toString('hex'));
391
392     this.send(response);
393   }
394 };
395
396 Gatt.prototype.handleMtuRequest = function(request) {
397   var mtu = request.readUInt16LE(1);
398
399   if (mtu < 23) {
400     mtu = 23;
401   } else if (mtu > this.maxMtu) {
402     mtu = this.maxMtu;
403   }
404
405   this._mtu = mtu;
406
407   this.emit('mtuChange', this._mtu);
408
409   var response = new Buffer(3);
410
411   response.writeUInt8(ATT_OP_MTU_RESP, 0);
412   response.writeUInt16LE(mtu, 1);
413
414   return response;
415 };
416
417 Gatt.prototype.handleFindInfoRequest = function(request) {
418   var response = null;
419
420   var startHandle = request.readUInt16LE(1);
421   var endHandle = request.readUInt16LE(3);
422
423   var infos = [];
424   var uuid = null;
425
426   for (i = startHandle; i <= endHandle; i++) {
427     var handle = this._handles[i];
428
429     if (!handle) {
430       break;
431     }
432
433     uuid = null;
434
435     if ('service' === handle.type) {
436       uuid = '2800';
437     } else if ('includedService' === handle.type) {
438       uuid = '2802';
439     } else if ('characteristic' === handle.type) {
440       uuid = '2803';
441     } else if ('characteristicValue' === handle.type) {
442       uuid = this._handles[i - 1].uuid;
443     } else if ('descriptor' === handle.type) {
444       uuid = handle.uuid;
445     }
446
447     if (uuid) {
448       infos.push({
449         handle: i,
450         uuid: uuid
451       });
452     }
453   }
454
455   if (infos.length) {
456     var uuidSize = infos[0].uuid.length / 2;
457     var numInfo = 1;
458
459     for (i = 1; i < infos.length; i++) {
460       if (infos[0].uuid.length !== infos[i].uuid.length) {
461         break;
462       }
463       numInfo++;
464     }
465
466     var lengthPerInfo = (uuidSize === 2) ? 4 : 18;
467     var maxInfo = Math.floor((this._mtu - 2) / lengthPerInfo);
468     numInfo = Math.min(numInfo, maxInfo);
469
470     response = new Buffer(2 + numInfo * lengthPerInfo);
471
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);
476
477     for (i = 0; i < numInfo; i++) {
478       var info = infos[i];
479
480       response.writeUInt16LE(info.handle, 2 + i * lengthPerInfo);
481
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);
486       }
487     }
488   } else {
489     response = this.errorResponse(ATT_OP_FIND_INFO_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
490   }
491
492   return response;
493 };
494
495 Gatt.prototype.handleFindByTypeRequest = function(request) {
496   var response = null;
497
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('');
502
503   var handles = [];
504   var handle;
505
506   for (var i = startHandle; i <= endHandle; i++) {
507     handle = this._handles[i];
508
509     if (!handle) {
510       break;
511     }
512
513     if ('2800' === uuid && handle.type === 'service' && handle.uuid === value) {
514       handles.push({
515         start: handle.startHandle,
516         end: handle.endHandle
517       });
518     }
519   }
520
521   if (handles.length) {
522     var lengthPerHandle = 4;
523     var numHandles = handles.length;
524     var maxHandles = Math.floor((this._mtu - 1) / lengthPerHandle);
525
526     numHandles = Math.min(numHandles, maxHandles);
527
528     response = new Buffer(1 + numHandles * lengthPerHandle);
529
530     //response[0] = ATT_OP_FIND_BY_TYPE_RESP;
531     response.writeUInt8(ATT_OP_FIND_BY_TYPE_RESP, 0);
532
533     for (i = 0; i < numHandles; i++) {
534       handle = handles[i];
535
536       response.writeUInt16LE(handle.start, 1 + i * lengthPerHandle);
537       response.writeUInt16LE(handle.end, 1 + i * lengthPerHandle + 2);
538     }
539   } else {
540     response = this.errorResponse(ATT_OP_FIND_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
541   }
542
543   return response;
544 };
545
546 Gatt.prototype.handleReadByGroupRequest = function(request) {
547   var response = null;
548
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('');
552
553   debug('read by group: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16));
554
555   if ('2800' === uuid || '2802' === uuid) {
556     var services = [];
557     var type = ('2800' === uuid) ? 'service' : 'includedService';
558     var i;
559
560     for (i = startHandle; i <= endHandle; i++) {
561       var handle = this._handles[i];
562
563       if (!handle) {
564         break;
565       }
566
567       if (handle.type === type) {
568         services.push(handle);
569       }
570     }
571
572     if (services.length) {
573       var uuidSize = services[0].uuid.length / 2;
574       var numServices = 1;
575
576       for (i = 1; i < services.length; i++) {
577         if (services[0].uuid.length !== services[i].uuid.length) {
578           break;
579         }
580         numServices++;
581       }
582
583       var lengthPerService = (uuidSize === 2) ? 6 : 20;
584       var maxServices = Math.floor((this._mtu - 2) / lengthPerService);
585       numServices = Math.min(numServices, maxServices);
586
587       response = new Buffer(2 + numServices * lengthPerService);
588
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);
593
594       for (i = 0; i < numServices; i++) {
595         var service = services[i];
596
597         response.writeUInt16LE(service.startHandle, 2 + i * lengthPerService);
598         response.writeUInt16LE(service.endHandle, 2 + i * lengthPerService + 2);
599
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);
604         }
605       }
606     } else {
607       response = this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
608     }
609   } else {
610     response = this.errorResponse(ATT_OP_READ_BY_GROUP_REQ, startHandle, ATT_ECODE_UNSUPP_GRP_TYPE);
611   }
612
613   return response;
614 };
615
616 Gatt.prototype.handleReadByTypeRequest = function(request) {
617   var response = null;
618
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('');
622   var i;
623   var handle;
624
625   debug('read by type: startHandle = 0x' + startHandle.toString(16) + ', endHandle = 0x' + endHandle.toString(16) + ', uuid = 0x' + uuid.toString(16));
626
627   if ('2803' === uuid) {
628     var characteristics = [];
629
630     for (i = startHandle; i <= endHandle; i++) {
631       handle = this._handles[i];
632
633       if (!handle) {
634         break;
635       }
636
637       if (handle.type === 'characteristic') {
638         characteristics.push(handle);
639       }
640     }
641
642     if (characteristics.length) {
643       var uuidSize = characteristics[0].uuid.length / 2;
644       var numCharacteristics = 1;
645
646       for (i = 1; i < characteristics.length; i++) {
647         if (characteristics[0].uuid.length !== characteristics[i].uuid.length) {
648           break;
649         }
650         numCharacteristics++;
651       }
652
653       var lengthPerCharacteristic = (uuidSize === 2) ? 7 : 21;
654       var maxCharacteristics = Math.floor((this._mtu - 2) / lengthPerCharacteristic);
655       numCharacteristics = Math.min(numCharacteristics, maxCharacteristics);
656
657       response = new Buffer(2 + numCharacteristics * lengthPerCharacteristic);
658
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);
663
664       for (i = 0; i < numCharacteristics; i++) {
665         var characteristic = characteristics[i];
666
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);
670
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);
675         }
676       }
677     } else {
678       response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
679     }
680   } else {
681     var handleAttribute = null;
682     var valueHandle = null;
683     var secure = false;
684
685     for (i = startHandle; i <= endHandle; i++) {
686       handle = this._handles[i];
687
688       if (!handle) {
689         break;
690       }
691
692       if (handle.type === 'characteristic' && handle.uuid === uuid) {
693         handleAttribute = handle.attribute;
694         valueHandle = handle.valueHandle;
695         secure = handle.secure & 0x02;
696         break;
697       } else if (handle.type === 'descriptor' && handle.uuid === uuid) {
698         valueHandle = i;
699         secure = handle.secure & 0x02;
700         break;
701       }
702     }
703
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;
710
711           if (ATT_ECODE_SUCCESS === result) {
712             var dataLength = Math.min(data.length, this._mtu - 4);
713             callbackResponse = new Buffer(4 + dataLength);
714
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);
723             }
724           } else {
725             callbackResponse = this.errorResponse(requestType, valueHandle, result);
726           }
727
728           debug('read by type response: ' + callbackResponse.toString('hex'));
729
730           this.send(callbackResponse);
731         }.bind(this);
732       }.bind(this))(valueHandle);
733
734       var data = this._handles[valueHandle].value;
735
736       if (data) {
737         callback(ATT_ECODE_SUCCESS, data);
738       } else if (handleAttribute) {
739         handleAttribute.emit('readRequest', 0, callback);
740       } else {
741         callback(ATT_ECODE_UNLIKELY);
742       }
743     } else {
744       response = this.errorResponse(ATT_OP_READ_BY_TYPE_REQ, startHandle, ATT_ECODE_ATTR_NOT_FOUND);
745     }
746   }
747
748   return response;
749 };
750
751 Gatt.prototype.handleReadOrReadBlobRequest = function(request) {
752   var response = null;
753
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;
758
759   var handle = this._handles[valueHandle];
760
761   if (handle) {
762     var result = null;
763     var data = null;
764     var handleType = handle.type;
765
766     var callback = (function(requestType, valueHandle) {
767       return function(result, data) {
768         var callbackResponse = null;
769
770         if (ATT_ECODE_SUCCESS === result) {
771           var dataLength = Math.min(data.length, this._mtu - 1);
772           callbackResponse = new Buffer(1 + dataLength);
773
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);
779           }
780         } else {
781           callbackResponse = this.errorResponse(requestType, valueHandle, result);
782         }
783
784         debug('read response: ' + callbackResponse.toString('hex'));
785
786         this.send(callbackResponse);
787       }.bind(this);
788     }.bind(this))(requestType, valueHandle);
789
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');
795
796       result = ATT_ECODE_SUCCESS;
797       data = new Buffer(3 + uuid.length);
798       data.writeUInt8(handle.properties, 0);
799       data.writeUInt16LE(handle.valueHandle, 1);
800
801       for (i = 0; i < uuid.length; i++) {
802         //data[i + 3] = uuid[i];
803         data.writeUInt8(uuid.readUInt8(i), i + 3);
804       }
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;
813       }
814
815       if (handleProperties & 0x02) {
816         if (handleSecure & 0x02 && !this._aclStream.encrypted) {
817           result = ATT_ECODE_AUTHENTICATION;
818         } else {
819           data = handle.value;
820
821           if (data) {
822             result = ATT_ECODE_SUCCESS;
823           } else {
824             handleAttribute.emit('readRequest', offset, callback);
825           }
826         }
827       } else {
828         result = ATT_ECODE_READ_NOT_PERM; // non-readable
829       }
830     }
831
832     if (data && typeof data === 'string') {
833       data = new Buffer(data);
834     }
835
836     if (result === ATT_ECODE_SUCCESS && data && offset) {
837       if (data.length < offset) {
838         errorCode = ATT_ECODE_INVALID_OFFSET;
839         data = null;
840       } else {
841         data = data.slice(offset);
842       }
843     }
844
845     if (result !== null) {
846       callback(result, data);
847     }
848   } else {
849     response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE);
850   }
851
852   return response;
853 };
854
855 Gatt.prototype.handleWriteRequestOrCommand = function(request) {
856   var response = null;
857
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);
863   var offset = 0;
864
865   var handle = this._handles[valueHandle];
866
867   if (handle) {
868     if (handle.type === 'characteristicValue') {
869       handle = this._handles[valueHandle - 1];
870     }
871
872     var handleProperties = handle.properties;
873     var handleSecure = handle.secure;
874
875     if (handleProperties && (withoutResponse ? (handleProperties & 0x04) : (handleProperties & 0x08))) {
876
877       var callback = (function(requestType, valueHandle, withoutResponse) {
878         return function(result) {
879           if (!withoutResponse) {
880             var callbackResponse = null;
881
882             if (ATT_ECODE_SUCCESS === result) {
883               callbackResponse = new Buffer([ATT_OP_WRITE_RESP]);
884             } else {
885               callbackResponse = this.errorResponse(requestType, valueHandle, result);
886             }
887
888             debug('write response: ' + callbackResponse.toString('hex'));
889
890             this.send(callbackResponse);
891           }
892         }.bind(this);
893       }.bind(this))(requestType, valueHandle, withoutResponse);
894
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') {
898         var result = null;
899
900         if (data.length !== 2) {
901           result = ATT_ECODE_INVAL_ATTR_VALUE_LEN;
902         } else {
903           var value = data.readUInt16LE(0);
904           var handleAttribute = handle.attribute;
905
906           handle.value = data;
907
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;
914                 var i;
915
916                 if (useNotify) {
917                   var notifyMessage = new Buffer(3 + dataLength);
918
919                   notifyMessage.writeUInt8(ATT_OP_HANDLE_NOTIFY, 0);
920                   notifyMessage.writeUInt16LE(valueHandle, 1);
921
922                   for (i = 0; i < dataLength; i++) {
923                     //notifyMessage[3 + i] = data[i];
924                     notifyMessage.writeUInt8(data.readUInt8(i), 3 + i);
925                   }
926
927                   debug('notify message: ' + notifyMessage.toString('hex'));
928                   this.send(notifyMessage);
929
930                   attribute.emit('notify');
931                 } else if (useIndicate) {
932                   var indicateMessage = new Buffer(3 + dataLength);
933
934                   indicateMessage.writeUInt8(ATT_OP_HANDLE_IND, 0);
935                   indicateMessage.writeUInt16LE(valueHandle, 1);
936
937                   for (i = 0; i < dataLength; i++) {
938                     //indicateMessage[3 + i] = data[i];
939                     indicateMessage.writeUInt8(data.readUInt8(i), 3 + i);
940                   }
941
942                   this._lastIndicatedAttribute = attribute;
943
944                   debug('indicate message: ' + indicateMessage.toString('hex'));
945                   this.send(indicateMessage);
946                 }
947               }.bind(this);
948             }.bind(this))(valueHandle - 1, handleAttribute);
949
950             if (handleAttribute.emit) {
951               handleAttribute.emit('subscribe', this._mtu - 3, updateValueCallback);
952             }
953           } else {
954             handleAttribute.emit('unsubscribe');
955           }
956
957           result = ATT_ECODE_SUCCESS;
958         }
959
960         callback(result);
961       } else {
962         handle.attribute.emit('writeRequest', data, offset, withoutResponse, callback);
963       }
964     } else {
965       response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM);
966     }
967   } else {
968     response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE);
969   }
970
971   return response;
972 };
973
974 Gatt.prototype.handlePrepareWriteRequest = function(request) {
975   var response = null;
976
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);
982
983   var handle = this._handles[valueHandle];
984
985   if (handle) {
986     if (handle.type === 'characteristicValue') {
987       handle = this._handles[valueHandle - 1];
988
989       var handleProperties = handle.properties;
990       var handleSecure = handle.secure;
991
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,
1001               data
1002             ]);
1003
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);
1008           } else {
1009             response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_OFFSET);
1010           }
1011         } else {
1012           this._preparedWriteRequest = {
1013             handle: handle,
1014             valueHandle: valueHandle,
1015             offset: offset,
1016             data: data
1017           };
1018
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);
1023         }
1024       } else {
1025         response = this.errorResponse(requestType, valueHandle, ATT_ECODE_WRITE_NOT_PERM);
1026       }
1027     } else {
1028       response = this.errorResponse(requestType, valueHandle, ATT_ECODE_ATTR_NOT_LONG);
1029     }
1030   } else {
1031     response = this.errorResponse(requestType, valueHandle, ATT_ECODE_INVALID_HANDLE);
1032   }
1033
1034   return response;
1035 };
1036
1037 Gatt.prototype.handleExecuteWriteRequest = function(request) {
1038   var response = null;
1039
1040   //var requestType = request[0];
1041   //var flag = request[1];
1042   var requestType = request.readUInt8(0);
1043   var flag = request.readUInt8(1);
1044
1045   if (this._preparedWriteRequest) {
1046     var valueHandle = this._preparedWriteRequest.valueHandle;
1047
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;
1054
1055           if (ATT_ECODE_SUCCESS === result) {
1056             callbackResponse = new Buffer([ATT_OP_EXEC_WRITE_RESP]);
1057           } else {
1058             callbackResponse = this.errorResponse(requestType, valueHandle, result);
1059           }
1060
1061           debug('execute write response: ' + callbackResponse.toString('hex'));
1062
1063           this.send(callbackResponse);
1064         }.bind(this);
1065       }.bind(this))(requestType, this._preparedWriteRequest.valueHandle);
1066
1067       this._preparedWriteRequest.handle.attribute.emit('writeRequest', this._preparedWriteRequest.data, this._preparedWriteRequest.offset, false, callback);
1068     } else {
1069       response = this.errorResponse(requestType, 0x0000, ATT_ECODE_UNLIKELY);
1070     }
1071
1072     this._preparedWriteRequest = null;
1073   } else {
1074     response = this.errorResponse(requestType, 0x0000, ATT_ECODE_UNLIKELY);
1075   }
1076
1077   return response;
1078 };
1079
1080 Gatt.prototype.handleConfirmation = function(request) {
1081   if (this._lastIndicatedAttribute) {
1082     if (this._lastIndicatedAttribute.emit) {
1083       this._lastIndicatedAttribute.emit('indicate');
1084     }
1085
1086     this._lastIndicatedAttribute = null;
1087   }
1088 };
1089
1090 module.exports = Gatt;