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