-
Notifications
You must be signed in to change notification settings - Fork 190
/
Copy pathindex.bs
4263 lines (4021 loc) · 174 KB
/
index.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<pre class='metadata'>
Title: Web Bluetooth
Repository: WebBluetoothCG/web-bluetooth
Status: CG-DRAFT
ED: https://webbluetoothcg.github.io/web-bluetooth/
Shortname: web-bluetooth
Level: 1
Editor: See contributors on GitHub, , https://github.com/WebBluetoothCG/web-bluetooth/graphs/contributors
Abstract: This document describes an API to discover and communicate with devices
Abstract: over the Bluetooth 4 wireless standard using the Generic Attribute Profile (GATT).
Group: web-bluetooth-cg
!Participate: <a href="https://www.w3.org/community/web-bluetooth/">Join the W3C Community Group</a>
!Participate: <a href="https://github.com/WebBluetoothCG/web-bluetooth">Fix the text through GitHub</a>
!Participate: <a href="mailto:[email protected]">[email protected]</a> (<a href="https://lists.w3.org/Archives/Public/public-web-bluetooth/" rel="discussion">archives</a>)
!Participate: <a href="irc://irc.w3.org:6665/#web-bluetooth">IRC: #web-bluetooth on W3C's IRC</a>
Markup Shorthands: css no, markdown yes
</pre>
<pre class=biblio>
{
"BLUETOOTH42": {
"href": "https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439",
"title": "BLUETOOTH SPECIFICATION Version 4.2",
"publisher": "Bluetooth SIG",
"date": "2 December 2014"
},
"BLUETOOTH-GATT-REST": {
"href": "https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=285910",
"title": "GATT REST API",
"publisher": "Bluetooth Internet WG",
"date": "7 April 2014"
},
"BLUETOOTH-ASSIGNED": {
"href": "https://www.bluetooth.org/en-us/specification/assigned-numbers",
"title": "Assigned Numbers",
"status": "Living Standard",
"publisher": "Bluetooth SIG"
},
"BLUETOOTH-ASSIGNED-BASEBAND": {
"href": "https://www.bluetooth.org/en-us/specification/assigned-numbers/baseband",
"title": "Assigned Numbers for Baseband",
"status": "Living Standard",
"publisher": "Bluetooth SIG"
},
"BLUETOOTH-ASSIGNED-SERVICES": {
"href": "https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx",
"title": "Bluetooth GATT Specifications > Services",
"status": "Living Standard",
"publisher": "Bluetooth SIG"
},
"BLUETOOTH-ASSIGNED-CHARACTERISTICS": {
"href": "https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx",
"title": "Bluetooth GATT Specifications > Characteristics",
"status": "Living Standard",
"publisher": "Bluetooth SIG"
},
"BLUETOOTH-ASSIGNED-DESCRIPTORS": {
"href": "https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorsHomePage.aspx",
"title": "Bluetooth GATT Specifications > Descriptors",
"status": "Living Standard",
"publisher": "Bluetooth SIG"
},
"BLUETOOTH-SUPPLEMENT5": {
"href": "https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=291904",
"title": "Supplement to the Bluetooth Core Specification Version 5",
"date": "2 December 2014",
"publisher": "Bluetooth SIG"
},
"promises-guide": {
"href": "https://www.w3.org/2001/tag/doc/promises-guide",
"title": "Writing Promise-Using Specifications",
"date": "24 July 2015",
"status": "Finding of the W3C TAG",
"publisher": "W3C TAG"
}
}
</pre>
<pre class="anchors">
spec: BLUETOOTH-ASSIGNED
type: enum; urlPrefix: https://developer.bluetooth.org/gatt/
urlPrefix: characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.
text: org.bluetooth.characteristic.body_sensor_location; url: body_sensor_location.xml#
text: org.bluetooth.characteristic.gap.appearance; url: gap.appearance.xml#
text: org.bluetooth.characteristic.heart_rate_control_point; url: heart_rate_control_point.xml#
text: org.bluetooth.characteristic.heart_rate_measurement; url: heart_rate_measurement.xml#
text: org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list; url: ieee_11073-20601_regulatory_certification_data_list.xml#
text: org.bluetooth.characteristic.pnp_id; url: pnp_id.xml#
urlPrefix: descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.
text: org.bluetooth.descriptor.gatt.characteristic_presentation_format; url: gatt.characteristic_presentation_format.xml#
text: org.bluetooth.descriptor.gatt.client_characteristic_configuration; url: gatt.client_characteristic_configuration.xml#
urlPrefix: services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.
text: org.bluetooth.service.cycling_power; url: cycling_power.xml#
text: org.bluetooth.service.device_information; url: device_information.xml#
text: org.bluetooth.service.heart_rate; url: heart_rate.xml#
type: dfn
text: Shortened Local Name; url: https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile#
spec: whatwg-dom; urlPrefix: http://dom.spec.whatwg.org/#; type: dfn
text: participate in a tree; url: concept-tree-participate
spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/#
type: dfn
text: global object; url: global-object
text: realm; url: sec-code-realms
type: method
text: Array.prototype.map; url: sec-array.prototype.map
type: interface
text: Array; url: sec-array-objects
text: ArrayBuffer; url: sec-arraybuffer-constructor
text: DataView; url: sec-dataview-constructor
text: Map; url: sec-map-constructor
text: Promise; url:sec-promise-objects
text: Set; url: sec-set-objects
text: TypeError; url: sec-native-error-types-used-in-this-standard-typeerror
text: TypedArray; url: sec-typedarray-constructors
spec: encoding; urlPrefix: https://encoding.spec.whatwg.org/#
type: dfn
text: utf-8 decode without BOM; url: utf-8-decode-without-bom
text: utf-8 encode; url: utf-8-encode
spec: powerful-features; urlPrefix: https://w3c.github.io/webappsec/specs/powerfulfeatures/#
type: dfn
text: secure context; url: secure-context
spec: promises-guide; urlPrefix: https://www.w3.org/2001/tag/doc/promises-guide#
type: dfn
text: A new promise; url: a-new-promise
text: A promise rejected with; url: a-promise-rejected-with
text: Reject; url: reject-promise
text: Resolve; url: resolve-promise
text: Transforming; url: transforming-by
text: Waiting for all; url: waiting-for-all
spec: WebIDL; urlPrefix: https://heycam.github.io/webidl/#
type: dfn
text: a copy of the bytes held; url: dfn-get-buffer-source-copy
type: exception
text: AbortError; url: aborterror
text: InvalidModificationError; url: invalidmodificationerror
text: NetworkError; url: networkerror
text: NotFoundError; url: notfounderror
text: NotSupportedError; url: notsupportederror
text: SecurityError; url: securityerror
text: SyntaxError; url: syntaxerror
type: interface
text: DOMString; url: idl-DOMString
text: FrozenArray; url: idl-frozen-array
type: typedef
text: BufferSource; url: common-BufferSource
</pre>
<pre class="link-defaults">
spec: html
type: dfn
text: allowed to show a popup
text: browsing context
text: environment settings object
text: event handler idl attribute
text: global object
text: in parallel
text: incumbent settings object
text: perform a microtask checkpoint
text: queue a task
text: relevant settings object
text: responsible event loop
spec: webidl
type: dfn
text: map entries
</pre>
<style>
.argument-list { display: inline-block; vertical-align: top; }
/* Show self-links for various elements. This is incompatible with nearby floats. */
.note, .why, .example, .issue { overflow: inherit; }
</style>
<section>
<h2 id="introduction">Introduction</h2>
<em>This section is non-normative.</em>
<p>
<a href="https://developer.bluetooth.org/">Bluetooth</a> is
a standard for short-range wireless communication between devices.
Bluetooth "Classic" (<abbr title="Basic Rate">BR</abbr>/<abbr title="Enhanced Data Rate">EDR</abbr>)
defines a set of binary protocols and supports speeds up to about 24Mbps.
Bluetooth 4.0 introduced a new "Low Energy" mode known as "Bluetooth Smart",
<abbr title="Bluetooth Low Energy">BLE</abbr>, or just <abbr title="Low Energy">LE</abbr>
which is limited to about 1Mbps but
allows devices to leave their transmitters off most of the time.
BLE provides most of its functionality through key/value pairs provided by
the <a lt="Generic Attribute Profile">Generic Attribute Profile
(<abbr title="Generic Attribute Profile">GATT</abbr>)</a>.
</p>
<p>
BLE defines multiple roles that devices can play.
The <a>Broadcaster</a> and <a>Observer</a> roles are
for transmitter- and receiver-only applications, respectively.
Devices acting in the <a>Peripheral</a> role can receive connections,
and devices acting in the <a>Central</a> role can connect to <a>Peripheral</a> devices.
</p>
<p>
A device acting in either the <a>Peripheral</a> or <a>Central</a> role
can host a <a>GATT Server</a>,
which exposes a hierarchy of <a>Service</a>s, <a>Characteristic</a>s, and <a>Descriptor</a>s.
See [[#information-model]] for more details about this hierarchy.
Despite being designed to support BLE transport,
the GATT protocol can also run over BR/EDR transport.
</p>
<p>
The first version of this specification allows web pages,
running on a UA in the <a>Central</a> role, to connect to <a>GATT Server</a>s
over either a BR/EDR or LE connection.
While this specification cites the [[BLUETOOTH42]] specification,
it intends to also support communication
among devices that only implement Bluetooth 4.0 or 4.1.
</p>
<section>
<h3 id="introduction-examples">Examples</h3>
<div class="example" id="example-heart-rate-monitor">
<p>
To discover and retrieve data from a standard heart rate monitor,
a website would use code like the following:
</p>
<pre highlight="js">
let chosenHeartRateService = null;
navigator.bluetooth.<a idl for="Bluetooth" lt="requestDevice()">requestDevice</a>({
filters: [{
services: ['heart_rate'],
}]
}).then(device => device.gatt.<a for="BluetoothRemoteGATTServer">connect()</a>)
.then(server => server.<a idl for="BluetoothRemoteGATTServer" lt="getPrimaryService()"
>getPrimaryService</a>(<a idl lt="org.bluetooth.service.heart_rate"
>'heart_rate'</a>))
.then(service => {
chosenHeartRateService = service;
return Promise.all([
service.<a idl for="BluetoothRemoteGATTService" lt="getCharacteristic()"
>getCharacteristic</a>(<a idl lt="org.bluetooth.characteristic.body_sensor_location"
>'body_sensor_location'</a>)
.then(handleBodySensorLocationCharacteristic),
service.<a idl for="BluetoothRemoteGATTService" lt="getCharacteristic()"
>getCharacteristic</a>(<a idl lt="org.bluetooth.characteristic.heart_rate_measurement"
>'heart_rate_measurement'</a>)
.then(handleHeartRateMeasurementCharacteristic),
]);
});
function handleBodySensorLocationCharacteristic(characteristic) {
if (characteristic === null) {
console.log("Unknown sensor location.");
return Promise.resolve();
}
return characteristic.<a for="BluetoothRemoteGATTCharacteristic">readValue()</a>
.then(sensorLocationData => {
let sensorLocation = sensorLocationData.getUint8(0);
switch (sensorLocation) {
case 0: return 'Other';
case 1: return 'Chest';
case 2: return 'Wrist';
case 3: return 'Finger';
case 4: return 'Hand';
case 5: return 'Ear Lobe';
case 6: return 'Foot';
default: return 'Unknown';
}
}).then(location => console.log(location));
}
function handleHeartRateMeasurementCharacteristic(characteristic) {
characteristic.addEventListener('<a event>characteristicvaluechanged</a>', onHeartRateChanged);
return characteristic.<a for="BluetoothRemoteGATTCharacteristic">startNotifications()</a>;
}
function onHeartRateChanged(event) {
let characteristic = event.target;
console.log(parseHeartRate(characteristic.<a attribute for="BluetoothRemoteGATTCharacteristic">value</a>));
}
</pre>
<p>
<code>parseHeartRate()</code> would be defined using the
<a idl lt="org.bluetooth.characteristic.heart_rate_measurement"
><code>heart_rate_measurement</code> documentation</a>
to read the {{DataView}} stored
in a {{BluetoothRemoteGATTCharacteristic}}'s
{{BluetoothRemoteGATTCharacteristic/value}} field.
</p>
<pre highlight="js">
function parseHeartRate(data) {
let flags = data.getUint8(0);
let rate16Bits = flags & 0x1;
let result = {};
let index = 1;
if (rate16Bits) {
result.heartRate = data.getUint16(index, /*littleEndian=*/true);
index += 2;
} else {
result.heartRate = data.getUint8(index);
index += 1;
}
let contactDetected = flags & 0x2;
let contactSensorPresent = flags & 0x4;
if (contactSensorPresent) {
result.contactDetected = !!contactDetected;
}
let energyPresent = flags & 0x8;
if (energyPresent) {
result.energyExpended = data.getUint16(index, /*littleEndian=*/true);
index += 2;
}
let rrIntervalPresent = flags & 0x10;
if (rrIntervalPresent) {
let rrIntervals = [];
for (; index + 1 < data.byteLength; index += 2) {
rrIntervals.push(data.getUint16(index, /*littleEndian=*/true));
}
result.rrIntervals = rrIntervals;
}
return result;
}
</pre>
<p>
<code>onHeartRateChanged()</code> might log an object like
</p>
<pre highlight="js">
{
heartRate: 70,
contactDetected: true,
energyExpended: 750, // Meaning 750kJ.
rrIntervals: [890, 870] // Meaning .87s and .85s.
}
</pre>
<p>
If the heart rate sensor reports the <code>energyExpended</code> field,
the web application can reset its value to <code>0</code> by writing to the
{{org.bluetooth.characteristic.heart_rate_control_point|heart_rate_control_point}}
characteristic:
</p>
<pre highlight="js">
function resetEnergyExpended() {
if (!chosenHeartRateService) {
return Promise.reject(new Error('No heart rate sensor selected yet.'));
}
return chosenHeartRateService.<a idl for="BluetoothRemoteGATTService" lt="getCharacteristic()"
>getCharacteristic</a>(<a idl lt="org.bluetooth.characteristic.heart_rate_control_point"
>'heart_rate_control_point'</a>)
.then(controlPoint => {
let resetEnergyExpended = new Uint8Array([1]);
return controlPoint.<a idl for="BluetoothRemoteGATTCharacteristic" lt="writeValue()">writeValue</a>(resetEnergyExpended);
});
}
</pre>
</div>
</section>
</section>
<section>
<h2 id="security-and-privacy">Security and privacy considerations</h2>
<section>
<h3 id="device-access-is-powerful">Device access is powerful</h3>
<p>
When a website requests access to devices using
{{Bluetooth/requestDevice()}},
it gets the ability to access all GATT services mentioned in the call.
The UA MUST inform the user what capabilities these services give the website
before asking which devices to entrust to it.
If any services in the list aren't known to the UA,
the UA MUST assume they give the site complete control over the device
and inform the user of this risk.
The UA MUST also allow the user to inspect what sites have access to what devices
and <a lt="revoke Bluetooth access">revoke</a> these pairings.
<p>
The UA MUST NOT allow the user to pair entire classes of devices with a website.
It is possible to construct a class of devices
for which each individual device sends the same Bluetooth-level identifying information.
UAs are not required to attempt to detect this sort of forgery
and MAY let a user pair this pseudo-device with a website.
<p>
To help ensure that only the entity the user approved for access actually has access,
this specification requires that only <a>secure context</a>s
can access Bluetooth devices
(<a href="#requestDevice-secure-context">requestDevice</a>).
</section>
<section>
<h3 id="attacks-on-devices">Attacks on devices</h3>
<em>This section is non-normative.</em>
<p>
Communication from websites can break the security model of some devices,
which assume they only receive messages from
the trusted operating system of a remote device.
Human Interface Devices are a prominent example,
where allowing a website to communicate would allow that site to log keystrokes.
This specification includes a blacklist of
such vulnerable services, characteristics, and descriptors
to prevent websites from taking advantage of them.
</p>
<p>
We expect that many devices are vulnerable to unexpected data delivered to their radio.
In the past, these devices had to be exploited one-by-one,
but this API makes it plausible to conduct large-scale attacks.
This specification takes several approaches to make such attacks more difficult:
<ul>
<li>
Pairing individual devices instead of device classes
requires at least a user action before a device can be exploited.
<li>
Constraining access to <a>GATT</a>, as opposed to generic byte-stream access,
denies malicious websites access to most parsers on the device.
<p>
On the other hand,
GATT's <a>Characteristic</a> and <a>Descriptor</a> values are still byte arrays,
which may be set to lengths and formats the device doesn't expect.
UAs are encouraged to validate these values when they can.
<li>
This API never exposes Bluetooth addressing, data signing or encryption keys
(<a>Definition of Keys and Values</a>) to websites.
This makes it more difficult for a website to predict the bits that will be sent over the radio,
which blocks <a href="https://www.usenix.org/legacy/events/woot11/tech/final_files/Goodspeed.pdf">packet-in-packet injection attacks</a>.
Unfortunately, this only works over encrypted links,
which not all BLE devices are required to support.
</li>
</ul>
<p>
UAs can also take further steps to protect their users:
<ul>
<li>
A web service may collect lists of malicious websites and vulnerable devices.
UAs can deny malicious websites access to any device
and any website access to vulnerable devices.
</ul>
</section>
<section>
<h3 id="bluetooth-device-identifiers">Bluetooth device identifiers</h3>
<em>This section is non-normative.</em>
<p>
Each Bluetooth BR/EDR device has a unique 48-bit MAC address
known as the <a>BD_ADDR</a>.
Each Bluetooth LE device has at least one of a <a>Public Device Address</a>
and a <a>Static Device Address</a>.
The <a>Public Device Address</a> is a MAC address.
The <a>Static Device Address</a> may be regenerated on each restart.
A BR/EDR/LE device will use the same value
for the <a>BD_ADDR</a> and the <a>Public Device Address</a>
(specified in the <a>Read BD_ADDR Command</a>).
</p>
<p>
An LE device may also have a unique, 128-bit <a>Identity Resolving Key</a>,
which is sent to trusted devices during the bonding process.
To avoid leaking a persistent identifier, an LE device may scan and advertise using
a random Resolvable or Non-Resolvable <a>Private Address</a>
instead of its Static or Public Address.
These are regenerated periodically (approximately every 15 minutes),
but a bonded device can check whether one of its stored <a>IRK</a>s matches
any given Resolvable Private Address
using the <a>Resolvable Private Address Resolution Procedure</a>.
</p>
<p>
Each Bluetooth device also has a human-readable <a>Bluetooth Device Name</a>.
These aren't guaranteed to be unique, but may well be, depending on the device type.
<section>
<h4 id="remote-device-identifiers">Identifiers for remote Bluetooth devices</h4>
<em>This section is non-normative.</em>
<p>
If a website can retrieve any of the persistent device IDs,
these can be used, in combination with a large effort to catalog ambient devices,
to discover a user's location.
A device ID can also be used to identify that a user who
pairs two different websites with the same Bluetooth device
is a single user.
On the other hand, many GATT services are available
that could be used to fingerprint a device,
and a device can easily expose a custom GATT service to make this easier.
</p>
<p>
This specification
<a lt="add an allowed Bluetooth device">generates a new device ID</a>
for each origin for a given device,
which makes it difficult for websites to abuse the device address like this.
Device makers can still design their devices to help track users,
but it takes work.
</p>
</section>
<section>
<h4 id="ua-bluetooth-address">The UA's Bluetooth address</h4>
<em>This section is non-normative.</em>
<p>
In BR/EDR mode, or in LE mode during active scanning without the <a>Privacy Feature</a>,
the UA broadcasts its persistent ID to any nearby Bluetooth radio.
This makes it easy to scatter hostile devices in an area and track the UA.
As of 2014-08, few or no platforms document that they implement the <a>Privacy Feature</a>,
so despite this spec recommending it, few UAs are likely to use it.
This spec does <a href="#requestDevice-user-gesture">require a user gesture</a>
for a website to trigger a scan, which reduces the frequency of scans some,
but it would still be better for more platforms to expose the <a>Privacy Feature</a>.
</section>
</section>
</section>
<section>
<h2 id="device-discovery">Device Discovery</h2>
<pre class="idl">
dictionary BluetoothScanFilter {
sequence<BluetoothServiceUUID> services;
DOMString name;
DOMString namePrefix;
};
dictionary RequestDeviceOptions {
required sequence<BluetoothScanFilter> filters;
sequence<BluetoothServiceUUID> optionalServices = [];
};
interface Bluetooth {
Promise<BluetoothDevice> requestDevice(RequestDeviceOptions options);
};
Bluetooth implements EventTarget;
Bluetooth implements BluetoothDeviceEventHandlers;
Bluetooth implements CharacteristicEventHandlers;
Bluetooth implements ServiceEventHandlers;
</pre>
<div class="note" heading="{{Bluetooth}} members">
<p>
{{Bluetooth/requestDevice(options)}} asks the user
to grant this origin access to a device
that <a>matches any filter</a> in <code>options.<dfn dict-member for="RequestDeviceOptions">filters</dfn></code>.
To <a>match a filter</a>, the device has to:
</p>
<ul>
<li>
support <em>all</em> the GATT service UUIDs
in the <dfn dict-member for="BluetoothScanFilter">services</dfn> list
if that member is present,
</li>
<li>
have a name equal to <dfn dict-member for="BluetoothScanFilter">name</dfn>
if that member is present, and
</li>
<li>
have a name starting with <dfn dict-member for="BluetoothScanFilter">namePrefix</dfn>
if that member is present.
</li>
</ul>
<p>
After the user selects a device to pair with this origin,
the origin is allowed to access to any service whose UUID was listed
in the {{BluetoothScanFilter/services}} list
in any element of <code>options.filters</code>
or in <code>options.<dfn dict-member for="RequestDeviceOptions">optionalServices</dfn></code>.
</p>
<p>
This implies that if developers filter just by name,
they must use {{RequestDeviceOptions/optionalServices}} to get access to any services.
</p>
</div>
<div class="example" id="example-filter-by-services">
<p>
Say the UA is close to the following devices:
</p>
<table class="data">
<thead><th>Device</th><th>Advertised Services</th></thead>
<tr><td>D1</td><td>A, B, C, D</td></tr>
<tr><td>D2</td><td>A, B, E</td></tr>
<tr><td>D3</td><td>C, D</td></tr>
<tr><td>D4</td><td>E</td></tr>
<tr><td>D5</td><td><i><none></i></td></tr>
</table>
<p>
If the website calls
</p>
<pre highlight="js">
navigator.bluetooth.requestDevice({
filters: [ {services: [A, B]} ]
});
</pre>
<p>
the user will be shown a dialog containing devices D1 and D2.
If the user selects D1, the website will not be able to access services C or D.
If the user selects D2, the website will not be able to access service E.
</p>
<p>
On the other hand, if the website calls
</p>
<pre highlight="js">
navigator.bluetooth.requestDevice({
filters: [
{services: [A, B]},
{services: [C, D]}
]
});
</pre>
<p>
the dialog will contain devices D1, D2, and D3,
and if the user selects D1,
the website will be able to access services A, B, C, and D.
</p>
<p>
The <code>optionalServices</code> list doesn't add any devices
to the dialog the user sees,
but it does affect which services the website can use from the device the user picks.
</p>
<pre highlight="js">
navigator.bluetooth.requestDevice({
filters: [ {services: [A, B]} ],
optionalServices: \[E]
});
</pre>
<p>
Shows a dialog containing D1 and D2,
but not D4, since D4 doesn't contain the required services.
If the user selects D2, unlike in the first example,
the website will be able to access services A, B, and E.
</p>
<p>
The allowed services also apply if the device changes after the user grants access.
For example, if the user selects D1 in the previous <code>requestDevice()</code> call,
and D1 later adds a new E service,
that will fire the {{serviceadded}} event,
and the web page will be able to access service E.
</p>
</div>
<div class="example" id="example-filter-by-name">
<p>
Say the devices in the <a href="#example-filter-by-services">previous example</a>
also advertise names as follows:
</p>
<table class="data">
<thead><th>Device</th><th>Advertised Device Name</th></thead>
<tr><td>D1</td><td>First De…</td></tr>
<tr><td>D2</td><td><i><none></i></td></tr>
<tr><td>D3</td><td>Device Third</td></tr>
<tr><td>D4</td><td>Device Fourth</td></tr>
<tr><td>D5</td><td>Unique Name</td></tr>
</table>
<p>
The following table shows which devices the user can select between
for several values of <var>filters</var> passed to
<code>navigator.bluetooth.requestDevice({filters: <var>filters</var>})</code>.
</p>
<table class="data">
<thead><th><var>filters</var></th><th>Devices</th><th>Notes</th></thead>
<tr>
<td>
<pre highlight="js">
[{name: "Unique Name"}]
</pre>
</td>
<td>D5</td>
<td></td>
</tr>
<tr>
<td>
<pre highlight="js">
[{namePrefix: "Device"}]
</pre>
</td>
<td>D3, D4</td>
<td></td>
</tr>
<tr>
<td>
<pre highlight="js">
[{name: "First De"},
{name: "First Device"}]
</pre>
</td>
<td><i><none></i></td>
<td>
D1 only advertises a prefix of its name,
so trying to match its whole name fails.
</td>
</tr>
<tr>
<td>
<pre highlight="js">
[{namePrefix: "First"},
{name: "Unique Name"}]
</pre>
</td>
<td>D1, D5</td>
<td></td>
</tr>
<tr>
<td>
<pre highlight="js">
[{services: \[C],
namePrefix: "Device"},
{name: "Unique Name"}]
</pre>
</td>
<td>D3, D5</td>
<td></td>
</tr>
</table>
</div>
<div class="example" id="example-disallowed-filters">
<p>
Filters that either accept or reject all possible devices cause {{TypeError}}s.
</p>
<table class="data">
<thead><th><var>filters</var></th><th>Notes</th></thead>
<tr>
<td>
<pre highlight="js">
[]
</pre>
</td>
<td>An empty list of filters doesn't accept any devices.</td>
</tr>
<tr>
<td>
<pre highlight="js">
[{}]
</pre>
</td>
<td>An empty filter accepts all devices, and so isn't allowed either.</td>
</tr>
<tr>
<td>
<pre highlight="js">
[{namePrefix: ""}]
</pre>
</td>
<td>`namePrefix`, if present, must be non-empty to filter devices.</td>
</tr>
</table>
</div>
<p>
Instances of {{Bluetooth}} are created with the internal slots
described in the following table:
</p>
<table class="data" dfn-for="Bluetooth" dfn-type="attribute">
<thead>
<th>Internal Slot</th>
<th>Initial Value</th>
<th>Description (non-normative)</th>
</thead>
<tr>
<td><dfn>\[[deviceInstanceMap]]</dfn></td>
<td>
An empty map from <a>Bluetooth device</a>s
to <code>Promise<{{BluetoothDevice}}></code> instances.
</td>
<td>
Ensures only one {{BluetoothDevice}} instance represents each <a>Bluetooth device</a>
inside a single global object.
</td>
</tr>
<tr>
<td><dfn>\[[attributeInstanceMap]]</dfn></td>
<td>
An empty map from <a>Bluetooth cache</a> entries to {{Promise}}s.
</td>
<td>
The {{Promise}}s resolve to either {{BluetoothRemoteGATTService}},
{{BluetoothRemoteGATTCharacteristic}}, or {{BluetoothRemoteGATTDescriptor}}
instances.
</td>
</tr>
</table>
<p>
A <a>Bluetooth device</a> <var>device</var>
<dfn local-lt="match a filter|matches any filter">matches a filter</dfn> <var>filter</var>
if the following steps return `match`:
</p>
<ol class="algorithm">
<li>
If <code><var>filter</var>.name</code> is present then,
if <var>device</var>'s <a>Bluetooth Device Name</a> isn't complete
and equal to <code><var>filter</var>.name</code>,
return `mismatch`.
</li>
<li>
If <code><var>filter</var>.namePrefix</code> is present then
if <var>device</var>'s <a>Bluetooth Device Name</a> isn't present
or doesn't start with <code><var>filter</var>.namePrefix</code>,
return `mismatch`.
</li>
<li>
For each <var>uuid</var> in <code><var>filter</var>.services</code>,
if the UA has not received advertising data, an <a>extended inquiry response</a>,
or a service discovery response indicating that
the device supports a primary (vs included) service with UUID <var>uuid</var>,
return `mismatch`.
</li>
<li>Return `match`.
</ol>
<p class="note">
The list of Service UUIDs that a device advertises
might not include all the UUIDs the device supports.
The advertising data does specify whether this list is complete.
If a website filters for a UUID that a nearby device supports but doesn't advertise,
that device might not be included in the list of devices presented to the user.
The UA would need to connect to the device to discover the full list of supported services,
which can impair radio performance and cause delays, so this spec doesn't require it.
</p>
<p>
The <code><dfn method for="Bluetooth">requestDevice(<var>options</var>)</dfn></code> method,
when invoked, MUST return <a>a new promise</a> <var>promise</var>
and run the following steps <a>in parallel</a>:
</p>
<ol class="algorithm">
<li id="requestDevice-secure-context">
If the <a>incumbent settings object</a> is not a <a>secure context</a>,
<a>reject</a> <var>promise</var> with a {{SecurityError}} and abort these steps.
</li>
<li id="requestDevice-user-gesture">
If the algorithm is not <a>allowed to show a popup</a>,
<a>reject</a> <var>promise</var> with a {{SecurityError}} and abort these steps.
</li>
<li>
In order to convert the arguments from service names and aliases to just <a>UUID</a>s,
do the following substeps:
<ol>
<li>
If <code><var>options</var>.filters.length === 0</code>,
<a>reject</a> <var>promise</var> with a {{TypeError}}
and abort these steps.
</li>
<li>
Let <var>uuidFilters</var> be a new {{Array}} and
<var>requiredServiceUUIDs</var> be a new {{Set}}.
</li>
<li>
For each <var>filter</var> in <code><var>options</var>.filters</code>,
do the following steps:
<ol>
<li>
If none of <var>filter</var>'s
<code>services</code>, <code>name</code>, or <code>namePrefix</code> members is present,
<a>reject</a> <var>promise</var> with a {{TypeError}}
and abort these steps.
</li>
<li>Let <var>canonicalizedFilter</var> be `{}`.</li>
<li>
If <code><var>filter</var>.services</code> is present, do the following sub-steps:
<ol>
<li>
If <code><var>filter</var>.services.length === 0</code>,
<a>reject</a> <var>promise</var> with a {{TypeError}}
and abort these steps.
</li>
<li>
Let <var>services</var> be
<code>{{Array.prototype.map}}.call(<var>filter</var>.services,
{{BluetoothUUID/getService()|BluetoothUUID.getService}})</code>.
</li>
<li>
If any of the {{BluetoothUUID/getService()|BluetoothUUID.getService()}} calls threw an exception,
<a>reject</a> <var>promise</var> with that exception and abort these steps.
</li>
<li>
If any <var>service</var> in <var>services</var> is <a>blacklisted</a>,
<a>reject</a> <var>promise</var> with a {{SecurityError}}
and abort these steps.
</li>
<li>
Set <code><var>canonicalizedFilter</var>.services</code> to <var>services</var>.
</li>
<li>Add the elements of <var>services</var> to <var>requiredServiceUUIDs</var>.</li>
</ol>
</li>
<li>
If <code><var>filter</var>.name</code> is present, do the following sub-steps.
<ol>
<li>
If the <a lt="utf-8 encode">UTF-8 encoding</a>
of <code><var>filter</var>.name</code>
is more than 248 bytes long,
<a>reject</a> <var>promise</var> with a {{TypeError}}
and abort these steps.
<p class="note">
248 is the maximum number of UTF-8 code units in
a <a>Bluetooth Device Name</a>.
</p>
</li>
<li>
Set <code><var>canonicalizedFilter</var>.name</code>
to <code><var>filter</var>.name</code>.
</li>
</ol>
</li>
<li>
If <code><var>filter</var>.namePrefix</code> is present, do the following sub-steps.
<ol>
<li>
If <code><var>filter</var>.namePrefix.length === 0</code>
or if the <a lt="utf-8 encode">UTF-8 encoding</a>
of <code><var>filter</var>.namePrefix</code>
is more than 248 bytes long,
<a>reject</a> <var>promise</var> with a {{TypeError}}
and abort these steps.
<p class="note">
248 is the maximum number of UTF-8 code units in
a <a>Bluetooth Device Name</a>.
</p>
</li>
<li>
Set <code><var>canonicalizedFilter</var>.namePrefix</code>
to <code><var>filter</var>.namePrefix</code>.
</li>
</ol>
</li>
<li>Append <var>canonicalizedFilter</var> to <var>uuidFilters</var>.</li>
</ol>
</li>
<li>
Let <var>optionalServiceUUIDs</var> be
<code>{{Array.prototype.map}}.call(<var>options</var>.optionalServices,
{{BluetoothUUID/getService()|BluetoothUUID.getService}})</code>.
</li>
<li>
If any of the {{BluetoothUUID/getService()|BluetoothUUID.getService()}} calls threw an exception,
<a>reject</a> <var>promise</var> with that exception and abort these steps.
</li>
<li>
Remove from <var>optionalServiceUUIDs</var> any UUIDs that are <a>blacklisted</a>.
</li>
</ol>
</li>
<li>
<a>Scan for devices</a> with
<var>requiredServiceUUIDs</var>
as the <var>set of <a>Service</a> UUIDs</var>,
and let <var>scanResult</var> be the result.
</li>
<li>
Remove devices from <var>scanResult</var> if
they do not <a>match a filter</a>
in <var>uuidFilters</var>.
</li>
<li id="requestDevice-prompt">
Even if <var>scanResult</var> is empty,
display a prompt to the user requesting that the user select a device from it.
The UA SHOULD show the user the human-readable name of each device.
If this name is not available because the UA's Bluetooth system doesn't support privacy-enabled scans,
the UA SHOULD allow the user to indicate interest and then perform a privacy-disabled scan to retrieve the name.
<p>
The UA MAY allow the user to select a nearby device
that does not match <var>uuidFilters</var>.
</p>
</li>
<li>
Wait for the user to have selected a <var>device</var> or cancelled the prompt.
</li>
<li>
If the user cancels the prompt,
<a>reject</a> <var>promise</var> with a {{NotFoundError}} and abort these steps.
</li>
<li>
<a lt="add an allowed bluetooth device"