@stoprocent/bleno 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.jshintrc +5 -0
  2. package/LICENSE +20 -0
  3. package/README.md +409 -0
  4. package/binding.gyp +17 -0
  5. package/examples/battery-service/README.md +14 -0
  6. package/examples/battery-service/battery-level-characteristic.js +45 -0
  7. package/examples/battery-service/battery-service.js +16 -0
  8. package/examples/battery-service/main.js +28 -0
  9. package/examples/battery-service/package-lock.json +1314 -0
  10. package/examples/battery-service/package.json +20 -0
  11. package/examples/blink1/README.md +44 -0
  12. package/examples/blink1/blink1-fade-rgb-characteristic.js +42 -0
  13. package/examples/blink1/blink1-rgb-characteristic.js +38 -0
  14. package/examples/blink1/blink1-service.js +19 -0
  15. package/examples/blink1/device-information-service.js +19 -0
  16. package/examples/blink1/hardware-revision-characteristic.js +32 -0
  17. package/examples/blink1/main.js +32 -0
  18. package/examples/blink1/serial-number-characteristic.js +21 -0
  19. package/examples/echo/characteristic.js +45 -0
  20. package/examples/echo/main.js +33 -0
  21. package/examples/pizza/README.md +16 -0
  22. package/examples/pizza/peripheral.js +57 -0
  23. package/examples/pizza/pizza-bake-characteristic.js +40 -0
  24. package/examples/pizza/pizza-crust-characteristic.js +52 -0
  25. package/examples/pizza/pizza-service.js +20 -0
  26. package/examples/pizza/pizza-toppings-characteristic.js +41 -0
  27. package/examples/pizza/pizza.js +58 -0
  28. package/examples/uart/main.js +23 -0
  29. package/index.d.ts +153 -0
  30. package/index.js +1 -0
  31. package/lib/bleno.js +231 -0
  32. package/lib/characteristic.js +91 -0
  33. package/lib/descriptor.js +17 -0
  34. package/lib/hci-socket/acl-stream.js +37 -0
  35. package/lib/hci-socket/bindings.js +219 -0
  36. package/lib/hci-socket/crypto.js +74 -0
  37. package/lib/hci-socket/gap.js +212 -0
  38. package/lib/hci-socket/gatt.js +1028 -0
  39. package/lib/hci-socket/hci-status.json +67 -0
  40. package/lib/hci-socket/hci.js +796 -0
  41. package/lib/hci-socket/mgmt.js +89 -0
  42. package/lib/hci-socket/smp.js +160 -0
  43. package/lib/hci-socket/vs.js +156 -0
  44. package/lib/mac/binding.gyp +39 -0
  45. package/lib/mac/bindings.js +12 -0
  46. package/lib/mac/src/ble_peripheral_manager.h +32 -0
  47. package/lib/mac/src/ble_peripheral_manager.mm +241 -0
  48. package/lib/mac/src/bleno_mac.h +26 -0
  49. package/lib/mac/src/bleno_mac.mm +167 -0
  50. package/lib/mac/src/callbacks.h +76 -0
  51. package/lib/mac/src/callbacks.mm +124 -0
  52. package/lib/mac/src/napi_objc.h +30 -0
  53. package/lib/mac/src/napi_objc.mm +286 -0
  54. package/lib/mac/src/noble_mac.h +34 -0
  55. package/lib/mac/src/noble_mac.mm +260 -0
  56. package/lib/mac/src/objc_cpp.h +27 -0
  57. package/lib/mac/src/objc_cpp.mm +144 -0
  58. package/lib/mac/src/peripheral.h +23 -0
  59. package/lib/primary-service.js +19 -0
  60. package/lib/resolve-bindings.js +19 -0
  61. package/lib/uuid-util.js +7 -0
  62. package/package.json +77 -0
  63. package/prebuilds/android-arm/node.napi.armv7.node +0 -0
  64. package/prebuilds/android-arm64/node.napi.armv8.node +0 -0
  65. package/prebuilds/darwin-x64+arm64/node.napi.node +0 -0
  66. package/prebuilds/linux-arm/node.napi.armv6.node +0 -0
  67. package/prebuilds/linux-arm/node.napi.armv7.node +0 -0
  68. package/prebuilds/linux-arm64/node.napi.armv8.node +0 -0
  69. package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
  70. package/prebuilds/linux-x64/node.napi.musl.node +0 -0
  71. package/prebuilds/win32-ia32/node.napi.node +0 -0
  72. package/prebuilds/win32-x64/node.napi.node +0 -0
  73. package/test/characteristic.test.js +174 -0
  74. package/test/descriptor.test.js +46 -0
  75. package/test/mocha.setup.js +0 -0
  76. package/with-bindings.js +5 -0
  77. package/with-custom-binding.js +6 -0
