Source code

Revision control

Copy as Markdown

Other Tools

'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
* 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:
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'],
})
]);
}