diff --git a/bluetooth/requestDevice/canonicalizeFilter/data-prefix-and-mask-size.https.window.js b/bluetooth/requestDevice/canonicalizeFilter/data-prefix-and-mask-size.https.window.js new file mode 100644 index 00000000000000..49cf41ce9eb077 --- /dev/null +++ b/bluetooth/requestDevice/canonicalizeFilter/data-prefix-and-mask-size.https.window.js @@ -0,0 +1,24 @@ +// META: script=/resources/testharness.js +// META: script=/resources/testharnessreport.js +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = + 'Manufacturer data mask size must be equal to dataPrefix size.'; + +bluetooth_test(async (t) => { + const companyIdentifier = 0x0001; + const dataPrefix = new Uint8Array([0x01, 0x02, 0x03, 0x04]); + const mask = new Uint8Array([0xff]); + + await promise_rejects_js( + t, TypeError, + requestDeviceWithTrustedClick( + {filters: [{manufacturerData: [{companyIdentifier, mask}]}]})); + await promise_rejects_js( + t, TypeError, requestDeviceWithTrustedClick({ + filters: [{manufacturerData: [{companyIdentifier, dataPrefix, mask}]}] + })); +}, test_desc); diff --git a/bluetooth/requestDevice/canonicalizeFilter/dataPrefix-buffer-is-detached.https.window.js b/bluetooth/requestDevice/canonicalizeFilter/dataPrefix-buffer-is-detached.https.window.js new file mode 100644 index 00000000000000..936ca4735c794a --- /dev/null +++ b/bluetooth/requestDevice/canonicalizeFilter/dataPrefix-buffer-is-detached.https.window.js @@ -0,0 +1,33 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'dataPrefix value buffer must not be detached'; + +function detachBuffer(buffer) { + window.postMessage('', '*', [buffer]); +} + +bluetooth_test(async (t) => { + const companyIdentifier = 0x0001; + + const typed_array = Uint8Array.of(1, 2); + detachBuffer(typed_array.buffer); + + await promise_rejects_dom( + t, 'InvalidStateError', requestDeviceWithTrustedClick({ + filters: + [{manufacturerData: [{companyIdentifier, dataPrefix: typed_array}]}] + })); + + const array_buffer = Uint8Array.of(3, 4).buffer; + detachBuffer(array_buffer); + + await promise_rejects_dom( + t, 'InvalidStateError', requestDeviceWithTrustedClick({ + filters: [ + {manufacturerData: [{companyIdentifier, dataPrefix: array_buffer}]} + ] + })); +}, test_desc); \ No newline at end of file diff --git a/bluetooth/requestDevice/canonicalizeFilter/empty-manufacturerData-member.https.window.js b/bluetooth/requestDevice/canonicalizeFilter/empty-manufacturerData-member.https.window.js new file mode 100644 index 00000000000000..af3118a48580dd --- /dev/null +++ b/bluetooth/requestDevice/canonicalizeFilter/empty-manufacturerData-member.https.window.js @@ -0,0 +1,37 @@ +// META: script=/resources/testharness.js +// META: script=/resources/testharnessreport.js +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'requestDevice with empty manufacturerData. ' + + 'Should reject with TypeError.'; +const test_specs = [ + {filters: [{manufacturerData: []}]}, + {filters: [{manufacturerData: [], name: 'Name'}]}, + {filters: [{manufacturerData: [], services: ['heart_rate']}]}, + {filters: [{manufacturerData: [], name: 'Name', services: ['heart_rate']}]}, + {filters: [{manufacturerData: []}], optionalServices: ['heart_rate']}, { + filters: [{manufacturerData: [], name: 'Name'}], + optionalServices: ['heart_rate'] + }, + { + filters: [{manufacturerData: [], services: ['heart_rate']}], + optionalServices: ['heart_rate'] + }, + { + filters: [{manufacturerData: [], name: 'Name', services: ['heart_rate']}], + optionalServices: ['heart_rate'] + } +]; + +bluetooth_test((t) => { + let test_promises = Promise.resolve(); + test_specs.forEach(args => { + test_promises = test_promises.then( + () => promise_rejects_js( + t, TypeError, requestDeviceWithTrustedClick(args))); + }); + return test_promises; +}, test_desc); \ No newline at end of file diff --git a/bluetooth/requestDevice/canonicalizeFilter/invalid-companyIdentifier.https.window.js b/bluetooth/requestDevice/canonicalizeFilter/invalid-companyIdentifier.https.window.js new file mode 100644 index 00000000000000..18cdbb4b4a85fa --- /dev/null +++ b/bluetooth/requestDevice/canonicalizeFilter/invalid-companyIdentifier.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'companyIdentifier must be in the [0, 65535] range'; + +bluetooth_test(async (t) => { + await promise_rejects_js( + t, TypeError, + requestDeviceWithTrustedClick( + {filters: [{manufacturerData: [{companyIdentifier: -1}]}]})); + await promise_rejects_js( + t, TypeError, + requestDeviceWithTrustedClick( + {filters: [{manufacturerData: [{companyIdentifier: 65536}]}]})); +}, test_desc); \ No newline at end of file diff --git a/bluetooth/requestDevice/canonicalizeFilter/mask-buffer-is-detached.https.window.js b/bluetooth/requestDevice/canonicalizeFilter/mask-buffer-is-detached.https.window.js new file mode 100644 index 00000000000000..502e2e40570168 --- /dev/null +++ b/bluetooth/requestDevice/canonicalizeFilter/mask-buffer-is-detached.https.window.js @@ -0,0 +1,36 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'mask value buffer must not be detached'; + +function detachBuffer(buffer) { + window.postMessage('', '*', [buffer]); +} + +bluetooth_test(async (t) => { + const companyIdentifier = 0x0001; + const dataPrefix = Uint8Array.of(1, 2); + + const typed_array = Uint8Array.of(1, 2); + detachBuffer(typed_array.buffer); + + await promise_rejects_dom( + t, 'InvalidStateError', requestDeviceWithTrustedClick({ + filters: [{ + manufacturerData: [{companyIdentifier, dataPrefix, mask: typed_array}] + }] + })); + + const array_buffer = Uint8Array.of(3, 4).buffer; + detachBuffer(array_buffer); + + await promise_rejects_dom( + t, 'InvalidStateError', requestDeviceWithTrustedClick({ + filters: [{ + manufacturerData: + [{companyIdentifier, dataPrefix, mask: array_buffer}] + }] + })); +}, test_desc); \ No newline at end of file diff --git a/bluetooth/requestDevice/canonicalizeFilter/same-company-identifier.https.window.js b/bluetooth/requestDevice/canonicalizeFilter/same-company-identifier.https.window.js new file mode 100644 index 00000000000000..7416202d3d5f21 --- /dev/null +++ b/bluetooth/requestDevice/canonicalizeFilter/same-company-identifier.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testharness.js +// META: script=/resources/testharnessreport.js +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Manufacturer data company identifier must be unique.'; +const expected = new TypeError(); + +let filters = [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + }, + { + companyIdentifier: 0x0001, + } + ] +}]; + +bluetooth_test( + (t) => promise_rejects_js( + t, TypeError, requestDeviceWithTrustedClick({filters})), + test_desc); diff --git a/bluetooth/requestDevice/filter-matches.https.html b/bluetooth/requestDevice/filter-matches.https.html index 1c42d779216f13..7815a1a5b10f99 100644 --- a/bluetooth/requestDevice/filter-matches.https.html +++ b/bluetooth/requestDevice/filter-matches.https.html @@ -11,6 +11,7 @@ let matching_services = [health_thermometer.uuid]; let matching_name = 'Health Thermometer'; let matching_namePrefix = 'Health'; +let matching_manufacturerData = [{ companyIdentifier: 0x0001 }]; let test_specs = [ { @@ -24,25 +25,50 @@ name: matching_name, }] }, - {filters: [{services: matching_services, namePrefix: matching_namePrefix}]}, { + { + filters: [{ + services: matching_services, + namePrefix: matching_namePrefix + }] + }, + { + filters: [{ + services: matching_services, + manufacturerData: matching_manufacturerData + }] + }, + { filters: [{ name: matching_name, }], optionalServices: matching_services }, { - filters: [{name: matching_name, namePrefix: matching_namePrefix}], + filters: [{ + namePrefix: matching_namePrefix + }], + optionalServices: matching_services + }, + { + filters: [{ + manufacturerData: matching_manufacturerData + }], optionalServices: matching_services }, { - filters: [{namePrefix: matching_namePrefix}], + filters: [{ + name: matching_name, + namePrefix: matching_namePrefix, + manufacturerData: matching_manufacturerData + }], optionalServices: matching_services }, { filters: [{ services: matching_services, name: matching_name, - namePrefix: matching_namePrefix + namePrefix: matching_namePrefix, + manufacturerData: matching_manufacturerData }] } ]; diff --git a/bluetooth/requestDevice/manufacturer-data-filter-matches.https.window.js b/bluetooth/requestDevice/manufacturer-data-filter-matches.https.window.js new file mode 100644 index 00000000000000..548b5d0b3fc7b1 --- /dev/null +++ b/bluetooth/requestDevice/manufacturer-data-filter-matches.https.window.js @@ -0,0 +1,141 @@ +// META: script=/resources/testharness.js +// META: script=/resources/testharnessreport.js +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Matches a filter when manufacturer data match.'; + +let test_specs = [ + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + }], + }], + }, + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01]), + }], + }], + }, + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01]), + mask: new Uint8Array([0xff]), + }], + }], + }, + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + }], + }], + }, + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + } + ], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + dataPrefix: new Uint8Array([0x03]), + } + ], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + dataPrefix: new Uint8Array([0x03]), + mask: new Uint8Array([0xff]), + } + ], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + dataPrefix: new Uint8Array([0x03, 0x04]), + } + ], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + dataPrefix: new Uint8Array([0x03, 0x04]), + mask: new Uint8Array([0xff, 0xff]) + } + ], + }], + }, +]; + +bluetooth_test( + () => setUpHealthThermometerDevice().then(() => { + let test_promises = Promise.resolve(); + test_specs.forEach(args => { + test_promises = test_promises.then(async () => { + const device = await requestDeviceWithTrustedClick(args); + assert_equals(device.name, 'Health Thermometer'); + }); + }); + return test_promises; + }), + test_desc); diff --git a/bluetooth/resources/bluetooth-fake-devices.js b/bluetooth/resources/bluetooth-fake-devices.js index c4d699ad40a5bb..bc142aa4de8e01 100644 --- a/bluetooth/resources/bluetooth-fake-devices.js +++ b/bluetooth/resources/bluetooth-fake-devices.js @@ -196,8 +196,19 @@ function generateRequestDeviceArgsWithServices(services = ['heart_rate']) { return [ {filters: [{services: services}]}, {filters: [{services: services, name: 'Name'}]}, - {filters: [{services: services, namePrefix: 'Pre'}]}, - {filters: [{services: services, name: 'Name', namePrefix: 'Pre'}]}, + {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'] @@ -207,7 +218,18 @@ function generateRequestDeviceArgsWithServices(services = ['heart_rate']) { optionalServices: ['heart_rate'] }, { - filters: [{services: services, name: 'Name', namePrefix: 'Pre'}], + filters: [ + {services: services, manufacturerData: [{companyIdentifier: 0x0001}]} + ], + optionalServices: ['heart_rate'] + }, + { + filters: [{ + services: services, + name: 'Name', + namePrefix: 'Pre', + manufacturerData: [{companyIdentifier: 0x0001}] + }], optionalServices: ['heart_rate'] } ]; @@ -242,6 +264,7 @@ async function initializeFakeCentral({state = 'powered-on'}) { /** * A dictionary for specifying fake Bluetooth device setup options. * @typedef {{address: !string, name: !string, + * manufacturerData: !Object>, * knownServiceUUIDs: !Array, connectable: !boolean, * serviceDiscoveryComplete: !boolean}} */ @@ -260,6 +283,7 @@ let SetupOptions; const fakeDeviceOptionsDefault = { address: '00:00:00:00:00:00', name: 'LE Device', + manufacturerData: {}, knownServiceUUIDs: [], connectable: false, serviceDiscoveryComplete: false, @@ -327,6 +351,7 @@ async function setUpPreconnectedFakeDevice(setupOptionsOverride) { await fake_central.simulatePreconnectedPeripheral({ address: setupOptions.fakeDeviceOptions.address, name: setupOptions.fakeDeviceOptions.name, + manufacturerData: setupOptions.fakeDeviceOptions.manufacturerData, knownServiceUUIDs: setupOptions.fakeDeviceOptions.knownServiceUUIDs, }); @@ -360,14 +385,16 @@ async function setUpPreconnectedFakeDevice(setupOptionsOverride) { /** * Deprecated: Use setUpPreconnectedFakeDevice() instead. - * Simulates a preconnected device with |address|, |name| and - * |knownServiceUUIDs|. 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. + * Simulates a preconnected device with |address|, |name|, |manufacturerData| + * and |knownServiceUUIDs|. 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. * TODO(https://crbug.com/1070816): Remove this method when all uses have been * converted to using setUpPreconnectedFakeDevice(); * @param {string} address The device MAC address. * @param {string} name The device name. + * @param {Object>} manufacturerData A map of company + * identifier and manufacturer data to set up the fake with. * @param {Array} knownServiceUUIDs An array of GATT service UUIDs to * set up the fake with. * @returns {Promise} The fake devices are initialized with the @@ -376,12 +403,14 @@ async function setUpPreconnectedFakeDevice(setupOptionsOverride) { async function setUpPreconnectedDevice({ address = '00:00:00:00:00:00', name = 'LE Device', + manufacturerData = {}, knownServiceUUIDs = [] }) { await initializeFakeCentral({state: 'powered-on'}); return await fake_central.simulatePreconnectedPeripheral({ address: address, name: name, + manufacturerData: manufacturerData, knownServiceUUIDs: knownServiceUUIDs, }); } @@ -705,7 +734,7 @@ async function getHIDDevice(options) { /** * Returns a FakePeripheral that corresponds to a simulated pre-connected device * called 'Health Thermometer'. The device has two known serviceUUIDs: - * 'generic_access' and 'health_thermometer'. + * 'generic_access' and 'health_thermometer' and some fake manufacturer data. * @returns {Promise} The device fake initialized as a Health * Thermometer device. */ @@ -713,6 +742,7 @@ function setUpHealthThermometerDevice() { return setUpPreconnectedDevice({ address: '09:09:09:09:09:09', name: 'Health Thermometer', + manufacturerData: {0x0001: manufacturer1Data, 0x0002: manufacturer2Data}, knownServiceUUIDs: ['generic_access', 'health_thermometer'], }); } @@ -1122,11 +1152,13 @@ async function setUpHealthThermometerAndHeartRateDevices() { 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'], }) ]); diff --git a/resources/chromium/web-bluetooth-test.js b/resources/chromium/web-bluetooth-test.js index ee835c224b4890..ecea5e760c6b4d 100644 --- a/resources/chromium/web-bluetooth-test.js +++ b/resources/chromium/web-bluetooth-test.js @@ -129,21 +129,23 @@ class FakeCentral { this.peripherals_ = new Map(); } - // Simulates a peripheral with |address|, |name| and |known_service_uuids| - // that has already been connected to the system. If the peripheral existed - // already it updates its name and known UUIDs. |known_service_uuids| should - // be an array of BluetoothServiceUUIDs + // 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, knownServiceUUIDs = []}) { - + async simulatePreconnectedPeripheral( + {address, name, manufacturerData = {}, knownServiceUUIDs = []}) { await this.fake_central_ptr_.simulatePreconnectedPeripheral( - address, name, canonicalizeAndConvertToMojoUUID(knownServiceUUIDs)); + address, name, + convertToMojoMap(manufacturerData, Number, true /* isNumberKey */), + canonicalizeAndConvertToMojoUUID(knownServiceUUIDs)); return this.fetchOrCreatePeripheral_(address); }