diff --git a/index.bs b/index.bs index 0852b43f..1f9b5c80 100644 --- a/index.bs +++ b/index.bs @@ -79,6 +79,10 @@ spec: BLUETOOTH-ASSIGNED text: Shortened Local Name; url: https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile# spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/# + type: abstract-op + text: CanonicalNumericIndexString; url: sec-canonicalnumericindexstring + text: CreateDataProperty; url: sec-createdataproperty + text: IsInteger; url: sec-isinteger type: dfn text: current realm; url: current-realm text: fulfilled; url: sec-promise-objects @@ -86,6 +90,7 @@ spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/# text: realm; url: sec-code-realms type: method text: Array.prototype.map; url: sec-array.prototype.map + text: [[OwnPropertyKeys]]; for: Object; url: sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys type: interface text: Array; url: sec-array-objects text: ArrayBuffer; url: sec-arraybuffer-constructor @@ -529,16 +534,22 @@ spec: html

Device Discovery

-    dictionary BluetoothRequestDeviceFilter {
+    dictionary BluetoothDataFilterInit {
+      BufferSource dataPrefix;
+      BufferSource mask;
+    };
+    dictionary BluetoothLEScanFilterInit {
       sequence<BluetoothServiceUUID> services;
       DOMString name;
       DOMString namePrefix;
-      unsigned short manufacturerId;
-      BluetoothServiceUUID serviceDataUUID;
+      // Maps unsigned shorts to BluetoothDataFilters.
+      object manufacturerData;
+      // Maps BluetoothServiceUUIDs to BluetoothDataFilters.
+      object serviceData;
     };
 
     dictionary RequestDeviceOptions {
-      sequence<BluetoothRequestDeviceFilter> filters;
+      sequence<BluetoothLEScanFilterInit> filters;
       sequence<BluetoothServiceUUID> optionalServices = [];
       boolean acceptAllDevices = false;
     };
@@ -584,31 +595,37 @@ spec: html
     
+    

+ Both Manufacturer Specific Data and Service Data + map a key to an array of bytes. + {{BluetoothDataFilterInit}} filters these arrays. + An array matches if it has a |prefix| such that + |prefix| & {{mask}} is equal to {{dataPrefix}} & {{mask}}. +

- Note that this means that + Note that if a device changes its behavior significantly when it connects, for example by not advertising its identifying manufacturer data anymore and instead having the client discover some identifying GATT services, @@ -628,7 +645,7 @@ spec: html

After the user selects a device to pair with this origin, the origin is allowed to access any service whose UUID was listed - in the {{BluetoothRequestDeviceFilter/services}} list + in the {{BluetoothLEScanFilterInit/services}} list in any element of options.filters or in options.optionalServices.

@@ -804,7 +821,7 @@ spec: html
-            [{manufacturerId: 17}]
+            [{manufacturerData: {17: {}}}]
           
D1 @@ -812,7 +829,7 @@ spec: html
-            [{serviceDataUUID: A}]
+            [{serviceData: {"A": {}}}]
           
D2 @@ -820,12 +837,73 @@ spec: html
-            [{manufacturerId: 17},
-             {serviceDataUUID: A}]
+            [{manufacturerData: {17: {}}},
+             {serviceData: {"A": {}}}]
           
D1, D2 + + +
+            [{manufacturerData: {17: {}},
+              serviceData: {"A": {}}}]
+          
+ + <none> + + + +
+            [{manufacturerData: {
+               17: {dataPrefix: new Uint8Array([1, 2, 3])},
+            }}]
+          
+ + D1 + + + +
+            [{manufacturerData: {
+               17: {dataPrefix: new Uint8Array([1, 2, 3, 4])},
+            }}]
+          
+ + <none> + + + +
+            [{manufacturerData: {
+               17: {dataPrefix: new Uint8Array([1])},
+            }}]
+          
+ + D1 + + + +
+            [{manufacturerData: {
+               17: {dataPrefix: new Uint8Array([0x91, 0xAA]),
+                    mask: new Uint8Array([0x0F, 0x57])},
+            }}]
+          
+ + D1 + + + +
+            [{manufacturerData: {
+               17: {},
+               18: {},
+            }}]
+          
+ + <none> + @@ -896,6 +974,32 @@ spec: html `namePrefix`, if present, must be non-empty to filter devices. + + +
+            requestDevice({
+              filters:[{manufacturerData: {}}]
+            })
+          
+ + + {{BluetoothLEScanFilterInit/manufacturerData}}, if present, + must be non-empty to filter devices. + + + + +
+            requestDevice({
+              filters:[{serviceData: {}}]
+            })
+          
+ + + {{BluetoothLEScanFilterInit/serviceData}}, if present, + must be non-empty to filter devices. + + @@ -991,43 +1095,82 @@ spec: html matches a filter filter if the following steps return `match`:

-
    +
    1. - If filter.name is present then, + If filter.{{name}} is present then, if device's Bluetooth Device Name isn't complete and equal to filter.name, return `mismatch`.
    2. - If filter.namePrefix is present then + If filter.{{namePrefix}} is present then if device's Bluetooth Device Name isn't present or doesn't start with filter.namePrefix, return `mismatch`.
    3. - For each uuid in filter.services, + For each uuid in filter.{{services}}, if the UA has not received advertising data, an extended inquiry response, or a service discovery response indicating that the device supports a primary (vs included) service with UUID uuid, return `mismatch`.
    4. - If filter.manufacturerId is present then + If filter.{{manufacturerData}} is present then + for each |manufacturerId| in + |filter|.manufacturerData.{{Object/[[OwnPropertyKeys]]}}(), if device hasn't advertised manufacturer specific data - with a company identifier code equal to - filter.manufacturerId, + with a company identifier code that stringifies in base 10 to |manufacturerId| + and with data that matches + |filter|.{{manufacturerData}}[|manufacturerId|] return `mismatch`.
    5. - If filter.serviceDataUUID is present then + If filter.{{serviceData}} is present then + for each |uuid| in + |filter|.{{serviceData}}.{{Object/[[OwnPropertyKeys]]}}(), if device hasn't advertised service data - with a UUID equal to filter.serviceDataUUID, + with a UUID whose 128-bit form is |uuid| + and with data that matches + |filter|.{{serviceData}}[|manufacturerId|], return `mismatch`.
    6. Return `match`.
    +
    +

    + An array of bytes |data| + matches a {{BluetoothDataFilterInit}} |filter| + if the following steps return `match`. +

    +

    + Note: This algorithm assumes that |filter| has already been + canonicalized. +

    +
      +
    1. + Let |expectedPrefix| be a copy of the bytes held by |filter|.{{dataPrefix}}. +
    2. +
    3. + Let |mask| be a copy of the bytes held by |filter|.{{mask}}. +
    4. +
    5. + If |data| has fewer bytes than |expectedPrefix|, return `mismatch`. +
    6. +
    7. + For each `1` bit in |mask|, + if the corresponding bit in |data| is not equal to + the corresponding bit in |expectedPrefix|, + return `mismatch`. +
    8. +
    9. + Return `match`. +
    10. +
    +
    +

    The list of Service UUIDs that a device advertises might not include all the UUIDs the device supports. @@ -1080,7 +1223,7 @@ spec: html

    To request Bluetooth devices, - given a sequence of {{BluetoothRequestDeviceFilter}}s, |filters|, + given a sequence of {{BluetoothLEScanFilterInit}}s, |filters|, which can be `null` to represent that all devices can match, and a sequence of {{BluetoothServiceUUID}}s, |optionalServices|, the UA MUST run the following steps: @@ -1121,114 +1264,16 @@ spec: html do the following steps:

    1. - If none of filter's - members is present, - throw a {{TypeError}} - and abort these steps. -
    2. -
    3. Let canonicalizedFilter be `{}`.
    4. -
    5. - If filter.services is present, do the following sub-steps: -
        -
      1. - If filter.services.length === 0, - throw a {{TypeError}} - and abort these steps. -
      2. -
      3. - Let services be - {{Array.prototype.map}}.call(filter.services, - {{BluetoothUUID/getService()|BluetoothUUID.getService}}). -
      4. -
      5. - If any of the {{BluetoothUUID/getService()|BluetoothUUID.getService()}} calls threw an exception, - throw that exception and abort these steps. -
      6. -
      7. - If any service in services is blacklisted, - throw a {{SecurityError}} - and abort these steps. -
      8. -
      9. - Set canonicalizedFilter.services to services. -
      10. -
      11. Add the elements of services to requiredServiceUUIDs.
      12. -
      -
    6. -
    7. - If filter.name is present, do the following sub-steps. -
        -
      1. - If the UTF-8 encoding - of filter.name - is more than 248 bytes long, - throw a {{TypeError}} - and abort these steps. - -

        - 248 is the maximum number of UTF-8 code units in - a Bluetooth Device Name. -

        -
      2. -
      3. - Set canonicalizedFilter.name - to filter.name. -
      4. -
      + Let |canonicalFilter| be + the result of canonicalizing |filter|.
    8. - If filter.namePrefix is present, do the following sub-steps. -
        -
      1. - If filter.namePrefix.length === 0 - or if the UTF-8 encoding - of filter.namePrefix - is more than 248 bytes long, - throw a {{TypeError}} - and abort these steps. - -

        - 248 is the maximum number of UTF-8 code units in - a Bluetooth Device Name. -

        -
      2. -
      3. - Set canonicalizedFilter.namePrefix - to filter.namePrefix. -
      4. -
      + Append |canonicalFilter| to |uuidFilters|.
    9. -
    10. - If |filter|.manufacturerId is present, - set |canonicalizedFilter|.manufacturerId - to |filter|.manufacturerId. + Add the contents of |canonicalFilter|.services + to |requiredServiceUUIDs|.
    11. - -
    12. - If |filter|.serviceDataUUID is present, do the following sub-steps. -
        -
      1. - Let |service| be - {{BluetoothUUID/getService()|BluetoothUUID.getService}}(|filter|.serviceDataUUID). -
      2. -
      3. - If the previous step threw an exception, - throw that exception and abort these steps. -
      4. -
      5. - If |service| is blacklisted, - throw a {{SecurityError}} - and abort these steps. -
      6. -
      7. - Set |canonicalizedFilter|.serviceDataUUID - to |service|. -
      8. -
      -
    13. - -
    14. Append canonicalizedFilter to uuidFilters.
  1. @@ -1328,6 +1373,212 @@ spec: html
+
+

+ The result of canonicalizing + the {{BluetoothLEScanFilterInit}} |filter|, + is the {{BluetoothLEScanFilterInit}} returned from the following steps: +

+
    +
  1. + If none of filter's + members is present, + throw a {{TypeError}} + and abort these steps. +
  2. +
  3. Let canonicalizedFilter be `{}`.
  4. +
  5. + If filter.{{BluetoothLEScanFilterInit/services}} is present, + do the following sub-steps: +
      +
    1. + If filter.services.length === 0, + throw a {{TypeError}} + and abort these steps. +
    2. +
    3. + Let services be + {{Array.prototype.map}}.call(filter.services, + {{BluetoothUUID/getService()|BluetoothUUID.getService}}). +
    4. +
    5. + If any of the {{BluetoothUUID/getService()|BluetoothUUID.getService()}} calls + threw an exception, + throw that exception and abort these steps. +
    6. +
    7. + If any service in services is blacklisted, + throw a {{SecurityError}} + and abort these steps. +
    8. +
    9. + Set canonicalizedFilter.services to services. +
    10. +
    +
  6. +
  7. + If filter.{{BluetoothLEScanFilterInit/name}} is present, + do the following sub-steps. +
      +
    1. + If the UTF-8 encoding + of filter.name + is more than 248 bytes long, + throw a {{TypeError}} + and abort these steps. + +

      + 248 is the maximum number of UTF-8 code units in + a Bluetooth Device Name. +

      +
    2. +
    3. + Set canonicalizedFilter.name + to filter.name. +
    4. +
    +
  8. +
  9. + If filter.{{BluetoothLEScanFilterInit/namePrefix}} is present, + do the following sub-steps. +
      +
    1. + If filter.namePrefix.length === 0 + or if the UTF-8 encoding + of filter.namePrefix + is more than 248 bytes long, + throw a {{TypeError}} + and abort these steps. + +

      + 248 is the maximum number of UTF-8 code units in + a Bluetooth Device Name. +

      +
    2. +
    3. + Set canonicalizedFilter.namePrefix + to filter.namePrefix. +
    4. +
    +
  10. + +
  11. + Set |canonicalizedFilter|.manufacturerData to `{}`. +
  12. +
  13. + If |filter|.{{BluetoothLEScanFilterInit/manufacturerData}} is present, + do the following sub-steps for each |key| in + |filter|.manufacturerData.{{Object/[[OwnPropertyKeys]]}}(). + If there are no such keys, throw a {{TypeError}} and abort these steps. +
      +
    1. + Let |manufacturerId| be CanonicalNumericIndexString(|key|). +
    2. +
    3. + If |manufacturerId| is `undefined` or `-0`, + or IsInteger(|manufacturerId|) is `false`, + or |manufacturerId| is outside the range from 0–65535 inclusive, + throw a {{TypeError}} and abort these steps. +
    4. +
    5. + Let |dataFilter| be + |filter|.manufacturerData[|manufacturerId|], + converted to an IDL value of type {{BluetoothDataFilterInit}}. + If this conversion throws an exception, propagate it and abort these steps. +
    6. +
    7. + Let |canonicalizedDataFilter| be + the result of canonicalizing |dataFilter|, + converted to an ECMAScript value. + If this throws an exception, propagate that exception and abort these steps. +
    8. +
    9. + Call CreateDataProperty(|canonicalizedFilter|.manufacturerData, + |key|, |canonicalizedDataFilter|). +
    10. +
    +
  14. + +
  15. + Set |canonicalizedFilter|.serviceData to `{}`. +
  16. +
  17. + If |filter|.{{BluetoothLEScanFilterInit/serviceData}} is present, + do the following sub-steps for each |key| in + |filter|.serviceData.{{Object/[[OwnPropertyKeys]]}}(). + If there are no such keys, throw a {{TypeError}} and abort these steps. +
      +
    1. + Let |serviceName| be CanonicalNumericIndexString(|key|). +
    2. +
    3. + If |serviceName| is `undefined`, set |serviceName| to |key|. +
    4. +
    5. + Let |service| be + {{BluetoothUUID/getService()|BluetoothUUID.getService}}(|serviceName|). +
    6. +
    7. + If the previous step threw an exception, + throw that exception and abort these steps. +
    8. +
    9. + If |service| is blacklisted, + throw a {{SecurityError}} + and abort these steps. +
    10. +
    11. + Let |dataFilter| be + |filter|.serviceData[|service|], + converted to an IDL value of type {{BluetoothDataFilterInit}}. + If this conversion throws an exception, propagate it and abort these steps. +
    12. +
    13. + Let |canonicalizedDataFilter| be + the result of canonicalizing |dataFilter|, + converted to an ECMAScript value. + If this throws an exception, propagate that exception and abort these steps. +
    14. +
    15. + Call CreateDataProperty(|canonicalizedFilter|.serviceData, + |service|, |canonicalizedDataFilter|). +
    16. +
    +
  18. + +
  19. Return |canonicalizedFilter|.
  20. +
+
+ +
+

+ The result of canonicalizing + the {{BluetoothDataFilterInit}} |filter|, + is the {{BluetoothDataFilterInit}} returned from the following steps: +

+
    +
  1. + If |filter|.{{BluetoothDataFilterInit/dataPrefix}} is present, + let |dataPrefix| be a copy of the bytes held + by |filter|.dataPrefix. + Otherwise, let |dataPrefix| be an empty sequence of bytes. +
  2. +
  3. + If |filter|.{{BluetoothDataFilterInit/mask}} is present, + let |mask| be a copy of the bytes held + by |filter|.mask. + Otherwise, let |mask| be a sequence of `0xFF` bytes the same length as |dataPrefix|. +
  4. +
  5. + If |mask| is not the same length as |dataPrefix|, + throw a {{TypeError}} and abort these steps. +
  6. +
  7. + Return `{dataPrefix: new Uint8Array(|dataPrefix|), mask: new Uint8Array(|mask|)}`. +
  8. +
+
+

To scan for devices with @@ -1514,7 +1765,7 @@ spec: html dictionary BluetoothPermissionDescriptor : PermissionDescriptor { DOMString deviceId; // These match RequestDeviceOptions. - sequence<BluetoothRequestDeviceFilter> filters; + sequence<BluetoothLEScanFilterInit> filters; sequence<BluetoothServiceUUID> optionalServices = []; boolean acceptAllDevices = false; }; diff --git a/scanning.bs b/scanning.bs index 8a803be3..a5298241 100644 --- a/scanning.bs +++ b/scanning.bs @@ -74,21 +74,38 @@ Markup Shorthands: css no, markdown yes spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/# type: method text: Array.prototype.map; url: sec-array.prototype.map + text: [[OwnPropertyKeys]]; for: Object; url: sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys type: interface text: TypeError; url: sec-native-error-types-used-in-this-standard-typeerror + +spec: WebIDL; urlPrefix: https://heycam.github.io/webidl/# + type: dfn + text: a copy of the bytes held; url: dfn-get-buffer-source-copy + spec: web-bluetooth; urlPrefix: index.html# + type: dfn + text: canonicalizing + for: BluetoothDataFilterInit; url: bluetoothdatafilterinit-canonicalizing + for: BluetoothLEScanFilterInit; url: bluetoothlescanfilterinit-canonicalizing + for: BluetoothDataFilterInit; text: matches; url: bluetoothdatafilterinit-matches + type: dictionary; urlPrefix: dictdef- + text: BluetoothDataFilterInit + text: BluetoothLEScanFilterInit type: dict-member for: AllowedBluetoothDevice; text: mayUseGATT; url: dom-allowedbluetoothdevice-mayusegatt + for: BluetoothLEScanFilterInit; urlPrefix: dom-bluetoothlescanfilterinit- + text: manufacturerData + text: serviceData + text: services + for: BluetoothDataFilterInit; urlPrefix: dom-bluetoothdatafilterinit- + text: dataPrefix + text: mask