tor-browser

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

web-bluetooth-bidi-test.js (14922B)


      1 'use strict'
      2 
      3 // Convert `manufacturerData` to an array of bluetooth.BluetoothManufacturerData
      4 // defined in
      5 // https://webbluetoothcg.github.io/web-bluetooth/#bluetooth-bidi-definitions.
      6 function convertToBidiManufacturerData(manufacturerData) {
      7  const bidiManufacturerData = [];
      8  for (const key in manufacturerData) {
      9    bidiManufacturerData.push({
     10      key: parseInt(key),
     11      data: btoa(String.fromCharCode(...manufacturerData[key]))
     12    })
     13  }
     14  return bidiManufacturerData;
     15 }
     16 
     17 function ArrayToMojoCharacteristicProperties(arr) {
     18  const struct = {};
     19  arr.forEach(property => {
     20    struct[property] = true;
     21  });
     22  return struct;
     23 }
     24 
     25 class FakeBluetooth {
     26  constructor() {
     27    this.fake_central_ = null;
     28  }
     29 
     30  // Returns a promise that resolves with a FakeCentral that clients can use
     31  // to simulate events that a device in the Central/Observer role would
     32  // receive as well as monitor the operations performed by the device in the
     33  // Central/Observer role.
     34  //
     35  // A "Central" object would allow its clients to receive advertising events
     36  // and initiate connections to peripherals i.e. operations of two roles
     37  // defined by the Bluetooth Spec: Observer and Central.
     38  // See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an
     39  // LE Physical Transport".
     40  async simulateCentral({state}) {
     41    if (this.fake_central_) {
     42      throw 'simulateCentral() should only be called once';
     43    }
     44 
     45    await test_driver.bidi.bluetooth.simulate_adapter({state: state});
     46    this.fake_central_ = new FakeCentral();
     47    return this.fake_central_;
     48  }
     49 }
     50 
     51 // FakeCentral allows clients to simulate events that a device in the
     52 // Central/Observer role would receive as well as monitor the operations
     53 // performed by the device in the Central/Observer role.
     54 class FakeCentral {
     55  constructor() {
     56    this.peripherals_ = new Map();
     57  }
     58 
     59  // Simulates a peripheral with |address|, |name|, |manufacturerData| and
     60  // |known_service_uuids| that has already been connected to the system. If the
     61  // peripheral existed already it updates its name, manufacturer data, and
     62  // known UUIDs. |known_service_uuids| should be an array of
     63  // BluetoothServiceUUIDs
     64  // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
     65  //
     66  // Platforms offer methods to retrieve devices that have already been
     67  // connected to the system or weren't connected through the UA e.g. a user
     68  // connected a peripheral through the system's settings. This method is
     69  // intended to simulate peripherals that those methods would return.
     70  async simulatePreconnectedPeripheral(
     71      {address, name, manufacturerData = {}, knownServiceUUIDs = []}) {
     72    await test_driver.bidi.bluetooth.simulate_preconnected_peripheral({
     73      address: address,
     74      name: name,
     75      manufacturerData: convertToBidiManufacturerData(manufacturerData),
     76      knownServiceUuids:
     77          knownServiceUUIDs.map(uuid => BluetoothUUID.getService(uuid))
     78    });
     79 
     80    return this.fetchOrCreatePeripheral_(address);
     81  }
     82 
     83  // Create a fake_peripheral object from the given address.
     84  fetchOrCreatePeripheral_(address) {
     85    let peripheral = this.peripherals_.get(address);
     86    if (peripheral === undefined) {
     87      peripheral = new FakePeripheral(address);
     88      this.peripherals_.set(address, peripheral);
     89    }
     90    return peripheral;
     91  }
     92 }
     93 
     94 class FakePeripheral {
     95  constructor(address) {
     96    this.address = address;
     97  }
     98 
     99  // Adds a fake GATT Service with |uuid| to be discovered when discovering
    100  // the peripheral's GATT Attributes. Returns a FakeRemoteGATTService
    101  // corresponding to this service. |uuid| should be a BluetoothServiceUUIDs
    102  // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
    103  async addFakeService({uuid}) {
    104    const service_uuid = BluetoothUUID.getService(uuid);
    105    await test_driver.bidi.bluetooth.simulate_service({
    106      address: this.address,
    107      uuid: service_uuid,
    108      type: 'add',
    109    });
    110    return new FakeRemoteGATTService(service_uuid, this.address);
    111  }
    112 
    113  // Sets the next GATT Connection request response to |code|. |code| could be
    114  // an HCI Error Code from BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a
    115  // number outside that range returned by specific platforms e.g. Android
    116  // returns 0x101 to signal a GATT failure
    117  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
    118  async setNextGATTConnectionResponse({code}) {
    119    const remove_handler =
    120        test_driver.bidi.bluetooth.gatt_connection_attempted.on((event) => {
    121          if (event.address != this.address) {
    122            return;
    123          }
    124          remove_handler();
    125          test_driver.bidi.bluetooth.simulate_gatt_connection_response({
    126            address: event.address,
    127            code,
    128          });
    129        });
    130  }
    131 
    132  async setNextGATTDiscoveryResponse({code}) {
    133    // No-op for Web Bluetooth Bidi test, it will be removed when migration
    134    // completes.
    135    return Promise.resolve();
    136  }
    137 
    138  // Simulates a GATT connection response with |code| from the peripheral.
    139  async simulateGATTConnectionResponse(code) {
    140    await test_driver.bidi.bluetooth.simulate_gatt_connection_response(
    141        {address: this.address, code});
    142  }
    143 
    144  // Simulates a GATT disconnection in the peripheral.
    145  async simulateGATTDisconnection() {
    146    await test_driver.bidi.bluetooth.simulate_gatt_disconnection(
    147        {address: this.address});
    148  }
    149 }
    150 
    151 class FakeRemoteGATTService {
    152  constructor(service_uuid, peripheral_address) {
    153    this.service_uuid_ = service_uuid;
    154    this.peripheral_address_ = peripheral_address;
    155  }
    156 
    157  // Adds a fake GATT Characteristic with |uuid| and |properties|
    158  // to this fake service. The characteristic will be found when discovering
    159  // the peripheral's GATT Attributes. Returns a FakeRemoteGATTCharacteristic
    160  // corresponding to the added characteristic.
    161  async addFakeCharacteristic({uuid, properties}) {
    162    const characteristic_uuid = BluetoothUUID.getCharacteristic(uuid);
    163    await test_driver.bidi.bluetooth.simulate_characteristic({
    164      address: this.peripheral_address_,
    165      serviceUuid: this.service_uuid_,
    166      characteristicUuid: characteristic_uuid,
    167      characteristicProperties: ArrayToMojoCharacteristicProperties(properties),
    168      type: 'add'
    169    });
    170    return new FakeRemoteGATTCharacteristic(
    171        characteristic_uuid, this.service_uuid_, this.peripheral_address_);
    172  }
    173 
    174  // Removes the fake GATT service from its fake peripheral.
    175  async remove() {
    176    await test_driver.bidi.bluetooth.simulate_service({
    177      address: this.peripheral_address_,
    178      uuid: this.service_uuid_,
    179      type: 'remove'
    180    });
    181  }
    182 }
    183 
    184 class FakeRemoteGATTCharacteristic {
    185  constructor(characteristic_uuid, service_uuid, peripheral_address) {
    186    this.characteristic_uuid_ = characteristic_uuid;
    187    this.service_uuid_ = service_uuid;
    188    this.peripheral_address_ = peripheral_address;
    189    this.last_written_value_ = {lastValue: null, lastWriteType: 'none'};
    190  }
    191 
    192  // Adds a fake GATT Descriptor with |uuid| to be discovered when
    193  // discovering the peripheral's GATT Attributes. Returns a
    194  // FakeRemoteGATTDescriptor corresponding to this descriptor. |uuid| should
    195  // be a BluetoothDescriptorUUID
    196  // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothdescriptoruuid
    197  async addFakeDescriptor({uuid}) {
    198    const descriptor_uuid = BluetoothUUID.getDescriptor(uuid);
    199    await test_driver.bidi.bluetooth.simulate_descriptor({
    200      address: this.peripheral_address_,
    201      serviceUuid: this.service_uuid_,
    202      characteristicUuid: this.characteristic_uuid_,
    203      descriptorUuid: descriptor_uuid,
    204      type: 'add'
    205    });
    206    return new FakeRemoteGATTDescriptor(
    207        descriptor_uuid, this.characteristic_uuid_, this.service_uuid_,
    208        this.peripheral_address_);
    209  }
    210 
    211  // Simulate a characteristic for operation |type| with response |code| and
    212  // |data|.
    213  async simulateResponse(type, code, data) {
    214    await test_driver.bidi.bluetooth.simulate_characteristic_response({
    215      address: this.peripheral_address_,
    216      serviceUuid: this.service_uuid_,
    217      characteristicUuid: this.characteristic_uuid_,
    218      type,
    219      code,
    220      data,
    221    });
    222  }
    223 
    224  // Simulate a characteristic response for read operation with response |code|
    225  // and |data|.
    226  async simulateReadResponse(code, data) {
    227    await this.simulateResponse('read', code, data);
    228  }
    229 
    230  // Simulate a characteristic response for write operation with response
    231  // |code|.
    232  async simulateWriteResponse(code) {
    233    await this.simulateResponse('write', code);
    234  }
    235 
    236  // Sets the next read response for characteristic to |code| and |value|.
    237  // |code| could be a GATT Error Response from
    238  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
    239  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
    240  // failure.
    241  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
    242  async setNextReadResponse(gatt_code, value = null) {
    243    if (gatt_code === 0 && value === null) {
    244      throw '|value| can\'t be null if read should success.';
    245    }
    246    if (gatt_code !== 0 && value !== null) {
    247      throw '|value| must be null if read should fail.';
    248    }
    249 
    250    const remove_handler =
    251        test_driver.bidi.bluetooth.characteristic_event_generated.on(
    252            (event) => {
    253              if (event.address != this.peripheral_address_) {
    254                return;
    255              }
    256              remove_handler();
    257              this.simulateReadResponse(gatt_code, value);
    258            });
    259  }
    260 
    261  // Sets the next write response for this characteristic to |code|. If
    262  // writing to a characteristic that only supports 'write-without-response'
    263  // the set response will be ignored.
    264  // |code| could be a GATT Error Response from
    265  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
    266  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
    267  // failure.
    268  async setNextWriteResponse(gatt_code) {
    269    const remove_handler =
    270        test_driver.bidi.bluetooth.characteristic_event_generated.on(
    271            (event) => {
    272              if (event.address != this.peripheral_address_) {
    273                return;
    274              }
    275              this.last_written_value_ = {
    276                lastValue: event.data,
    277                lastWriteType: event.type
    278              };
    279              remove_handler();
    280              if (event.type == 'write-with-response') {
    281                this.simulateWriteResponse(gatt_code);
    282              }
    283            });
    284  }
    285 
    286  // Gets the last successfully written value to the characteristic and its
    287  // write type. Write type is one of 'none', 'default-deprecated',
    288  // 'with-response', 'without-response'. Returns {lastValue: null,
    289  // lastWriteType: 'none'} if no value has yet been written to the
    290  // characteristic.
    291  async getLastWrittenValue() {
    292    return this.last_written_value_;
    293  }
    294 
    295  // Removes the fake GATT Characteristic from its fake service.
    296  async remove() {
    297    await test_driver.bidi.bluetooth.simulate_characteristic({
    298      address: this.peripheral_address_,
    299      serviceUuid: this.service_uuid_,
    300      characteristicUuid: this.characteristic_uuid_,
    301      characteristicProperties: undefined,
    302      type: 'remove'
    303    });
    304  }
    305 }
    306 
    307 class FakeRemoteGATTDescriptor {
    308  constructor(
    309      descriptor_uuid, characteristic_uuid, service_uuid, peripheral_address) {
    310    this.descriptor_uuid_ = descriptor_uuid;
    311    this.characteristic_uuid_ = characteristic_uuid;
    312    this.service_uuid_ = service_uuid;
    313    this.peripheral_address_ = peripheral_address;
    314    this.last_written_value_ = null;
    315  }
    316 
    317  // Simulate a descriptor for operation |type| with response |code| and
    318  // |data|.
    319  async simulateResponse(type, code, data) {
    320    await test_driver.bidi.bluetooth.simulate_descriptor_response({
    321      address: this.peripheral_address_,
    322      serviceUuid: this.service_uuid_,
    323      characteristicUuid: this.characteristic_uuid_,
    324      descriptorUuid: this.descriptor_uuid_,
    325      type,
    326      code,
    327      data,
    328    });
    329  }
    330 
    331  // Simulate a descriptor response for read operation with response |code| and
    332  // |data|.
    333  async simulateReadResponse(code, data) {
    334    await this.simulateResponse('read', code, data);
    335  }
    336 
    337  // Simulate a descriptor response for write operation with response |code|.
    338  async simulateWriteResponse(code) {
    339    await this.simulateResponse('write', code);
    340  }
    341 
    342  // Sets the next read response for descriptor to |code| and |value|.
    343  // |code| could be a GATT Error Response from
    344  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
    345  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
    346  // failure.
    347  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
    348  async setNextReadResponse(gatt_code, value = null) {
    349    if (gatt_code === 0 && value === null) {
    350      throw '|value| can\'t be null if read should success.';
    351    }
    352    if (gatt_code !== 0 && value !== null) {
    353      throw '|value| must be null if read should fail.';
    354    }
    355 
    356    const remove_handler =
    357        test_driver.bidi.bluetooth.descriptor_event_generated.on((event) => {
    358          if (event.address != this.peripheral_address_) {
    359            return;
    360          }
    361          remove_handler();
    362          this.simulateReadResponse(gatt_code, value);
    363        });
    364  }
    365 
    366  // Sets the next write response for this descriptor to |code|.
    367  // |code| could be a GATT Error Response from
    368  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
    369  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
    370  // failure.
    371  async setNextWriteResponse(gatt_code) {
    372    const remove_handler =
    373        test_driver.bidi.bluetooth.descriptor_event_generated.on((event) => {
    374          if (event.address != this.peripheral_address_) {
    375            return;
    376          }
    377          this.last_written_value_ = {
    378            lastValue: event.data,
    379            lastWriteType: event.type
    380          };
    381          remove_handler();
    382          if (event.type == 'write-with-response') {
    383            this.simulateWriteResponse(gatt_code);
    384          }
    385        });
    386  }
    387 
    388  // Gets the last successfully written value to the descriptor.
    389  // Returns null if no value has yet been written to the descriptor.
    390  async getLastWrittenValue() {
    391    return this.last_written_value_;
    392  }
    393 
    394  // Removes the fake GATT Descriptor from its fake characteristic.
    395  async remove() {
    396    await test_driver.bidi.bluetooth.simulate_descriptor({
    397      address: this.peripheral_address_,
    398      serviceUuid: this.service_uuid_,
    399      characteristicUuid: this.characteristic_uuid_,
    400      descriptorUuid: this.descriptor_uuid_,
    401      type: 'remove'
    402    });
    403  }
    404 }
    405 
    406 function initializeBluetoothBidiResources() {
    407  navigator.bluetooth.test = new FakeBluetooth();
    408 }