@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,1089 @@
1
+ const should = require('should');
2
+ const sinon = require('sinon');
3
+
4
+ const { assert } = sinon;
5
+
6
+ const Gap = require('../../../lib/hci-socket/gap');
7
+
8
+ describe('hci-socket gap', () => {
9
+ it('constructor', () => {
10
+ const hci = {
11
+ on: sinon.spy()
12
+ };
13
+
14
+ const gap = new Gap(hci);
15
+
16
+ should(gap._hci).equal(hci);
17
+ should(gap._scanState).equal(null);
18
+ should(gap._scanFilterDuplicates).equal(null);
19
+ should(gap._discoveries).deepEqual({});
20
+
21
+ assert.callCount(hci.on, 6);
22
+ assert.calledWithMatch(hci.on, 'error', sinon.match.func);
23
+ assert.calledWithMatch(hci.on, 'leScanParametersSet', sinon.match.func);
24
+ assert.calledWithMatch(hci.on, 'leScanEnableSet', sinon.match.func);
25
+ assert.calledWithMatch(hci.on, 'leAdvertisingReport', sinon.match.func);
26
+ assert.calledWithMatch(hci.on, 'leScanEnableSetCmd', sinon.match.func);
27
+ assert.calledWithMatch(hci.on, 'leExtendedAdvertisingReport', sinon.match.func);
28
+ });
29
+
30
+ it('setScanParameters', () => {
31
+ const hci = {
32
+ on: sinon.spy(),
33
+ setScanParameters: sinon.spy()
34
+ };
35
+
36
+ const interval = 'interval';
37
+ const window = 'window';
38
+
39
+ const gap = new Gap(hci);
40
+ gap.setScanParameters(interval, window);
41
+
42
+ assert.calledOnceWithExactly(hci.setScanParameters, interval, window);
43
+ });
44
+
45
+ it('startScanning', () => {
46
+ const hci = {
47
+ on: sinon.spy(),
48
+ setScanEnabled: sinon.spy(),
49
+ setScanParameters: sinon.spy()
50
+ };
51
+
52
+ const gap = new Gap(hci);
53
+ gap.startScanning(true);
54
+
55
+ should(gap._scanState).equal('starting');
56
+ should(gap._scanFilterDuplicates).equal(false);
57
+
58
+ assert.callCount(hci.setScanEnabled, 2);
59
+ assert.calledWithExactly(hci.setScanEnabled, false, true);
60
+ assert.calledWithExactly(hci.setScanEnabled, true, false);
61
+ assert.calledOnceWithExactly(hci.setScanParameters);
62
+ });
63
+
64
+ it('stopScanning', () => {
65
+ const hci = {
66
+ on: sinon.spy(),
67
+ setScanEnabled: sinon.spy()
68
+ };
69
+
70
+ const gap = new Gap(hci);
71
+ gap.stopScanning();
72
+
73
+ should(gap._scanState).equal('stopping');
74
+
75
+ assert.calledOnceWithExactly(hci.setScanEnabled, false, true);
76
+ });
77
+
78
+ it('onHciLeScanParametersSet', () => {
79
+ const hci = {
80
+ on: sinon.spy()
81
+ };
82
+ const callback = sinon.spy();
83
+
84
+ const gap = new Gap(hci);
85
+ gap.on('scanParametersSet', callback);
86
+ gap.onHciLeScanParametersSet();
87
+
88
+ assert.calledOnceWithExactly(callback);
89
+ });
90
+
91
+ describe('onHciLeScanEnableSet', () => {
92
+ it('status 1', () => {
93
+ const hci = {
94
+ on: sinon.spy()
95
+ };
96
+ const scanStartCallback = sinon.spy();
97
+ const scanStopCallback = sinon.spy();
98
+
99
+ const gap = new Gap(hci);
100
+ gap.on('scanStart', scanStartCallback);
101
+ gap.on('scanStop', scanStopCallback);
102
+ gap.onHciLeScanEnableSet(1);
103
+
104
+ assert.notCalled(scanStartCallback);
105
+ assert.notCalled(scanStopCallback);
106
+ });
107
+
108
+ it('status 0 but invalid state', () => {
109
+ const hci = {
110
+ on: sinon.spy()
111
+ };
112
+ const scanStartCallback = sinon.spy();
113
+ const scanStopCallback = sinon.spy();
114
+
115
+ const gap = new Gap(hci);
116
+ gap._scanState = 'unknown';
117
+ gap.on('scanStart', scanStartCallback);
118
+ gap.on('scanStop', scanStopCallback);
119
+ gap.onHciLeScanEnableSet(0);
120
+
121
+ should(gap._scanState).equal('unknown');
122
+
123
+ assert.notCalled(scanStartCallback);
124
+ assert.notCalled(scanStopCallback);
125
+ });
126
+
127
+ it('status 0 state starting', () => {
128
+ const hci = {
129
+ on: sinon.spy()
130
+ };
131
+ const scanStartCallback = sinon.spy();
132
+ const scanStopCallback = sinon.spy();
133
+
134
+ const gap = new Gap(hci);
135
+ gap._scanFilterDuplicates = true;
136
+ gap._scanState = 'starting';
137
+ gap.on('scanStart', scanStartCallback);
138
+ gap.on('scanStop', scanStopCallback);
139
+ gap.onHciLeScanEnableSet(0);
140
+
141
+ should(gap._scanState).equal('started');
142
+
143
+ assert.calledOnceWithExactly(scanStartCallback, true);
144
+ assert.notCalled(scanStopCallback);
145
+ });
146
+
147
+ it('status 0 state stopping', () => {
148
+ const hci = {
149
+ on: sinon.spy()
150
+ };
151
+ const scanStartCallback = sinon.spy();
152
+ const scanStopCallback = sinon.spy();
153
+
154
+ const gap = new Gap(hci);
155
+ gap._scanState = 'stopping';
156
+ gap.on('scanStart', scanStartCallback);
157
+ gap.on('scanStop', scanStopCallback);
158
+ gap.onHciLeScanEnableSet(0);
159
+
160
+ should(gap._scanState).equal('stopped');
161
+
162
+ assert.notCalled(scanStartCallback);
163
+ assert.calledOnceWithExactly(scanStopCallback);
164
+ });
165
+ });
166
+
167
+ describe('onLeScanEnableSetCmd', () => {
168
+ it('invalid state', () => {
169
+ const hci = {
170
+ on: sinon.spy()
171
+ };
172
+ const scanStartCallback = sinon.spy();
173
+ const scanStopCallback = sinon.spy();
174
+
175
+ const enable = false;
176
+ const filterDuplicates = false;
177
+
178
+ const gap = new Gap(hci);
179
+ gap._scanState = 'unknown';
180
+ gap.on('scanStart', scanStartCallback);
181
+ gap.on('scanStop', scanStopCallback);
182
+ gap.onLeScanEnableSetCmd(enable, filterDuplicates);
183
+
184
+ should(gap._scanState).equal('unknown');
185
+ should(gap._scanFilterDuplicates).equal(null);
186
+
187
+ assert.notCalled(scanStartCallback);
188
+ assert.notCalled(scanStopCallback);
189
+ });
190
+
191
+ ['starting', 'started'].forEach(state => {
192
+ it(`state ${state}, do not enable`, () => {
193
+ const hci = {
194
+ on: sinon.spy()
195
+ };
196
+ const scanStartCallback = sinon.spy();
197
+ const scanStopCallback = sinon.spy();
198
+
199
+ const enable = false;
200
+ const filterDuplicates = false;
201
+
202
+ const gap = new Gap(hci);
203
+ gap._scanState = state;
204
+ gap.on('scanStart', scanStartCallback);
205
+ gap.on('scanStop', scanStopCallback);
206
+ gap.onLeScanEnableSetCmd(enable, filterDuplicates);
207
+
208
+ should(gap._scanState).equal(state);
209
+ should(gap._scanFilterDuplicates).equal(null);
210
+
211
+ assert.notCalled(scanStartCallback);
212
+ assert.calledOnceWithExactly(scanStopCallback);
213
+ });
214
+ });
215
+
216
+ ['starting', 'started'].forEach(state => {
217
+ it(`state ${state}, do enable`, () => {
218
+ const hci = {
219
+ on: sinon.spy()
220
+ };
221
+ const scanStartCallback = sinon.spy();
222
+ const scanStopCallback = sinon.spy();
223
+
224
+ const enable = true;
225
+ const filterDuplicates = false;
226
+
227
+ const gap = new Gap(hci);
228
+ gap._scanState = state;
229
+ gap.on('scanStart', scanStartCallback);
230
+ gap.on('scanStop', scanStopCallback);
231
+ gap.onLeScanEnableSetCmd(enable, filterDuplicates);
232
+
233
+ should(gap._scanState).equal(state);
234
+ should(gap._scanFilterDuplicates).equal(filterDuplicates);
235
+
236
+ assert.calledOnceWithExactly(scanStartCallback, filterDuplicates);
237
+ assert.notCalled(scanStopCallback);
238
+ });
239
+ });
240
+
241
+ ['starting', 'started'].forEach(state => {
242
+ it(`state ${state}, do enable, same filter`, () => {
243
+ const hci = {
244
+ on: sinon.spy()
245
+ };
246
+ const scanStartCallback = sinon.spy();
247
+ const scanStopCallback = sinon.spy();
248
+
249
+ const enable = true;
250
+ const filterDuplicates = false;
251
+
252
+ const gap = new Gap(hci);
253
+ gap._scanState = state;
254
+ gap._scanFilterDuplicates = filterDuplicates;
255
+ gap.on('scanStart', scanStartCallback);
256
+ gap.on('scanStop', scanStopCallback);
257
+ gap.onLeScanEnableSetCmd(enable, filterDuplicates);
258
+
259
+ should(gap._scanState).equal(state);
260
+ should(gap._scanFilterDuplicates).equal(filterDuplicates);
261
+
262
+ assert.notCalled(scanStartCallback);
263
+ assert.notCalled(scanStopCallback);
264
+ });
265
+ });
266
+
267
+ ['stopping', 'stopped'].forEach(state => {
268
+ it(`state ${state}, do not enable`, () => {
269
+ const hci = {
270
+ on: sinon.spy()
271
+ };
272
+ const scanStartCallback = sinon.spy();
273
+ const scanStopCallback = sinon.spy();
274
+
275
+ const enable = false;
276
+ const filterDuplicates = false;
277
+
278
+ const gap = new Gap(hci);
279
+ gap._scanState = state;
280
+ gap.on('scanStart', scanStartCallback);
281
+ gap.on('scanStop', scanStopCallback);
282
+ gap.onLeScanEnableSetCmd(enable, filterDuplicates);
283
+
284
+ should(gap._scanState).equal(state);
285
+ should(gap._scanFilterDuplicates).equal(null);
286
+
287
+ assert.notCalled(scanStartCallback);
288
+ assert.notCalled(scanStopCallback);
289
+ });
290
+ });
291
+
292
+ ['stopping', 'stopped'].forEach(state => {
293
+ it(`state ${state}, do enable`, () => {
294
+ const hci = {
295
+ on: sinon.spy()
296
+ };
297
+ const scanStartCallback = sinon.spy();
298
+ const scanStopCallback = sinon.spy();
299
+
300
+ const enable = true;
301
+ const filterDuplicates = false;
302
+
303
+ const gap = new Gap(hci);
304
+ gap._scanState = state;
305
+ gap.on('scanStart', scanStartCallback);
306
+ gap.on('scanStop', scanStopCallback);
307
+ gap.onLeScanEnableSetCmd(enable, filterDuplicates);
308
+
309
+ should(gap._scanState).equal(state);
310
+ should(gap._scanFilterDuplicates).equal(null);
311
+
312
+ assert.calledOnceWithExactly(scanStartCallback, null);
313
+ assert.notCalled(scanStopCallback);
314
+ });
315
+ });
316
+ });
317
+
318
+ describe('onHciLeAdvertisingReport', () => {
319
+ it('type === 0x04 / no eir', () => {
320
+ const hci = {
321
+ on: sinon.spy()
322
+ };
323
+
324
+ const status = 'status';
325
+ const type = 0x04;
326
+ const address = 'a:d:d:r:e:s:s';
327
+ const addressType = 'addressType';
328
+ const eir = [];
329
+ const rssi = 'rssi';
330
+
331
+ const discoverCallback = sinon.spy();
332
+
333
+ const gap = new Gap(hci);
334
+ gap.on('discover', discoverCallback);
335
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
336
+
337
+ const expectedDiscovery = {
338
+ address,
339
+ addressType,
340
+ connectable: true,
341
+ advertisement: {
342
+ localName: undefined,
343
+ txPowerLevel: undefined,
344
+ manufacturerData: undefined,
345
+ serviceData: [],
346
+ serviceUuids: [],
347
+ solicitationServiceUuids: []
348
+ },
349
+ rssi,
350
+ count: 1,
351
+ hasScanResponse: true
352
+ };
353
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
354
+
355
+ assert.calledOnceWithExactly(discoverCallback, status, address, addressType, expectedDiscovery.connectable, expectedDiscovery.advertisement, rssi, false);
356
+ });
357
+
358
+ it('type === 0x04 / no eir / previously discovered', () => {
359
+ const hci = {
360
+ on: sinon.spy()
361
+ };
362
+
363
+ const status = 'status';
364
+ const type = 0x04;
365
+ const address = 'a:d:d:r:e:s:s';
366
+ const addressType = 'addressType';
367
+ const eir = [];
368
+ const rssi = 'rssi';
369
+
370
+ const discoverCallback = sinon.spy();
371
+
372
+ const advertisement = {
373
+ localName: 'localName',
374
+ txPowerLevel: 'txPowerLevel',
375
+ manufacturerData: 'manufacturerData',
376
+ serviceData: ['data'],
377
+ serviceUuids: ['uuids'],
378
+ solicitationServiceUuids: ['solicitation']
379
+ };
380
+ const count = 8;
381
+ const hasScanResponse = true;
382
+ const connectable = false;
383
+
384
+ const gap = new Gap(hci);
385
+ gap._discoveries[address] = { advertisement, count, hasScanResponse, connectable };
386
+ gap.on('discover', discoverCallback);
387
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
388
+
389
+ const expectedDiscovery = {
390
+ address,
391
+ addressType,
392
+ connectable,
393
+ advertisement,
394
+ rssi,
395
+ count: count + 1,
396
+ hasScanResponse
397
+ };
398
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
399
+
400
+ assert.calledOnceWithExactly(discoverCallback, status, address, addressType, expectedDiscovery.connectable, expectedDiscovery.advertisement, rssi, false);
401
+ });
402
+
403
+ it('type === 0x06 / scannable', () => {
404
+ const hci = {
405
+ on: sinon.spy()
406
+ };
407
+
408
+ const status = 'status';
409
+ const type = 0x06;
410
+ const address = 'a:d:d:r:e:s:s';
411
+ const addressType = 'addressType';
412
+ const eir = [];
413
+ const rssi = 'rssi';
414
+
415
+ const discoverCallback = sinon.spy();
416
+
417
+ const advertisement = {
418
+ localName: 'localName',
419
+ txPowerLevel: 'txPowerLevel',
420
+ manufacturerData: 'manufacturerData',
421
+ serviceData: ['data'],
422
+ serviceUuids: ['uuids'],
423
+ solicitationServiceUuids: ['solicitation']
424
+ };
425
+ const count = 8;
426
+ const hasScanResponse = false;
427
+ const connectable = false;
428
+
429
+ const gap = new Gap(hci);
430
+ gap._discoveries[address] = { advertisement, count, hasScanResponse, connectable };
431
+ gap.on('discover', discoverCallback);
432
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
433
+
434
+ const expectedDiscovery = {
435
+ address,
436
+ addressType,
437
+ connectable: true,
438
+ advertisement,
439
+ rssi,
440
+ count: count + 1,
441
+ hasScanResponse
442
+ };
443
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
444
+
445
+ assert.calledOnceWithExactly(discoverCallback, status, address, addressType, expectedDiscovery.connectable, expectedDiscovery.advertisement, rssi, true);
446
+ });
447
+
448
+ it('type !== 0x04 / no eir', () => {
449
+ const hci = {
450
+ on: sinon.spy()
451
+ };
452
+
453
+ const status = 'status';
454
+ const type = 0x01;
455
+ const address = 'a:d:d:r:e:s:s';
456
+ const addressType = 'addressType';
457
+ const eir = [];
458
+ const rssi = 'rssi';
459
+
460
+ const discoverCallback = sinon.spy();
461
+
462
+ const gap = new Gap(hci);
463
+ gap.on('discover', discoverCallback);
464
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
465
+
466
+ const expectedDiscovery = {
467
+ address,
468
+ addressType,
469
+ connectable: true,
470
+ advertisement: {
471
+ localName: undefined,
472
+ txPowerLevel: undefined,
473
+ manufacturerData: undefined,
474
+ serviceData: [],
475
+ serviceUuids: [],
476
+ solicitationServiceUuids: [],
477
+ serviceSolicitationUuids: []
478
+ },
479
+ rssi,
480
+ count: 1,
481
+ hasScanResponse: false
482
+ };
483
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
484
+
485
+ assert.notCalled(discoverCallback);
486
+ });
487
+
488
+ it('invalid EIR data, length === 0', () => {
489
+ const hci = {
490
+ on: sinon.spy()
491
+ };
492
+
493
+ const status = 'status';
494
+ const type = 0x01;
495
+ const address = 'a:d:d:r:e:s:s';
496
+ const addressType = 'addressType';
497
+ const eir = Buffer.from([0x00, 0x00]);
498
+ const rssi = 'rssi';
499
+
500
+ const discoverCallback = sinon.spy();
501
+
502
+ const gap = new Gap(hci);
503
+ gap.on('discover', discoverCallback);
504
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
505
+
506
+ const expectedDiscovery = {
507
+ address,
508
+ addressType,
509
+ connectable: true,
510
+ advertisement: {
511
+ localName: undefined,
512
+ txPowerLevel: undefined,
513
+ manufacturerData: undefined,
514
+ serviceData: [],
515
+ serviceUuids: [],
516
+ solicitationServiceUuids: [],
517
+ serviceSolicitationUuids: []
518
+ },
519
+ rssi,
520
+ count: 1,
521
+ hasScanResponse: false
522
+ };
523
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
524
+
525
+ assert.notCalled(discoverCallback);
526
+ });
527
+
528
+ it('invalid EIR data, out of range of buffer length', () => {
529
+ const hci = {
530
+ on: sinon.spy()
531
+ };
532
+
533
+ const status = 'status';
534
+ const type = 0x01;
535
+ const address = 'a:d:d:r:e:s:s';
536
+ const addressType = 'addressType';
537
+ const eir = Buffer.from([0x03, 0x01, 0x02]);
538
+ const rssi = 'rssi';
539
+
540
+ const discoverCallback = sinon.spy();
541
+
542
+ const gap = new Gap(hci);
543
+ gap.on('discover', discoverCallback);
544
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
545
+
546
+ const expectedDiscovery = {
547
+ address,
548
+ addressType,
549
+ connectable: true,
550
+ advertisement: {
551
+ localName: undefined,
552
+ txPowerLevel: undefined,
553
+ manufacturerData: undefined,
554
+ serviceData: [],
555
+ serviceUuids: [],
556
+ solicitationServiceUuids: [],
557
+ serviceSolicitationUuids: []
558
+ },
559
+ rssi,
560
+ count: 1,
561
+ hasScanResponse: false
562
+ };
563
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
564
+
565
+ assert.notCalled(discoverCallback);
566
+ });
567
+
568
+ [0x02, 0x03].forEach(eirType => {
569
+ it(`List of 16-bit Service Class UUIDs (EIR type = ${eirType})`, () => {
570
+ const hci = {
571
+ on: sinon.spy()
572
+ };
573
+
574
+ const status = 'status';
575
+ const type = 0x01;
576
+ const address = 'a:d:d:r:e:s:s';
577
+ const addressType = 'addressType';
578
+ const rssi = 'rssi';
579
+
580
+ const serviceUuid = Buffer.from([0x01, 0x02]);
581
+ const eirLength = (serviceUuid.length * 2) + 1;
582
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
583
+ // Add service twice to check unicity
584
+ const eir = Buffer.concat([eirLengthAndType, serviceUuid, serviceUuid]);
585
+
586
+ const discoverCallback = sinon.spy();
587
+
588
+ const gap = new Gap(hci);
589
+ gap.on('discover', discoverCallback);
590
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
591
+
592
+ const expectedDiscovery = {
593
+ address,
594
+ addressType,
595
+ connectable: true,
596
+ advertisement: {
597
+ localName: undefined,
598
+ txPowerLevel: undefined,
599
+ manufacturerData: undefined,
600
+ serviceData: [],
601
+ serviceUuids: ['201'],
602
+ solicitationServiceUuids: [],
603
+ serviceSolicitationUuids: []
604
+ },
605
+ rssi,
606
+ count: 1,
607
+ hasScanResponse: false
608
+ };
609
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
610
+
611
+ assert.notCalled(discoverCallback);
612
+ });
613
+ });
614
+
615
+ [0x06, 0x07].forEach(eirType => {
616
+ it(`List of 128-bit Service Class UUIDs (EIR type = ${eirType})`, () => {
617
+ const hci = {
618
+ on: sinon.spy()
619
+ };
620
+
621
+ const status = 'status';
622
+ const type = 0x01;
623
+ const address = 'a:d:d:r:e:s:s';
624
+ const addressType = 'addressType';
625
+ const rssi = 'rssi';
626
+
627
+ const serviceUuid = Buffer.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]);
628
+ const eirLength = (serviceUuid.length * 2) + 1;
629
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
630
+ // Add service twice to check unicity
631
+ const eir = Buffer.concat([eirLengthAndType, serviceUuid, serviceUuid]);
632
+
633
+ const discoverCallback = sinon.spy();
634
+
635
+ const gap = new Gap(hci);
636
+ gap.on('discover', discoverCallback);
637
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
638
+
639
+ const expectedDiscovery = {
640
+ address,
641
+ addressType,
642
+ connectable: true,
643
+ advertisement: {
644
+ localName: undefined,
645
+ txPowerLevel: undefined,
646
+ manufacturerData: undefined,
647
+ serviceData: [],
648
+ serviceUuids: ['0f0e0d0c0b0a09080706050403020100'],
649
+ solicitationServiceUuids: [],
650
+ serviceSolicitationUuids: []
651
+ },
652
+ rssi,
653
+ count: 1,
654
+ hasScanResponse: false
655
+ };
656
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
657
+
658
+ assert.notCalled(discoverCallback);
659
+ });
660
+ });
661
+
662
+ [0x08, 0x09].forEach(eirType => {
663
+ it(`Local name (EIR type = ${eirType})`, () => {
664
+ const hci = {
665
+ on: sinon.spy()
666
+ };
667
+
668
+ const status = 'status';
669
+ const type = 0x01;
670
+ const address = 'a:d:d:r:e:s:s';
671
+ const addressType = 'addressType';
672
+ const rssi = 'rssi';
673
+
674
+ const localNameHex = Buffer.from([0x6d, 0x79, 0x2d, 0x6e, 0x61, 0x6d, 0x65]);
675
+ const eirLength = localNameHex.length + 1;
676
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
677
+ const eir = Buffer.concat([eirLengthAndType, localNameHex]);
678
+
679
+ const discoverCallback = sinon.spy();
680
+
681
+ const gap = new Gap(hci);
682
+ gap.on('discover', discoverCallback);
683
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
684
+
685
+ const expectedDiscovery = {
686
+ address,
687
+ addressType,
688
+ connectable: true,
689
+ advertisement: {
690
+ localName: 'my-name',
691
+ txPowerLevel: undefined,
692
+ manufacturerData: undefined,
693
+ serviceData: [],
694
+ serviceUuids: [],
695
+ solicitationServiceUuids: [],
696
+ serviceSolicitationUuids: []
697
+ },
698
+ rssi,
699
+ count: 1,
700
+ hasScanResponse: false
701
+ };
702
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
703
+
704
+ assert.notCalled(discoverCallback);
705
+ });
706
+ });
707
+
708
+ it('txPowerLevel (EIR type = 10)', () => {
709
+ const hci = {
710
+ on: sinon.spy()
711
+ };
712
+
713
+ const status = 'status';
714
+ const type = 0x01;
715
+ const address = 'a:d:d:r:e:s:s';
716
+ const addressType = 'addressType';
717
+ const rssi = 'rssi';
718
+
719
+ const eirType = 0x0a;
720
+ const localNameHex = Buffer.from([0x20]);
721
+ const eirLength = localNameHex.length + 1;
722
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
723
+ const eir = Buffer.concat([eirLengthAndType, localNameHex]);
724
+
725
+ const discoverCallback = sinon.spy();
726
+
727
+ const gap = new Gap(hci);
728
+ gap.on('discover', discoverCallback);
729
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
730
+
731
+ const expectedDiscovery = {
732
+ address,
733
+ addressType,
734
+ connectable: true,
735
+ advertisement: {
736
+ localName: undefined,
737
+ txPowerLevel: 32,
738
+ manufacturerData: undefined,
739
+ serviceData: [],
740
+ serviceUuids: [],
741
+ solicitationServiceUuids: [],
742
+ serviceSolicitationUuids: []
743
+ },
744
+ rssi,
745
+ count: 1,
746
+ hasScanResponse: false
747
+ };
748
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
749
+
750
+ assert.notCalled(discoverCallback);
751
+ });
752
+
753
+ it('List of 16 bit solicitation UUIDs (EIR type = 20)', () => {
754
+ const hci = {
755
+ on: sinon.spy()
756
+ };
757
+
758
+ const status = 'status';
759
+ const type = 0x01;
760
+ const address = 'a:d:d:r:e:s:s';
761
+ const addressType = 'addressType';
762
+ const rssi = 'rssi';
763
+
764
+ const eirType = 0x14;
765
+ const serviceUuid = Buffer.from([0x01, 0x02]);
766
+ const eirLength = (serviceUuid.length * 2) + 1;
767
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
768
+ // Add service twice to check unicity
769
+ const eir = Buffer.concat([eirLengthAndType, serviceUuid, serviceUuid]);
770
+
771
+ const discoverCallback = sinon.spy();
772
+
773
+ const gap = new Gap(hci);
774
+ gap.on('discover', discoverCallback);
775
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
776
+
777
+ const expectedDiscovery = {
778
+ address,
779
+ addressType,
780
+ connectable: true,
781
+ advertisement: {
782
+ localName: undefined,
783
+ txPowerLevel: undefined,
784
+ manufacturerData: undefined,
785
+ serviceData: [],
786
+ serviceUuids: [],
787
+ solicitationServiceUuids: [],
788
+ serviceSolicitationUuids: ['201']
789
+ },
790
+ rssi,
791
+ count: 1,
792
+ hasScanResponse: false
793
+ };
794
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
795
+
796
+ assert.notCalled(discoverCallback);
797
+ });
798
+
799
+ it('List of 128 bit solicitation UUIDs (EIR type = 21)', () => {
800
+ const hci = {
801
+ on: sinon.spy()
802
+ };
803
+
804
+ const status = 'status';
805
+ const type = 0x01;
806
+ const address = 'a:d:d:r:e:s:s';
807
+ const addressType = 'addressType';
808
+ const rssi = 'rssi';
809
+
810
+ const eirType = 0x15;
811
+ const serviceUuid = Buffer.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]);
812
+ const eirLength = (serviceUuid.length * 2) + 1;
813
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
814
+ // Add service twice to check unicity
815
+ const eir = Buffer.concat([eirLengthAndType, serviceUuid, serviceUuid]);
816
+
817
+ const discoverCallback = sinon.spy();
818
+
819
+ const gap = new Gap(hci);
820
+ gap.on('discover', discoverCallback);
821
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
822
+
823
+ const expectedDiscovery = {
824
+ address,
825
+ addressType,
826
+ connectable: true,
827
+ advertisement: {
828
+ localName: undefined,
829
+ txPowerLevel: undefined,
830
+ manufacturerData: undefined,
831
+ serviceData: [],
832
+ serviceUuids: [],
833
+ solicitationServiceUuids: [],
834
+ serviceSolicitationUuids: ['0f0e0d0c0b0a09080706050403020100']
835
+ },
836
+ rssi,
837
+ count: 1,
838
+ hasScanResponse: false
839
+ };
840
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
841
+
842
+ assert.notCalled(discoverCallback);
843
+ });
844
+
845
+ it('16-bit Service Data (EIR type = 22)', () => {
846
+ const hci = {
847
+ on: sinon.spy()
848
+ };
849
+
850
+ const status = 'status';
851
+ const type = 0x01;
852
+ const address = 'a:d:d:r:e:s:s';
853
+ const addressType = 'addressType';
854
+ const rssi = 'rssi';
855
+
856
+ const eirType = 0x16;
857
+ const serviceUuid = Buffer.from([0x01, 0x02]);
858
+ const serviceData = Buffer.from([0x03, 0x04]);
859
+ const eirLength = serviceUuid.length + serviceData.length + 1;
860
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
861
+ const eir = Buffer.concat([eirLengthAndType, serviceUuid, serviceData]);
862
+
863
+ const discoverCallback = sinon.spy();
864
+
865
+ const gap = new Gap(hci);
866
+ gap.on('discover', discoverCallback);
867
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
868
+
869
+ const expectedDiscovery = {
870
+ address,
871
+ addressType,
872
+ connectable: true,
873
+ advertisement: {
874
+ localName: undefined,
875
+ txPowerLevel: undefined,
876
+ manufacturerData: undefined,
877
+ serviceData: [
878
+ {
879
+ uuid: '0201',
880
+ data: serviceData
881
+ }
882
+ ],
883
+ serviceUuids: [],
884
+ solicitationServiceUuids: [],
885
+ serviceSolicitationUuids: []
886
+ },
887
+ rssi,
888
+ count: 1,
889
+ hasScanResponse: false
890
+ };
891
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
892
+
893
+ assert.notCalled(discoverCallback);
894
+ });
895
+
896
+ it('32-bit Service Data (EIR type = 32)', () => {
897
+ const hci = {
898
+ on: sinon.spy()
899
+ };
900
+
901
+ const status = 'status';
902
+ const type = 0x01;
903
+ const address = 'a:d:d:r:e:s:s';
904
+ const addressType = 'addressType';
905
+ const rssi = 'rssi';
906
+
907
+ const eirType = 0x20;
908
+ const serviceUuid = Buffer.from([0x01, 0x02, 0x03, 0x04]);
909
+ const serviceData = Buffer.from([0x05, 0x06]);
910
+ const eirLength = serviceUuid.length + serviceData.length + 1;
911
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
912
+ const eir = Buffer.concat([eirLengthAndType, serviceUuid, serviceData]);
913
+
914
+ const discoverCallback = sinon.spy();
915
+
916
+ const gap = new Gap(hci);
917
+ gap.on('discover', discoverCallback);
918
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
919
+
920
+ const expectedDiscovery = {
921
+ address,
922
+ addressType,
923
+ connectable: true,
924
+ advertisement: {
925
+ localName: undefined,
926
+ txPowerLevel: undefined,
927
+ manufacturerData: undefined,
928
+ serviceData: [
929
+ {
930
+ uuid: '04030201',
931
+ data: serviceData
932
+ }
933
+ ],
934
+ serviceUuids: [],
935
+ solicitationServiceUuids: [],
936
+ serviceSolicitationUuids: []
937
+ },
938
+ rssi,
939
+ count: 1,
940
+ hasScanResponse: false
941
+ };
942
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
943
+
944
+ assert.notCalled(discoverCallback);
945
+ });
946
+
947
+ it('128-bit Service Data (EIR type = 33)', () => {
948
+ const hci = {
949
+ on: sinon.spy()
950
+ };
951
+
952
+ const status = 'status';
953
+ const type = 0x01;
954
+ const address = 'a:d:d:r:e:s:s';
955
+ const addressType = 'addressType';
956
+ const rssi = 'rssi';
957
+
958
+ const eirType = 0x21;
959
+ const serviceUuid = Buffer.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]);
960
+ const serviceData = Buffer.from([0x05, 0x06]);
961
+ const eirLength = serviceUuid.length + serviceData.length + 1;
962
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
963
+ const eir = Buffer.concat([eirLengthAndType, serviceUuid, serviceData]);
964
+
965
+ const discoverCallback = sinon.spy();
966
+
967
+ const gap = new Gap(hci);
968
+ gap.on('discover', discoverCallback);
969
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
970
+
971
+ const expectedDiscovery = {
972
+ address,
973
+ addressType,
974
+ connectable: true,
975
+ advertisement: {
976
+ localName: undefined,
977
+ txPowerLevel: undefined,
978
+ manufacturerData: undefined,
979
+ serviceData: [
980
+ {
981
+ uuid: '0f0e0d0c0b0a09080706050403020100',
982
+ data: serviceData
983
+ }
984
+ ],
985
+ serviceUuids: [],
986
+ solicitationServiceUuids: [],
987
+ serviceSolicitationUuids: []
988
+ },
989
+ rssi,
990
+ count: 1,
991
+ hasScanResponse: false
992
+ };
993
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
994
+
995
+ assert.notCalled(discoverCallback);
996
+ });
997
+
998
+ it('List of 32 bit solicitation UUIDs (EIR type = 31)', () => {
999
+ const hci = {
1000
+ on: sinon.spy()
1001
+ };
1002
+
1003
+ const status = 'status';
1004
+ const type = 0x01;
1005
+ const address = 'a:d:d:r:e:s:s';
1006
+ const addressType = 'addressType';
1007
+ const rssi = 'rssi';
1008
+
1009
+ const eirType = 0x1f;
1010
+ const serviceUuid = Buffer.from([0x01, 0x02, 0x03, 0x04]);
1011
+ const eirLength = (serviceUuid.length * 2) + 1;
1012
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
1013
+ // Add service twice to check unicity
1014
+ const eir = Buffer.concat([eirLengthAndType, serviceUuid, serviceUuid]);
1015
+
1016
+ const discoverCallback = sinon.spy();
1017
+
1018
+ const gap = new Gap(hci);
1019
+ gap.on('discover', discoverCallback);
1020
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
1021
+
1022
+ const expectedDiscovery = {
1023
+ address,
1024
+ addressType,
1025
+ connectable: true,
1026
+ advertisement: {
1027
+ localName: undefined,
1028
+ txPowerLevel: undefined,
1029
+ manufacturerData: undefined,
1030
+ serviceData: [],
1031
+ serviceUuids: [],
1032
+ solicitationServiceUuids: [],
1033
+ serviceSolicitationUuids: ['4030201']
1034
+ },
1035
+ rssi,
1036
+ count: 1,
1037
+ hasScanResponse: false
1038
+ };
1039
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
1040
+
1041
+ assert.notCalled(discoverCallback);
1042
+ });
1043
+
1044
+ it('Manufacturer Specific Data (EIR type = 255)', () => {
1045
+ const hci = {
1046
+ on: sinon.spy()
1047
+ };
1048
+
1049
+ const status = 'status';
1050
+ const type = 0x01;
1051
+ const address = 'a:d:d:r:e:s:s';
1052
+ const addressType = 'addressType';
1053
+ const rssi = 'rssi';
1054
+
1055
+ const eirType = 0xff;
1056
+ const manufacturerData = Buffer.from([0x01, 0x02, 0x03, 0x04]);
1057
+ const eirLength = manufacturerData.length + 1;
1058
+ const eirLengthAndType = Buffer.from([eirLength, eirType]);
1059
+ const eir = Buffer.concat([eirLengthAndType, manufacturerData, manufacturerData]);
1060
+
1061
+ const discoverCallback = sinon.spy();
1062
+
1063
+ const gap = new Gap(hci);
1064
+ gap.on('discover', discoverCallback);
1065
+ gap.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi);
1066
+
1067
+ const expectedDiscovery = {
1068
+ address,
1069
+ addressType,
1070
+ connectable: true,
1071
+ advertisement: {
1072
+ localName: undefined,
1073
+ txPowerLevel: undefined,
1074
+ manufacturerData,
1075
+ serviceData: [],
1076
+ serviceUuids: [],
1077
+ solicitationServiceUuids: [],
1078
+ serviceSolicitationUuids: []
1079
+ },
1080
+ rssi,
1081
+ count: 1,
1082
+ hasScanResponse: false
1083
+ };
1084
+ should(gap._discoveries[address]).deepEqual(expectedDiscovery);
1085
+
1086
+ assert.notCalled(discoverCallback);
1087
+ });
1088
+ });
1089
+ });