summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/bluetooth/resources/bluetooth-fake-devices.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/bluetooth/resources/bluetooth-fake-devices.js')
-rw-r--r--testing/web-platform/tests/bluetooth/resources/bluetooth-fake-devices.js1203
1 files changed, 1203 insertions, 0 deletions
diff --git a/testing/web-platform/tests/bluetooth/resources/bluetooth-fake-devices.js b/testing/web-platform/tests/bluetooth/resources/bluetooth-fake-devices.js
new file mode 100644
index 0000000000..b718ab579a
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/resources/bluetooth-fake-devices.js
@@ -0,0 +1,1203 @@
+'use strict';
+
+/* Bluetooth Constants */
+
+/**
+ * HCI Error Codes.
+ * Used for simulateGATT{Dis}ConnectionResponse. For a complete list of
+ * possible error codes see BT 4.2 Vol 2 Part D 1.3 List Of Error Codes.
+ */
+const HCI_SUCCESS = 0x0000;
+const HCI_CONNECTION_TIMEOUT = 0x0008;
+
+/**
+ * GATT Error codes.
+ * Used for GATT operations responses. BT 4.2 Vol 3 Part F 3.4.1.1 Error
+ * Response
+ */
+const GATT_SUCCESS = 0x0000;
+const GATT_INVALID_HANDLE = 0x0001;
+
+/* Bluetooth UUID Constants */
+
+/* Service UUIDs */
+var blocklist_test_service_uuid = '611c954a-263b-4f4a-aab6-01ddb953f985';
+var request_disconnection_service_uuid = '01d7d889-7451-419f-aeb8-d65e7b9277af';
+
+/* Characteristic UUIDs */
+var blocklist_exclude_reads_characteristic_uuid =
+ 'bad1c9a2-9a5b-4015-8b60-1579bbbf2135';
+var request_disconnection_characteristic_uuid =
+ '01d7d88a-7451-419f-aeb8-d65e7b9277af';
+
+/* Descriptor UUIDs */
+var blocklist_test_descriptor_uuid = 'bad2ddcf-60db-45cd-bef9-fd72b153cf7c';
+var blocklist_exclude_reads_descriptor_uuid =
+ 'bad3ec61-3cc3-4954-9702-7977df514114';
+
+/**
+ * Helper objects that associate Bluetooth names, aliases, and UUIDs. These are
+ * useful for tests that check that the same result is produces when using all
+ * three methods of referring to a Bluetooth UUID.
+ */
+var generic_access = {
+ alias: 0x1800,
+ name: 'generic_access',
+ uuid: '00001800-0000-1000-8000-00805f9b34fb'
+};
+var device_name = {
+ alias: 0x2a00,
+ name: 'gap.device_name',
+ uuid: '00002a00-0000-1000-8000-00805f9b34fb'
+};
+var reconnection_address = {
+ alias: 0x2a03,
+ name: 'gap.reconnection_address',
+ uuid: '00002a03-0000-1000-8000-00805f9b34fb'
+};
+var heart_rate = {
+ alias: 0x180d,
+ name: 'heart_rate',
+ uuid: '0000180d-0000-1000-8000-00805f9b34fb'
+};
+var health_thermometer = {
+ alias: 0x1809,
+ name: 'health_thermometer',
+ uuid: '00001809-0000-1000-8000-00805f9b34fb'
+};
+var body_sensor_location = {
+ alias: 0x2a38,
+ name: 'body_sensor_location',
+ uuid: '00002a38-0000-1000-8000-00805f9b34fb'
+};
+var glucose = {
+ alias: 0x1808,
+ name: 'glucose',
+ uuid: '00001808-0000-1000-8000-00805f9b34fb'
+};
+var battery_service = {
+ alias: 0x180f,
+ name: 'battery_service',
+ uuid: '0000180f-0000-1000-8000-00805f9b34fb'
+};
+var battery_level = {
+ alias: 0x2A19,
+ name: 'battery_level',
+ uuid: '00002a19-0000-1000-8000-00805f9b34fb'
+};
+var user_description = {
+ alias: 0x2901,
+ name: 'gatt.characteristic_user_description',
+ uuid: '00002901-0000-1000-8000-00805f9b34fb'
+};
+var client_characteristic_configuration = {
+ alias: 0x2902,
+ name: 'gatt.client_characteristic_configuration',
+ uuid: '00002902-0000-1000-8000-00805f9b34fb'
+};
+var measurement_interval = {
+ alias: 0x2a21,
+ name: 'measurement_interval',
+ uuid: '00002a21-0000-1000-8000-00805f9b34fb'
+};
+
+/**
+ * An advertisement packet object that simulates a Health Thermometer device.
+ * @type {ScanResult}
+ */
+const health_thermometer_ad_packet = {
+ deviceAddress: '09:09:09:09:09:09',
+ rssi: -10,
+ scanRecord: {
+ name: 'Health Thermometer',
+ uuids: [health_thermometer.uuid],
+ },
+};
+
+/**
+ * An advertisement packet object that simulates a Heart Rate device.
+ * @type {ScanResult}
+ */
+const heart_rate_ad_packet = {
+ deviceAddress: '08:08:08:08:08:08',
+ rssi: -10,
+ scanRecord: {
+ name: 'Heart Rate',
+ uuids: [heart_rate.uuid],
+ },
+};
+
+const uuid1234 = BluetoothUUID.getService(0x1234);
+const uuid5678 = BluetoothUUID.getService(0x5678);
+const uuidABCD = BluetoothUUID.getService(0xABCD);
+const manufacturer1Data = new Uint8Array([1, 2]);
+const manufacturer2Data = new Uint8Array([3, 4]);
+const uuid1234Data = new Uint8Array([5, 6]);
+const uuid5678Data = new Uint8Array([7, 8]);
+const uuidABCDData = new Uint8Array([9, 10]);
+
+// TODO(crbug.com/1163207): Add the blocklist link.
+// Fake manufacturer data following iBeacon format listed in
+// https://en.wikipedia.org/wiki/IBeacon, which will be blocked according to [TBD blocklist link].
+const blocklistedManufacturerId = 0x4c;
+const blocklistedManufacturerData = new Uint8Array([
+ 0x02, 0x15, 0xb3, 0xeb, 0x8d, 0xb1, 0x30, 0xa5, 0x44, 0x8d, 0xb4, 0xac,
+ 0xfb, 0x68, 0xc9, 0x23, 0xa3, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xbf
+]);
+// Fake manufacturer data that is not in [TBD blocklist link].
+const nonBlocklistedManufacturerId = 0x0001;
+const nonBlocklistedManufacturerData = new Uint8Array([1, 2]);
+
+/**
+ * An advertisement packet object that simulates a device that advertises
+ * service and manufacturer data.
+ * @type {ScanResult}
+ */
+const service_and_manufacturer_data_ad_packet = {
+ deviceAddress: '07:07:07:07:07:07',
+ rssi: -10,
+ scanRecord: {
+ name: 'LE Device',
+ uuids: [uuid1234],
+ manufacturerData: {0x0001: manufacturer1Data, 0x0002: manufacturer2Data},
+ serviceData: {
+ [uuid1234]: uuid1234Data,
+ [uuid5678]: uuid5678Data,
+ [uuidABCD]: uuidABCDData
+ }
+ }
+};
+
+/** Bluetooth Helpers */
+
+/**
+ * Helper class to create a BluetoothCharacteristicProperties object using an
+ * array of strings corresponding to the property bit to set.
+ */
+class TestCharacteristicProperties {
+ /** @param {Array<string>} properties */
+ constructor(properties) {
+ this.broadcast = false;
+ this.read = false;
+ this.writeWithoutResponse = false;
+ this.write = false;
+ this.notify = false;
+ this.indicate = false;
+ this.authenticatedSignedWrites = false;
+ this.reliableWrite = false;
+ this.writableAuxiliaries = false;
+
+ properties.forEach(val => {
+ if (this.hasOwnProperty(val))
+ this[val] = true;
+ else
+ throw `Invalid member '${val}'`;
+ });
+ }
+}
+
+/**
+ * Produces an array of BluetoothLEScanFilterInit objects containing the list of
+ * services in |services| and various permutations of the other
+ * BluetoothLEScanFilterInit properties. This method is used to test that the
+ * |services| are valid so the other properties do not matter.
+ * @param {BluetoothServiceUUID} services
+ * @returns {Array<RequestDeviceOptions>} A list of options containing
+ * |services| and various permutations of other options.
+ */
+function generateRequestDeviceArgsWithServices(services = ['heart_rate']) {
+ return [
+ {filters: [{services: services}]},
+ {filters: [{services: services, name: 'Name'}]},
+ {filters: [{services: services, namePrefix: 'Pre'}]}, {
+ filters: [
+ {services: services, manufacturerData: [{companyIdentifier: 0x0001}]}
+ ]
+ },
+ {
+ filters: [{
+ services: services,
+ name: 'Name',
+ namePrefix: 'Pre',
+ manufacturerData: [{companyIdentifier: 0x0001}]
+ }]
+ },
+ {filters: [{services: services}], optionalServices: ['heart_rate']}, {
+ filters: [{services: services, name: 'Name'}],
+ optionalServices: ['heart_rate']
+ },
+ {
+ filters: [{services: services, namePrefix: 'Pre'}],
+ optionalServices: ['heart_rate']
+ },
+ {
+ filters: [
+ {services: services, manufacturerData: [{companyIdentifier: 0x0001}]}
+ ],
+ optionalServices: ['heart_rate']
+ },
+ {
+ filters: [{
+ services: services,
+ name: 'Name',
+ namePrefix: 'Pre',
+ manufacturerData: [{companyIdentifier: 0x0001}]
+ }],
+ optionalServices: ['heart_rate']
+ }
+ ];
+}
+
+/**
+ * Causes |fake_peripheral| to disconnect and returns a promise that resolves
+ * once `gattserverdisconnected` has been fired on |device|.
+ * @param {BluetoothDevice} device The device to check if the
+ * `gattserverdisconnected` promise was fired.
+ * @param {FakePeripheral} fake_peripheral The device fake that represents
+ * |device|.
+ * @returns {Promise<Array<Object>>} A promise that resolves when the device has
+ * successfully disconnected.
+ */
+function simulateGATTDisconnectionAndWait(device, fake_peripheral) {
+ return Promise.all([
+ eventPromise(device, 'gattserverdisconnected'),
+ fake_peripheral.simulateGATTDisconnection(),
+ ]);
+}
+
+/** @type {FakeCentral} The fake adapter for the current test. */
+let fake_central = null;
+
+async function initializeFakeCentral({state = 'powered-on'}) {
+ if (!fake_central) {
+ fake_central = await navigator.bluetooth.test.simulateCentral({state});
+ }
+}
+
+/**
+ * A dictionary for specifying fake Bluetooth device setup options.
+ * @typedef {{address: !string, name: !string,
+ * manufacturerData: !Object<uint16,Array<uint8>>,
+ * knownServiceUUIDs: !Array<string>, connectable: !boolean,
+ * serviceDiscoveryComplete: !boolean}}
+ */
+let FakeDeviceOptions;
+
+/**
+ * @typedef {{fakeDeviceOptions: FakeDeviceOptions,
+ * requestDeviceOptions: RequestDeviceOptions}}
+ */
+let SetupOptions;
+
+/**
+ * Default options for setting up a Bluetooth device.
+ * @type {FakeDeviceOptions}
+ */
+const fakeDeviceOptionsDefault = {
+ address: '00:00:00:00:00:00',
+ name: 'LE Device',
+ manufacturerData: {},
+ knownServiceUUIDs: [],
+ connectable: false,
+ serviceDiscoveryComplete: false,
+};
+
+/**
+ * A dictionary containing the fake Bluetooth device object. The dictionary can
+ * optionally contain its fake services and its BluetoothDevice counterpart.
+ * @typedef {{fake_peripheral: !FakePeripheral,
+ * fake_services: Object<string, FakeService>,
+ * device: BluetoothDevice}}
+ */
+let FakeDevice;
+
+/**
+ * Creates a SetupOptions object using |setupOptionsDefault| as the base options
+ * object with the options from |setupOptionsOverride| overriding these
+ * defaults.
+ * @param {SetupOptions} setupOptionsDefault The default options object to use
+ * as the base.
+ * @param {SetupOptions} setupOptionsOverride The options to override the
+ * defaults with.
+ * @returns {SetupOptions} The merged setup options containing the defaults with
+ * the overrides applied.
+ */
+function createSetupOptions(setupOptionsDefault, setupOptionsOverride) {
+ // Merge the properties of |setupOptionsDefault| and |setupOptionsOverride|
+ // without modifying |setupOptionsDefault|.
+ let fakeDeviceOptions = Object.assign(
+ {...setupOptionsDefault.fakeDeviceOptions},
+ setupOptionsOverride.fakeDeviceOptions);
+ let requestDeviceOptions = Object.assign(
+ {...setupOptionsDefault.requestDeviceOptions},
+ setupOptionsOverride.requestDeviceOptions);
+
+ return {fakeDeviceOptions, requestDeviceOptions};
+}
+
+/**
+ * Adds a preconnected device with the given options. A preconnected device is a
+ * device that has been paired with the system previously. This can be done if,
+ * for example, the user pairs the device using the OS'es settings.
+ *
+ * By default, the preconnected device will be set up using the
+ * |fakeDeviceOptionsDefault| and will not use a RequestDeviceOption object.
+ * This means that the device will not be requested during the setup.
+ *
+ * If |setupOptionsOverride| is provided, these options will override the
+ * defaults. If |setupOptionsOverride| includes the requestDeviceOptions
+ * property, then the device will be requested using those options.
+ * @param {SetupOptions} setupOptionsOverride An object containing options for
+ * setting up a fake Bluetooth device and for requesting the device.
+ * @returns {Promise<FakeDevice>} The device fake initialized with the
+ * parameter values.
+ */
+async function setUpPreconnectedFakeDevice(setupOptionsOverride) {
+ await initializeFakeCentral({state: 'powered-on'});
+
+ let setupOptions = createSetupOptions(
+ {fakeDeviceOptions: fakeDeviceOptionsDefault}, setupOptionsOverride);
+
+ // Simulate the fake peripheral.
+ let preconnectedDevice = {};
+ preconnectedDevice.fake_peripheral =
+ await fake_central.simulatePreconnectedPeripheral({
+ address: setupOptions.fakeDeviceOptions.address,
+ name: setupOptions.fakeDeviceOptions.name,
+ manufacturerData: setupOptions.fakeDeviceOptions.manufacturerData,
+ knownServiceUUIDs: setupOptions.fakeDeviceOptions.knownServiceUUIDs,
+ });
+
+ if (setupOptions.fakeDeviceOptions.connectable) {
+ await preconnectedDevice.fake_peripheral.setNextGATTConnectionResponse(
+ {code: HCI_SUCCESS});
+ }
+
+ // Add known services.
+ preconnectedDevice.fake_services = new Map();
+ for (let service of setupOptions.fakeDeviceOptions.knownServiceUUIDs) {
+ let fake_service = await preconnectedDevice.fake_peripheral.addFakeService(
+ {uuid: service});
+ preconnectedDevice.fake_services.set(service, fake_service);
+ }
+
+ // Request the device if the request option isn't empty.
+ if (Object.keys(setupOptions.requestDeviceOptions).length !== 0) {
+ preconnectedDevice.device =
+ await requestDeviceWithTrustedClick(setupOptions.requestDeviceOptions);
+ }
+
+ // Set up services discovered state.
+ if (setupOptions.fakeDeviceOptions.serviceDiscoveryComplete) {
+ await preconnectedDevice.fake_peripheral.setNextGATTDiscoveryResponse(
+ {code: HCI_SUCCESS});
+ }
+
+ return preconnectedDevice;
+}
+
+/** Blocklisted GATT Device Helper Methods */
+
+/** @type {FakeDeviceOptions} */
+const blocklistFakeDeviceOptionsDefault = {
+ address: '11:11:11:11:11:11',
+ name: 'Blocklist Device',
+ knownServiceUUIDs: ['generic_access', blocklist_test_service_uuid],
+ connectable: true,
+ serviceDiscoveryComplete: true
+};
+
+/** @type {RequestDeviceOptions} */
+const blocklistRequestDeviceOptionsDefault = {
+ filters: [{services: [blocklist_test_service_uuid]}]
+};
+
+/** @type {SetupOptions} */
+const blocklistSetupOptionsDefault = {
+ fakeDeviceOptions: blocklistFakeDeviceOptionsDefault,
+ requestDeviceOptions: blocklistRequestDeviceOptionsDefault
+};
+
+/**
+ * Returns an object containing a BluetoothDevice discovered using |options|,
+ * its corresponding FakePeripheral and FakeRemoteGATTServices.
+ * The simulated device is called 'Blocklist Device' and it has one known
+ * service UUID |blocklist_test_service_uuid|. The |blocklist_test_service_uuid|
+ * service contains two characteristics:
+ * - |blocklist_exclude_reads_characteristic_uuid| (read, write)
+ * - 'gap.peripheral_privacy_flag' (read, write)
+ * The 'gap.peripheral_privacy_flag' characteristic contains three descriptors:
+ * - |blocklist_test_descriptor_uuid|
+ * - |blocklist_exclude_reads_descriptor_uuid|
+ * - 'gatt.client_characteristic_configuration'
+ * These are special UUIDs that have been added to the blocklist found at
+ * https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt
+ * There are also test UUIDs that have been added to the test environment which
+ * other implementations should add as test UUIDs as well.
+ * The device has been connected to and its attributes are ready to be
+ * discovered.
+ * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral,
+ * fake_blocklist_test_service: FakeRemoteGATTService,
+ * fake_blocklist_exclude_reads_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_exclude_writes_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor}>} An
+ * object containing the BluetoothDevice object and its corresponding
+ * GATT fake objects.
+ */
+async function getBlocklistDevice(setupOptionsOverride = {}) {
+ let setupOptions =
+ createSetupOptions(blocklistSetupOptionsDefault, setupOptionsOverride);
+ let fakeDevice = await setUpPreconnectedFakeDevice(setupOptions);
+ await fakeDevice.device.gatt.connect();
+
+ let fake_blocklist_test_service =
+ fakeDevice.fake_services.get(blocklist_test_service_uuid);
+
+ let fake_blocklist_exclude_reads_characteristic =
+ await fake_blocklist_test_service.addFakeCharacteristic({
+ uuid: blocklist_exclude_reads_characteristic_uuid,
+ properties: ['read', 'write'],
+ });
+ let fake_blocklist_exclude_writes_characteristic =
+ await fake_blocklist_test_service.addFakeCharacteristic({
+ uuid: 'gap.peripheral_privacy_flag',
+ properties: ['read', 'write'],
+ });
+
+ let fake_blocklist_descriptor =
+ await fake_blocklist_exclude_writes_characteristic.addFakeDescriptor(
+ {uuid: blocklist_test_descriptor_uuid});
+ let fake_blocklist_exclude_reads_descriptor =
+ await fake_blocklist_exclude_writes_characteristic.addFakeDescriptor(
+ {uuid: blocklist_exclude_reads_descriptor_uuid});
+ let fake_blocklist_exclude_writes_descriptor =
+ await fake_blocklist_exclude_writes_characteristic.addFakeDescriptor(
+ {uuid: 'gatt.client_characteristic_configuration'});
+ return {
+ device: fakeDevice.device,
+ fake_peripheral: fakeDevice.fake_peripheral,
+ fake_blocklist_test_service,
+ fake_blocklist_exclude_reads_characteristic,
+ fake_blocklist_exclude_writes_characteristic,
+ fake_blocklist_descriptor,
+ fake_blocklist_exclude_reads_descriptor,
+ fake_blocklist_exclude_writes_descriptor,
+ };
+}
+
+/**
+ * Returns an object containing a Blocklist Test BluetoothRemoteGattService and
+ * its corresponding FakeRemoteGATTService.
+ * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral,
+ * fake_blocklist_test_service: FakeRemoteGATTService,
+ * fake_blocklist_exclude_reads_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_exclude_writes_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor,
+ * service: BluetoothRemoteGATTService,
+ * fake_service: FakeBluetoothRemoteGATTService}>} An object containing the
+ * BluetoothDevice object and its corresponding GATT fake objects.
+ */
+async function getBlocklistTestService() {
+ let result = await getBlocklistDevice();
+ let service =
+ await result.device.gatt.getPrimaryService(blocklist_test_service_uuid);
+ return Object.assign(result, {
+ service,
+ fake_service: result.fake_blocklist_test_service,
+ });
+}
+
+/**
+ * Returns an object containing a blocklisted BluetoothRemoteGATTCharacteristic
+ * that excludes reads and its corresponding FakeRemoteGATTCharacteristic.
+ * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral,
+ * fake_blocklist_test_service: FakeRemoteGATTService,
+ * fake_blocklist_exclude_reads_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_exclude_writes_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor,
+ * service: BluetoothRemoteGATTService,
+ * fake_service: FakeBluetoothRemoteGATTService,
+ * characteristic: BluetoothRemoteGATTCharacteristic,
+ * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic}>} An object
+ * containing the BluetoothDevice object and its corresponding GATT fake
+ * objects.
+ */
+async function getBlocklistExcludeReadsCharacteristic() {
+ let result = await getBlocklistTestService();
+ let characteristic = await result.service.getCharacteristic(
+ blocklist_exclude_reads_characteristic_uuid);
+ return Object.assign(result, {
+ characteristic,
+ fake_characteristic: result.fake_blocklist_exclude_reads_characteristic
+ });
+}
+
+/**
+ * Returns an object containing a blocklisted BluetoothRemoteGATTCharacteristic
+ * that excludes writes and its corresponding FakeRemoteGATTCharacteristic.
+ * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral,
+ * fake_blocklist_test_service: FakeRemoteGATTService,
+ * fake_blocklist_exclude_reads_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_exclude_writes_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor,
+ * service: BluetoothRemoteGATTService,
+ * fake_service: FakeBluetoothRemoteGATTService,
+ * characteristic: BluetoothRemoteGATTCharacteristic,
+ * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic}>} An object
+ * containing the BluetoothDevice object and its corresponding GATT fake
+ * objects.
+ */
+async function getBlocklistExcludeWritesCharacteristic() {
+ let result = await getBlocklistTestService();
+ let characteristic =
+ await result.service.getCharacteristic('gap.peripheral_privacy_flag');
+ return Object.assign(result, {
+ characteristic,
+ fake_characteristic: result.fake_blocklist_exclude_writes_characteristic
+ });
+}
+
+/**
+ * Returns an object containing a blocklisted BluetoothRemoteGATTDescriptor that
+ * excludes reads and its corresponding FakeRemoteGATTDescriptor.
+ * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral,
+ * fake_blocklist_test_service: FakeRemoteGATTService,
+ * fake_blocklist_exclude_reads_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_exclude_writes_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor,
+ * service: BluetoothRemoteGATTService,
+ * fake_service: FakeBluetoothRemoteGATTService,
+ * characteristic: BluetoothRemoteGATTCharacteristic,
+ * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic,
+ * descriptor: BluetoothRemoteGATTDescriptor,
+ * fake_descriptor: FakeBluetoothRemoteGATTDescriptor}>} An object
+ * containing the BluetoothDevice object and its corresponding GATT fake
+ * objects.
+ */
+async function getBlocklistExcludeReadsDescriptor() {
+ let result = await getBlocklistExcludeWritesCharacteristic();
+ let descriptor = await result.characteristic.getDescriptor(
+ blocklist_exclude_reads_descriptor_uuid);
+ return Object.assign(result, {
+ descriptor,
+ fake_descriptor: result.fake_blocklist_exclude_reads_descriptor
+ });
+}
+
+/**
+ * Returns an object containing a blocklisted BluetoothRemoteGATTDescriptor that
+ * excludes writes and its corresponding FakeRemoteGATTDescriptor.
+ * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral,
+ * fake_blocklist_test_service: FakeRemoteGATTService,
+ * fake_blocklist_exclude_reads_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_exclude_writes_characteristic:
+ * FakeRemoteGATTCharacteristic,
+ * fake_blocklist_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor,
+ * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor,
+ * service: BluetoothRemoteGATTService,
+ * fake_service: FakeBluetoothRemoteGATTService,
+ * characteristic: BluetoothRemoteGATTCharacteristic,
+ * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic,
+ * descriptor: BluetoothRemoteGATTDescriptor,
+ * fake_descriptor: FakeBluetoothRemoteGATTDescriptor}>} An object
+ * containing the BluetoothDevice object and its corresponding GATT fake
+ * objects.
+ */
+async function getBlocklistExcludeWritesDescriptor() {
+ let result = await getBlocklistExcludeWritesCharacteristic();
+ let descriptor = await result.characteristic.getDescriptor(
+ 'gatt.client_characteristic_configuration');
+ return Object.assign(result, {
+ descriptor: descriptor,
+ fake_descriptor: result.fake_blocklist_exclude_writes_descriptor,
+ });
+}
+
+/** Bluetooth HID Device Helper Methods */
+
+/** @type {FakeDeviceOptions} */
+const connectedHIDFakeDeviceOptionsDefault = {
+ address: '10:10:10:10:10:10',
+ name: 'HID Device',
+ knownServiceUUIDs: [
+ 'generic_access',
+ 'device_information',
+ 'human_interface_device',
+ ],
+ connectable: true,
+ serviceDiscoveryComplete: false
+};
+
+/** @type {RequestDeviceOptions} */
+const connectedHIDRequestDeviceOptionsDefault = {
+ filters: [{services: ['device_information']}],
+ optionalServices: ['human_interface_device']
+};
+
+/** @type {SetupOptions} */
+const connectedHIDSetupOptionsDefault = {
+ fakeDeviceOptions: connectedHIDFakeDeviceOptionsDefault,
+ requestDeviceOptions: connectedHIDRequestDeviceOptionsDefault
+};
+
+/**
+ * Similar to getHealthThermometerDevice except the GATT discovery
+ * response has not been set yet so more attributes can still be added.
+ * TODO(crbug.com/719816): Add descriptors.
+ * @param {RequestDeviceOptions} options The options for requesting a Bluetooth
+ * Device.
+ * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object
+ * containing a requested BluetoothDevice and its fake counter part.
+ */
+async function getConnectedHIDDevice(
+ requestDeviceOptionsOverride, fakeDeviceOptionsOverride) {
+ let setupOptions = createSetupOptions(connectedHIDSetupOptionsDefault, {
+ fakeDeviceOptions: fakeDeviceOptionsOverride,
+ requestDeviceOptions: requestDeviceOptionsOverride
+ });
+
+ let fakeDevice = await setUpPreconnectedFakeDevice(setupOptions);
+ await fakeDevice.device.gatt.connect();
+
+ // Blocklisted Characteristic:
+ // https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt
+ let dev_info = fakeDevice.fake_services.get('device_information');
+ await dev_info.addFakeCharacteristic({
+ uuid: 'serial_number_string',
+ properties: ['read'],
+ });
+ return fakeDevice;
+}
+
+/**
+ * Returns a BluetoothDevice discovered using |options| and its
+ * corresponding FakePeripheral.
+ * The simulated device is called 'HID Device' it has three known service
+ * UUIDs: 'generic_access', 'device_information', 'human_interface_device'.
+ * The primary service with 'device_information' UUID has a characteristics
+ * with UUID 'serial_number_string'. The device has been connected to and its
+ * attributes are ready to be discovered.
+ * @param {RequestDeviceOptions} options The options for requesting a Bluetooth
+ * Device.
+ * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object
+ * containing a requested BluetoothDevice and its fake counter part.
+ */
+async function getHIDDevice(options) {
+ let result =
+ await getConnectedHIDDevice(options, {serviceDiscoveryComplete: true});
+ return result;
+}
+
+/** Health Thermometer Bluetooth Device Helper Methods */
+
+/** @type {FakeDeviceOptions} */
+const healthTherometerFakeDeviceOptionsDefault = {
+ address: '09:09:09:09:09:09',
+ name: 'Health Thermometer',
+ manufacturerData: {0x0001: manufacturer1Data, 0x0002: manufacturer2Data},
+ knownServiceUUIDs: ['generic_access', 'health_thermometer'],
+};
+
+/**
+ * Returns a FakeDevice that corresponds to a simulated pre-connected device
+ * called 'Health Thermometer'. The device has two known serviceUUIDs:
+ * 'generic_access' and 'health_thermometer' and some fake manufacturer data.
+ * @returns {Promise<FakeDevice>} The device fake initialized as a Health
+ * Thermometer device.
+ */
+async function setUpHealthThermometerDevice(setupOptionsOverride = {}) {
+ let setupOptions = createSetupOptions(
+ {fakeDeviceOptions: healthTherometerFakeDeviceOptionsDefault},
+ setupOptionsOverride);
+ return await setUpPreconnectedFakeDevice(setupOptions);
+}
+
+/**
+ * Returns the same fake device as setUpHealthThermometerDevice() except
+ * that connecting to the peripheral will succeed.
+ * @returns {Promise<FakeDevice>} The device fake initialized as a
+ * connectable Health Thermometer device.
+ */
+async function setUpConnectableHealthThermometerDevice() {
+ let fake_device = await setUpHealthThermometerDevice(
+ {fakeDeviceOptions: {connectable: true}});
+ return fake_device;
+}
+
+/**
+ * Populates a fake_device with various fakes appropriate for a health
+ * thermometer. This resolves to an associative array composed of the fakes,
+ * including the |fake_peripheral|.
+ * @param {FakeDevice} fake_device The Bluetooth fake to populate GATT
+ * services, characteristics, and descriptors on.
+ * @returns {Promise<{fake_peripheral: FakePeripheral,
+ * fake_generic_access: FakeRemoteGATTService,
+ * fake_health_thermometer: FakeRemoteGATTService,
+ * fake_measurement_interval: FakeRemoteGATTCharacteristic,
+ * fake_cccd: FakeRemoteGATTDescriptor,
+ * fake_user_description: FakeRemoteGATTDescriptor,
+ * fake_temperature_measurement: FakeRemoteGATTCharacteristic,
+ * fake_temperature_type: FakeRemoteGATTCharacteristic}>} The FakePeripheral
+ * passed into this method along with the fake GATT services, characteristics,
+ * and descriptors added to it.
+ */
+async function populateHealthThermometerFakes(fake_device) {
+ let fake_peripheral = fake_device.fake_peripheral;
+ let fake_generic_access = fake_device.fake_services.get('generic_access');
+ let fake_health_thermometer =
+ fake_device.fake_services.get('health_thermometer');
+ let fake_measurement_interval =
+ await fake_health_thermometer.addFakeCharacteristic({
+ uuid: 'measurement_interval',
+ properties: ['read', 'write', 'indicate'],
+ });
+ let fake_user_description =
+ await fake_measurement_interval.addFakeDescriptor({
+ uuid: 'gatt.characteristic_user_description',
+ });
+ let fake_cccd = await fake_measurement_interval.addFakeDescriptor({
+ uuid: 'gatt.client_characteristic_configuration',
+ });
+ let fake_temperature_measurement =
+ await fake_health_thermometer.addFakeCharacteristic({
+ uuid: 'temperature_measurement',
+ properties: ['indicate'],
+ });
+ let fake_temperature_type =
+ await fake_health_thermometer.addFakeCharacteristic({
+ uuid: 'temperature_type',
+ properties: ['read'],
+ });
+ return {
+ fake_peripheral,
+ fake_generic_access,
+ fake_health_thermometer,
+ fake_measurement_interval,
+ fake_cccd,
+ fake_user_description,
+ fake_temperature_measurement,
+ fake_temperature_type,
+ };
+}
+
+/**
+ * Returns the same device and fake peripheral as getHealthThermometerDevice()
+ * after another frame (an iframe we insert) discovered the device,
+ * connected to it and discovered its services.
+ * @param {RequestDeviceOptions} options The options for requesting a Bluetooth
+ * Device.
+ * @returns {Promise<{device: BluetoothDevice, fakes: {
+ * fake_peripheral: FakePeripheral,
+ * fake_generic_access: FakeRemoteGATTService,
+ * fake_health_thermometer: FakeRemoteGATTService,
+ * fake_measurement_interval: FakeRemoteGATTCharacteristic,
+ * fake_cccd: FakeRemoteGATTDescriptor,
+ * fake_user_description: FakeRemoteGATTDescriptor,
+ * fake_temperature_measurement: FakeRemoteGATTCharacteristic,
+ * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object
+ * containing a requested BluetoothDevice and all of the GATT fake
+ * objects.
+ */
+async function getHealthThermometerDeviceWithServicesDiscovered(options) {
+ let iframe = document.createElement('iframe');
+ let fake_device = await setUpConnectableHealthThermometerDevice();
+ let fakes = populateHealthThermometerFakes(fake_device);
+ await fake_device.fake_peripheral.setNextGATTDiscoveryResponse({
+ code: HCI_SUCCESS,
+ });
+ await new Promise(resolve => {
+ let src = '/bluetooth/resources/health-thermometer-iframe.html';
+ // TODO(509038): Can be removed once LayoutTests/bluetooth/* that
+ // use health-thermometer-iframe.html have been moved to
+ // LayoutTests/external/wpt/bluetooth/*
+ if (window.location.pathname.includes('/LayoutTests/')) {
+ src =
+ '../../../external/wpt/bluetooth/resources/health-thermometer-iframe.html';
+ }
+ iframe.src = src;
+ document.body.appendChild(iframe);
+ iframe.addEventListener('load', resolve);
+ });
+ await new Promise((resolve, reject) => {
+ callWithTrustedClick(() => {
+ iframe.contentWindow.postMessage(
+ {type: 'DiscoverServices', options: options}, '*');
+ });
+
+ function messageHandler(messageEvent) {
+ if (messageEvent.data == 'DiscoveryComplete') {
+ window.removeEventListener('message', messageHandler);
+ resolve();
+ } else {
+ reject(new Error(`Unexpected message: ${messageEvent.data}`));
+ }
+ }
+ window.addEventListener('message', messageHandler);
+ });
+ let device = await requestDeviceWithTrustedClick(options);
+ await device.gatt.connect();
+ return Object.assign({device}, fakes);
+}
+
+/**
+ * Returns the device requested and connected in the given iframe context and
+ * fakes from populateHealthThermometerFakes().
+ * @param {object} iframe The iframe element set up by the caller document.
+ * @returns {Promise<{device: BluetoothDevice, fakes: {
+ * fake_peripheral: FakePeripheral,
+ * fake_generic_access: FakeRemoteGATTService,
+ * fake_health_thermometer: FakeRemoteGATTService,
+ * fake_measurement_interval: FakeRemoteGATTCharacteristic,
+ * fake_cccd: FakeRemoteGATTDescriptor,
+ * fake_user_description: FakeRemoteGATTDescriptor,
+ * fake_temperature_measurement: FakeRemoteGATTCharacteristic,
+ * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object
+ * containing a requested BluetoothDevice and all of the GATT fake
+ * objects.
+ */
+async function getHealthThermometerDeviceFromIframe(iframe) {
+ const fake_device = await setUpConnectableHealthThermometerDevice();
+ const fakes = await populateHealthThermometerFakes(fake_device);
+ await new Promise(resolve => {
+ let src = '/bluetooth/resources/health-thermometer-iframe.html';
+ iframe.src = src;
+ document.body.appendChild(iframe);
+ iframe.addEventListener('load', resolve, {once: true});
+ });
+ await new Promise((resolve, reject) => {
+ callWithTrustedClick(() => {
+ iframe.contentWindow.postMessage(
+ {
+ type: 'RequestAndConnect',
+ options: {filters: [{services: [health_thermometer.name]}]}
+ },
+ '*');
+ });
+
+ function messageHandler(messageEvent) {
+ if (messageEvent.data == 'Connected') {
+ window.removeEventListener('message', messageHandler);
+ resolve();
+ } else {
+ reject(new Error(`Unexpected message: ${messageEvent.data}`));
+ }
+ }
+ window.addEventListener('message', messageHandler, {once: true});
+ });
+ const devices = await iframe.contentWindow.navigator.bluetooth.getDevices();
+ assert_equals(devices.length, 1);
+ return Object.assign({device: devices[0]}, {fakes});
+}
+
+/**
+ * Similar to getHealthThermometerDevice() except the device
+ * is not connected and thus its services have not been
+ * discovered.
+ * @param {RequestDeviceOptions} options The options for requesting a Bluetooth
+ * Device.
+ * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object
+ * containing a requested BluetoothDevice and its fake counter part.
+ */
+async function getDiscoveredHealthThermometerDevice(options = {
+ filters: [{services: ['health_thermometer']}]
+}) {
+ return await setUpHealthThermometerDevice({requestDeviceOptions: options});
+}
+
+/**
+ * Similar to getHealthThermometerDevice() except the device has no services,
+ * characteristics, or descriptors.
+ * @param {RequestDeviceOptions} options The options for requesting a Bluetooth
+ * Device.
+ * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object
+ * containing a requested BluetoothDevice and its fake counter part.
+ */
+async function getEmptyHealthThermometerDevice(options) {
+ let fake_device = await getDiscoveredHealthThermometerDevice(options);
+ let fake_generic_access = fake_device.fake_services.get('generic_access');
+ let fake_health_thermometer =
+ fake_device.fake_services.get('health_thermometer');
+ // Remove services that have been set up by previous steps.
+ await fake_generic_access.remove();
+ await fake_health_thermometer.remove();
+ await fake_device.fake_peripheral.setNextGATTConnectionResponse(
+ {code: HCI_SUCCESS});
+ await fake_device.device.gatt.connect();
+ await fake_device.fake_peripheral.setNextGATTDiscoveryResponse(
+ {code: HCI_SUCCESS});
+ return fake_device;
+}
+
+/**
+ * Similar to getHealthThermometerService() except the service has no
+ * characteristics or included services.
+ * @param {RequestDeviceOptions} options The options for requesting a Bluetooth
+ * Device.
+ * @returns {service: BluetoothRemoteGATTService,
+ * fake_health_thermometer: FakeRemoteGATTService} An object containing the
+ * health themometer service object and its corresponding fake.
+ */
+async function getEmptyHealthThermometerService(options) {
+ let result = await getDiscoveredHealthThermometerDevice(options);
+ await result.fake_peripheral.setNextGATTConnectionResponse(
+ {code: HCI_SUCCESS});
+ await result.device.gatt.connect();
+ let fake_health_thermometer =
+ await result.fake_peripheral.addFakeService({uuid: 'health_thermometer'});
+ await result.fake_peripheral.setNextGATTDiscoveryResponse(
+ {code: HCI_SUCCESS});
+ let service =
+ await result.device.gatt.getPrimaryService('health_thermometer');
+ return {
+ service: service,
+ fake_health_thermometer: fake_health_thermometer,
+ };
+}
+
+/**
+ * Similar to getHealthThermometerDevice except the GATT discovery
+ * response has not been set yet so more attributes can still be added.
+ * @param {RequestDeviceOptions} options The options for requesting a Bluetooth
+ * Device.
+ * @returns {Promise<{device: BluetoothDevice, fakes: {
+ * fake_peripheral: FakePeripheral,
+ * fake_generic_access: FakeRemoteGATTService,
+ * fake_health_thermometer: FakeRemoteGATTService,
+ * fake_measurement_interval: FakeRemoteGATTCharacteristic,
+ * fake_cccd: FakeRemoteGATTDescriptor,
+ * fake_user_description: FakeRemoteGATTDescriptor,
+ * fake_temperature_measurement: FakeRemoteGATTCharacteristic,
+ * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object
+ * containing a requested BluetoothDevice and all of the GATT fake
+ * objects.
+ */
+async function getConnectedHealthThermometerDevice(options) {
+ let fake_device = await getDiscoveredHealthThermometerDevice(options);
+ await fake_device.fake_peripheral.setNextGATTConnectionResponse({
+ code: HCI_SUCCESS,
+ });
+ let fakes = await populateHealthThermometerFakes(fake_device);
+ await fake_device.device.gatt.connect();
+ return Object.assign({device: fake_device.device}, fakes);
+}
+
+/**
+ * Returns an object containing a BluetoothDevice discovered using |options|,
+ * its corresponding FakePeripheral and FakeRemoteGATTServices.
+ * The simulated device is called 'Health Thermometer' it has two known service
+ * UUIDs: 'generic_access' and 'health_thermometer' which correspond to two
+ * services with the same UUIDs. The 'health thermometer' service contains three
+ * characteristics:
+ * - 'temperature_measurement' (indicate),
+ * - 'temperature_type' (read),
+ * - 'measurement_interval' (read, write, indicate)
+ * The 'measurement_interval' characteristic contains a
+ * 'gatt.client_characteristic_configuration' descriptor and a
+ * 'characteristic_user_description' descriptor.
+ * The device has been connected to and its attributes are ready to be
+ * discovered.
+ * @param {RequestDeviceOptions} options The options for requesting a Bluetooth
+ * Device.
+ * @returns {Promise<{device: BluetoothDevice, fakes: {
+ * fake_peripheral: FakePeripheral,
+ * fake_generic_access: FakeRemoteGATTService,
+ * fake_health_thermometer: FakeRemoteGATTService,
+ * fake_measurement_interval: FakeRemoteGATTCharacteristic,
+ * fake_cccd: FakeRemoteGATTDescriptor,
+ * fake_user_description: FakeRemoteGATTDescriptor,
+ * fake_temperature_measurement: FakeRemoteGATTCharacteristic,
+ * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object
+ * containing a requested BluetoothDevice and all of the GATT fake
+ * objects.
+ */
+async function getHealthThermometerDevice(options) {
+ let result = await getConnectedHealthThermometerDevice(options);
+ await result.fake_peripheral.setNextGATTDiscoveryResponse({
+ code: HCI_SUCCESS,
+ });
+ return result;
+}
+
+/**
+ * Similar to getHealthThermometerDevice except that the peripheral has two
+ * 'health_thermometer' services.
+ * @param {RequestDeviceOptions} options The options for requesting a Bluetooth
+ * Device.
+ * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral,
+ * fake_generic_access: FakeRemoteGATTService, fake_health_thermometer1:
+ * FakeRemoteGATTService, fake_health_thermometer2: FakeRemoteGATTService}>} An
+ * object containing a requested Bluetooth device and two fake health
+ * thermometer GATT services.
+ */
+async function getTwoHealthThermometerServicesDevice(options) {
+ let result = await getConnectedHealthThermometerDevice(options);
+ let fake_health_thermometer2 =
+ await result.fake_peripheral.addFakeService({uuid: 'health_thermometer'});
+ await result.fake_peripheral.setNextGATTDiscoveryResponse(
+ {code: HCI_SUCCESS});
+ return {
+ device: result.device,
+ fake_peripheral: result.fake_peripheral,
+ fake_generic_access: result.fake_generic_access,
+ fake_health_thermometer1: result.fake_health_thermometer,
+ fake_health_thermometer2: fake_health_thermometer2
+ };
+}
+
+/**
+ * Returns an object containing a Health Thermometer BluetoothRemoteGattService
+ * and its corresponding FakeRemoteGATTService.
+ * @returns {Promise<{device: BluetoothDevice, fakes: {
+ * fake_peripheral: FakePeripheral,
+ * fake_generic_access: FakeRemoteGATTService,
+ * fake_health_thermometer: FakeRemoteGATTService,
+ * fake_measurement_interval: FakeRemoteGATTCharacteristic,
+ * fake_cccd: FakeRemoteGATTDescriptor,
+ * fake_user_description: FakeRemoteGATTDescriptor,
+ * fake_temperature_measurement: FakeRemoteGATTCharacteristic,
+ * fake_temperature_type: FakeRemoteGATTCharacteristic,
+ * service: BluetoothRemoteGATTService,
+ * fake_service: FakeRemoteGATTService}}>} An object
+ * containing a requested BluetoothDevice and all of the GATT fake
+ * objects.
+ */
+async function getHealthThermometerService() {
+ let result = await getHealthThermometerDevice();
+ let service =
+ await result.device.gatt.getPrimaryService('health_thermometer');
+ return Object.assign(result, {
+ service,
+ fake_service: result.fake_health_thermometer,
+ });
+}
+
+/**
+ * Returns an object containing a Measurement Interval
+ * BluetoothRemoteGATTCharacteristic and its corresponding
+ * FakeRemoteGATTCharacteristic.
+ * @returns {Promise<{device: BluetoothDevice, fakes: {
+ * fake_peripheral: FakePeripheral,
+ * fake_generic_access: FakeRemoteGATTService,
+ * fake_health_thermometer: FakeRemoteGATTService,
+ * fake_measurement_interval: FakeRemoteGATTCharacteristic,
+ * fake_cccd: FakeRemoteGATTDescriptor,
+ * fake_user_description: FakeRemoteGATTDescriptor,
+ * fake_temperature_measurement: FakeRemoteGATTCharacteristic,
+ * fake_temperature_type: FakeRemoteGATTCharacteristic,
+ * service: BluetoothRemoteGATTService,
+ * fake_service: FakeRemoteGATTService,
+ * characteristic: BluetoothRemoteGATTCharacteristic,
+ * fake_characteristic: FakeRemoteGATTCharacteristic}}>} An object
+ * containing a requested BluetoothDevice and all of the GATT fake
+ * objects.
+ */
+async function getMeasurementIntervalCharacteristic() {
+ let result = await getHealthThermometerService();
+ let characteristic =
+ await result.service.getCharacteristic('measurement_interval');
+ return Object.assign(result, {
+ characteristic,
+ fake_characteristic: result.fake_measurement_interval,
+ });
+}
+
+/**
+ * Returns an object containing a User Description
+ * BluetoothRemoteGATTDescriptor and its corresponding
+ * FakeRemoteGATTDescriptor.
+ * @returns {Promise<{device: BluetoothDevice, fakes: {
+ * fake_peripheral: FakePeripheral,
+ * fake_generic_access: FakeRemoteGATTService,
+ * fake_health_thermometer: FakeRemoteGATTService,
+ * fake_measurement_interval: FakeRemoteGATTCharacteristic,
+ * fake_cccd: FakeRemoteGATTDescriptor,
+ * fake_user_description: FakeRemoteGATTDescriptor,
+ * fake_temperature_measurement: FakeRemoteGATTCharacteristic,
+ * fake_temperature_type: FakeRemoteGATTCharacteristic,
+ * service: BluetoothRemoteGATTService,
+ * fake_service: FakeRemoteGATTService,
+ * characteristic: BluetoothRemoteGATTCharacteristic,
+ * fake_characteristic: FakeRemoteGATTCharacteristic
+ * descriptor: BluetoothRemoteGATTDescriptor,
+ * fake_descriptor: FakeRemoteGATTDescriptor}}>} An object
+ * containing a requested BluetoothDevice and all of the GATT fake
+ * objects.
+ */
+async function getUserDescriptionDescriptor() {
+ let result = await getMeasurementIntervalCharacteristic();
+ let descriptor = await result.characteristic.getDescriptor(
+ 'gatt.characteristic_user_description');
+ return Object.assign(result, {
+ descriptor,
+ fake_descriptor: result.fake_user_description,
+ });
+}
+
+/** Heart Rate Bluetooth Device Helper Methods */
+
+/** @type {FakeDeviceOptions} */
+const heartRateFakeDeviceOptionsDefault = {
+ address: '08:08:08:08:08:08',
+ name: 'Heart Rate',
+ knownServiceUUIDs: ['generic_access', 'heart_rate'],
+ connectable: false,
+ serviceDiscoveryComplete: false,
+};
+
+/** @type {RequestDeviceOptions} */
+const heartRateRequestDeviceOptionsDefault = {
+ filters: [{services: ['heart_rate']}]
+};
+
+async function getHeartRateDevice(setupOptionsOverride) {
+ let setupOptions = createSetupOptions(
+ {fakeDeviceOptions: heartRateFakeDeviceOptionsDefault},
+ setupOptionsOverride);
+ return await setUpPreconnectedFakeDevice(setupOptions);
+}
+
+/**
+ * Returns an array containing two FakePeripherals corresponding
+ * to the simulated devices.
+ * @returns {Promise<Array<FakePeripheral>>} The device fakes initialized as
+ * Health Thermometer and Heart Rate devices.
+ */
+async function setUpHealthThermometerAndHeartRateDevices() {
+ await initializeFakeCentral({state: 'powered-on'});
+ return Promise.all([
+ fake_central.simulatePreconnectedPeripheral({
+ address: '09:09:09:09:09:09',
+ name: 'Health Thermometer',
+ manufacturerData: {},
+ knownServiceUUIDs: ['generic_access', 'health_thermometer'],
+ }),
+ fake_central.simulatePreconnectedPeripheral({
+ address: '08:08:08:08:08:08',
+ name: 'Heart Rate',
+ manufacturerData: {},
+ knownServiceUUIDs: ['generic_access', 'heart_rate'],
+ })
+ ]);
+}