@stoprocent/noble 1.9.2-16

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 (112) hide show
  1. package/.editorconfig +11 -0
  2. package/.eslintrc.js +25 -0
  3. package/.github/FUNDING.yml +2 -0
  4. package/.github/workflows/fediverse-action.yml +16 -0
  5. package/.github/workflows/nodepackage.yml +77 -0
  6. package/.github/workflows/npm-publish.yml +26 -0
  7. package/.github/workflows/prebuild.yml +65 -0
  8. package/.nycrc.json +4 -0
  9. package/CHANGELOG.md +119 -0
  10. package/LICENSE +20 -0
  11. package/MAINTAINERS.md +1 -0
  12. package/README.md +833 -0
  13. package/assets/noble-logo.png +0 -0
  14. package/assets/noble-logo.svg +13 -0
  15. package/binding.gyp +19 -0
  16. package/codecov.yml +5 -0
  17. package/examples/advertisement-discovery.js +65 -0
  18. package/examples/cache-gatt-discovery.js +198 -0
  19. package/examples/cache-gatt-reconnect.js +164 -0
  20. package/examples/echo.js +104 -0
  21. package/examples/enter-exit.js +78 -0
  22. package/examples/peripheral-explorer-async.js +133 -0
  23. package/examples/peripheral-explorer.js +225 -0
  24. package/examples/pizza/README.md +15 -0
  25. package/examples/pizza/central.js +194 -0
  26. package/examples/pizza/pizza.js +60 -0
  27. package/index.d.ts +203 -0
  28. package/index.js +6 -0
  29. package/lib/characteristic.js +161 -0
  30. package/lib/characteristics.json +449 -0
  31. package/lib/descriptor.js +72 -0
  32. package/lib/descriptors.json +47 -0
  33. package/lib/distributed/bindings.js +326 -0
  34. package/lib/hci-socket/acl-stream.js +60 -0
  35. package/lib/hci-socket/bindings.js +788 -0
  36. package/lib/hci-socket/crypto.js +74 -0
  37. package/lib/hci-socket/gap.js +432 -0
  38. package/lib/hci-socket/gatt.js +809 -0
  39. package/lib/hci-socket/hci-status.json +71 -0
  40. package/lib/hci-socket/hci.js +1264 -0
  41. package/lib/hci-socket/signaling.js +76 -0
  42. package/lib/hci-socket/smp.js +140 -0
  43. package/lib/hci-uart/bindings.js +569 -0
  44. package/lib/hci-uart/hci-serial-parser.js +70 -0
  45. package/lib/hci-uart/hci.js +1336 -0
  46. package/lib/mac/binding.gyp +26 -0
  47. package/lib/mac/bindings.js +11 -0
  48. package/lib/mac/src/ble_manager.h +41 -0
  49. package/lib/mac/src/ble_manager.mm +435 -0
  50. package/lib/mac/src/callbacks.cc +222 -0
  51. package/lib/mac/src/callbacks.h +84 -0
  52. package/lib/mac/src/napi_objc.h +12 -0
  53. package/lib/mac/src/napi_objc.mm +50 -0
  54. package/lib/mac/src/noble_mac.h +34 -0
  55. package/lib/mac/src/noble_mac.mm +264 -0
  56. package/lib/mac/src/objc_cpp.h +26 -0
  57. package/lib/mac/src/objc_cpp.mm +126 -0
  58. package/lib/mac/src/peripheral.h +23 -0
  59. package/lib/manufacture.js +48 -0
  60. package/lib/noble.js +593 -0
  61. package/lib/peripheral.js +219 -0
  62. package/lib/resolve-bindings-web.js +9 -0
  63. package/lib/resolve-bindings.js +44 -0
  64. package/lib/service.js +72 -0
  65. package/lib/services.json +92 -0
  66. package/lib/webbluetooth/bindings.js +368 -0
  67. package/lib/websocket/bindings.js +321 -0
  68. package/lib/win/binding.gyp +23 -0
  69. package/lib/win/bindings.js +11 -0
  70. package/lib/win/src/ble_manager.cc +802 -0
  71. package/lib/win/src/ble_manager.h +77 -0
  72. package/lib/win/src/callbacks.cc +274 -0
  73. package/lib/win/src/callbacks.h +33 -0
  74. package/lib/win/src/napi_winrt.cc +76 -0
  75. package/lib/win/src/napi_winrt.h +12 -0
  76. package/lib/win/src/noble_winrt.cc +308 -0
  77. package/lib/win/src/noble_winrt.h +34 -0
  78. package/lib/win/src/notify_map.cc +62 -0
  79. package/lib/win/src/notify_map.h +50 -0
  80. package/lib/win/src/peripheral.h +23 -0
  81. package/lib/win/src/peripheral_winrt.cc +296 -0
  82. package/lib/win/src/peripheral_winrt.h +82 -0
  83. package/lib/win/src/radio_watcher.cc +125 -0
  84. package/lib/win/src/radio_watcher.h +61 -0
  85. package/lib/win/src/winrt_cpp.cc +82 -0
  86. package/lib/win/src/winrt_cpp.h +11 -0
  87. package/lib/win/src/winrt_guid.cc +12 -0
  88. package/lib/win/src/winrt_guid.h +13 -0
  89. package/misc/nrf52840dk.hex +6921 -0
  90. package/misc/prj.conf +43 -0
  91. package/package.json +96 -0
  92. package/test/lib/characteristic.test.js +791 -0
  93. package/test/lib/descriptor.test.js +249 -0
  94. package/test/lib/distributed/bindings.test.js +918 -0
  95. package/test/lib/hci-socket/acl-stream.test.js +188 -0
  96. package/test/lib/hci-socket/bindings.test.js +1756 -0
  97. package/test/lib/hci-socket/crypto.test.js +55 -0
  98. package/test/lib/hci-socket/gap.test.js +1089 -0
  99. package/test/lib/hci-socket/gatt.test.js +2392 -0
  100. package/test/lib/hci-socket/hci.test.js +1891 -0
  101. package/test/lib/hci-socket/signaling.test.js +94 -0
  102. package/test/lib/hci-socket/smp.test.js +268 -0
  103. package/test/lib/manufacture.test.js +77 -0
  104. package/test/lib/peripheral.test.js +623 -0
  105. package/test/lib/resolve-bindings.test.js +102 -0
  106. package/test/lib/service.test.js +195 -0
  107. package/test/lib/webbluetooth/bindings.test.js +190 -0
  108. package/test/lib/websocket/bindings.test.js +456 -0
  109. package/test/noble.test.js +1565 -0
  110. package/test.js +131 -0
  111. package/with-bindings.js +5 -0
  112. package/ws-slave.js +404 -0
