| 'use strict' |
| |
| // Convert `manufacturerData` to an array of bluetooth.BluetoothManufacturerData |
| // defined in |
| // https://webbluetoothcg.github.io/web-bluetooth/#bluetooth-bidi-definitions. |
| function convertToBidiManufacturerData(manufacturerData) { |
| const bidiManufacturerData = []; |
| for (const key in manufacturerData) { |
| bidiManufacturerData.push({ |
| key: parseInt(key), |
| data: btoa(String.fromCharCode(...manufacturerData[key])) |
| }) |
| } |
| return bidiManufacturerData; |
| } |
| |
| function ArrayToMojoCharacteristicProperties(arr) { |
| const struct = {}; |
| arr.forEach(property => { |
| struct[property] = true; |
| }); |
| return struct; |
| } |
| |
| class FakeBluetooth { |
| constructor() { |
| this.fake_central_ = null; |
| } |
| |
| // Returns a promise that resolves with a FakeCentral that clients can use |
| // to simulate events that a device in the Central/Observer role would |
| // receive as well as monitor the operations performed by the device in the |
| // Central/Observer role. |
| // |
| // A "Central" object would allow its clients to receive advertising events |
| // and initiate connections to peripherals i.e. operations of two roles |
| // defined by the Bluetooth Spec: Observer and Central. |
| // See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an |
| // LE Physical Transport". |
| async simulateCentral({state}) { |
| if (this.fake_central_) { |
| throw 'simulateCentral() should only be called once'; |
| } |
| |
| await test_driver.bidi.bluetooth.simulate_adapter({state: state}); |
| this.fake_central_ = new FakeCentral(); |
| return this.fake_central_; |
| } |
| } |
| |
| // FakeCentral allows clients to simulate events that a device in the |
| // Central/Observer role would receive as well as monitor the operations |
| // performed by the device in the Central/Observer role. |
| class FakeCentral { |
| constructor() { |
| this.peripherals_ = new Map(); |
| } |
| |
| // Simulates a peripheral with |address|, |name|, |manufacturerData| and |
| // |known_service_uuids| that has already been connected to the system. If the |
| // peripheral existed already it updates its name, manufacturer data, and |
| // known UUIDs. |known_service_uuids| should be an array of |
| // BluetoothServiceUUIDs |
| // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid |
| // |
| // Platforms offer methods to retrieve devices that have already been |
| // connected to the system or weren't connected through the UA e.g. a user |
| // connected a peripheral through the system's settings. This method is |
| // intended to simulate peripherals that those methods would return. |
| async simulatePreconnectedPeripheral( |
| {address, name, manufacturerData = {}, knownServiceUUIDs = []}) { |
| await test_driver.bidi.bluetooth.simulate_preconnected_peripheral({ |
| address: address, |
| name: name, |
| manufacturerData: convertToBidiManufacturerData(manufacturerData), |
| knownServiceUuids: |
| knownServiceUUIDs.map(uuid => BluetoothUUID.getService(uuid)) |
| }); |
| |
| return this.fetchOrCreatePeripheral_(address); |
| } |
| |
| // Create a fake_peripheral object from the given address. |
| fetchOrCreatePeripheral_(address) { |
| let peripheral = this.peripherals_.get(address); |
| if (peripheral === undefined) { |
| peripheral = new FakePeripheral(address); |
| this.peripherals_.set(address, peripheral); |
| } |
| return peripheral; |
| } |
| } |
| |
| class FakePeripheral { |
| constructor(address) { |
| this.address = address; |
| } |
| |
| // Adds a fake GATT Service with |uuid| to be discovered when discovering |
| // the peripheral's GATT Attributes. Returns a FakeRemoteGATTService |
| // corresponding to this service. |uuid| should be a BluetoothServiceUUIDs |
| // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid |
| async addFakeService({uuid}) { |
| const service_uuid = BluetoothUUID.getService(uuid); |
| await test_driver.bidi.bluetooth.simulate_service({ |
| address: this.address, |
| uuid: service_uuid, |
| type: 'add', |
| }); |
| return new FakeRemoteGATTService(service_uuid, this.address); |
| } |
| |
| // Sets the next GATT Connection request response to |code|. |code| could be |
| // an HCI Error Code from BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a |
| // number outside that range returned by specific platforms e.g. Android |
| // returns 0x101 to signal a GATT failure |
| // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE |
| async setNextGATTConnectionResponse({code}) { |
| const remove_handler = |
| test_driver.bidi.bluetooth.gatt_connection_attempted.on((event) => { |
| if (event.address != this.address) { |
| return; |
| } |
| remove_handler(); |
| test_driver.bidi.bluetooth.simulate_gatt_connection_response({ |
| address: event.address, |
| code, |
| }); |
| }); |
| } |
| |
| async setNextGATTDiscoveryResponse({code}) { |
| // No-op for Web Bluetooth Bidi test, it will be removed when migration |
| // completes. |
| return Promise.resolve(); |
| } |
| |
| // Simulates a GATT connection response with |code| from the peripheral. |
| async simulateGATTConnectionResponse(code) { |
| await test_driver.bidi.bluetooth.simulate_gatt_connection_response( |
| {address: this.address, code}); |
| } |
| |
| // Simulates a GATT disconnection in the peripheral. |
| async simulateGATTDisconnection() { |
| await test_driver.bidi.bluetooth.simulate_gatt_disconnection( |
| {address: this.address}); |
| } |
| } |
| |
| class FakeRemoteGATTService { |
| constructor(service_uuid, peripheral_address) { |
| this.service_uuid_ = service_uuid; |
| this.peripheral_address_ = peripheral_address; |
| } |
| |
| // Adds a fake GATT Characteristic with |uuid| and |properties| |
| // to this fake service. The characteristic will be found when discovering |
| // the peripheral's GATT Attributes. Returns a FakeRemoteGATTCharacteristic |
| // corresponding to the added characteristic. |
| async addFakeCharacteristic({uuid, properties}) { |
| const characteristic_uuid = BluetoothUUID.getCharacteristic(uuid); |
| await test_driver.bidi.bluetooth.simulate_characteristic({ |
| address: this.peripheral_address_, |
| serviceUuid: this.service_uuid_, |
| characteristicUuid: characteristic_uuid, |
| characteristicProperties: ArrayToMojoCharacteristicProperties(properties), |
| type: 'add' |
| }); |
| return new FakeRemoteGATTCharacteristic( |
| characteristic_uuid, this.service_uuid_, this.peripheral_address_); |
| } |
| |
| // Removes the fake GATT service from its fake peripheral. |
| async remove() { |
| await test_driver.bidi.bluetooth.simulate_service({ |
| address: this.peripheral_address_, |
| uuid: this.service_uuid_, |
| type: 'remove' |
| }); |
| } |
| } |
| |
| class FakeRemoteGATTCharacteristic { |
| constructor(characteristic_uuid, service_uuid, peripheral_address) { |
| this.characteristic_uuid_ = characteristic_uuid; |
| this.service_uuid_ = service_uuid; |
| this.peripheral_address_ = peripheral_address; |
| this.last_written_value_ = {lastValue: null, lastWriteType: 'none'}; |
| } |
| |
| // Adds a fake GATT Descriptor with |uuid| to be discovered when |
| // discovering the peripheral's GATT Attributes. Returns a |
| // FakeRemoteGATTDescriptor corresponding to this descriptor. |uuid| should |
| // be a BluetoothDescriptorUUID |
| // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothdescriptoruuid |
| async addFakeDescriptor({uuid}) { |
| const descriptor_uuid = BluetoothUUID.getDescriptor(uuid); |
| await test_driver.bidi.bluetooth.simulate_descriptor({ |
| address: this.peripheral_address_, |
| serviceUuid: this.service_uuid_, |
| characteristicUuid: this.characteristic_uuid_, |
| descriptorUuid: descriptor_uuid, |
| type: 'add' |
| }); |
| return new FakeRemoteGATTDescriptor( |
| descriptor_uuid, this.characteristic_uuid_, this.service_uuid_, |
| this.peripheral_address_); |
| } |
| |
| // Simulate a characteristic for operation |type| with response |code| and |
| // |data|. |
| async simulateResponse(type, code, data) { |
| await test_driver.bidi.bluetooth.simulate_characteristic_response({ |
| address: this.peripheral_address_, |
| serviceUuid: this.service_uuid_, |
| characteristicUuid: this.characteristic_uuid_, |
| type, |
| code, |
| data, |
| }); |
| } |
| |
| // Simulate a characteristic response for read operation with response |code| |
| // and |data|. |
| async simulateReadResponse(code, data) { |
| await this.simulateResponse('read', code, data); |
| } |
| |
| // Simulate a characteristic response for write operation with response |
| // |code|. |
| async simulateWriteResponse(code) { |
| await this.simulateResponse('write', code); |
| } |
| |
| // Sets the next read response for characteristic to |code| and |value|. |
| // |code| could be a GATT Error Response from |
| // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range |
| // returned by specific platforms e.g. Android returns 0x101 to signal a GATT |
| // failure. |
| // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE |
| async setNextReadResponse(gatt_code, value = null) { |
| if (gatt_code === 0 && value === null) { |
| throw '|value| can\'t be null if read should success.'; |
| } |
| if (gatt_code !== 0 && value !== null) { |
| throw '|value| must be null if read should fail.'; |
| } |
| |
| const remove_handler = |
| test_driver.bidi.bluetooth.characteristic_event_generated.on( |
| (event) => { |
| if (event.address != this.peripheral_address_) { |
| return; |
| } |
| remove_handler(); |
| this.simulateReadResponse(gatt_code, value); |
| }); |
| } |
| |
| // Sets the next write response for this characteristic to |code|. If |
| // writing to a characteristic that only supports 'write-without-response' |
| // the set response will be ignored. |
| // |code| could be a GATT Error Response from |
| // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range |
| // returned by specific platforms e.g. Android returns 0x101 to signal a GATT |
| // failure. |
| async setNextWriteResponse(gatt_code) { |
| const remove_handler = |
| test_driver.bidi.bluetooth.characteristic_event_generated.on( |
| (event) => { |
| if (event.address != this.peripheral_address_) { |
| return; |
| } |
| this.last_written_value_ = { |
| lastValue: event.data, |
| lastWriteType: event.type |
| }; |
| remove_handler(); |
| if (event.type == 'write-with-response') { |
| this.simulateWriteResponse(gatt_code); |
| } |
| }); |
| } |
| |
| // Gets the last successfully written value to the characteristic and its |
| // write type. Write type is one of 'none', 'default-deprecated', |
| // 'with-response', 'without-response'. Returns {lastValue: null, |
| // lastWriteType: 'none'} if no value has yet been written to the |
| // characteristic. |
| async getLastWrittenValue() { |
| return this.last_written_value_; |
| } |
| |
| // Removes the fake GATT Characteristic from its fake service. |
| async remove() { |
| await test_driver.bidi.bluetooth.simulate_characteristic({ |
| address: this.peripheral_address_, |
| serviceUuid: this.service_uuid_, |
| characteristicUuid: this.characteristic_uuid_, |
| characteristicProperties: undefined, |
| type: 'remove' |
| }); |
| } |
| } |
| |
| class FakeRemoteGATTDescriptor { |
| constructor( |
| descriptor_uuid, characteristic_uuid, service_uuid, peripheral_address) { |
| this.descriptor_uuid_ = descriptor_uuid; |
| this.characteristic_uuid_ = characteristic_uuid; |
| this.service_uuid_ = service_uuid; |
| this.peripheral_address_ = peripheral_address; |
| this.last_written_value_ = null; |
| } |
| |
| // Simulate a descriptor for operation |type| with response |code| and |
| // |data|. |
| async simulateResponse(type, code, data) { |
| await test_driver.bidi.bluetooth.simulate_descriptor_response({ |
| address: this.peripheral_address_, |
| serviceUuid: this.service_uuid_, |
| characteristicUuid: this.characteristic_uuid_, |
| descriptorUuid: this.descriptor_uuid_, |
| type, |
| code, |
| data, |
| }); |
| } |
| |
| // Simulate a descriptor response for read operation with response |code| and |
| // |data|. |
| async simulateReadResponse(code, data) { |
| await this.simulateResponse('read', code, data); |
| } |
| |
| // Simulate a descriptor response for write operation with response |code|. |
| async simulateWriteResponse(code) { |
| await this.simulateResponse('write', code); |
| } |
| |
| // Sets the next read response for descriptor to |code| and |value|. |
| // |code| could be a GATT Error Response from |
| // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range |
| // returned by specific platforms e.g. Android returns 0x101 to signal a GATT |
| // failure. |
| // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE |
| async setNextReadResponse(gatt_code, value = null) { |
| if (gatt_code === 0 && value === null) { |
| throw '|value| can\'t be null if read should success.'; |
| } |
| if (gatt_code !== 0 && value !== null) { |
| throw '|value| must be null if read should fail.'; |
| } |
| |
| const remove_handler = |
| test_driver.bidi.bluetooth.descriptor_event_generated.on((event) => { |
| if (event.address != this.peripheral_address_) { |
| return; |
| } |
| remove_handler(); |
| this.simulateReadResponse(gatt_code, value); |
| }); |
| } |
| |
| // Sets the next write response for this descriptor to |code|. |
| // |code| could be a GATT Error Response from |
| // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range |
| // returned by specific platforms e.g. Android returns 0x101 to signal a GATT |
| // failure. |
| async setNextWriteResponse(gatt_code) { |
| const remove_handler = |
| test_driver.bidi.bluetooth.descriptor_event_generated.on((event) => { |
| if (event.address != this.peripheral_address_) { |
| return; |
| } |
| this.last_written_value_ = { |
| lastValue: event.data, |
| lastWriteType: event.type |
| }; |
| remove_handler(); |
| if (event.type == 'write-with-response') { |
| this.simulateWriteResponse(gatt_code); |
| } |
| }); |
| } |
| |
| // Gets the last successfully written value to the descriptor. |
| // Returns null if no value has yet been written to the descriptor. |
| async getLastWrittenValue() { |
| return this.last_written_value_; |
| } |
| |
| // Removes the fake GATT Descriptor from its fake characteristic. |
| async remove() { |
| await test_driver.bidi.bluetooth.simulate_descriptor({ |
| address: this.peripheral_address_, |
| serviceUuid: this.service_uuid_, |
| characteristicUuid: this.characteristic_uuid_, |
| descriptorUuid: this.descriptor_uuid_, |
| type: 'remove' |
| }); |
| } |
| } |
| |
| function initializeBluetoothBidiResources() { |
| navigator.bluetooth.test = new FakeBluetooth(); |
| } |