bluetooth-fake-devices.js (47416B)
1 'use strict'; 2 3 /* Bluetooth Constants */ 4 5 /** 6 * HCI Error Codes. 7 * Used for simulateGATT{Dis}ConnectionResponse. For a complete list of 8 * possible error codes see BT 4.2 Vol 2 Part D 1.3 List Of Error Codes. 9 */ 10 const HCI_SUCCESS = 0x0000; 11 const HCI_CONNECTION_TIMEOUT = 0x0008; 12 13 /** 14 * GATT Error codes. 15 * Used for GATT operations responses. BT 4.2 Vol 3 Part F 3.4.1.1 Error 16 * Response 17 */ 18 const GATT_SUCCESS = 0x0000; 19 const GATT_INVALID_HANDLE = 0x0001; 20 21 /* Bluetooth UUID Constants */ 22 23 /* Service UUIDs */ 24 var blocklist_test_service_uuid = '611c954a-263b-4f4a-aab6-01ddb953f985'; 25 var request_disconnection_service_uuid = '01d7d889-7451-419f-aeb8-d65e7b9277af'; 26 27 /* Characteristic UUIDs */ 28 var blocklist_exclude_reads_characteristic_uuid = 29 'bad1c9a2-9a5b-4015-8b60-1579bbbf2135'; 30 var request_disconnection_characteristic_uuid = 31 '01d7d88a-7451-419f-aeb8-d65e7b9277af'; 32 33 /* Descriptor UUIDs */ 34 var blocklist_test_descriptor_uuid = 'bad2ddcf-60db-45cd-bef9-fd72b153cf7c'; 35 var blocklist_exclude_reads_descriptor_uuid = 36 'bad3ec61-3cc3-4954-9702-7977df514114'; 37 38 /** 39 * Helper objects that associate Bluetooth names, aliases, and UUIDs. These are 40 * useful for tests that check that the same result is produces when using all 41 * three methods of referring to a Bluetooth UUID. 42 */ 43 var generic_access = { 44 alias: 0x1800, 45 name: 'generic_access', 46 uuid: '00001800-0000-1000-8000-00805f9b34fb' 47 }; 48 var device_name = { 49 alias: 0x2a00, 50 name: 'gap.device_name', 51 uuid: '00002a00-0000-1000-8000-00805f9b34fb' 52 }; 53 var reconnection_address = { 54 alias: 0x2a03, 55 name: 'gap.reconnection_address', 56 uuid: '00002a03-0000-1000-8000-00805f9b34fb' 57 }; 58 var heart_rate = { 59 alias: 0x180d, 60 name: 'heart_rate', 61 uuid: '0000180d-0000-1000-8000-00805f9b34fb' 62 }; 63 var health_thermometer = { 64 alias: 0x1809, 65 name: 'health_thermometer', 66 uuid: '00001809-0000-1000-8000-00805f9b34fb' 67 }; 68 var body_sensor_location = { 69 alias: 0x2a38, 70 name: 'body_sensor_location', 71 uuid: '00002a38-0000-1000-8000-00805f9b34fb' 72 }; 73 var glucose = { 74 alias: 0x1808, 75 name: 'glucose', 76 uuid: '00001808-0000-1000-8000-00805f9b34fb' 77 }; 78 var battery_service = { 79 alias: 0x180f, 80 name: 'battery_service', 81 uuid: '0000180f-0000-1000-8000-00805f9b34fb' 82 }; 83 var battery_level = { 84 alias: 0x2A19, 85 name: 'battery_level', 86 uuid: '00002a19-0000-1000-8000-00805f9b34fb' 87 }; 88 var user_description = { 89 alias: 0x2901, 90 name: 'gatt.characteristic_user_description', 91 uuid: '00002901-0000-1000-8000-00805f9b34fb' 92 }; 93 var client_characteristic_configuration = { 94 alias: 0x2902, 95 name: 'gatt.client_characteristic_configuration', 96 uuid: '00002902-0000-1000-8000-00805f9b34fb' 97 }; 98 var measurement_interval = { 99 alias: 0x2a21, 100 name: 'measurement_interval', 101 uuid: '00002a21-0000-1000-8000-00805f9b34fb' 102 }; 103 104 /** 105 * An advertisement packet object that simulates a Health Thermometer device. 106 * @type {ScanResult} 107 */ 108 const health_thermometer_ad_packet = { 109 deviceAddress: '09:09:09:09:09:09', 110 rssi: -10, 111 scanRecord: { 112 name: 'Health Thermometer', 113 uuids: [health_thermometer.uuid], 114 }, 115 }; 116 117 /** 118 * An advertisement packet object that simulates a Heart Rate device. 119 * @type {ScanResult} 120 */ 121 const heart_rate_ad_packet = { 122 deviceAddress: '08:08:08:08:08:08', 123 rssi: -10, 124 scanRecord: { 125 name: 'Heart Rate', 126 uuids: [heart_rate.uuid], 127 }, 128 }; 129 130 const uuid1234 = BluetoothUUID.getService(0x1234); 131 const uuid5678 = BluetoothUUID.getService(0x5678); 132 const uuidABCD = BluetoothUUID.getService(0xABCD); 133 const manufacturer1Data = new Uint8Array([1, 2]); 134 const manufacturer2Data = new Uint8Array([3, 4]); 135 const uuid1234Data = new Uint8Array([5, 6]); 136 const uuid5678Data = new Uint8Array([7, 8]); 137 const uuidABCDData = new Uint8Array([9, 10]); 138 139 // TODO(crbug.com/1163207): Add the blocklist link. 140 // Fake manufacturer data following iBeacon format listed in 141 // https://en.wikipedia.org/wiki/IBeacon, which will be blocked according to [TBD blocklist link]. 142 const blocklistedManufacturerId = 0x4c; 143 const blocklistedManufacturerData = new Uint8Array([ 144 0x02, 0x15, 0xb3, 0xeb, 0x8d, 0xb1, 0x30, 0xa5, 0x44, 0x8d, 0xb4, 0xac, 145 0xfb, 0x68, 0xc9, 0x23, 0xa3, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xbf 146 ]); 147 // Fake manufacturer data that is not in [TBD blocklist link]. 148 const nonBlocklistedManufacturerId = 0x0001; 149 const nonBlocklistedManufacturerData = new Uint8Array([1, 2]); 150 151 /** 152 * An advertisement packet object that simulates a device that advertises 153 * service and manufacturer data. 154 * @type {ScanResult} 155 */ 156 const service_and_manufacturer_data_ad_packet = { 157 deviceAddress: '07:07:07:07:07:07', 158 rssi: -10, 159 scanRecord: { 160 name: 'LE Device', 161 uuids: [uuid1234], 162 manufacturerData: {0x0001: manufacturer1Data, 0x0002: manufacturer2Data}, 163 serviceData: { 164 [uuid1234]: uuid1234Data, 165 [uuid5678]: uuid5678Data, 166 [uuidABCD]: uuidABCDData 167 } 168 } 169 }; 170 171 /** 172 * An advertisement packet object that simulates a device that advertises 173 * manufacturer data with special id 0x0000 and 0xffff. 174 * @type {ScanResult} 175 */ 176 const zero_and_ffff_manufacturer_uuid_ad_packet = { 177 deviceAddress: '07:07:07:07:07:07', 178 rssi: -10, 179 scanRecord: { 180 name: 'LE Device', 181 uuids: [uuid1234], 182 manufacturerData: {0x0000: manufacturer1Data, 0xFFFF: manufacturer2Data}, 183 } 184 }; 185 186 /** Bluetooth Helpers */ 187 188 /** 189 * Helper class to create a BluetoothCharacteristicProperties object using an 190 * array of strings corresponding to the property bit to set. 191 */ 192 class TestCharacteristicProperties { 193 /** @param {Array<string>} properties */ 194 constructor(properties) { 195 this.broadcast = false; 196 this.read = false; 197 this.writeWithoutResponse = false; 198 this.write = false; 199 this.notify = false; 200 this.indicate = false; 201 this.authenticatedSignedWrites = false; 202 this.reliableWrite = false; 203 this.writableAuxiliaries = false; 204 205 properties.forEach(val => { 206 if (this.hasOwnProperty(val)) 207 this[val] = true; 208 else 209 throw `Invalid member '${val}'`; 210 }); 211 } 212 } 213 214 /** 215 * Produces an array of BluetoothLEScanFilterInit objects containing the list of 216 * services in |services| and various permutations of the other 217 * BluetoothLEScanFilterInit properties. This method is used to test that the 218 * |services| are valid so the other properties do not matter. 219 * @param {BluetoothServiceUUID} services 220 * @returns {Array<RequestDeviceOptions>} A list of options containing 221 * |services| and various permutations of other options. 222 */ 223 function generateRequestDeviceArgsWithServices(services = ['heart_rate']) { 224 return [ 225 {filters: [{services: services}]}, 226 {filters: [{services: services, name: 'Name'}]}, 227 {filters: [{services: services, namePrefix: 'Pre'}]}, { 228 filters: [ 229 {services: services, manufacturerData: [{companyIdentifier: 0x0001}]} 230 ] 231 }, 232 { 233 filters: [{ 234 services: services, 235 name: 'Name', 236 namePrefix: 'Pre', 237 manufacturerData: [{companyIdentifier: 0x0001}] 238 }] 239 }, 240 {filters: [{services: services}], optionalServices: ['heart_rate']}, { 241 filters: [{services: services, name: 'Name'}], 242 optionalServices: ['heart_rate'] 243 }, 244 { 245 filters: [{services: services, namePrefix: 'Pre'}], 246 optionalServices: ['heart_rate'] 247 }, 248 { 249 filters: [ 250 {services: services, manufacturerData: [{companyIdentifier: 0x0001}]} 251 ], 252 optionalServices: ['heart_rate'] 253 }, 254 { 255 filters: [{ 256 services: services, 257 name: 'Name', 258 namePrefix: 'Pre', 259 manufacturerData: [{companyIdentifier: 0x0001}] 260 }], 261 optionalServices: ['heart_rate'] 262 } 263 ]; 264 } 265 266 /** 267 * Causes |fake_peripheral| to disconnect and returns a promise that resolves 268 * once `gattserverdisconnected` has been fired on |device|. 269 * @param {BluetoothDevice} device The device to check if the 270 * `gattserverdisconnected` promise was fired. 271 * @param {FakePeripheral} fake_peripheral The device fake that represents 272 * |device|. 273 * @returns {Promise<Array<Object>>} A promise that resolves when the device has 274 * successfully disconnected. 275 */ 276 function simulateGATTDisconnectionAndWait(device, fake_peripheral) { 277 return Promise.all([ 278 eventPromise(device, 'gattserverdisconnected'), 279 fake_peripheral.simulateGATTDisconnection(), 280 ]); 281 } 282 283 /** @type {FakeCentral} The fake adapter for the current test. */ 284 let fake_central = null; 285 286 async function initializeFakeCentral({state = 'powered-on'}) { 287 if (!fake_central) { 288 fake_central = await navigator.bluetooth.test.simulateCentral({state}); 289 } 290 } 291 292 /** 293 * A dictionary for specifying fake Bluetooth device setup options. 294 * @typedef {{address: !string, name: !string, 295 * manufacturerData: !Object<uint16,Array<uint8>>, 296 * knownServiceUUIDs: !Array<string>, connectable: !boolean, 297 * serviceDiscoveryComplete: !boolean}} 298 */ 299 let FakeDeviceOptions; 300 301 /** 302 * @typedef {{fakeDeviceOptions: FakeDeviceOptions, 303 * requestDeviceOptions: RequestDeviceOptions}} 304 */ 305 let SetupOptions; 306 307 /** 308 * Default options for setting up a Bluetooth device. 309 * @type {FakeDeviceOptions} 310 */ 311 const fakeDeviceOptionsDefault = { 312 address: '00:00:00:00:00:00', 313 name: 'LE Device', 314 manufacturerData: {}, 315 knownServiceUUIDs: [], 316 connectable: false, 317 serviceDiscoveryComplete: false, 318 }; 319 320 /** 321 * A dictionary containing the fake Bluetooth device object. The dictionary can 322 * optionally contain its fake services and its BluetoothDevice counterpart. 323 * @typedef {{fake_peripheral: !FakePeripheral, 324 * fake_services: Object<string, FakeService>, 325 * device: BluetoothDevice}} 326 */ 327 let FakeDevice; 328 329 /** 330 * Creates a SetupOptions object using |setupOptionsDefault| as the base options 331 * object with the options from |setupOptionsOverride| overriding these 332 * defaults. 333 * @param {SetupOptions} setupOptionsDefault The default options object to use 334 * as the base. 335 * @param {SetupOptions} setupOptionsOverride The options to override the 336 * defaults with. 337 * @returns {SetupOptions} The merged setup options containing the defaults with 338 * the overrides applied. 339 */ 340 function createSetupOptions(setupOptionsDefault, setupOptionsOverride) { 341 // Merge the properties of |setupOptionsDefault| and |setupOptionsOverride| 342 // without modifying |setupOptionsDefault|. 343 let fakeDeviceOptions = Object.assign( 344 {...setupOptionsDefault.fakeDeviceOptions}, 345 setupOptionsOverride.fakeDeviceOptions); 346 let requestDeviceOptions = Object.assign( 347 {...setupOptionsDefault.requestDeviceOptions}, 348 setupOptionsOverride.requestDeviceOptions); 349 350 return {fakeDeviceOptions, requestDeviceOptions}; 351 } 352 353 /** 354 * Adds a preconnected device with the given options. A preconnected device is a 355 * device that has been paired with the system previously. This can be done if, 356 * for example, the user pairs the device using the OS'es settings. 357 * 358 * By default, the preconnected device will be set up using the 359 * |fakeDeviceOptionsDefault| and will not use a RequestDeviceOption object. 360 * This means that the device will not be requested during the setup. 361 * 362 * If |setupOptionsOverride| is provided, these options will override the 363 * defaults. If |setupOptionsOverride| includes the requestDeviceOptions 364 * property, then the device will be requested using those options. 365 * @param {SetupOptions} setupOptionsOverride An object containing options for 366 * setting up a fake Bluetooth device and for requesting the device. 367 * @returns {Promise<FakeDevice>} The device fake initialized with the 368 * parameter values. 369 */ 370 async function setUpPreconnectedFakeDevice(setupOptionsOverride) { 371 await initializeFakeCentral({state: 'powered-on'}); 372 373 let setupOptions = createSetupOptions( 374 {fakeDeviceOptions: fakeDeviceOptionsDefault}, setupOptionsOverride); 375 376 // Simulate the fake peripheral. 377 let preconnectedDevice = {}; 378 preconnectedDevice.fake_peripheral = 379 await fake_central.simulatePreconnectedPeripheral({ 380 address: setupOptions.fakeDeviceOptions.address, 381 name: setupOptions.fakeDeviceOptions.name, 382 manufacturerData: setupOptions.fakeDeviceOptions.manufacturerData, 383 knownServiceUUIDs: setupOptions.fakeDeviceOptions.knownServiceUUIDs, 384 }); 385 386 if (setupOptions.fakeDeviceOptions.connectable) { 387 await preconnectedDevice.fake_peripheral.setNextGATTConnectionResponse( 388 {code: HCI_SUCCESS}); 389 } 390 391 // Add known services. 392 preconnectedDevice.fake_services = new Map(); 393 for (let service of setupOptions.fakeDeviceOptions.knownServiceUUIDs) { 394 let fake_service = await preconnectedDevice.fake_peripheral.addFakeService( 395 {uuid: service}); 396 preconnectedDevice.fake_services.set(service, fake_service); 397 } 398 399 // Request the device if the request option isn't empty. 400 if (Object.keys(setupOptions.requestDeviceOptions).length !== 0) { 401 preconnectedDevice.device = 402 await requestDeviceWithTrustedClick(setupOptions.requestDeviceOptions); 403 } 404 405 // Set up services discovered state. 406 if (setupOptions.fakeDeviceOptions.serviceDiscoveryComplete) { 407 await preconnectedDevice.fake_peripheral.setNextGATTDiscoveryResponse( 408 {code: HCI_SUCCESS}); 409 } 410 411 return preconnectedDevice; 412 } 413 414 /** Blocklisted GATT Device Helper Methods */ 415 416 /** @type {FakeDeviceOptions} */ 417 const blocklistFakeDeviceOptionsDefault = { 418 address: '11:11:11:11:11:11', 419 name: 'Blocklist Device', 420 knownServiceUUIDs: ['generic_access', blocklist_test_service_uuid], 421 connectable: true, 422 serviceDiscoveryComplete: true 423 }; 424 425 /** @type {RequestDeviceOptions} */ 426 const blocklistRequestDeviceOptionsDefault = { 427 filters: [{services: [blocklist_test_service_uuid]}] 428 }; 429 430 /** @type {SetupOptions} */ 431 const blocklistSetupOptionsDefault = { 432 fakeDeviceOptions: blocklistFakeDeviceOptionsDefault, 433 requestDeviceOptions: blocklistRequestDeviceOptionsDefault 434 }; 435 436 /** 437 * Returns an object containing a BluetoothDevice discovered using |options|, 438 * its corresponding FakePeripheral and FakeRemoteGATTServices. 439 * The simulated device is called 'Blocklist Device' and it has one known 440 * service UUID |blocklist_test_service_uuid|. The |blocklist_test_service_uuid| 441 * service contains two characteristics: 442 * - |blocklist_exclude_reads_characteristic_uuid| (read, write) 443 * - 'gap.peripheral_privacy_flag' (read, write) 444 * The 'gap.peripheral_privacy_flag' characteristic contains three descriptors: 445 * - |blocklist_test_descriptor_uuid| 446 * - |blocklist_exclude_reads_descriptor_uuid| 447 * - 'gatt.client_characteristic_configuration' 448 * These are special UUIDs that have been added to the blocklist found at 449 * https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt 450 * There are also test UUIDs that have been added to the test environment which 451 * other implementations should add as test UUIDs as well. 452 * The device has been connected to and its attributes are ready to be 453 * discovered. 454 * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, 455 * fake_blocklist_test_service: FakeRemoteGATTService, 456 * fake_blocklist_exclude_reads_characteristic: 457 * FakeRemoteGATTCharacteristic, 458 * fake_blocklist_exclude_writes_characteristic: 459 * FakeRemoteGATTCharacteristic, 460 * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, 461 * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, 462 * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor}>} An 463 * object containing the BluetoothDevice object and its corresponding 464 * GATT fake objects. 465 */ 466 async function getBlocklistDevice(setupOptionsOverride = {}) { 467 let setupOptions = 468 createSetupOptions(blocklistSetupOptionsDefault, setupOptionsOverride); 469 let fakeDevice = await setUpPreconnectedFakeDevice(setupOptions); 470 await fakeDevice.device.gatt.connect(); 471 472 let fake_blocklist_test_service = 473 fakeDevice.fake_services.get(blocklist_test_service_uuid); 474 475 let fake_blocklist_exclude_reads_characteristic = 476 await fake_blocklist_test_service.addFakeCharacteristic({ 477 uuid: blocklist_exclude_reads_characteristic_uuid, 478 properties: ['read', 'write'], 479 }); 480 let fake_blocklist_exclude_writes_characteristic = 481 await fake_blocklist_test_service.addFakeCharacteristic({ 482 uuid: 'gap.peripheral_privacy_flag', 483 properties: ['read', 'write'], 484 }); 485 486 let fake_blocklist_descriptor = 487 await fake_blocklist_exclude_writes_characteristic.addFakeDescriptor( 488 {uuid: blocklist_test_descriptor_uuid}); 489 let fake_blocklist_exclude_reads_descriptor = 490 await fake_blocklist_exclude_writes_characteristic.addFakeDescriptor( 491 {uuid: blocklist_exclude_reads_descriptor_uuid}); 492 let fake_blocklist_exclude_writes_descriptor = 493 await fake_blocklist_exclude_writes_characteristic.addFakeDescriptor( 494 {uuid: 'gatt.client_characteristic_configuration'}); 495 return { 496 device: fakeDevice.device, 497 fake_peripheral: fakeDevice.fake_peripheral, 498 fake_blocklist_test_service, 499 fake_blocklist_exclude_reads_characteristic, 500 fake_blocklist_exclude_writes_characteristic, 501 fake_blocklist_descriptor, 502 fake_blocklist_exclude_reads_descriptor, 503 fake_blocklist_exclude_writes_descriptor, 504 }; 505 } 506 507 /** 508 * Returns an object containing a Blocklist Test BluetoothRemoteGattService and 509 * its corresponding FakeRemoteGATTService. 510 * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, 511 * fake_blocklist_test_service: FakeRemoteGATTService, 512 * fake_blocklist_exclude_reads_characteristic: 513 * FakeRemoteGATTCharacteristic, 514 * fake_blocklist_exclude_writes_characteristic: 515 * FakeRemoteGATTCharacteristic, 516 * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, 517 * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, 518 * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, 519 * service: BluetoothRemoteGATTService, 520 * fake_service: FakeBluetoothRemoteGATTService}>} An object containing the 521 * BluetoothDevice object and its corresponding GATT fake objects. 522 */ 523 async function getBlocklistTestService() { 524 let result = await getBlocklistDevice(); 525 let service = 526 await result.device.gatt.getPrimaryService(blocklist_test_service_uuid); 527 return Object.assign(result, { 528 service, 529 fake_service: result.fake_blocklist_test_service, 530 }); 531 } 532 533 /** 534 * Returns an object containing a blocklisted BluetoothRemoteGATTCharacteristic 535 * that excludes reads and its corresponding FakeRemoteGATTCharacteristic. 536 * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, 537 * fake_blocklist_test_service: FakeRemoteGATTService, 538 * fake_blocklist_exclude_reads_characteristic: 539 * FakeRemoteGATTCharacteristic, 540 * fake_blocklist_exclude_writes_characteristic: 541 * FakeRemoteGATTCharacteristic, 542 * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, 543 * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, 544 * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, 545 * service: BluetoothRemoteGATTService, 546 * fake_service: FakeBluetoothRemoteGATTService, 547 * characteristic: BluetoothRemoteGATTCharacteristic, 548 * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic}>} An object 549 * containing the BluetoothDevice object and its corresponding GATT fake 550 * objects. 551 */ 552 async function getBlocklistExcludeReadsCharacteristic() { 553 let result = await getBlocklistTestService(); 554 let characteristic = await result.service.getCharacteristic( 555 blocklist_exclude_reads_characteristic_uuid); 556 return Object.assign(result, { 557 characteristic, 558 fake_characteristic: result.fake_blocklist_exclude_reads_characteristic 559 }); 560 } 561 562 /** 563 * Returns an object containing a blocklisted BluetoothRemoteGATTCharacteristic 564 * that excludes writes and its corresponding FakeRemoteGATTCharacteristic. 565 * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, 566 * fake_blocklist_test_service: FakeRemoteGATTService, 567 * fake_blocklist_exclude_reads_characteristic: 568 * FakeRemoteGATTCharacteristic, 569 * fake_blocklist_exclude_writes_characteristic: 570 * FakeRemoteGATTCharacteristic, 571 * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, 572 * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, 573 * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, 574 * service: BluetoothRemoteGATTService, 575 * fake_service: FakeBluetoothRemoteGATTService, 576 * characteristic: BluetoothRemoteGATTCharacteristic, 577 * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic}>} An object 578 * containing the BluetoothDevice object and its corresponding GATT fake 579 * objects. 580 */ 581 async function getBlocklistExcludeWritesCharacteristic() { 582 let result = await getBlocklistTestService(); 583 let characteristic = 584 await result.service.getCharacteristic('gap.peripheral_privacy_flag'); 585 return Object.assign(result, { 586 characteristic, 587 fake_characteristic: result.fake_blocklist_exclude_writes_characteristic 588 }); 589 } 590 591 /** 592 * Returns an object containing a blocklisted BluetoothRemoteGATTDescriptor that 593 * excludes reads and its corresponding FakeRemoteGATTDescriptor. 594 * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, 595 * fake_blocklist_test_service: FakeRemoteGATTService, 596 * fake_blocklist_exclude_reads_characteristic: 597 * FakeRemoteGATTCharacteristic, 598 * fake_blocklist_exclude_writes_characteristic: 599 * FakeRemoteGATTCharacteristic, 600 * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, 601 * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, 602 * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, 603 * service: BluetoothRemoteGATTService, 604 * fake_service: FakeBluetoothRemoteGATTService, 605 * characteristic: BluetoothRemoteGATTCharacteristic, 606 * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic, 607 * descriptor: BluetoothRemoteGATTDescriptor, 608 * fake_descriptor: FakeBluetoothRemoteGATTDescriptor}>} An object 609 * containing the BluetoothDevice object and its corresponding GATT fake 610 * objects. 611 */ 612 async function getBlocklistExcludeReadsDescriptor() { 613 let result = await getBlocklistExcludeWritesCharacteristic(); 614 let descriptor = await result.characteristic.getDescriptor( 615 blocklist_exclude_reads_descriptor_uuid); 616 return Object.assign(result, { 617 descriptor, 618 fake_descriptor: result.fake_blocklist_exclude_reads_descriptor 619 }); 620 } 621 622 /** 623 * Returns an object containing a blocklisted BluetoothRemoteGATTDescriptor that 624 * excludes writes and its corresponding FakeRemoteGATTDescriptor. 625 * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, 626 * fake_blocklist_test_service: FakeRemoteGATTService, 627 * fake_blocklist_exclude_reads_characteristic: 628 * FakeRemoteGATTCharacteristic, 629 * fake_blocklist_exclude_writes_characteristic: 630 * FakeRemoteGATTCharacteristic, 631 * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, 632 * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, 633 * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, 634 * service: BluetoothRemoteGATTService, 635 * fake_service: FakeBluetoothRemoteGATTService, 636 * characteristic: BluetoothRemoteGATTCharacteristic, 637 * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic, 638 * descriptor: BluetoothRemoteGATTDescriptor, 639 * fake_descriptor: FakeBluetoothRemoteGATTDescriptor}>} An object 640 * containing the BluetoothDevice object and its corresponding GATT fake 641 * objects. 642 */ 643 async function getBlocklistExcludeWritesDescriptor() { 644 let result = await getBlocklistExcludeWritesCharacteristic(); 645 let descriptor = await result.characteristic.getDescriptor( 646 'gatt.client_characteristic_configuration'); 647 return Object.assign(result, { 648 descriptor: descriptor, 649 fake_descriptor: result.fake_blocklist_exclude_writes_descriptor, 650 }); 651 } 652 653 /** Bluetooth HID Device Helper Methods */ 654 655 /** @type {FakeDeviceOptions} */ 656 const connectedHIDFakeDeviceOptionsDefault = { 657 address: '10:10:10:10:10:10', 658 name: 'HID Device', 659 knownServiceUUIDs: [ 660 'generic_access', 661 'device_information', 662 'human_interface_device', 663 ], 664 connectable: true, 665 serviceDiscoveryComplete: false 666 }; 667 668 /** @type {RequestDeviceOptions} */ 669 const connectedHIDRequestDeviceOptionsDefault = { 670 filters: [{services: ['device_information']}], 671 optionalServices: ['human_interface_device'] 672 }; 673 674 /** @type {SetupOptions} */ 675 const connectedHIDSetupOptionsDefault = { 676 fakeDeviceOptions: connectedHIDFakeDeviceOptionsDefault, 677 requestDeviceOptions: connectedHIDRequestDeviceOptionsDefault 678 }; 679 680 /** 681 * Similar to getHealthThermometerDevice except the GATT discovery 682 * response has not been set yet so more attributes can still be added. 683 * TODO(crbug.com/719816): Add descriptors. 684 * @param {RequestDeviceOptions} options The options for requesting a Bluetooth 685 * Device. 686 * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object 687 * containing a requested BluetoothDevice and its fake counter part. 688 */ 689 async function getConnectedHIDDevice( 690 requestDeviceOptionsOverride, fakeDeviceOptionsOverride) { 691 let setupOptions = createSetupOptions(connectedHIDSetupOptionsDefault, { 692 fakeDeviceOptions: fakeDeviceOptionsOverride, 693 requestDeviceOptions: requestDeviceOptionsOverride 694 }); 695 696 let fakeDevice = await setUpPreconnectedFakeDevice(setupOptions); 697 await fakeDevice.device.gatt.connect(); 698 699 // Blocklisted Characteristic: 700 // https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt 701 let dev_info = fakeDevice.fake_services.get('device_information'); 702 await dev_info.addFakeCharacteristic({ 703 uuid: 'serial_number_string', 704 properties: ['read'], 705 }); 706 return fakeDevice; 707 } 708 709 /** 710 * Returns a BluetoothDevice discovered using |options| and its 711 * corresponding FakePeripheral. 712 * The simulated device is called 'HID Device' it has three known service 713 * UUIDs: 'generic_access', 'device_information', 'human_interface_device'. 714 * The primary service with 'device_information' UUID has a characteristics 715 * with UUID 'serial_number_string'. The device has been connected to and its 716 * attributes are ready to be discovered. 717 * @param {RequestDeviceOptions} options The options for requesting a Bluetooth 718 * Device. 719 * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object 720 * containing a requested BluetoothDevice and its fake counter part. 721 */ 722 async function getHIDDevice(options) { 723 let result = 724 await getConnectedHIDDevice(options, {serviceDiscoveryComplete: true}); 725 return result; 726 } 727 728 /** Health Thermometer Bluetooth Device Helper Methods */ 729 730 /** @type {FakeDeviceOptions} */ 731 const healthTherometerFakeDeviceOptionsDefault = { 732 address: '09:09:09:09:09:09', 733 name: 'Health Thermometer', 734 manufacturerData: {0x0001: manufacturer1Data, 0x0002: manufacturer2Data}, 735 knownServiceUUIDs: ['generic_access', 'health_thermometer'], 736 }; 737 738 /** 739 * Returns a FakeDevice that corresponds to a simulated pre-connected device 740 * called 'Health Thermometer'. The device has two known serviceUUIDs: 741 * 'generic_access' and 'health_thermometer' and some fake manufacturer data. 742 * @returns {Promise<FakeDevice>} The device fake initialized as a Health 743 * Thermometer device. 744 */ 745 async function setUpHealthThermometerDevice(setupOptionsOverride = {}) { 746 let setupOptions = createSetupOptions( 747 {fakeDeviceOptions: healthTherometerFakeDeviceOptionsDefault}, 748 setupOptionsOverride); 749 return await setUpPreconnectedFakeDevice(setupOptions); 750 } 751 752 /** 753 * Returns the same fake device as setUpHealthThermometerDevice() except 754 * that connecting to the peripheral will succeed. 755 * @returns {Promise<FakeDevice>} The device fake initialized as a 756 * connectable Health Thermometer device. 757 */ 758 async function setUpConnectableHealthThermometerDevice() { 759 let fake_device = await setUpHealthThermometerDevice( 760 {fakeDeviceOptions: {connectable: true}}); 761 return fake_device; 762 } 763 764 /** 765 * Populates a fake_device with various fakes appropriate for a health 766 * thermometer. This resolves to an associative array composed of the fakes, 767 * including the |fake_peripheral|. 768 * @param {FakeDevice} fake_device The Bluetooth fake to populate GATT 769 * services, characteristics, and descriptors on. 770 * @returns {Promise<{fake_peripheral: FakePeripheral, 771 * fake_generic_access: FakeRemoteGATTService, 772 * fake_health_thermometer: FakeRemoteGATTService, 773 * fake_measurement_interval: FakeRemoteGATTCharacteristic, 774 * fake_cccd: FakeRemoteGATTDescriptor, 775 * fake_user_description: FakeRemoteGATTDescriptor, 776 * fake_temperature_measurement: FakeRemoteGATTCharacteristic, 777 * fake_temperature_type: FakeRemoteGATTCharacteristic}>} The FakePeripheral 778 * passed into this method along with the fake GATT services, characteristics, 779 * and descriptors added to it. 780 */ 781 async function populateHealthThermometerFakes(fake_device) { 782 let fake_peripheral = fake_device.fake_peripheral; 783 let fake_generic_access = fake_device.fake_services.get('generic_access'); 784 let fake_health_thermometer = 785 fake_device.fake_services.get('health_thermometer'); 786 let fake_measurement_interval = 787 await fake_health_thermometer.addFakeCharacteristic({ 788 uuid: 'measurement_interval', 789 properties: ['read', 'write', 'indicate'], 790 }); 791 let fake_user_description = 792 await fake_measurement_interval.addFakeDescriptor({ 793 uuid: 'gatt.characteristic_user_description', 794 }); 795 let fake_cccd = await fake_measurement_interval.addFakeDescriptor({ 796 uuid: 'gatt.client_characteristic_configuration', 797 }); 798 let fake_temperature_measurement = 799 await fake_health_thermometer.addFakeCharacteristic({ 800 uuid: 'temperature_measurement', 801 properties: ['indicate'], 802 }); 803 let fake_temperature_type = 804 await fake_health_thermometer.addFakeCharacteristic({ 805 uuid: 'temperature_type', 806 properties: ['read'], 807 }); 808 return { 809 fake_peripheral, 810 fake_generic_access, 811 fake_health_thermometer, 812 fake_measurement_interval, 813 fake_cccd, 814 fake_user_description, 815 fake_temperature_measurement, 816 fake_temperature_type, 817 }; 818 } 819 820 /** 821 * Returns the same device and fake peripheral as getHealthThermometerDevice() 822 * after another frame (an iframe we insert) discovered the device, 823 * connected to it and discovered its services. 824 * @param {RequestDeviceOptions} options The options for requesting a Bluetooth 825 * Device. 826 * @returns {Promise<{device: BluetoothDevice, fakes: { 827 * fake_peripheral: FakePeripheral, 828 * fake_generic_access: FakeRemoteGATTService, 829 * fake_health_thermometer: FakeRemoteGATTService, 830 * fake_measurement_interval: FakeRemoteGATTCharacteristic, 831 * fake_cccd: FakeRemoteGATTDescriptor, 832 * fake_user_description: FakeRemoteGATTDescriptor, 833 * fake_temperature_measurement: FakeRemoteGATTCharacteristic, 834 * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object 835 * containing a requested BluetoothDevice and all of the GATT fake 836 * objects. 837 */ 838 async function getHealthThermometerDeviceWithServicesDiscovered(options) { 839 let iframe = document.createElement('iframe'); 840 let fake_device = await setUpConnectableHealthThermometerDevice(); 841 let fakes = populateHealthThermometerFakes(fake_device); 842 await fake_device.fake_peripheral.setNextGATTDiscoveryResponse({ 843 code: HCI_SUCCESS, 844 }); 845 await new Promise(resolve => { 846 let src = '/bluetooth/resources/health-thermometer-iframe.html'; 847 // TODO(509038): Can be removed once LayoutTests/bluetooth/* that 848 // use health-thermometer-iframe.html have been moved to 849 // LayoutTests/external/wpt/bluetooth/* 850 if (window.location.pathname.includes('/LayoutTests/')) { 851 src = 852 '../../../external/wpt/bluetooth/resources/health-thermometer-iframe.html'; 853 } 854 iframe.src = src; 855 document.body.appendChild(iframe); 856 iframe.addEventListener('load', resolve); 857 }); 858 await new Promise((resolve, reject) => { 859 callWithTrustedClick(() => { 860 iframe.contentWindow.postMessage( 861 {type: 'DiscoverServices', options: options}, '*'); 862 }); 863 864 function messageHandler(messageEvent) { 865 if (messageEvent.data == 'DiscoveryComplete') { 866 window.removeEventListener('message', messageHandler); 867 resolve(); 868 } else { 869 reject(new Error(`Unexpected message: ${messageEvent.data}`)); 870 } 871 } 872 window.addEventListener('message', messageHandler); 873 }); 874 let device = await requestDeviceWithTrustedClick(options); 875 await device.gatt.connect(); 876 return Object.assign({device}, fakes); 877 } 878 879 /** 880 * Returns the device requested and connected in the given iframe context and 881 * fakes from populateHealthThermometerFakes(). 882 * @param {object} iframe The iframe element set up by the caller document. 883 * @returns {Promise<{device: BluetoothDevice, fakes: { 884 * fake_peripheral: FakePeripheral, 885 * fake_generic_access: FakeRemoteGATTService, 886 * fake_health_thermometer: FakeRemoteGATTService, 887 * fake_measurement_interval: FakeRemoteGATTCharacteristic, 888 * fake_cccd: FakeRemoteGATTDescriptor, 889 * fake_user_description: FakeRemoteGATTDescriptor, 890 * fake_temperature_measurement: FakeRemoteGATTCharacteristic, 891 * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object 892 * containing a requested BluetoothDevice and all of the GATT fake 893 * objects. 894 */ 895 async function getHealthThermometerDeviceFromIframe(iframe) { 896 const fake_device = await setUpConnectableHealthThermometerDevice(); 897 const fakes = await populateHealthThermometerFakes(fake_device); 898 await new Promise(resolve => { 899 let src = '/bluetooth/resources/health-thermometer-iframe.html'; 900 iframe.src = src; 901 document.body.appendChild(iframe); 902 iframe.addEventListener('load', resolve, {once: true}); 903 }); 904 await new Promise((resolve, reject) => { 905 callWithTrustedClick(() => { 906 iframe.contentWindow.postMessage( 907 { 908 type: 'RequestAndConnect', 909 options: {filters: [{services: [health_thermometer.name]}]} 910 }, 911 '*'); 912 }); 913 914 function messageHandler(messageEvent) { 915 if (messageEvent.data == 'Connected') { 916 window.removeEventListener('message', messageHandler); 917 resolve(); 918 } else { 919 reject(new Error(`Unexpected message: ${messageEvent.data}`)); 920 } 921 } 922 window.addEventListener('message', messageHandler, {once: true}); 923 }); 924 const devices = await iframe.contentWindow.navigator.bluetooth.getDevices(); 925 assert_equals(devices.length, 1); 926 return Object.assign({device: devices[0]}, {fakes}); 927 } 928 929 /** 930 * Similar to getHealthThermometerDevice() except the device 931 * is not connected and thus its services have not been 932 * discovered. 933 * @param {RequestDeviceOptions} options The options for requesting a Bluetooth 934 * Device. 935 * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object 936 * containing a requested BluetoothDevice and its fake counter part. 937 */ 938 async function getDiscoveredHealthThermometerDevice(options = { 939 filters: [{services: ['health_thermometer']}] 940 }) { 941 return await setUpHealthThermometerDevice({requestDeviceOptions: options}); 942 } 943 944 /** 945 * Similar to getHealthThermometerDevice() except the device has no services, 946 * characteristics, or descriptors. 947 * @param {RequestDeviceOptions} options The options for requesting a Bluetooth 948 * Device. 949 * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object 950 * containing a requested BluetoothDevice and its fake counter part. 951 */ 952 async function getEmptyHealthThermometerDevice(options) { 953 let fake_device = await getDiscoveredHealthThermometerDevice(options); 954 let fake_generic_access = fake_device.fake_services.get('generic_access'); 955 let fake_health_thermometer = 956 fake_device.fake_services.get('health_thermometer'); 957 // Remove services that have been set up by previous steps. 958 await fake_generic_access.remove(); 959 await fake_health_thermometer.remove(); 960 await fake_device.fake_peripheral.setNextGATTConnectionResponse( 961 {code: HCI_SUCCESS}); 962 await fake_device.device.gatt.connect(); 963 await fake_device.fake_peripheral.setNextGATTDiscoveryResponse( 964 {code: HCI_SUCCESS}); 965 return fake_device; 966 } 967 968 /** 969 * Similar to getHealthThermometerService() except the service has no 970 * characteristics or included services. 971 * @param {RequestDeviceOptions} options The options for requesting a Bluetooth 972 * Device. 973 * @returns {service: BluetoothRemoteGATTService, 974 * fake_health_thermometer: FakeRemoteGATTService} An object containing the 975 * health themometer service object and its corresponding fake. 976 */ 977 async function getEmptyHealthThermometerService(options) { 978 let result = await getDiscoveredHealthThermometerDevice(options); 979 await result.fake_peripheral.setNextGATTConnectionResponse( 980 {code: HCI_SUCCESS}); 981 await result.device.gatt.connect(); 982 let fake_health_thermometer = 983 await result.fake_peripheral.addFakeService({uuid: 'health_thermometer'}); 984 await result.fake_peripheral.setNextGATTDiscoveryResponse( 985 {code: HCI_SUCCESS}); 986 let service = 987 await result.device.gatt.getPrimaryService('health_thermometer'); 988 return { 989 service: service, 990 fake_health_thermometer: fake_health_thermometer, 991 }; 992 } 993 994 /** 995 * Similar to getHealthThermometerDevice except the GATT discovery 996 * response has not been set yet so more attributes can still be added. 997 * @param {RequestDeviceOptions} options The options for requesting a Bluetooth 998 * Device. 999 * @returns {Promise<{device: BluetoothDevice, fakes: { 1000 * fake_peripheral: FakePeripheral, 1001 * fake_generic_access: FakeRemoteGATTService, 1002 * fake_health_thermometer: FakeRemoteGATTService, 1003 * fake_measurement_interval: FakeRemoteGATTCharacteristic, 1004 * fake_cccd: FakeRemoteGATTDescriptor, 1005 * fake_user_description: FakeRemoteGATTDescriptor, 1006 * fake_temperature_measurement: FakeRemoteGATTCharacteristic, 1007 * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object 1008 * containing a requested BluetoothDevice and all of the GATT fake 1009 * objects. 1010 */ 1011 async function getConnectedHealthThermometerDevice(options) { 1012 let fake_device = await getDiscoveredHealthThermometerDevice(options); 1013 await fake_device.fake_peripheral.setNextGATTConnectionResponse({ 1014 code: HCI_SUCCESS, 1015 }); 1016 let fakes = await populateHealthThermometerFakes(fake_device); 1017 await fake_device.device.gatt.connect(); 1018 return Object.assign({device: fake_device.device}, fakes); 1019 } 1020 1021 /** 1022 * Returns an object containing a BluetoothDevice discovered using |options|, 1023 * its corresponding FakePeripheral and FakeRemoteGATTServices. 1024 * The simulated device is called 'Health Thermometer' it has two known service 1025 * UUIDs: 'generic_access' and 'health_thermometer' which correspond to two 1026 * services with the same UUIDs. The 'health thermometer' service contains three 1027 * characteristics: 1028 * - 'temperature_measurement' (indicate), 1029 * - 'temperature_type' (read), 1030 * - 'measurement_interval' (read, write, indicate) 1031 * The 'measurement_interval' characteristic contains a 1032 * 'gatt.client_characteristic_configuration' descriptor and a 1033 * 'characteristic_user_description' descriptor. 1034 * The device has been connected to and its attributes are ready to be 1035 * discovered. 1036 * @param {RequestDeviceOptions} options The options for requesting a Bluetooth 1037 * Device. 1038 * @returns {Promise<{device: BluetoothDevice, fakes: { 1039 * fake_peripheral: FakePeripheral, 1040 * fake_generic_access: FakeRemoteGATTService, 1041 * fake_health_thermometer: FakeRemoteGATTService, 1042 * fake_measurement_interval: FakeRemoteGATTCharacteristic, 1043 * fake_cccd: FakeRemoteGATTDescriptor, 1044 * fake_user_description: FakeRemoteGATTDescriptor, 1045 * fake_temperature_measurement: FakeRemoteGATTCharacteristic, 1046 * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object 1047 * containing a requested BluetoothDevice and all of the GATT fake 1048 * objects. 1049 */ 1050 async function getHealthThermometerDevice(options) { 1051 let result = await getConnectedHealthThermometerDevice(options); 1052 await result.fake_peripheral.setNextGATTDiscoveryResponse({ 1053 code: HCI_SUCCESS, 1054 }); 1055 return result; 1056 } 1057 1058 /** 1059 * Similar to getHealthThermometerDevice except that the peripheral has two 1060 * 'health_thermometer' services. 1061 * @param {RequestDeviceOptions} options The options for requesting a Bluetooth 1062 * Device. 1063 * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, 1064 * fake_generic_access: FakeRemoteGATTService, fake_health_thermometer1: 1065 * FakeRemoteGATTService, fake_health_thermometer2: FakeRemoteGATTService}>} An 1066 * object containing a requested Bluetooth device and two fake health 1067 * thermometer GATT services. 1068 */ 1069 async function getTwoHealthThermometerServicesDevice(options) { 1070 let result = await getConnectedHealthThermometerDevice(options); 1071 let fake_health_thermometer2 = 1072 await result.fake_peripheral.addFakeService({uuid: 'health_thermometer'}); 1073 await result.fake_peripheral.setNextGATTDiscoveryResponse( 1074 {code: HCI_SUCCESS}); 1075 return { 1076 device: result.device, 1077 fake_peripheral: result.fake_peripheral, 1078 fake_generic_access: result.fake_generic_access, 1079 fake_health_thermometer1: result.fake_health_thermometer, 1080 fake_health_thermometer2: fake_health_thermometer2 1081 }; 1082 } 1083 1084 /** 1085 * Returns an object containing a Health Thermometer BluetoothRemoteGattService 1086 * and its corresponding FakeRemoteGATTService. 1087 * @returns {Promise<{device: BluetoothDevice, fakes: { 1088 * fake_peripheral: FakePeripheral, 1089 * fake_generic_access: FakeRemoteGATTService, 1090 * fake_health_thermometer: FakeRemoteGATTService, 1091 * fake_measurement_interval: FakeRemoteGATTCharacteristic, 1092 * fake_cccd: FakeRemoteGATTDescriptor, 1093 * fake_user_description: FakeRemoteGATTDescriptor, 1094 * fake_temperature_measurement: FakeRemoteGATTCharacteristic, 1095 * fake_temperature_type: FakeRemoteGATTCharacteristic, 1096 * service: BluetoothRemoteGATTService, 1097 * fake_service: FakeRemoteGATTService}}>} An object 1098 * containing a requested BluetoothDevice and all of the GATT fake 1099 * objects. 1100 */ 1101 async function getHealthThermometerService() { 1102 let result = await getHealthThermometerDevice(); 1103 let service = 1104 await result.device.gatt.getPrimaryService('health_thermometer'); 1105 return Object.assign(result, { 1106 service, 1107 fake_service: result.fake_health_thermometer, 1108 }); 1109 } 1110 1111 /** 1112 * Returns an object containing a Measurement Interval 1113 * BluetoothRemoteGATTCharacteristic and its corresponding 1114 * FakeRemoteGATTCharacteristic. 1115 * @returns {Promise<{device: BluetoothDevice, fakes: { 1116 * fake_peripheral: FakePeripheral, 1117 * fake_generic_access: FakeRemoteGATTService, 1118 * fake_health_thermometer: FakeRemoteGATTService, 1119 * fake_measurement_interval: FakeRemoteGATTCharacteristic, 1120 * fake_cccd: FakeRemoteGATTDescriptor, 1121 * fake_user_description: FakeRemoteGATTDescriptor, 1122 * fake_temperature_measurement: FakeRemoteGATTCharacteristic, 1123 * fake_temperature_type: FakeRemoteGATTCharacteristic, 1124 * service: BluetoothRemoteGATTService, 1125 * fake_service: FakeRemoteGATTService, 1126 * characteristic: BluetoothRemoteGATTCharacteristic, 1127 * fake_characteristic: FakeRemoteGATTCharacteristic}}>} An object 1128 * containing a requested BluetoothDevice and all of the GATT fake 1129 * objects. 1130 */ 1131 async function getMeasurementIntervalCharacteristic() { 1132 let result = await getHealthThermometerService(); 1133 let characteristic = 1134 await result.service.getCharacteristic('measurement_interval'); 1135 return Object.assign(result, { 1136 characteristic, 1137 fake_characteristic: result.fake_measurement_interval, 1138 }); 1139 } 1140 1141 /** 1142 * Returns an object containing a User Description 1143 * BluetoothRemoteGATTDescriptor and its corresponding 1144 * FakeRemoteGATTDescriptor. 1145 * @returns {Promise<{device: BluetoothDevice, fakes: { 1146 * fake_peripheral: FakePeripheral, 1147 * fake_generic_access: FakeRemoteGATTService, 1148 * fake_health_thermometer: FakeRemoteGATTService, 1149 * fake_measurement_interval: FakeRemoteGATTCharacteristic, 1150 * fake_cccd: FakeRemoteGATTDescriptor, 1151 * fake_user_description: FakeRemoteGATTDescriptor, 1152 * fake_temperature_measurement: FakeRemoteGATTCharacteristic, 1153 * fake_temperature_type: FakeRemoteGATTCharacteristic, 1154 * service: BluetoothRemoteGATTService, 1155 * fake_service: FakeRemoteGATTService, 1156 * characteristic: BluetoothRemoteGATTCharacteristic, 1157 * fake_characteristic: FakeRemoteGATTCharacteristic 1158 * descriptor: BluetoothRemoteGATTDescriptor, 1159 * fake_descriptor: FakeRemoteGATTDescriptor}}>} An object 1160 * containing a requested BluetoothDevice and all of the GATT fake 1161 * objects. 1162 */ 1163 async function getUserDescriptionDescriptor() { 1164 let result = await getMeasurementIntervalCharacteristic(); 1165 let descriptor = await result.characteristic.getDescriptor( 1166 'gatt.characteristic_user_description'); 1167 return Object.assign(result, { 1168 descriptor, 1169 fake_descriptor: result.fake_user_description, 1170 }); 1171 } 1172 1173 /** Heart Rate Bluetooth Device Helper Methods */ 1174 1175 /** @type {FakeDeviceOptions} */ 1176 const heartRateFakeDeviceOptionsDefault = { 1177 address: '08:08:08:08:08:08', 1178 name: 'Heart Rate', 1179 knownServiceUUIDs: ['generic_access', 'heart_rate'], 1180 connectable: false, 1181 serviceDiscoveryComplete: false, 1182 }; 1183 1184 /** @type {RequestDeviceOptions} */ 1185 const heartRateRequestDeviceOptionsDefault = { 1186 filters: [{services: ['heart_rate']}] 1187 }; 1188 1189 async function getHeartRateDevice(setupOptionsOverride) { 1190 let setupOptions = createSetupOptions( 1191 {fakeDeviceOptions: heartRateFakeDeviceOptionsDefault}, 1192 setupOptionsOverride); 1193 return await setUpPreconnectedFakeDevice(setupOptions); 1194 } 1195 1196 /** 1197 * Returns an array containing two FakePeripherals corresponding 1198 * to the simulated devices. 1199 * @returns {Promise<Array<FakePeripheral>>} The device fakes initialized as 1200 * Health Thermometer and Heart Rate devices. 1201 */ 1202 async function setUpHealthThermometerAndHeartRateDevices() { 1203 await initializeFakeCentral({state: 'powered-on'}); 1204 return Promise.all([ 1205 fake_central.simulatePreconnectedPeripheral({ 1206 address: '09:09:09:09:09:09', 1207 name: 'Health Thermometer', 1208 manufacturerData: {}, 1209 knownServiceUUIDs: ['generic_access', 'health_thermometer'], 1210 }), 1211 fake_central.simulatePreconnectedPeripheral({ 1212 address: '08:08:08:08:08:08', 1213 name: 'Heart Rate', 1214 manufacturerData: {}, 1215 knownServiceUUIDs: ['generic_access', 'heart_rate'], 1216 }) 1217 ]); 1218 }