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 }