@@ -0,0 +1,796 @@
1
+ const debug = require('debug')('hci');
2
+
3
+ const { EventEmitter } = require('events');
4
+
5
+ const BluetoothHciSocket = require('@stoprocent/bluetooth-hci-socket');
6
+ const vendorSpecific = require('./vs');
7
+
8
+ const HCI_COMMAND_PKT = 0x01;
9
+ const HCI_ACLDATA_PKT = 0x02;
10
+ const HCI_EVENT_PKT = 0x04;
11
+
12
+ const ACL_START_NO_FLUSH = 0x00;
13
+ const ACL_CONT = 0x01;
14
+ const ACL_START = 0x02;
15
+
16
+ const EVT_DISCONN_COMPLETE = 0x05;
17
+ const EVT_ENCRYPT_CHANGE = 0x08;
18
+ const EVT_CMD_COMPLETE = 0x0e;
19
+ const EVT_CMD_STATUS = 0x0f;
20
+ const EVT_NUMBER_OF_COMPLETED_PACKETS = 0x13;
21
+ const EVT_LE_META_EVENT = 0x3e;
22
+
23
+ const EVT_LE_CONN_COMPLETE = 0x01;
24
+ const EVT_LE_CONN_UPDATE_COMPLETE = 0x03;
25
+
26
+ const OGF_LINK_CTL = 0x01;
27
+ const OCF_DISCONNECT = 0x0006;
28
+
29
+ const OGF_HOST_CTL = 0x03;
30
+ const OCF_SET_EVENT_MASK = 0x0001;
31
+ const OCF_RESET = 0x0003;
32
+ const OCF_READ_LE_HOST_SUPPORTED = 0x006c;
33
+ const OCF_WRITE_LE_HOST_SUPPORTED = 0x006d;
34
+
35
+ const OGF_INFO_PARAM = 0x04;
36
+ const OCF_READ_LOCAL_VERSION = 0x0001;
37
+ const OCF_READ_BUFFER_SIZE = 0x0005;
38
+ const OCF_READ_BD_ADDR = 0x0009;
39
+
40
+ const OGF_STATUS_PARAM = 0x05;
41
+ const OCF_READ_RSSI = 0x0005;
42
+
43
+ const OGF_LE_CTL = 0x08;
44
+ const OCF_LE_SET_EVENT_MASK = 0x0001;
45
+ const OCF_LE_READ_BUFFER_SIZE = 0x0002;
46
+ const OCF_LE_SET_ADVERTISING_PARAMETERS = 0x0006;
47
+ const OCF_LE_SET_ADVERTISING_DATA = 0x0008;
48
+ const OCF_LE_SET_SCAN_RESPONSE_DATA = 0x0009;
49
+ const OCF_LE_SET_ADVERTISE_ENABLE = 0x000a;
50
+ const OCF_LE_LTK_NEG_REPLY = 0x001B;
51
+
52
+ const DISCONNECT_CMD = OCF_DISCONNECT | OGF_LINK_CTL << 10;
53
+
54
+ const SET_EVENT_MASK_CMD = OCF_SET_EVENT_MASK | OGF_HOST_CTL << 10;
55
+ const RESET_CMD = OCF_RESET | OGF_HOST_CTL << 10;
56
+ const READ_LE_HOST_SUPPORTED_CMD = OCF_READ_LE_HOST_SUPPORTED | OGF_HOST_CTL << 10;
57
+ const WRITE_LE_HOST_SUPPORTED_CMD = OCF_WRITE_LE_HOST_SUPPORTED | OGF_HOST_CTL << 10;
58
+
59
+ const READ_LOCAL_VERSION_CMD = OCF_READ_LOCAL_VERSION | (OGF_INFO_PARAM << 10);
60
+ const READ_BUFFER_SIZE_CMD = OCF_READ_BUFFER_SIZE | (OGF_INFO_PARAM << 10);
61
+ const READ_BD_ADDR_CMD = OCF_READ_BD_ADDR | (OGF_INFO_PARAM << 10);
62
+
63
+ const READ_RSSI_CMD = OCF_READ_RSSI | OGF_STATUS_PARAM << 10;
64
+
65
+ const LE_SET_EVENT_MASK_CMD = OCF_LE_SET_EVENT_MASK | OGF_LE_CTL << 10;
66
+ const LE_READ_BUFFER_SIZE_CMD = OCF_LE_READ_BUFFER_SIZE | OGF_LE_CTL << 10;
67
+ const LE_SET_ADVERTISING_PARAMETERS_CMD = OCF_LE_SET_ADVERTISING_PARAMETERS | OGF_LE_CTL << 10;
68
+ const LE_SET_ADVERTISING_DATA_CMD = OCF_LE_SET_ADVERTISING_DATA | OGF_LE_CTL << 10;
69
+ const LE_SET_SCAN_RESPONSE_DATA_CMD = OCF_LE_SET_SCAN_RESPONSE_DATA | OGF_LE_CTL << 10;
70
+ const LE_SET_ADVERTISE_ENABLE_CMD = OCF_LE_SET_ADVERTISE_ENABLE | OGF_LE_CTL << 10;
71
+ const LE_LTK_NEG_REPLY_CMD = OCF_LE_LTK_NEG_REPLY | OGF_LE_CTL << 10;
72
+
73
+ const HCI_OE_USER_ENDED_CONNECTION = 0x13;
74
+
75
+ const STATUS_MAPPER = require('./hci-status');
76
+
77
+ class Hci extends EventEmitter {
78
+ constructor (options) {
79
+ super();
80
+
81
+ options = options || {};
82
+ this._manufacturer = null;
83
+ this._socket = new BluetoothHciSocket();
84
+ this._isDevUp = null;
85
+ this._state = null;
86
+ this._deviceId = null;
87
+ // le-u min payload size + l2cap header size
88
+ // see Bluetooth spec 4.2 [Vol 3, Part A, Chapter 4]
89
+ this._aclMtu = 23 + 4;
90
+ this._aclMaxInProgress = 1;
91
+ this._bindParams = 'bindParams' in options ? options.bindParams : undefined;
92
+
93
+ this.resetBuffers();
94
+
95
+ this.on('stateChange', this.onStateChange.bind(this));
96
+ }
97
+
98
+ init () {
99
+ this._socket.on('data', this.onSocketData.bind(this));
100
+ this._socket.on('error', this.onSocketError.bind(this));
101
+
102
+ const deviceId = process.env.BLENO_HCI_DEVICE_ID ? parseInt(process.env.BLENO_HCI_DEVICE_ID) : undefined;
103
+
104
+ if (process.env.HCI_CHANNEL_USER) {
105
+ this._deviceId = this._socket.bindUser(deviceId, this._bindParams);
106
+
107
+ this._socket.start();
108
+
109
+ this.reset();
110
+ } else {
111
+ this._deviceId = this._socket.bindRaw(deviceId, this._bindParams);
112
+ this._socket.start();
113
+
114
+ this.pollIsDevUp();
115
+ }
116
+ }
117
+
118
+ resetBuffers () {
119
+ this._mainHandle = null;
120
+ this._handleAclsInProgress = {};
121
+ this._handleBuffers = {};
122
+ this._aclOutQueue = [];
123
+ }
124
+
125
+ pollIsDevUp () {
126
+ const isDevUp = this._socket.isDevUp();
127
+
128
+ if (this._isDevUp !== isDevUp) {
129
+ if (isDevUp) {
130
+ this.setSocketFilter();
131
+ this.initDev();
132
+ } else {
133
+ this.emit('stateChange', 'poweredOff');
134
+ }
135
+
136
+ this._isDevUp = isDevUp;
137
+ }
138
+
139
+ setTimeout(this.pollIsDevUp.bind(this), 1000);
140
+ }
141
+
142
+ initDev () {
143
+ this.resetBuffers();
144
+ this.setEventMask();
145
+ this.setLeEventMask();
146
+ this.readLocalVersion();
147
+ this.writeLeHostSupported();
148
+ this.readLeHostSupported();
149
+ this.readBdAddr();
150
+ this.leReadBufferSize();
151
+ }
152
+
153
+ setSocketFilter () {
154
+ const filter = Buffer.alloc(14);
155
+ const typeMask = (1 << HCI_EVENT_PKT) | (1 << HCI_ACLDATA_PKT);
156
+ const eventMask1 = (1 << EVT_DISCONN_COMPLETE) | (1 << EVT_ENCRYPT_CHANGE) | (1 << EVT_CMD_COMPLETE) | (1 << EVT_CMD_STATUS) | (1 << EVT_NUMBER_OF_COMPLETED_PACKETS);
157
+ const eventMask2 = (1 << (EVT_LE_META_EVENT - 32));
158
+ const opcode = 0;
159
+
160
+ filter.writeUInt32LE(typeMask, 0);
161
+ filter.writeUInt32LE(eventMask1, 4);
162
+ filter.writeUInt32LE(eventMask2, 8);
163
+ filter.writeUInt16LE(opcode, 12);
164
+
165
+ debug('setting filter to: ' + filter.toString('hex'));
166
+ this._socket.setFilter(filter);
167
+ }
168
+
169
+ setEventMask () {
170
+ const cmd = Buffer.alloc(12);
171
+ const eventMask = Buffer.from('fffffbff07f8bf3d', 'hex');
172
+
173
+ // header
174
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
175
+ cmd.writeUInt16LE(SET_EVENT_MASK_CMD, 1);
176
+
177
+ // length
178
+ cmd.writeUInt8(eventMask.length, 3);
179
+
180
+ eventMask.copy(cmd, 4);
181
+
182
+ debug('set event mask - writing: ' + cmd.toString('hex'));
183
+ this._socket.write(cmd);
184
+ }
185
+
186
+ setAddress (address) {
187
+ // Command
188
+ const addrCmd = vendorSpecific.setAddressCmd(this._manufacturer, address);
189
+
190
+ if (addrCmd !== null && Buffer.isBuffer(addrCmd)) {
191
+ // Make Command Buffer
192
+ const cmd = Buffer.alloc(1 + addrCmd.byteLength);
193
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
194
+ addrCmd.copy(cmd, 1);
195
+
196
+ debug(`set address - writing: ${cmd.toString('hex')}`);
197
+ this._socket.write(cmd);
198
+ this.readBdAddr();
199
+ }
200
+ }
201
+
202
+ reset () {
203
+ const cmd = Buffer.alloc(4);
204
+
205
+ // header
206
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
207
+ cmd.writeUInt16LE(OCF_RESET | OGF_HOST_CTL << 10, 1);
208
+
209
+ // length
210
+ cmd.writeUInt8(0x00, 3);
211
+
212
+ debug('reset - writing: ' + cmd.toString('hex'));
213
+ this._socket.write(cmd);
214
+ }
215
+
216
+ readLeHostSupported () {
217
+ const cmd = Buffer.alloc(4);
218
+
219
+ // header
220
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
221
+ cmd.writeUInt16LE(READ_LE_HOST_SUPPORTED_CMD, 1);
222
+
223
+ // length
224
+ cmd.writeUInt8(0x00, 3);
225
+
226
+ debug('read LE host supported - writing: ' + cmd.toString('hex'));
227
+ this._socket.write(cmd);
228
+ }
229
+
230
+ writeLeHostSupported () {
231
+ const cmd = Buffer.alloc(6);
232
+
233
+ // header
234
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
235
+ cmd.writeUInt16LE(WRITE_LE_HOST_SUPPORTED_CMD, 1);
236
+
237
+ // length
238
+ cmd.writeUInt8(0x02, 3);
239
+
240
+ // data
241
+ cmd.writeUInt8(0x01, 4); // le
242
+ cmd.writeUInt8(0x00, 5); // simul
243
+
244
+ debug('write LE host supported - writing: ' + cmd.toString('hex'));
245
+ this._socket.write(cmd);
246
+ }
247
+
248
+ readLocalVersion () {
249
+ const cmd = Buffer.alloc(4);
250
+
251
+ // header
252
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
253
+ cmd.writeUInt16LE(READ_LOCAL_VERSION_CMD, 1);
254
+
255
+ // length
256
+ cmd.writeUInt8(0x0, 3);
257
+
258
+ debug('read local version - writing: ' + cmd.toString('hex'));
259
+ this._socket.write(cmd);
260
+ }
261
+
262
+ readBdAddr () {
263
+ const cmd = Buffer.alloc(4);
264
+
265
+ // header
266
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
267
+ cmd.writeUInt16LE(READ_BD_ADDR_CMD, 1);
268
+
269
+ // length
270
+ cmd.writeUInt8(0x0, 3);
271
+
272
+ debug('read bd addr - writing: ' + cmd.toString('hex'));
273
+ this._socket.write(cmd);
274
+ }
275
+
276
+ setLeEventMask () {
277
+ const cmd = Buffer.alloc(12);
278
+ const leEventMask = Buffer.from('1f00000000000000', 'hex');
279
+
280
+ // header
281
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
282
+ cmd.writeUInt16LE(LE_SET_EVENT_MASK_CMD, 1);
283
+
284
+ // length
285
+ cmd.writeUInt8(leEventMask.length, 3);
286
+
287
+ leEventMask.copy(cmd, 4);
288
+
289
+ debug('set le event mask - writing: ' + cmd.toString('hex'));
290
+ this._socket.write(cmd);
291
+ }
292
+
293
+ setAdvertisingParameters () {
294
+ const cmd = Buffer.alloc(19);
295
+
296
+ // header
297
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
298
+ cmd.writeUInt16LE(LE_SET_ADVERTISING_PARAMETERS_CMD, 1);
299
+
300
+ // length
301
+ cmd.writeUInt8(15, 3);
302
+
303
+ const advertisementInterval = Math.floor((process.env.BLENO_ADVERTISING_INTERVAL ? parseFloat(process.env.BLENO_ADVERTISING_INTERVAL) : 100) * 1.6);
304
+
305
+ // data
306
+ cmd.writeUInt16LE(advertisementInterval, 4); // min interval
307
+ cmd.writeUInt16LE(advertisementInterval, 6); // max interval
308
+ cmd.writeUInt8(0x00, 8); // adv type
309
+ cmd.writeUInt8(0x00, 9); // own addr typ
310
+ cmd.writeUInt8(0x00, 10); // direct addr type
311
+ (Buffer.from('000000000000', 'hex')).copy(cmd, 11); // direct addr
312
+ cmd.writeUInt8(0x07, 17);
313
+ cmd.writeUInt8(0x00, 18);
314
+
315
+ debug('set advertisement parameters - writing: ' + cmd.toString('hex'));
316
+ this._socket.write(cmd);
317
+ }
318
+
319
+ setAdvertisingData (data) {
320
+ const cmd = Buffer.alloc(36);
321
+
322
+ cmd.fill(0x00);
323
+
324
+ // header
325
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
326
+ cmd.writeUInt16LE(LE_SET_ADVERTISING_DATA_CMD, 1);
327
+
328
+ // length
329
+ cmd.writeUInt8(32, 3);
330
+
331
+ // data
332
+ cmd.writeUInt8(data.length, 4);
333
+ data.copy(cmd, 5);
334
+
335
+ debug('set advertisement data - writing: ' + cmd.toString('hex'));
336
+ this._socket.write(cmd);
337
+ }
338
+
339
+ setScanResponseData (data) {
340
+ const cmd = Buffer.alloc(36);
341
+
342
+ cmd.fill(0x00);
343
+
344
+ // header
345
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
346
+ cmd.writeUInt16LE(LE_SET_SCAN_RESPONSE_DATA_CMD, 1);
347
+
348
+ // length
349
+ cmd.writeUInt8(32, 3);
350
+
351
+ // data
352
+ cmd.writeUInt8(data.length, 4);
353
+ data.copy(cmd, 5);
354
+
355
+ debug('set scan response data - writing: ' + cmd.toString('hex'));
356
+ this._socket.write(cmd);
357
+ }
358
+
359
+ setAdvertiseEnable (enabled) {
360
+ const cmd = Buffer.alloc(5);
361
+
362
+ // header
363
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
364
+ cmd.writeUInt16LE(LE_SET_ADVERTISE_ENABLE_CMD, 1);
365
+
366
+ // length
367
+ cmd.writeUInt8(0x01, 3);
368
+
369
+ // data
370
+ cmd.writeUInt8(enabled ? 0x01 : 0x00, 4); // enable: 0 -> disabled, 1 -> enabled
371
+
372
+ debug('set advertise enable - writing: ' + cmd.toString('hex'));
373
+ this._socket.write(cmd);
374
+ }
375
+
376
+ disconnect (handle, reason) {
377
+ const cmd = Buffer.alloc(7);
378
+
379
+ reason = reason || HCI_OE_USER_ENDED_CONNECTION;
380
+
381
+ // header
382
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
383
+ cmd.writeUInt16LE(DISCONNECT_CMD, 1);
384
+
385
+ // length
386
+ cmd.writeUInt8(0x03, 3);
387
+
388
+ // data
389
+ cmd.writeUInt16LE(handle, 4); // handle
390
+ cmd.writeUInt8(reason, 6); // reason
391
+
392
+ debug('disconnect - writing: ' + cmd.toString('hex'));
393
+ this._socket.write(cmd);
394
+ }
395
+
396
+ readRssi (handle) {
397
+ const cmd = Buffer.alloc(6);
398
+
399
+ // header
400
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
401
+ cmd.writeUInt16LE(READ_RSSI_CMD, 1);
402
+
403
+ // length
404
+ cmd.writeUInt8(0x02, 3);
405
+
406
+ // data
407
+ cmd.writeUInt16LE(handle, 4); // handle
408
+
409
+ debug('read rssi - writing: ' + cmd.toString('hex'));
410
+ this._socket.write(cmd);
411
+ }
412
+
413
+ leReadBufferSize () {
414
+ const cmd = Buffer.alloc(4);
415
+
416
+ // header
417
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
418
+ cmd.writeUInt16LE(LE_READ_BUFFER_SIZE_CMD, 1);
419
+
420
+ // length
421
+ cmd.writeUInt8(0x0, 3);
422
+
423
+ debug('le read buffer size - writing: ' + cmd.toString('hex'));
424
+ this._socket.write(cmd);
425
+ }
426
+
427
+ readBufferSize () {
428
+ const cmd = Buffer.alloc(4);
429
+
430
+ // header
431
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
432
+ cmd.writeUInt16LE(READ_BUFFER_SIZE_CMD, 1);
433
+
434
+ // length
435
+ cmd.writeUInt8(0x0, 3);
436
+
437
+ debug('read buffer size - writing: ' + cmd.toString('hex'));
438
+ this._socket.write(cmd);
439
+ }
440
+
441
+ queueAclDataPkt (handle, cid, data) {
442
+ let hf = handle | ACL_START_NO_FLUSH << 12;
443
+ // l2cap pdu may be fragmented on hci level
444
+ let l2capPdu = Buffer.alloc(4 + data.length);
445
+ l2capPdu.writeUInt16LE(data.length, 0);
446
+ l2capPdu.writeUInt16LE(cid, 2);
447
+ data.copy(l2capPdu, 4);
448
+ let fragId = 0;
449
+
450
+ while (l2capPdu.length) {
451
+ const frag = l2capPdu.slice(0, this._aclMtu);
452
+ l2capPdu = l2capPdu.slice(frag.length);
453
+ const pkt = Buffer.alloc(5 + frag.length);
454
+
455
+ // hci header
456
+ pkt.writeUInt8(HCI_ACLDATA_PKT, 0);
457
+ pkt.writeUInt16LE(hf, 1);
458
+ hf |= ACL_CONT << 12;
459
+ pkt.writeUInt16LE(frag.length, 3); // hci pdu length
460
+
461
+ frag.copy(pkt, 5);
462
+
463
+ this._aclOutQueue.push({
464
+ handle,
465
+ pkt,
466
+ fragId: fragId++
467
+ });
468
+ }
469
+
470
+ this.pushAclOutQueue();
471
+ }
472
+
473
+ pushAclOutQueue () {
474
+ let inProgress = 0;
475
+ for (const handle in this._handleAclsInProgress) {
476
+ inProgress += this._handleAclsInProgress[handle];
477
+ }
478
+ while (inProgress < this._aclMaxInProgress && this._aclOutQueue.length) {
479
+ inProgress++;
480
+ this.writeOneAclDataPkt();
481
+ }
482
+
483
+ if (inProgress >= this._aclMaxInProgress && this._aclOutQueue.length) {
484
+ debug('acl out queue congested');
485
+ debug('\tin progress = ' + inProgress);
486
+ debug('\twaiting = ' + this._aclOutQueue.length);
487
+ }
488
+ }
489
+
490
+ writeOneAclDataPkt () {
491
+ const pkt = this._aclOutQueue.shift();
492
+ this._handleAclsInProgress[pkt.handle]++;
493
+ debug('write acl data pkt frag ' + pkt.fragId + ' handle ' + pkt.handle + ' - writing: ' + pkt.pkt.toString('hex'));
494
+ this._socket.write(pkt.pkt);
495
+ }
496
+
497
+ onSocketData (data) {
498
+ debug('onSocketData: ' + data.toString('hex'));
499
+
500
+ const eventType = data.readUInt8(0);
501
+ let handle;
502
+
503
+ debug('\tevent type = ' + eventType);
504
+
505
+ if (HCI_EVENT_PKT === eventType) {
506
+ const subEventType = data.readUInt8(1);
507
+
508
+ debug('\tsub event type = ' + subEventType);
509
+
510
+ if (subEventType === EVT_DISCONN_COMPLETE) {
511
+ handle = data.readUInt16LE(4);
512
+ debug('\t\thandle = ' + handle);
513
+ if (handle !== this._mainHandle) {
514
+ debug('\tignoring event because handle is unknown to bleno.');
515
+ debug('This might be OK in a multi role scenario in which the handle is part of a noble connection.');
516
+ return;
517
+ }
518
+
519
+ const reason = data.readUInt8(6);
520
+ debug('\t\treason = ' + reason);
521
+
522
+ /* As per Bluetooth Core specs:
523
+ When the Host receives a Disconnection Complete, Disconnection Physical
524
+ Link Complete or Disconnection Logical Link Complete event, the Host shall
525
+ assume that all unacknowledged HCI Data Packets that have been sent to the
526
+ Controller for the returned Handle have been flushed, and that the
527
+ corresponding data buffers have been freed. */
528
+ delete this._handleAclsInProgress[handle];
529
+ this._mainHandle = null;
530
+ const aclOutQueue = [];
531
+ let discarded = 0;
532
+ for (const i in this._aclOutQueue) {
533
+ if (this._aclOutQueue[i].handle !== handle) {
534
+ aclOutQueue.push(this._aclOutQueue[i]);
535
+ } else {
536
+ discarded++;
537
+ }
538
+ }
539
+ if (discarded) {
540
+ debug('\t\tacls discarded = ' + discarded);
541
+ }
542
+ this._aclOutQueue = aclOutQueue;
543
+ this.pushAclOutQueue();
544
+ this.emit('disconnComplete', handle, reason);
545
+ } else if (subEventType === EVT_ENCRYPT_CHANGE) {
546
+ handle = data.readUInt16LE(4);
547
+ const encrypt = data.readUInt8(6);
548
+
549
+ debug('\t\thandle = ' + handle);
550
+ debug('\t\tencrypt = ' + encrypt);
551
+
552
+ this.emit('encryptChange', handle, encrypt);
553
+ } else if (subEventType === EVT_CMD_COMPLETE) {
554
+ const ncmd = data.readUInt8(3);
555
+ const cmd = data.readUInt16LE(4);
556
+ const status = data.readUInt8(6);
557
+ const result = data.slice(7);
558
+
559
+ debug('\t\tncmd = ' + ncmd);
560
+ debug('\t\tcmd = ' + cmd);
561
+ debug('\t\tstatus = ' + status);
562
+ debug('\t\tresult = ' + result.toString('hex'));
563
+
564
+ this.processCmdCompleteEvent(cmd, status, result);
565
+ } else if (subEventType === EVT_LE_META_EVENT) {
566
+ const leMetaEventType = data.readUInt8(3);
567
+ const leMetaEventStatus = data.readUInt8(4);
568
+ const leMetaEventData = data.slice(5);
569
+
570
+ debug('\t\tLE meta event type = ' + leMetaEventType);
571
+ debug('\t\tLE meta event status = ' + leMetaEventStatus);
572
+ debug('\t\tLE meta event data = ' + leMetaEventData.toString('hex'));
573
+
574
+ this.processLeMetaEvent(leMetaEventType, leMetaEventStatus, leMetaEventData);
575
+ } else if (subEventType === EVT_NUMBER_OF_COMPLETED_PACKETS) {
576
+ const handles = data.readUInt8(3);
577
+ for (let j = 0; j < handles; j++) {
578
+ const handle_ = data.readUInt16LE(4 + j * 4);
579
+ const pkts = data.readUInt16LE(6 + j * 4);
580
+ debug('\thandle = ' + handle_);
581
+ debug('\t\tcompleted = ' + pkts);
582
+ if (this._handleAclsInProgress[handle_] === undefined) {
583
+ debug('\t\talready closed');
584
+ continue;
585
+ }
586
+ if (pkts > this._handleAclsInProgress[handle_]) {
587
+ // Linux kernel may send acl packets by itself, so be ready for underflow
588
+ this._handleAclsInProgress[handle_] = 0;
589
+ } else {
590
+ this._handleAclsInProgress[handle_] -= pkts;
591
+ }
592
+ debug('\t\tin progress = ' + this._handleAclsInProgress[handle_]);
593
+ }
594
+ this.pushAclOutQueue();
595
+ }
596
+ } else if (HCI_ACLDATA_PKT === eventType) {
597
+ const flags = data.readUInt16LE(1) >> 12;
598
+ handle = data.readUInt16LE(1) & 0x0fff;
599
+
600
+ if (ACL_START === flags) {
601
+ const cid = data.readUInt16LE(7);
602
+
603
+ const length = data.readUInt16LE(5);
604
+ const pktData = data.slice(9);
605
+
606
+ debug('\t\tcid = ' + cid);
607
+
608
+ if (length === pktData.length) {
609
+ debug('\t\thandle = ' + handle);
610
+ debug('\t\tdata = ' + pktData.toString('hex'));
611
+
612
+ this.emit('aclDataPkt', handle, cid, pktData);
613
+ } else {
614
+ this._handleBuffers[handle] = {
615
+ length,
616
+ cid,
617
+ data: pktData
618
+ };
619
+ }
620
+ } else if (ACL_CONT === flags) {
621
+ if (!this._handleBuffers[handle] || !this._handleBuffers[handle].data) {
622
+ return;
623
+ }
624
+
625
+ this._handleBuffers[handle].data = Buffer.concat([
626
+ this._handleBuffers[handle].data,
627
+ data.slice(5)
628
+ ]);
629
+
630
+ if (this._handleBuffers[handle].data.length === this._handleBuffers[handle].length) {
631
+ this.emit('aclDataPkt', handle, this._handleBuffers[handle].cid, this._handleBuffers[handle].data);
632
+
633
+ delete this._handleBuffers[handle];
634
+ }
635
+ }
636
+ }
637
+ }
638
+
639
+ onSocketError (error) {
640
+ debug('onSocketError: ' + error.message);
641
+
642
+ if (error.code === 'EPERM') {
643
+ this.emit('stateChange', 'unauthorized');
644
+ } else if (error.message === 'Network is down') {
645
+ // no-op
646
+ }
647
+ }
648
+
649
+ processCmdCompleteEvent (cmd, status, result) {
650
+ if (cmd === RESET_CMD) {
651
+ this.initDev();
652
+ } else if (cmd === READ_LE_HOST_SUPPORTED_CMD) {
653
+ if (status === 0) {
654
+ const le = result.readUInt8(0);
655
+ const simul = result.readUInt8(1);
656
+
657
+ debug('\t\t\tle = ' + le);
658
+ debug('\t\t\tsimul = ' + simul);
659
+ }
660
+ } else if (cmd === READ_LOCAL_VERSION_CMD) {
661
+ const hciVer = result.readUInt8(0);
662
+ const hciRev = result.readUInt16LE(1);
663
+ const lmpVer = result.readInt8(3);
664
+ const manufacturer = result.readUInt16LE(4);
665
+ const lmpSubVer = result.readUInt16LE(6);
666
+
667
+ // Set manufacturer
668
+ this._manufacturer = manufacturer;
669
+
670
+ if (hciVer < 0x06) {
671
+ this.emit('stateChange', 'unsupported');
672
+ } else if (this._state !== 'poweredOn') {
673
+ this.setAdvertiseEnable(false);
674
+ this.setAdvertisingParameters();
675
+ }
676
+
677
+ this.emit('readLocalVersion', hciVer, hciRev, lmpVer, manufacturer, lmpSubVer);
678
+ } else if (cmd === READ_BD_ADDR_CMD) {
679
+ this.addressType = 'public';
680
+ this.address = result.toString('hex').match(/.{1,2}/g).reverse().join(':');
681
+
682
+ debug('address = ' + this.address);
683
+
684
+ this.emit('addressChange', this.address);
685
+ } else if (cmd === LE_SET_ADVERTISING_PARAMETERS_CMD) {
686
+ this.emit('stateChange', 'poweredOn');
687
+
688
+ this.emit('leAdvertisingParametersSet', status);
689
+ } else if (cmd === LE_SET_ADVERTISING_DATA_CMD) {
690
+ this.emit('leAdvertisingDataSet', status);
691
+ } else if (cmd === LE_SET_SCAN_RESPONSE_DATA_CMD) {
692
+ this.emit('leScanResponseDataSet', status);
693
+ } else if (cmd === LE_SET_ADVERTISE_ENABLE_CMD) {
694
+ this.emit('leAdvertiseEnableSet', status);
695
+ } else if (cmd === READ_RSSI_CMD) {
696
+ const handle = result.readUInt16LE(0);
697
+ const rssi = result.readInt8(2);
698
+
699
+ debug('\t\t\thandle = ' + handle);
700
+ debug('\t\t\trssi = ' + rssi);
701
+
702
+ this.emit('rssiRead', handle, rssi);
703
+ } else if (cmd === LE_LTK_NEG_REPLY_CMD) {
704
+ const handle = result.readUInt16LE(0);
705
+
706
+ debug('\t\t\thandle = ' + handle);
707
+ this.emit('leLtkNegReply', handle);
708
+ } else if (cmd === LE_READ_BUFFER_SIZE_CMD) {
709
+ if (!status) {
710
+ this.processLeReadBufferSize(result);
711
+ }
712
+ } else if (cmd === READ_BUFFER_SIZE_CMD) {
713
+ if (!status) {
714
+ const aclMtu = result.readUInt16LE(0);
715
+ const aclMaxInProgress = result.readUInt16LE(3);
716
+ // sanity
717
+ if (aclMtu && aclMaxInProgress) {
718
+ debug('br/edr acl mtu = ' + aclMtu);
719
+ debug('br/edr acl max pkts = ' + aclMaxInProgress);
720
+ this._aclMtu = aclMtu;
721
+ this._aclMaxInProgress = aclMaxInProgress;
722
+ }
723
+ }
724
+ }
725
+ }
726
+
727
+ processLeReadBufferSize (result) {
728
+ const aclMtu = result.readUInt16LE(0);
729
+ const aclMaxInProgress = result.readUInt8(2);
730
+ if (!aclMtu) {
731
+ // as per Bluetooth specs
732
+ debug('falling back to br/edr buffer size');
733
+ this.readBufferSize();
734
+ } else {
735
+ debug('le acl mtu = ' + aclMtu);
736
+ debug('le acl max in progress = ' + aclMaxInProgress);
737
+ this._aclMtu = aclMtu;
738
+ this._aclMaxInProgress = aclMaxInProgress;
739
+ }
740
+ }
741
+
742
+ processLeMetaEvent (eventType, status, data) {
743
+ if (eventType === EVT_LE_CONN_COMPLETE) {
744
+ this.processLeConnComplete(status, data);
745
+ } else if (eventType === EVT_LE_CONN_UPDATE_COMPLETE) {
746
+ this.processLeConnUpdateComplete(status, data);
747
+ }
748
+ }
749
+
750
+ processLeConnComplete (status, data) {
751
+ const handle = data.readUInt16LE(0);
752
+ const role = data.readUInt8(2);
753
+ const addressType = data.readUInt8(3) === 0x01 ? 'random' : 'public';
754
+ const address = data.slice(4, 10).toString('hex').match(/.{1,2}/g).reverse().join(':');
755
+ const interval = data.readUInt16LE(10) * 1.25;
756
+ const latency = data.readUInt16LE(12); // TODO: multiplier?
757
+ const supervisionTimeout = data.readUInt16LE(14) * 10;
758
+ const masterClockAccuracy = data.readUInt8(16); // TODO: multiplier?
759
+
760
+ debug('\t\t\thandle = ' + handle);
761
+ debug('\t\t\trole = ' + role);
762
+ debug('\t\t\taddress type = ' + addressType);
763
+ debug('\t\t\taddress = ' + address);
764
+ debug('\t\t\tinterval = ' + interval);
765
+ debug('\t\t\tlatency = ' + latency);
766
+ debug('\t\t\tsupervision timeout = ' + supervisionTimeout);
767
+ debug('\t\t\tmaster clock accuracy = ' + masterClockAccuracy);
768
+
769
+ this._mainHandle = handle;
770
+ this._handleAclsInProgress[handle] = 0;
771
+
772
+ this.emit('leConnComplete', status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy);
773
+ }
774
+
775
+ processLeConnUpdateComplete (status, data) {
776
+ const handle = data.readUInt16LE(0);
777
+ const interval = data.readUInt16LE(2) * 1.25;
778
+ const latency = data.readUInt16LE(4); // TODO: multiplier?
779
+ const supervisionTimeout = data.readUInt16LE(6) * 10;
780
+
781
+ debug('\t\t\thandle = ' + handle);
782
+ debug('\t\t\tinterval = ' + interval);
783
+ debug('\t\t\tlatency = ' + latency);
784
+ debug('\t\t\tsupervision timeout = ' + supervisionTimeout);
785
+
786
+ this.emit('leConnUpdateComplete', status, handle, interval, latency, supervisionTimeout);
787
+ }
788
+
789
+ onStateChange (state) {
790
+ this._state = state;
791
+ }
792
+ }
793
+
794
+ Hci.STATUS_MAPPER = Hci.prototype.STATUS_MAPPER = STATUS_MAPPER;
795
+
796
+ module.exports = Hci;