@@ -0,0 +1,1264 @@
1
+ const debug = require('debug')('hci');
2
+
3
+ const events = require('events');
4
+ const util = require('util');
5
+
6
+ const BluetoothHciSocket = require('@abandonware/bluetooth-hci-socket');
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_ADVERTISING_REPORT = 0x02;
25
+ const EVT_LE_ENHANCED_CONN_COMPLETE = 0x0a;
26
+ const EVT_LE_EXTENDED_ADVERTISING_REPORT = 0x0d;
27
+ const EVT_LE_CONN_UPDATE_COMPLETE = 0x03;
28
+
29
+ const OGF_LINK_CTL = 0x01;
30
+ const OCF_DISCONNECT = 0x0006;
31
+
32
+ const OGF_HOST_CTL = 0x03;
33
+ const OCF_SET_EVENT_MASK = 0x0001;
34
+ const OCF_RESET = 0x0003;
35
+ const OCF_SET_RANDOM_MAC = 0x0005;
36
+ const OCF_SET_PHY = 0x0031;
37
+ const OCF_READ_LE_HOST_SUPPORTED = 0x006c;
38
+ const OCF_WRITE_LE_HOST_SUPPORTED = 0x006d;
39
+
40
+ const OGF_INFO_PARAM = 0x04;
41
+ const OCF_READ_LOCAL_VERSION = 0x0001;
42
+ const OCF_READ_SUPPORTED_COMMANDS = 0x0002;
43
+ const OCF_READ_BUFFER_SIZE = 0x0005;
44
+ const OCF_READ_BD_ADDR = 0x0009;
45
+
46
+ const OGF_STATUS_PARAM = 0x05;
47
+ const OCF_READ_RSSI = 0x0005;
48
+
49
+ const OGF_LE_CTL = 0x08;
50
+ const OCF_LE_SET_EVENT_MASK = 0x0001;
51
+ const OCF_LE_READ_BUFFER_SIZE = 0x0002;
52
+ const OCF_LE_SET_EXTENDED_SCAN_PARAMETERS = 0x0041;
53
+ const OCF_LE_SET_EXTENDED_SCAN_ENABLE = 0x0042;
54
+ const OCF_LE_SET_SCAN_PARAMETERS = 0x000b;
55
+ const OCF_LE_SET_SCAN_ENABLE = 0x000c;
56
+ const OCF_LE_CREATE_CONN = 0x000d;
57
+ const OCF_LE_CREATE_EXTENDED_CONN = 0x0043;
58
+ const OCF_LE_CANCEL_CONN = 0x000e;
59
+ const OCF_LE_CONN_UPDATE = 0x0013;
60
+ const OCF_LE_START_ENCRYPTION = 0x0019;
61
+ const DISCONNECT_CMD = OCF_DISCONNECT | (OGF_LINK_CTL << 10);
62
+
63
+ const SET_EVENT_MASK_CMD = OCF_SET_EVENT_MASK | (OGF_HOST_CTL << 10);
64
+ const RESET_CMD = OCF_RESET | (OGF_HOST_CTL << 10);
65
+ const READ_LE_HOST_SUPPORTED_CMD =
66
+ OCF_READ_LE_HOST_SUPPORTED | (OGF_HOST_CTL << 10);
67
+ const WRITE_LE_HOST_SUPPORTED_CMD =
68
+ OCF_WRITE_LE_HOST_SUPPORTED | (OGF_HOST_CTL << 10);
69
+
70
+ const READ_LOCAL_VERSION_CMD = OCF_READ_LOCAL_VERSION | (OGF_INFO_PARAM << 10);
71
+ const READ_SUPPORTED_COMMANDS_CMD =
72
+ OCF_READ_SUPPORTED_COMMANDS | (OGF_INFO_PARAM << 10);
73
+ const READ_BUFFER_SIZE_CMD = OCF_READ_BUFFER_SIZE | (OGF_INFO_PARAM << 10);
74
+ const READ_BD_ADDR_CMD = OCF_READ_BD_ADDR | (OGF_INFO_PARAM << 10);
75
+
76
+ const READ_RSSI_CMD = OCF_READ_RSSI | (OGF_STATUS_PARAM << 10);
77
+
78
+ const LE_SET_EVENT_MASK_CMD = OCF_LE_SET_EVENT_MASK | (OGF_LE_CTL << 10);
79
+ const LE_READ_BUFFER_SIZE_CMD = OCF_LE_READ_BUFFER_SIZE | (OGF_LE_CTL << 10);
80
+ const LE_SET_EXTENDED_SCAN_PARAMETERS_CMD =
81
+ OCF_LE_SET_EXTENDED_SCAN_PARAMETERS | (OGF_LE_CTL << 10);
82
+ const LE_SET_EXTENDED_SCAN_ENABLE_CMD =
83
+ OCF_LE_SET_EXTENDED_SCAN_ENABLE | (OGF_LE_CTL << 10);
84
+ const LE_SET_SCAN_PARAMETERS_CMD =
85
+ OCF_LE_SET_SCAN_PARAMETERS | (OGF_LE_CTL << 10);
86
+ const LE_SET_SCAN_ENABLE_CMD = OCF_LE_SET_SCAN_ENABLE | (OGF_LE_CTL << 10);
87
+ const LE_CREATE_CONN_CMD = OCF_LE_CREATE_CONN | (OGF_LE_CTL << 10);
88
+ const LE_CREATE_EXTENDED_CONN_CMD =
89
+ OCF_LE_CREATE_EXTENDED_CONN | (OGF_LE_CTL << 10);
90
+ const LE_CONN_UPDATE_CMD = OCF_LE_CONN_UPDATE | (OGF_LE_CTL << 10);
91
+ const LE_CANCEL_CONN_CMD = OCF_LE_CANCEL_CONN | (OGF_LE_CTL << 10);
92
+ const LE_START_ENCRYPTION_CMD = OCF_LE_START_ENCRYPTION | (OGF_LE_CTL << 10);
93
+ const HCI_OE_USER_ENDED_CONNECTION = 0x13;
94
+
95
+ const STATUS_MAPPER = require('./hci-status');
96
+
97
+ const Hci = function (options) {
98
+ options = options || {};
99
+ this._socket = new BluetoothHciSocket();
100
+ this._isDevUp = null;
101
+ this._isExtended = 'extended' in options && options.extended;
102
+ this._state = null;
103
+
104
+ this._handleBuffers = {};
105
+
106
+ this._aclBuffers = undefined;
107
+ this._resolveAclBuffers = undefined;
108
+ const aclBuffersPromise = new Promise((resolve) => {
109
+ this._resolveAclBuffers = resolve;
110
+ });
111
+ this.getAclBuffers = async function () {
112
+ if (this._aclBuffers) return this._aclBuffers;
113
+ return await aclBuffersPromise;
114
+ }.bind(this);
115
+ this.setAclBuffers = function (length, num) {
116
+ if (this._aclBuffers) {
117
+ this._aclBuffers.length = length;
118
+ this._aclBuffers.num = num;
119
+ return;
120
+ }
121
+ this._aclBuffers = {
122
+ length,
123
+ num
124
+ };
125
+ this._resolveAclBuffers(this._aclBuffers);
126
+ }.bind(this);
127
+
128
+ this._aclConnections = new Map();
129
+
130
+ this._aclQueue = [];
131
+
132
+ this._deviceId = options.deviceId != null
133
+ ? parseInt(options.deviceId, 10)
134
+ : process.env.NOBLE_HCI_DEVICE_ID
135
+ ? parseInt(process.env.NOBLE_HCI_DEVICE_ID, 10)
136
+ : undefined;
137
+
138
+ this._userChannel =
139
+ (typeof options.userChannel === 'undefined' && options.userChannel) ||
140
+ process.env.HCI_CHANNEL_USER;
141
+
142
+ this.on('stateChange', this.onStateChange.bind(this));
143
+ };
144
+
145
+ util.inherits(Hci, events.EventEmitter);
146
+
147
+ Hci.STATUS_MAPPER = STATUS_MAPPER;
148
+
149
+ Hci.prototype.init = function (options) {
150
+ this._socket.on('data', this.onSocketData.bind(this));
151
+ this._socket.on('error', this.onSocketError.bind(this));
152
+
153
+ if (this._userChannel) {
154
+ this._socket.bindUser(this._deviceId);
155
+ this._socket.start();
156
+
157
+ this.reset();
158
+ } else {
159
+ if (!this._bound) {
160
+ this._socket.bindRaw(this._deviceId);
161
+ this._bound = true;
162
+ }
163
+ this._socket.start();
164
+
165
+ this.pollIsDevUp();
166
+ }
167
+ };
168
+
169
+ Hci.prototype.pollIsDevUp = function () {
170
+ const isDevUp = this._socket.isDevUp();
171
+
172
+ if (this._isDevUp !== isDevUp) {
173
+ if (isDevUp) {
174
+ if (this._state === 'poweredOff') {
175
+ this._socket.removeAllListeners();
176
+ this._state = null;
177
+ this.init();
178
+ return;
179
+ }
180
+
181
+ if (this._isExtended) {
182
+ this.setCodedPhySupport();
183
+ }
184
+ this.setSocketFilter();
185
+ this.setEventMask();
186
+ this.setLeEventMask();
187
+ this.readLocalVersion();
188
+ this.writeLeHostSupported();
189
+ this.readLeHostSupported();
190
+ this.readLeBufferSize();
191
+ this.readBdAddr();
192
+ } else {
193
+ this.emit('stateChange', 'poweredOff');
194
+ }
195
+
196
+ this._isDevUp = isDevUp;
197
+ }
198
+
199
+ setTimeout(this.pollIsDevUp.bind(this), 1000);
200
+ };
201
+
202
+ Hci.prototype.setCodedPhySupport = function () {
203
+ const cmd = Buffer.alloc(7);
204
+
205
+ // header
206
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
207
+ cmd.writeUInt16LE(OCF_SET_PHY | (OGF_LE_CTL << 10), 1);
208
+
209
+ // length
210
+ cmd.writeUInt8(0x03, 3);
211
+
212
+ // data
213
+ cmd.writeUInt8(0x00, 4); // all phy prefs
214
+ cmd.writeUInt8(0x05, 5); // tx phy: 0x01 - LE 1M, 0x03 - LE 1M + LE 2M, 0x05 - LE 1M + LE CODED, 0x07 - LE 1M + LE 2M + LE CODED
215
+ cmd.writeUInt8(0x05, 6); // rx phy: 0x01 - LE 1M, 0x03 - LE 1M + LE 2M, 0x05 - LE 1M + LE CODED, 0x07 - LE 1M + LE 2M + LE CODED
216
+
217
+ debug(`set all phys supporting - writing: ${cmd.toString('hex')}`);
218
+ this._socket.write(cmd);
219
+ };
220
+
221
+ Hci.prototype.setRandomMAC = function () {
222
+ const cmd = Buffer.alloc(10);
223
+
224
+ // header
225
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
226
+ cmd.writeUInt16LE(OCF_SET_RANDOM_MAC | (OGF_LE_CTL << 10), 1);
227
+
228
+ // length
229
+ cmd.writeUInt8(0x06, 3);
230
+
231
+ // data
232
+ cmd.writeUInt8(0x05, 4); // mac 6 byte
233
+ cmd.writeUInt8(0x04, 5); // mac 5 byte
234
+ cmd.writeUInt8(0x03, 6); // mac 4 byte
235
+ cmd.writeUInt8(0x02, 7); // mac 3 byte
236
+ cmd.writeUInt8(0x01, 8); // mac 2 byte
237
+ cmd.writeUInt8(0x00, 9); // mac 1 byte
238
+
239
+ debug(`set random mac address - writing: ${cmd.toString('hex')}`);
240
+ this._socket.write(cmd);
241
+ };
242
+
243
+ Hci.prototype.setSocketFilter = function () {
244
+ const filter = Buffer.alloc(14);
245
+ const typeMask =
246
+ (1 << HCI_COMMAND_PKT) | (1 << HCI_EVENT_PKT) | (1 << HCI_ACLDATA_PKT);
247
+ const eventMask1 =
248
+ (1 << EVT_DISCONN_COMPLETE) |
249
+ (1 << EVT_ENCRYPT_CHANGE) |
250
+ (1 << EVT_CMD_COMPLETE) |
251
+ (1 << EVT_CMD_STATUS) |
252
+ (1 << EVT_NUMBER_OF_COMPLETED_PACKETS);
253
+ const eventMask2 = 1 << (EVT_LE_META_EVENT - 32);
254
+ const opcode = 0;
255
+
256
+ filter.writeUInt32LE(typeMask, 0);
257
+ filter.writeUInt32LE(eventMask1, 4);
258
+ filter.writeUInt32LE(eventMask2, 8);
259
+ filter.writeUInt16LE(opcode, 12);
260
+
261
+ debug(`setting filter to: ${filter.toString('hex')}`);
262
+ this._socket.setFilter(filter);
263
+ };
264
+
265
+ Hci.prototype.setEventMask = function () {
266
+ const cmd = Buffer.alloc(12);
267
+ const eventMask = Buffer.from('fffffbff07f8bf3d', 'hex');
268
+
269
+ // header
270
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
271
+ cmd.writeUInt16LE(SET_EVENT_MASK_CMD, 1);
272
+
273
+ // length
274
+ cmd.writeUInt8(eventMask.length, 3);
275
+
276
+ eventMask.copy(cmd, 4);
277
+
278
+ debug(`set event mask - writing: ${cmd.toString('hex')}`);
279
+ this._socket.write(cmd);
280
+ };
281
+
282
+ Hci.prototype.reset = function () {
283
+ const cmd = Buffer.alloc(4);
284
+
285
+ // header
286
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
287
+ cmd.writeUInt16LE(RESET_CMD, 1);
288
+
289
+ // length
290
+ cmd.writeUInt8(0x00, 3);
291
+
292
+ debug(`reset - writing: ${cmd.toString('hex')}`);
293
+ this._socket.write(cmd);
294
+ };
295
+
296
+ Hci.prototype.readSupportedCommands = function () {
297
+ const cmd = Buffer.alloc(4);
298
+
299
+ // header
300
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
301
+ cmd.writeUInt16LE(READ_SUPPORTED_COMMANDS_CMD, 1);
302
+
303
+ // length
304
+ cmd.writeUInt8(0x0, 3);
305
+
306
+ debug(`read supported commands - writing: ${cmd.toString('hex')}`);
307
+ this._socket.write(cmd);
308
+ };
309
+
310
+ Hci.prototype.readLocalVersion = function () {
311
+ const cmd = Buffer.alloc(4);
312
+
313
+ // header
314
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
315
+ cmd.writeUInt16LE(READ_LOCAL_VERSION_CMD, 1);
316
+
317
+ // length
318
+ cmd.writeUInt8(0x0, 3);
319
+
320
+ debug(`read local version - writing: ${cmd.toString('hex')}`);
321
+ this._socket.write(cmd);
322
+ };
323
+
324
+ Hci.prototype.readBufferSize = function () {
325
+ const cmd = Buffer.alloc(4);
326
+
327
+ // header
328
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
329
+ cmd.writeUInt16LE(READ_BUFFER_SIZE_CMD, 1);
330
+
331
+ // length
332
+ cmd.writeUInt8(0x0, 3);
333
+
334
+ debug(`read buffer size - writing: ${cmd.toString('hex')}`);
335
+ this._socket.write(cmd);
336
+ };
337
+
338
+ Hci.prototype.readBdAddr = function () {
339
+ const cmd = Buffer.alloc(4);
340
+
341
+ // header
342
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
343
+ cmd.writeUInt16LE(READ_BD_ADDR_CMD, 1);
344
+
345
+ // length
346
+ cmd.writeUInt8(0x0, 3);
347
+
348
+ debug(`read bd addr - writing: ${cmd.toString('hex')}`);
349
+ this._socket.write(cmd);
350
+ };
351
+
352
+ Hci.prototype.setLeEventMask = function () {
353
+ const cmd = Buffer.alloc(12);
354
+ const leEventMask = this._isExtended
355
+ ? Buffer.from('1fff000000000000', 'hex')
356
+ : Buffer.from('1f00000000000000', 'hex');
357
+
358
+ // header
359
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
360
+ cmd.writeUInt16LE(LE_SET_EVENT_MASK_CMD, 1);
361
+
362
+ // length
363
+ cmd.writeUInt8(leEventMask.length, 3);
364
+
365
+ leEventMask.copy(cmd, 4);
366
+
367
+ debug(`set le event mask - writing: ${cmd.toString('hex')}`);
368
+ this._socket.write(cmd);
369
+ };
370
+
371
+ Hci.prototype.readLeBufferSize = function () {
372
+ const cmd = Buffer.alloc(4);
373
+
374
+ // header
375
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
376
+ cmd.writeUInt16LE(LE_READ_BUFFER_SIZE_CMD, 1);
377
+
378
+ // length
379
+ cmd.writeUInt8(0x0, 3);
380
+
381
+ debug(`le read buffer size - writing: ${cmd.toString('hex')}`);
382
+ this._socket.write(cmd);
383
+ };
384
+
385
+ Hci.prototype.readLeHostSupported = function () {
386
+ const cmd = Buffer.alloc(4);
387
+
388
+ // header
389
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
390
+ cmd.writeUInt16LE(READ_LE_HOST_SUPPORTED_CMD, 1);
391
+
392
+ // length
393
+ cmd.writeUInt8(0x00, 3);
394
+
395
+ debug(`read LE host supported - writing: ${cmd.toString('hex')}`);
396
+ this._socket.write(cmd);
397
+ };
398
+
399
+ Hci.prototype.writeLeHostSupported = function () {
400
+ const cmd = Buffer.alloc(6);
401
+
402
+ // header
403
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
404
+ cmd.writeUInt16LE(WRITE_LE_HOST_SUPPORTED_CMD, 1);
405
+
406
+ // length
407
+ cmd.writeUInt8(0x02, 3);
408
+
409
+ // data
410
+ cmd.writeUInt8(0x01, 4); // le
411
+ cmd.writeUInt8(0x00, 5); // simul
412
+
413
+ debug(`write LE host supported - writing: ${cmd.toString('hex')}`);
414
+ this._socket.write(cmd);
415
+ };
416
+
417
+ Hci.prototype.setScanParameters = function (
418
+ interval = 0x0012,
419
+ window = 0x0012
420
+ ) {
421
+ const cmd = Buffer.alloc(this._isExtended ? 17 : 11);
422
+
423
+ // header
424
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
425
+ cmd.writeUInt16LE(
426
+ this._isExtended
427
+ ? LE_SET_EXTENDED_SCAN_PARAMETERS_CMD
428
+ : LE_SET_SCAN_PARAMETERS_CMD,
429
+ 1
430
+ );
431
+
432
+ if (this._isExtended) {
433
+ // length
434
+ cmd.writeUInt8(0x0d, 3);
435
+
436
+ // data
437
+ cmd.writeUInt8(0x00, 4); // own address type: 0 -> public, 1 -> random
438
+ cmd.writeUInt8(0x00, 5); // filter: 0 -> all event types
439
+ cmd.writeUInt8(0x05, 6); // phy: LE 1M + LE CODED
440
+ // phy 1M
441
+ cmd.writeUInt8(0x01, 7); // type: 0 -> passive, 1 -> active
442
+ cmd.writeUInt16LE(interval, 8); // interval, ms * 1.6
443
+ cmd.writeUInt16LE(window, 10); // window, ms * 1.6
444
+ // phy coded
445
+ cmd.writeUInt8(0x01, 12); // type: 0 -> passive, 1 -> active
446
+ cmd.writeUInt16LE(interval, 13); // interval, ms * 1.6
447
+ cmd.writeUInt16LE(window, 15); // window, ms * 1.6
448
+ } else {
449
+ // length
450
+ cmd.writeUInt8(0x07, 3);
451
+
452
+ // data
453
+ cmd.writeUInt8(0x01, 4); // type: 0 -> passive, 1 -> active
454
+ cmd.writeUInt16LE(interval, 5); // interval, ms * 1.6
455
+ cmd.writeUInt16LE(window, 7); // window, ms * 1.6
456
+ cmd.writeUInt8(0x00, 9); // own address type: 0 -> public, 1 -> random
457
+ cmd.writeUInt8(0x00, 10); // filter: 0 -> all event types
458
+ }
459
+
460
+ debug(`set scan parameters - writing: ${cmd.toString('hex')}`);
461
+ this._socket.write(cmd);
462
+ };
463
+
464
+ Hci.prototype.setScanEnabled = function (enabled, filterDuplicates) {
465
+ const cmd = Buffer.alloc(this._isExtended ? 10 : 6);
466
+
467
+ // header
468
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
469
+ cmd.writeUInt16LE(
470
+ this._isExtended ? LE_SET_EXTENDED_SCAN_ENABLE_CMD : LE_SET_SCAN_ENABLE_CMD,
471
+ 1
472
+ );
473
+
474
+ if (this._isExtended) {
475
+ // length
476
+ cmd.writeUInt8(0x06, 3);
477
+
478
+ // data
479
+ cmd.writeUInt8(enabled ? 0x01 : 0x00, 4); // enable: 0 -> disabled, 1 -> enabled
480
+ cmd.writeUInt8(filterDuplicates ? 0x01 : 0x00, 5); // duplicates: 0 -> duplicates, 1 -> all
481
+ cmd.writeUInt16LE(0x0000, 6); // duration
482
+ cmd.writeUInt16LE(0x0000, 8); // period
483
+ } else {
484
+ // length
485
+ cmd.writeUInt8(0x02, 3);
486
+
487
+ // data
488
+ cmd.writeUInt8(enabled ? 0x01 : 0x00, 4); // enable: 0 -> disabled, 1 -> enabled
489
+ cmd.writeUInt8(filterDuplicates ? 0x01 : 0x00, 5); // duplicates: 0 -> duplicates, 0 -> duplicates
490
+ }
491
+
492
+ debug(`set scan enabled - writing: ${cmd.toString('hex')}`);
493
+ this._socket.write(cmd);
494
+ };
495
+
496
+ Hci.prototype.createLeConn = function (address, addressType, parameters = {}) {
497
+ const {
498
+ minInterval = 0x0006,
499
+ maxInterval = 0x0012,
500
+ latency = 0x0000,
501
+ timeout = 0x002a
502
+ } = parameters;
503
+
504
+ const cmd = Buffer.alloc(this._isExtended ? 46 : 29);
505
+
506
+ // header
507
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
508
+ cmd.writeUInt16LE(
509
+ this._isExtended
510
+ ? LE_CREATE_EXTENDED_CONN_CMD
511
+ : LE_CREATE_CONN_CMD,
512
+ 1
513
+ );
514
+
515
+ if (this._isExtended) {
516
+ // length
517
+ cmd.writeUInt8(0x2a, 3);
518
+
519
+ // data
520
+ cmd.writeUInt8(0x00, 4); // filter policy: white list is not used
521
+ cmd.writeUInt8(0x00, 5); // own address type
522
+ cmd.writeUInt8(addressType === 'random' ? 0x01 : 0x00, 6); // peer address type
523
+ Buffer.from(address.split(':').reverse().join(''), 'hex').copy(cmd, 7); // peer address
524
+ cmd.writeUInt8(0x05, 13); // initiating PHYs: LE 1M + LE Coded
525
+
526
+ // LE 1M PHY
527
+ cmd.writeUInt16LE(0x0060, 14); // scan interval 60 msec
528
+ cmd.writeUInt16LE(0x0060, 16); // scan window 60 msec
529
+ cmd.writeUInt16LE(minInterval, 18); // min interval
530
+ cmd.writeUInt16LE(maxInterval, 20); // max interval
531
+ cmd.writeUInt16LE(latency, 22); // latency
532
+ cmd.writeUInt16LE(timeout, 24); // supervision timeout
533
+ cmd.writeUInt16LE(0x0000, 26); // min ce length
534
+ cmd.writeUInt16LE(0x0000, 28); // max ce length
535
+
536
+ // LE Coded PHY
537
+ cmd.writeUInt16LE(0x0060, 30); // scan interval 60 msec
538
+ cmd.writeUInt16LE(0x0060, 32); // scan window 60 msec
539
+ cmd.writeUInt16LE(minInterval, 34); // min interval
540
+ cmd.writeUInt16LE(maxInterval, 36); // max interval
541
+ cmd.writeUInt16LE(latency, 38); // latency
542
+ cmd.writeUInt16LE(timeout, 40); // supervision timeout
543
+ cmd.writeUInt16LE(0x0000, 42); // min ce length
544
+ cmd.writeUInt16LE(0x0000, 44); // max ce length
545
+ } else {
546
+ // length
547
+ cmd.writeUInt8(0x19, 3);
548
+
549
+ // data
550
+ cmd.writeUInt16LE(0x0060, 4); // interval
551
+ cmd.writeUInt16LE(0x0030, 6); // window
552
+ cmd.writeUInt8(0x00, 8); // initiator filter
553
+
554
+ cmd.writeUInt8(addressType === 'random' ? 0x01 : 0x00, 9); // peer address type
555
+ Buffer.from(address.split(':').reverse().join(''), 'hex').copy(cmd, 10); // peer address
556
+
557
+ cmd.writeUInt8(0x00, 16); // own address type
558
+
559
+ cmd.writeUInt16LE(minInterval, 17); // min interval
560
+ cmd.writeUInt16LE(maxInterval, 19); // max interval
561
+ cmd.writeUInt16LE(latency, 21); // latency
562
+ cmd.writeUInt16LE(timeout, 23); // supervision timeout
563
+ cmd.writeUInt16LE(0x0004, 25); // min ce length
564
+ cmd.writeUInt16LE(0x0006, 27); // max ce length
565
+ }
566
+
567
+ debug(`create le conn - writing: ${cmd.toString('hex')}`);
568
+ this._socket.write(cmd);
569
+ };
570
+
571
+ Hci.prototype.connUpdateLe = function (
572
+ handle,
573
+ minInterval,
574
+ maxInterval,
575
+ latency,
576
+ supervisionTimeout
577
+ ) {
578
+ const cmd = Buffer.alloc(18);
579
+
580
+ // header
581
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
582
+ cmd.writeUInt16LE(LE_CONN_UPDATE_CMD, 1);
583
+
584
+ // length
585
+ cmd.writeUInt8(0x0e, 3);
586
+
587
+ // data
588
+ cmd.writeUInt16LE(handle, 4);
589
+ cmd.writeUInt16LE(Math.floor(minInterval / 1.25), 6); // min interval
590
+ cmd.writeUInt16LE(Math.floor(maxInterval / 1.25), 8); // max interval
591
+ cmd.writeUInt16LE(latency, 10); // latency
592
+ cmd.writeUInt16LE(Math.floor(supervisionTimeout / 10), 12); // supervision timeout
593
+ cmd.writeUInt16LE(0x0000, 14); // min ce length
594
+ cmd.writeUInt16LE(0x0000, 16); // max ce length
595
+
596
+ debug(`conn update le - writing: ${cmd.toString('hex')}`);
597
+ this._socket.write(cmd);
598
+ };
599
+
600
+ Hci.prototype.cancelConnect = function () {
601
+ const cmd = Buffer.alloc(4);
602
+
603
+ // header
604
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
605
+ cmd.writeUInt16LE(LE_CANCEL_CONN_CMD, 1);
606
+
607
+ // length
608
+ cmd.writeUInt8(0x0, 3);
609
+
610
+ debug('cancel le conn - writing: ' + cmd.toString('hex'));
611
+ this._socket.write(cmd);
612
+ };
613
+
614
+ Hci.prototype.startLeEncryption = function (handle, random, diversifier, key) {
615
+ const cmd = Buffer.alloc(32);
616
+
617
+ // header
618
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
619
+ cmd.writeUInt16LE(LE_START_ENCRYPTION_CMD, 1);
620
+
621
+ // length
622
+ cmd.writeUInt8(0x1c, 3);
623
+
624
+ // data
625
+ cmd.writeUInt16LE(handle, 4); // handle
626
+ random.copy(cmd, 6);
627
+ diversifier.copy(cmd, 14);
628
+ key.copy(cmd, 16);
629
+
630
+ debug(`start le encryption - writing: ${cmd.toString('hex')}`);
631
+ this._socket.write(cmd);
632
+ };
633
+
634
+ Hci.prototype.disconnect = function (handle, reason) {
635
+ const cmd = Buffer.alloc(7);
636
+
637
+ reason = reason || HCI_OE_USER_ENDED_CONNECTION;
638
+
639
+ // header
640
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
641
+ cmd.writeUInt16LE(DISCONNECT_CMD, 1);
642
+
643
+ // length
644
+ cmd.writeUInt8(0x03, 3);
645
+
646
+ // data
647
+ cmd.writeUInt16LE(handle, 4); // handle
648
+ cmd.writeUInt8(reason, 6); // reason
649
+
650
+ debug(`disconnect - writing: ${cmd.toString('hex')}`);
651
+ this._socket.write(cmd);
652
+ };
653
+
654
+ Hci.prototype.readRssi = function (handle) {
655
+ const cmd = Buffer.alloc(6);
656
+
657
+ // header
658
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
659
+ cmd.writeUInt16LE(READ_RSSI_CMD, 1);
660
+
661
+ // length
662
+ cmd.writeUInt8(0x02, 3);
663
+
664
+ // data
665
+ cmd.writeUInt16LE(handle, 4); // handle
666
+
667
+ debug(`read rssi - writing: ${cmd.toString('hex')}`);
668
+ this._socket.write(cmd);
669
+ };
670
+
671
+ Hci.prototype.writeAclDataPkt = async function (handle, cid, data) {
672
+ const l2capLength = 4 /* l2cap header */ + data.length;
673
+
674
+ const aclBuffers = await this.getAclBuffers();
675
+ const aclLength = Math.min(l2capLength, aclBuffers.length);
676
+
677
+ const minFirstLength = Math.max(aclLength + 5 /* acl header */, 9 /* prevent buffer overflow */);
678
+ const first = Buffer.alloc(minFirstLength);
679
+
680
+ // acl header
681
+ first.writeUInt8(HCI_ACLDATA_PKT, 0);
682
+ first.writeUInt16LE(handle | (ACL_START_NO_FLUSH << 12), 1);
683
+ first.writeUInt16LE(aclLength, 3);
684
+
685
+ // l2cap header
686
+ first.writeUInt16LE(data.length, 5);
687
+ first.writeUInt16LE(cid, 7);
688
+
689
+ data.copy(first, 9);
690
+ data = data.slice(first.length - 9);
691
+
692
+ debug(`push to acl queue: ${first.toString('hex')}`);
693
+ this._aclQueue.push({ handle, packet: first });
694
+
695
+ while (data.length > 0) {
696
+ const fragAclLength = Math.min(data.length, aclBuffers.length);
697
+ const frag = Buffer.alloc(fragAclLength + 5 /* acl header */);
698
+ // acl header
699
+ frag.writeUInt8(HCI_ACLDATA_PKT, 0);
700
+ frag.writeUInt16LE(handle | (ACL_CONT << 12), 1);
701
+ frag.writeUInt16LE(fragAclLength, 3);
702
+
703
+ data.copy(frag, 5);
704
+ data = data.slice(frag.length - 5);
705
+
706
+ debug(`push fragment to acl queue: ${frag.toString('hex')}`);
707
+ this._aclQueue.push({ handle, packet: frag });
708
+ }
709
+
710
+ this.flushAcl();
711
+ };
712
+
713
+ Hci.prototype.flushAcl = async function () {
714
+ const pendingPackets = () => {
715
+ let totalPending = 0;
716
+ for (const { pending } of this._aclConnections.values()) {
717
+ totalPending += pending;
718
+ }
719
+ return totalPending;
720
+ };
721
+
722
+ debug(
723
+ `flush - pending: ${pendingPackets()} queue length: ${
724
+ this._aclQueue.length
725
+ }`
726
+ );
727
+
728
+ const aclBuffers = await this.getAclBuffers();
729
+ while (this._aclQueue.length > 0 && pendingPackets() < aclBuffers.num) {
730
+ const { handle, packet } = this._aclQueue.shift();
731
+ this._aclConnections.get(handle).pending++;
732
+ debug(`write acl data packet - writing: ${packet.toString('hex')}`);
733
+ this._socket.write(packet);
734
+ }
735
+ };
736
+
737
+ Hci.prototype.onSocketData = function (data) {
738
+ debug(`onSocketData: ${data.toString('hex')}`);
739
+
740
+ const eventType = data.readUInt8(0);
741
+ let handle;
742
+ let cmd;
743
+ let status;
744
+
745
+ debug(`\tevent type = ${eventType}`);
746
+
747
+ if (HCI_EVENT_PKT === eventType) {
748
+ const subEventType = data.readUInt8(1);
749
+
750
+ debug(`\tsub event type = ${subEventType}`);
751
+
752
+ if (subEventType === EVT_DISCONN_COMPLETE) {
753
+ handle = data.readUInt16LE(4);
754
+ const reason = data.readUInt8(6);
755
+
756
+ debug(`\t\thandle = ${handle}`);
757
+ debug(`\t\treason = ${reason}`);
758
+
759
+ this._aclQueue = this._aclQueue.filter((acl) => acl.handle !== handle);
760
+ this._aclConnections.delete(handle);
761
+ this.flushAcl();
762
+
763
+ this.emit('disconnComplete', handle, reason);
764
+ } else if (subEventType === EVT_ENCRYPT_CHANGE) {
765
+ handle = data.readUInt16LE(4);
766
+ const encrypt = data.readUInt8(6);
767
+
768
+ debug(`\t\thandle = ${handle}`);
769
+ debug(`\t\tencrypt = ${encrypt}`);
770
+
771
+ this.emit('encryptChange', handle, encrypt);
772
+ } else if (subEventType === EVT_CMD_COMPLETE) {
773
+ cmd = data.readUInt16LE(4);
774
+ status = data.readUInt8(6);
775
+ const result = data.slice(7);
776
+
777
+ debug(`\t\tcmd = ${cmd}`);
778
+ debug(`\t\tstatus = ${status}`);
779
+ debug(`\t\tresult = ${result.toString('hex')}`);
780
+
781
+ this.processCmdCompleteEvent(cmd, status, result);
782
+ } else if (subEventType === EVT_CMD_STATUS) {
783
+ status = data.readUInt8(3);
784
+ cmd = data.readUInt16LE(5);
785
+
786
+ debug(`\t\tstatus = ${status}`);
787
+ debug(`\t\tcmd = ${cmd}`);
788
+
789
+ this.processCmdStatusEvent(cmd, status);
790
+ } else if (subEventType === EVT_LE_META_EVENT) {
791
+ const leMetaEventLength = data.readUInt8(2);
792
+ const leMetaEventType = data.readUInt8(3);
793
+ const leMetaEventNumReports = data.readUInt8(4);
794
+ const leMetaEventData = data.slice(5);
795
+
796
+ debug(`\t\tLE meta event type = ${leMetaEventType}`);
797
+ debug(`\t\tLE meta event data length = ${leMetaEventLength}`);
798
+ debug(`\t\tLE meta event num reports = ${leMetaEventNumReports}`);
799
+ debug(`\t\tLE meta event data = ${leMetaEventData.toString('hex')}`);
800
+
801
+ this.processLeMetaEvent(
802
+ leMetaEventType,
803
+ leMetaEventNumReports,
804
+ leMetaEventData
805
+ );
806
+ } else if (subEventType === EVT_NUMBER_OF_COMPLETED_PACKETS) {
807
+ const handles = data.readUInt8(3);
808
+ for (let h = 0; h < handles; h++) {
809
+ const handle = data.readUInt16LE(4 + h * 4);
810
+ const pkts = data.readUInt16LE(6 + h * 4);
811
+ debug(`\thandle = ${handle}`);
812
+ debug(`\t\tcompleted = ${pkts}`);
813
+
814
+ if (!this._aclConnections.has(handle)) {
815
+ debug('\t\tclosed');
816
+ continue;
817
+ }
818
+
819
+ const connection = this._aclConnections.get(handle);
820
+
821
+ connection.pending -= pkts;
822
+
823
+ if (connection.pending < 0) {
824
+ connection.pending = 0;
825
+ }
826
+ }
827
+ this.flushAcl();
828
+ }
829
+ } else if (HCI_ACLDATA_PKT === eventType) {
830
+ const flags = data.readUInt16LE(1) >> 12;
831
+ handle = data.readUInt16LE(1) & 0x0fff;
832
+
833
+ if (ACL_START === flags) {
834
+ const cid = data.readUInt16LE(7);
835
+
836
+ const length = data.readUInt16LE(5);
837
+ const pktData = data.slice(9);
838
+
839
+ debug(`\t\tcid = ${cid}`);
840
+
841
+ if (length === pktData.length) {
842
+ debug(`\t\thandle = ${handle}`);
843
+ debug(`\t\tdata = ${pktData.toString('hex')}`);
844
+
845
+ this.emit('aclDataPkt', handle, cid, pktData);
846
+ } else {
847
+ this._handleBuffers[handle] = {
848
+ length: length,
849
+ cid: cid,
850
+ data: pktData
851
+ };
852
+ }
853
+ } else if (ACL_CONT === flags) {
854
+ if (!this._handleBuffers[handle] || !this._handleBuffers[handle].data) {
855
+ return;
856
+ }
857
+
858
+ this._handleBuffers[handle].data = Buffer.concat([
859
+ this._handleBuffers[handle].data,
860
+ data.slice(5)
861
+ ]);
862
+
863
+ if (
864
+ this._handleBuffers[handle].data.length ===
865
+ this._handleBuffers[handle].length
866
+ ) {
867
+ this.emit(
868
+ 'aclDataPkt',
869
+ handle,
870
+ this._handleBuffers[handle].cid,
871
+ this._handleBuffers[handle].data
872
+ );
873
+
874
+ delete this._handleBuffers[handle];
875
+ }
876
+ }
877
+ } else if (HCI_COMMAND_PKT === eventType) {
878
+ cmd = data.readUInt16LE(1);
879
+ const len = data.readUInt8(3);
880
+
881
+ debug(`\t\tcmd = ${cmd}`);
882
+ debug(`\t\tdata len = ${len}`);
883
+
884
+ if (
885
+ cmd === LE_SET_SCAN_ENABLE_CMD ||
886
+ cmd === LE_SET_EXTENDED_SCAN_ENABLE_CMD
887
+ ) {
888
+ const enable = data.readUInt8(4) === 0x1;
889
+ const filterDuplicates = data.readUInt8(5) === 0x1;
890
+
891
+ debug('\t\t\tLE enable scan command');
892
+ debug(`\t\t\tenable scanning = ${enable}`);
893
+ debug(`\t\t\tfilter duplicates = ${filterDuplicates}`);
894
+
895
+ this.emit('leScanEnableSetCmd', enable, filterDuplicates);
896
+ }
897
+ }
898
+ };
899
+
900
+ Hci.prototype.onSocketError = function (error) {
901
+ debug(`onSocketError: ${error.message}`);
902
+
903
+ if (error.code === 'EPERM') {
904
+ this.emit('stateChange', 'unauthorized');
905
+ } else if (error.message === 'Network is down') {
906
+ // no-op
907
+ }
908
+ };
909
+
910
+ Hci.prototype.processCmdCompleteEvent = function (cmd, status, result) {
911
+ if (cmd === RESET_CMD) {
912
+ if (this._isExtended) {
913
+ this.setCodedPhySupport();
914
+ }
915
+ this.setEventMask();
916
+ this.setLeEventMask();
917
+ this.readLocalVersion();
918
+ this.readBdAddr();
919
+ } else if (cmd === READ_LE_HOST_SUPPORTED_CMD) {
920
+ if (status === 0) {
921
+ const le = result.readUInt8(0);
922
+ const simul = result.readUInt8(1);
923
+
924
+ debug(`\t\t\tle = ${le}`);
925
+ debug(`\t\t\tsimul = ${simul}`);
926
+ }
927
+ } else if (cmd === READ_LOCAL_VERSION_CMD) {
928
+ const hciVer = result.readUInt8(0);
929
+ const hciRev = result.readUInt16LE(1);
930
+ const lmpVer = result.readInt8(3);
931
+ const manufacturer = result.readUInt16LE(4);
932
+ const lmpSubVer = result.readUInt16LE(6);
933
+
934
+ if (hciVer < 0x06) {
935
+ this.emit('stateChange', 'unsupported');
936
+ } else if (this._state !== 'poweredOn') {
937
+ this.setScanEnabled(false, true);
938
+ this.setScanParameters();
939
+ }
940
+
941
+ this.emit(
942
+ 'readLocalVersion',
943
+ hciVer,
944
+ hciRev,
945
+ lmpVer,
946
+ manufacturer,
947
+ lmpSubVer
948
+ );
949
+ } else if (cmd === READ_SUPPORTED_COMMANDS_CMD) {
950
+ const extendedScanParameters = result.readUInt8(37) & 0x10; // LE Set Extended Scan Parameters (Octet 37 - Bit 5)
951
+ const extendedScan = result.readUInt8(37) & 0x20; // LE Set Extended Scan Enable (Octet 37 - Bit 6)
952
+
953
+ debug(
954
+ `Extended advertising features: parameters = ${
955
+ extendedScanParameters ? 'true' : 'false'
956
+ }, num = ${extendedScan ? 'true' : 'false'}`
957
+ );
958
+
959
+ this._isExtended = extendedScanParameters && extendedScan;
960
+ } else if (cmd === READ_BD_ADDR_CMD) {
961
+ this.addressType = 'public';
962
+ this.address = result
963
+ .toString('hex')
964
+ .match(/.{1,2}/g)
965
+ .reverse()
966
+ .join(':');
967
+
968
+ debug(`address = ${this.address}`);
969
+
970
+ this.emit('addressChange', this.address);
971
+ } else if (
972
+ cmd === LE_SET_SCAN_PARAMETERS_CMD ||
973
+ cmd === LE_SET_EXTENDED_SCAN_PARAMETERS_CMD
974
+ ) {
975
+ this.emit('stateChange', 'poweredOn');
976
+
977
+ this.emit('leScanParametersSet');
978
+ } else if (
979
+ cmd === LE_SET_SCAN_ENABLE_CMD ||
980
+ cmd === LE_SET_EXTENDED_SCAN_ENABLE_CMD
981
+ ) {
982
+ this.emit('leScanEnableSet', status);
983
+ } else if (cmd === READ_RSSI_CMD) {
984
+ const handle = result.readUInt16LE(0);
985
+ const rssi = result.readInt8(2);
986
+
987
+ debug(`\t\t\thandle = ${handle}`);
988
+ debug(`\t\t\trssi = ${rssi}`);
989
+
990
+ this.emit('rssiRead', handle, rssi);
991
+ } else if (cmd === LE_READ_BUFFER_SIZE_CMD) {
992
+ if (status === 0) {
993
+ const aclLength = result.readUInt16LE(0);
994
+ const aclNum = result.readUInt8(2);
995
+
996
+ /* Spec Vol 4 Part E.7.8
997
+ /* No dedicated LE Buffer exists. Use the HCI_Read_Buffer_Size command. */
998
+ if (aclLength === 0 || aclNum === 0) {
999
+ debug('using br/edr buffer size');
1000
+ this.readBufferSize();
1001
+ } else {
1002
+ debug(`le buffer size: length = ${aclLength}, num = ${aclNum}`);
1003
+ this.setAclBuffers(aclLength, aclNum);
1004
+ }
1005
+ }
1006
+ } else if (cmd === READ_BUFFER_SIZE_CMD) {
1007
+ const aclLength = result.readUInt16LE(0);
1008
+ const aclNum = result.readUInt16LE(3);
1009
+
1010
+ debug(`buffer size: length = ${aclLength}, num = ${aclNum}`);
1011
+ this.setAclBuffers(aclLength, aclNum);
1012
+ }
1013
+ };
1014
+
1015
+ Hci.prototype.processLeMetaEvent = function (eventType, numReports, data) {
1016
+ if (eventType === EVT_LE_CONN_COMPLETE) {
1017
+ this.processLeConnComplete(numReports, data);
1018
+ } else if (eventType === EVT_LE_ENHANCED_CONN_COMPLETE) {
1019
+ this.processLeEnhancedConnComplete(numReports, data);
1020
+ } else if (eventType === EVT_LE_ADVERTISING_REPORT) {
1021
+ this.processLeAdvertisingReport(numReports, data);
1022
+ } else if (eventType === EVT_LE_EXTENDED_ADVERTISING_REPORT) {
1023
+ this.processLeExtendedAdvertisingReport(numReports, data);
1024
+ } else if (eventType === EVT_LE_CONN_UPDATE_COMPLETE) {
1025
+ this.processLeConnUpdateComplete(numReports, data);
1026
+ }
1027
+ };
1028
+
1029
+ Hci.prototype.processLeConnComplete = function (status, data) {
1030
+ const handle = data.readUInt16LE(0);
1031
+ const role = data.readUInt8(2);
1032
+ const addressType = data.readUInt8(3) === 0x01 ? 'random' : 'public';
1033
+ const address = data
1034
+ .slice(4, 10)
1035
+ .toString('hex')
1036
+ .match(/.{1,2}/g)
1037
+ .reverse()
1038
+ .join(':');
1039
+ const interval = data.readUInt16LE(10) * 1.25;
1040
+ const latency = data.readUInt16LE(12); // TODO: multiplier?
1041
+ const supervisionTimeout = data.readUInt16LE(14) * 10;
1042
+ const masterClockAccuracy = data.readUInt8(16); // TODO: multiplier?
1043
+
1044
+ debug(`\t\t\thandle = ${handle}`);
1045
+ debug(`\t\t\trole = ${role}`);
1046
+ debug(`\t\t\taddress type = ${addressType}`);
1047
+ debug(`\t\t\taddress = ${address}`);
1048
+ debug(`\t\t\tinterval = ${interval}`);
1049
+ debug(`\t\t\tlatency = ${latency}`);
1050
+ debug(`\t\t\tsupervision timeout = ${supervisionTimeout}`);
1051
+ debug(`\t\t\tmaster clock accuracy = ${masterClockAccuracy}`);
1052
+
1053
+ this._aclConnections.set(handle, { pending: 0 });
1054
+
1055
+ this.emit(
1056
+ 'leConnComplete',
1057
+ status,
1058
+ handle,
1059
+ role,
1060
+ addressType,
1061
+ address,
1062
+ interval,
1063
+ latency,
1064
+ supervisionTimeout,
1065
+ masterClockAccuracy
1066
+ );
1067
+ };
1068
+
1069
+ Hci.prototype.processLeEnhancedConnComplete = function (status, data) {
1070
+ const handle = data.readUInt16LE(0);
1071
+ const role = data.readUInt8(2);
1072
+ const addressType = data.readUInt8(3) === 0x01 ? 'random' : 'public';
1073
+ const address = data
1074
+ .slice(4, 10)
1075
+ .toString('hex')
1076
+ .match(/.{1,2}/g)
1077
+ .reverse()
1078
+ .join(':');
1079
+ const localResolvablePrivateAddress = data
1080
+ .slice(10, 16)
1081
+ .toString('hex')
1082
+ .match(/.{1,2}/g)
1083
+ .reverse()
1084
+ .join(':');
1085
+ const peerResolvablePrivateAddress = data
1086
+ .slice(16, 22)
1087
+ .toString('hex')
1088
+ .match(/.{1,2}/g)
1089
+ .reverse()
1090
+ .join(':');
1091
+ const interval = data.readUInt16LE(22) * 1.25;
1092
+ const latency = data.readUInt16LE(24); // TODO: multiplier?
1093
+ const supervisionTimeout = data.readUInt16LE(26) * 10;
1094
+ const masterClockAccuracy = data.readUInt8(28); // TODO: multiplier?
1095
+
1096
+ debug(`\t\t\thandle = ${handle}`);
1097
+ debug(`\t\t\trole = ${role}`);
1098
+ debug(`\t\t\taddress type = ${addressType}`);
1099
+ debug(`\t\t\taddress = ${address}`);
1100
+ debug(
1101
+ `\t\t\tLocal resolvable private address = ${localResolvablePrivateAddress}`
1102
+ );
1103
+ debug(
1104
+ `\t\t\tPeer resolvable private address = ${peerResolvablePrivateAddress}`
1105
+ );
1106
+ debug(`\t\t\tinterval = ${interval}`);
1107
+ debug(`\t\t\tlatency = ${latency}`);
1108
+ debug(`\t\t\tsupervision timeout = ${supervisionTimeout}`);
1109
+ debug(`\t\t\tmaster clock accuracy = ${masterClockAccuracy}`);
1110
+
1111
+ this._aclConnections.set(handle, { pending: 0 });
1112
+
1113
+ this.emit(
1114
+ 'leConnComplete',
1115
+ status,
1116
+ handle,
1117
+ role,
1118
+ addressType,
1119
+ address,
1120
+ interval,
1121
+ latency,
1122
+ supervisionTimeout,
1123
+ masterClockAccuracy
1124
+ );
1125
+ };
1126
+
1127
+ Hci.prototype.processLeAdvertisingReport = function (numReports, data) {
1128
+ try {
1129
+ for (let i = 0; i < numReports; i++) {
1130
+ const type = data.readUInt8(0);
1131
+ const addressType = data.readUInt8(1) === 0x01 ? 'random' : 'public';
1132
+ const address = data
1133
+ .slice(2, 8)
1134
+ .toString('hex')
1135
+ .match(/.{1,2}/g)
1136
+ .reverse()
1137
+ .join(':');
1138
+ const eirLength = data.readUInt8(8);
1139
+ const eir = data.slice(9, eirLength + 9);
1140
+ const rssi = data.readInt8(eirLength + 9);
1141
+
1142
+ debug(`\t\t\ttype = ${type}`);
1143
+ debug(`\t\t\taddress = ${address}`);
1144
+ debug(`\t\t\taddress type = ${addressType}`);
1145
+ debug(`\t\t\teir = ${eir.toString('hex')}`);
1146
+ debug(`\t\t\trssi = ${rssi}`);
1147
+
1148
+ this.emit(
1149
+ 'leAdvertisingReport',
1150
+ 0,
1151
+ type,
1152
+ address,
1153
+ addressType,
1154
+ eir,
1155
+ rssi
1156
+ );
1157
+
1158
+ data = data.slice(eirLength + 10);
1159
+ }
1160
+ } catch (e) {
1161
+ console.warn(
1162
+ `processLeAdvertisingReport: Caught illegal packet (buffer overflow): ${e}`
1163
+ );
1164
+ }
1165
+ };
1166
+
1167
+ Hci.prototype.processLeExtendedAdvertisingReport = function (numReports, data) {
1168
+ try {
1169
+ for (let i = 0; i < numReports; i++) {
1170
+ const type = data.readUInt16LE(0);
1171
+ const addressType = data.readUInt8(2) === 0x01 ? 'random' : 'public';
1172
+ const address = data
1173
+ .slice(3, 9)
1174
+ .toString('hex')
1175
+ .match(/.{1,2}/g)
1176
+ .reverse()
1177
+ .join(':');
1178
+ const primaryPHY = data.readUInt8(9);
1179
+ const secondaryPHY = data.readUInt8(10);
1180
+ const sid = data.readUInt8(11);
1181
+ const txpower = data.readUInt8(12);
1182
+ const rssi = data.readInt8(13);
1183
+ const periodicAdvInterval = data.readUInt16LE(14);
1184
+ const directAddressType =
1185
+ data.readUInt8(16) === 0x01 ? 'random' : 'public';
1186
+ const directAddress = data
1187
+ .slice(17, 23)
1188
+ .toString('hex')
1189
+ .match(/.{1,2}/g)
1190
+ .reverse()
1191
+ .join(':');
1192
+ const eirLength = data.readUInt8(23);
1193
+ const eir = data.slice(24);
1194
+
1195
+ debug(`\t\t\ttype = ${type}`);
1196
+ debug(`\t\t\taddress = ${address}`);
1197
+ debug(`\t\t\taddress type = ${addressType}`);
1198
+ debug(`\t\t\tprimary phy = ${primaryPHY.toString(16)}`);
1199
+ debug(`\t\t\tsecondary phy = ${secondaryPHY.toString(16)}`);
1200
+ debug(`\t\t\tSID = ${sid.toString(16)}`);
1201
+ debug(`\t\t\tTX power = ${txpower}`);
1202
+ debug(`\t\t\tRSSI = ${rssi}`);
1203
+ debug(
1204
+ `\t\t\tperiodic advertising interval = ${periodicAdvInterval} msec`
1205
+ );
1206
+ debug(`\t\t\tdirect address type = ${directAddressType}`);
1207
+ debug(`\t\t\tdirect address = ${directAddress}`);
1208
+ debug(`\t\t\teir length = ${eirLength}`);
1209
+ debug(`\t\t\teir = ${eir.toString('hex')}`);
1210
+
1211
+ this.emit(
1212
+ 'leExtendedAdvertisingReport',
1213
+ 0,
1214
+ type,
1215
+ address,
1216
+ addressType,
1217
+ txpower,
1218
+ rssi,
1219
+ eir
1220
+ );
1221
+
1222
+ data = data.slice(eirLength + 24);
1223
+ }
1224
+ } catch (e) {
1225
+ console.warn(
1226
+ `processLeExtendedAdvertisingReport: Caught illegal packet (buffer overflow): ${e}`
1227
+ );
1228
+ }
1229
+ };
1230
+
1231
+ Hci.prototype.processLeConnUpdateComplete = function (status, data) {
1232
+ const handle = data.readUInt16LE(0);
1233
+ const interval = data.readUInt16LE(2) * 1.25;
1234
+ const latency = data.readUInt16LE(4); // TODO: multiplier?
1235
+ const supervisionTimeout = data.readUInt16LE(6) * 10;
1236
+
1237
+ debug(`\t\t\thandle = ${handle}`);
1238
+ debug(`\t\t\tinterval = ${interval}`);
1239
+ debug(`\t\t\tlatency = ${latency}`);
1240
+ debug(`\t\t\tsupervision timeout = ${supervisionTimeout}`);
1241
+
1242
+ this.emit(
1243
+ 'leConnUpdateComplete',
1244
+ status,
1245
+ handle,
1246
+ interval,
1247
+ latency,
1248
+ supervisionTimeout
1249
+ );
1250
+ };
1251
+
1252
+ Hci.prototype.processCmdStatusEvent = function (cmd, status) {
1253
+ if (cmd === LE_CREATE_CONN_CMD || cmd === LE_CREATE_EXTENDED_CONN_CMD) {
1254
+ if (status !== 0) {
1255
+ this.emit('leConnComplete', status);
1256
+ }
1257
+ }
1258
+ };
1259
+
1260
+ Hci.prototype.onStateChange = function (state) {
1261
+ this._state = state;
1262
+ };
1263
+
1264
+ module.exports = Hci;