tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }