forked from WebBluetoothCG/web-bluetooth
-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.html
3667 lines (3459 loc) · 163 KB
/
index.html
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
<!DOCTYPE html>
<html>
<head>
<title>Web Bluetooth</title>
<meta charset='utf-8'>
<script src='https://www.w3.org/Tools/respec/respec-w3c-common'
async class='remove'></script>
<script class='remove'>
var respecConfig = {
specStatus: "CG-DRAFT",
shortName: "web-bluetooth",
includePermalinks: true,
editors: [
{ name: "See contributors on GitHub",
url: "https://github.com/WebBluetoothCG/web-bluetooth/graphs/contributors"
},
],
edDraftURI: "https://webbluetoothcg.github.io/web-bluetooth/",
wg: "Web Bluetooth Community Group",
wgURI: "https://www.w3.org/community/web-bluetooth/",
wgPublicList: "public-web-bluetooth",
wgPatentURI: "https://www.w3.org/2004/01/pp-impl/72794/status",
otherLinks: [{
key: "Participate",
data: [
{ value: "Join the W3C Community Group",
href: "https://www.w3.org/community/web-bluetooth/",
},
{ value: "File bugs and feature requests",
href: "https://github.com/WebBluetoothCG/web-bluetooth/issues",
},
{ value: "Fix the text through GitHub",
href: "https://github.com/WebBluetoothCG/web-bluetooth",
},
{ value: "IRC: #web-bluetooth on W3C's IRC",
href: "irc://irc.w3.org:6665/#web-bluetooth",
},
{ value: "Public Mailing List",
href: "https://lists.w3.org/Archives/Public/public-web-bluetooth/",
},
],
}],
localBiblio: {
"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",
},
},
};
</script>
</head>
<body>
<section id='abstract'>
<p>
This document describes an API to discover and communicate with devices over the Bluetooth 4 wireless standard using the Generic Attribute Profile (GATT).
</p>
</section>
<section id='sotd'>
<p>
Changes to this document may be tracked at <a href="https://github.com/WebBluetoothCG/web-bluetooth/commits/gh-pages">https://github.com/WebBluetoothCG/web-bluetooth/commits/gh-pages</a>.
</p>
</section>
<section class='informative'>
<h2>Introduction</h2>
<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 title="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 <a href="#information-model"></a> 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>
<h2>Examples</h2>
<aside class="example">
<p>
To discover and retrieve data from a standard heart rate monitor,
a website would use code like the following:
</p>
<pre class="highlight">let chosenHeartRateService = null;
navigator.bluetooth.<a for="Bluetooth">requestDevice</a>({
filters: [{
services: ['heart_rate'],
}]
}).then(device => device.<a for="BluetoothDevice">connectGATT</a>())
.then(server => server.<a for="BluetoothGATTRemoteServer">getPrimaryService</a>(<a
href="https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.heart_rate.xml"
>'heart_rate'</a>))
.then(service => {
chosenHeartRateService = service;
return Promise.all([
service.<a for="BluetoothGATTService">getCharacteristic</a>(<a
href="https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.body_sensor_location.xml"
>'body_sensor_location'</a>)
.then(handleBodySensorLocationCharacteristic),
service.<a for="BluetoothGATTService">getCharacteristic</a>(<a
href="https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml"
>'heart_rate_measurement'</a>))
.then(handleHeartRateMeasurementCharacteristic),
]);
});
function handleBodySensorLocationCharacteristic(characteristic) {
if (characteristic === null) {
console.log("Unknown sensor location.");
return Promise.resolve();
}
return characteristic.<a for="BluetoothGATTCharacteristic">readValue</a>()
.then(sensorLocationArray => {
let sensorLocation = new Uint8Array(sensorLocationArray)[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>characteristicvaluechanged</a>', onHeartRateChanged);
return characteristic.<a for="BluetoothGATTCharacteristic">startNotifications</a>();
}
function onHeartRateChanged(event) {
let characteristic = event.target;
console.log(parseHeartRate(characteristic.<a for="BluetoothGATTCharacteristic">value</a>));
}</pre>
<p>
<code>parseHeartRate()</code> would be defined using the <a
href="https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml"
>heart_rate_measurement documentation</a> to read the <a>ArrayBuffer</a> stored
in a <a>BluetoothGATTCharacteristic</a>'s
<a for="BluetoothGATTCharacteristic">value</a> field.
</p>
<pre class="highlight">function parseHeartRate(buffer) {
let data = new DataView(buffer);
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 class="highlight">{
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
<a href="https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_control_point.xml"
>heart_rate_control_point</a> characteristic:
</p>
<pre class="highlight">function resetEnergyExpended() {
if (!chosenHeartRateService) {
return Promise.reject(new Error('No heart rate sensor selected yet.'));
}
return chosenHeartRateService.<a for="BluetoothGATTService">getCharacteristic</a>(<a
href="https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_control_point.xml"
>'heart_rate_control_point'</a>))
.then(controlPoint => {
let resetEnergyExpended = new Uint8Array([1]);
return controlPoint.<a for="BluetoothGATTCharacteristic">writeValue</a>(resetEnergyExpended);
});
}</pre>
</aside>
</section>
</section>
<section>
<h2>Security and privacy considerations</h2>
<section>
<h2>Device access is powerful</h2>
<p>
When a website requests access to devices using
<code><a for="Bluetooth">requestDevice</a></code>,
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 revoke 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 class="informative">
<h2>Attacks on devices</h2>
<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 class="informative">
<h2>Bluetooth device identifiers</h2>
<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 class="informative">
<h2>Identifiers for remote Bluetooth devices</h2>
<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>
Because it would be so easy to work around an attempt to block device identification,
this spec doesn't try to do so.
</section>
<section class="informative">
<h2>The UA's Bluetooth address</h2>
<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>Device Discovery</h2>
<pre class="idl">
dictionary BluetoothScanFilter {
sequence<BluetoothServiceUUID> services;
};
dictionary RequestDeviceOptions {
required sequence<BluetoothScanFilter> filters;
sequence<BluetoothServiceUUID> optionalServices = [];
};
interface Bluetooth {
Promise<BluetoothDevice> requestDevice(RequestDeviceOptions options);
};
Bluetooth implements EventTarget;
Bluetooth implements CharacteristicEventHandlers;
Bluetooth implements ServiceEventHandlers;
</pre>
<div class="note" title="Bluetooth members">
<p>
<code><a for="Bluetooth">requestDevice</a>(options)</code> asks the user
to grant this origin access to a device
that <a>matches a filter</a> in <code>options.<dfn for="RequestDeviceOptions">filters</dfn></code>.
The user will be shown devices that support
<em>all</em> the GATT service UUIDs in the <dfn for="BluetoothScanFilter">services</dfn> list
of <em>any</em> <a>BluetoothScanFilter</a> in <code>filters</code>.
</p>
<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 <a for="BluetoothScanFilter">services</a> list
in any element of <code>options.filters</code>
or in <code>options.<dfn for="RequestDeviceOptions">optionalServices</dfn></code>.
</p>
</div>
<aside class="example">
<p>
Say the UA is close to the following devices:
</p>
<table>
<tr><th>Device</th><th>Advertised Services</th></tr>
<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>
</table>
<p>
If the website calls
</p>
<pre class="highlight">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 class="highlight">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 class="highlight">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 <code><a>serviceadded</a></code> event,
and the web page will be able to access service E.
</p>
</aside>
<p>
A device <dfn>matches a filter</dfn> <var>filter</var> if
the UA has received advertising data, an <a>extended inquiry response</a>,
or a service discovery response indicating that
the device supports each of the <a>Service</a> UUIDs
included in <code><var>filter</var>.services</code> as a primary (vs included) service.
</p>
<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 dfn-for="Bluetooth">
The <code><dfn>requestDevice</dfn>(<var>options</var>)</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>
<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 <a>SecurityError</a> 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 <a>SecurityError</a> 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>
Let <var>uuidFilters</var> be a new <a>Array</a> and
<var>requiredServiceUUIDs</var> be a new <a>Set</a>.
</li>
<li>
For each <var>filter</var> in <code><var>options</var>.filters</code>,
do the following steps:
<ol>
<li>
If <code><var>filter</var>.services.length === 0</code>,
<a>reject</a> <var>promise</var> with a <a>TypeError</a>
and abort these steps.
</li>
<li>
Let <var>services</var> be
<code>Array.prototype.map.call(<var>filter</var>.services,
<a>BluetoothUUID.getService</a>)</code>.
</li>
<li>
If any of the <a>BluetoothUUID.getService</a> 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 <a>SecurityError</a>
and abort these steps.
</li>
<li>Append <code>{services: <var>services</var>}</code> to <var>uuidFilters</var>.</li>
<li>Add the elements of <var>services</var> to <var>requiredServiceUUIDs</var>.</li>
</ol>
</li>
<li>
Let <var>optionalServiceUUIDs</var> be
<code>Array.prototype.map.call(<var>options</var>.optionalServices,
<a>BluetoothUUID.getService</a>)</code>.
</li>
<li>
If any of the <a>BluetoothUUID.getService</a> 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 title="matches a filter">match a filter</a>
in <var>uuidFilters</var>.
</li>
<li>
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 <a>NotFoundError</a> and abort these steps.
</li>
<li>
<a title="add an allowed bluetooth device"
>Add <var>device</var> to the origin's allowed devices map.</a>
with the union of <var>requiredServiceUUIDs</var>
and <var>optionalServiceUUIDs</var> as <var>allowed services</var>.
</li>
<li>
<a>Get the <code>BluetoothDevice</code> representing</a> <var>device</var>
and <a>resolve</a> <var>promise</var> with the result.
</li>
</ol>
<p>
To <dfn>scan for devices</dfn> with
an optional <var>set of <a>Service</a> UUIDs</var>, defaulting to the set of all UUIDs,
the UA MUST perform the following steps:
<ol>
<li>
If the UA has scanned for devices recently
<span class="issue">TODO: Nail down the amount of time.</span>
with a set of UUIDs that was a superset of the UUIDs for the current scan,
then the UA MAY return the result of that scan and abort these steps.
</li>
<li>Let <var>nearbyDevices</var> be a set of <a>Bluetooth device</a>s, initially empty.</li>
<li>
If the UA supports the LE transport, perform the <a>General Discovery Procedure</a>
and add the discovered <a>Bluetooth device</a>s to <var>nearbyDevices</var>.
The UA SHOULD enable the <a>Privacy Feature</a>.
<p class="issue">
Both <a>passive scanning</a> and
the <a>Privacy Feature</a> avoid leaking the unique, immutable device ID.
We ought to require UAs to use either one,
but none of the OS APIs appear to expose either.
Bluetooth also makes it hard to use <a>passive scanning</a> since
it doesn't require <a>Central</a> devices to support the
<a>Observation Procedure</a>.
</p>
</li>
<li>
If the UA supports the BR/EDR transport, perform the <a>Device Discovery</a> procedure
and add the discovered <a>Bluetooth device</a>s to <var>nearbyDevices</var>.
<p class="issue">
All forms of BR/EDR inquiry/discovery
appear to leak the unique, immutable device address.
</p>
</li>
<li>Let <var>result</var> be a set of <a>Bluetooth device</a>s, initially empty.</li>
<li>
For each <a>Bluetooth device</a> <var>device</var> in <var>nearbyDevices</var>,
do the following substeps:
<ol>
<li>
If <var>device</var>'s <a>supported physical transports</a> include LE and
its <a>Bluetooth Device Name</a> is partial or absent,
the UA SHOULD perform the <a>Name Discovery Procedure</a>
to acquire a complete name.
</li>
<li>
If <var>device</var>'s advertised <a title="Service UUID Data Type">Service UUIDs</a>
have a non-empty intersection with the <var>set of <a>Service</a> UUIDs</var>,
add <var>device</var> to <var>result</var> and abort these substeps.
<p class="note">
For BR/EDR devices, there is no way to distinguish GATT from non-GATT services
in the <a>Extended Inquiry Response</a>.
If a site filters to the UUID of a non-GATT service,
the user may be able to select a device
for the result of <code>requestDevice</code>
that this API provides no way to interact with.
</p>
</li>
<li>
The UA MAY connect to <var>device</var> and <a>populate the Bluetooth cache</a>
with all Services whose UUIDs are in the <var>set of <a>Service</a> UUIDs</var>.
If <var>device</var>'s <a>supported physical transports</a> include BR/EDR,
then in addition to the standard GATT procedures,
the UA MAY use the Service Discovery Protocol (<a>Searching for Services</a>)
when populating the cache.
<div class="note" id="note-extra-discovery">
<p>
Connecting to every nearby device to discover services costs power and
can slow down other use of the Bluetooth radio.
UAs should only discover extra services on a device if
they have some reason to expect that device to be interesting.
</p>
<p>
UAs should also help developers avoid relying on this extra discovery behavior.
For example, say a developer has previously connected to a device,
so the UA knows the device's full set of supported services.
If this developer then filters using a non-advertised UUID,
the dialog they see may include this device,
even if the filter would likely exclude the device on users' machines.
The UA could provide a developer option to warn when this happens or
to include only advertised services in matching filters.
</p>
</div>
</li>
<li>
If the <a>Bluetooth cache</a> contains
known-present Services inside <var>device</var>
with UUIDs in the <var>set of <a>Service</a> UUIDs</var>,
the UA MAY add <var>device</var> to <var>result</var>.
</li>
</ol>
</li>
<li>Return <var>result</var> from the scan.</li>
</ol>
<p class="issue">
We need a way for a site to register to receive an event
when an interesting device comes within range.
</p>
</section>
<section>
<h2>Device Representation</h2>
<p>
The UA needs to track Bluetooth device properties at several levels:
globally, per origin, and per <a>script execution environment</a>.
</p>
<section>
<h2>Global Bluetooth device properties</h2>
<p>
The physical Bluetooth device may be guaranteed to have
some properties that the UA may not have received.
Those properties are described as optional here.
</p>
<p>
A <dfn>Bluetooth device</dfn> has the following properties.
Optional properties are not present, and sequence and map properties are empty,
unless/until described otherwise.
Other properties have a default specified or are specified when a device is introduced.
</p>
<ul>
<li>
A set of <dfn>supported physical transports</dfn>,
including one or both of BR/EDR and LE.
This set will generally be filled based on
the transports over which the device was discovered
and the <a>Flags Data Type</a>
in the <a>Advertising Data</a> or <a>Extended Inquiry Response</a>.
</li>
<li>
One or more of several kinds of 48-bit address:
a <a>Public Bluetooth Address</a>, a (random) <a>Static Address</a>,
and a resolvable or non-resolvable <a>Private Address</a>.
</li>
<li>An optional 128-bit <a>Identity Resolving Key</a>.</li>
<li>
An optional partial or complete <a>Bluetooth Device Name</a>.
A device has a partial name when the <a>Shortened Local Name</a> AD data was received,
but the full name hasn't been read yet.
</li>
<li>
An <a>ATT Bearer</a>, over which all GATT communication happens.
The <a>ATT Bearer</a> is created by procedures described in
"Connection Establishment" under <a>GAP Interoperability Requirements</a>.
It is disconnected in ways [[BLUETOOTH42]] isn't entirely clear about.
</li>
<li>
A <dfn>bonded flag</dfn>, indicating whether the Bluetooth device is bonded to the UA.
Devices bond using the <a>Bonding Procedure</a>.
<p class="note">
The Bluetooth standard uses "pairing" to refer to
creating and exchanging a shared secret,
and "bonding" to refer to storing that secret for use in subsequent connections.
Platform APIs don't give independent control of the two processes,
and they present the combined process to users under the label "pairing",
so this specification uses "pairing" and "bonding" interchangeably
to refer to Bluetooth bonding.
</p>
</li>
<li>
The following optional elements
from <a>Advertising Data</a> or the <a>Extended Inquiry Response</a>:
<ul>
<li>A list of advertised <a title="Service UUID Data Type">Service UUID</a>s.</li>
<li>
<a>Manufacturer Specific Data</a>,
consisting of a map from 16-bit Company Identifier Codes to byte arrays.
This is treated as an empty map if it's not present.
</li>
<li>A <a>TX Power Level</a>, from -127dBm to 127dBm.</li>
<li>
<a>Service Data</a>, consisting of a map from UUIDs to byte arrays.
This is treated as an empty map if it's not present.
</li>
<li>
An <a>Appearance</a>, one of the values defined by
the <a>org.bluetooth.characteristic.gap.appearance</a> characteristic.
</li>
<li>
A <a>Received Signal Strength Indication</a> (RSSI), from -127dBm to 20dBm.
This is received from the local Bluetooth adapter
rather than being sent by the remote device.
</li>
</ul>
</li>
<li>
An optional <a>Class of Device</a>,
one of the values defined in [[!BLUETOOTH-ASSIGNED-BASEBAND]].
The Class of Device is only used over the BR/EDR physical transport, not LE.
</li>
<li>
A hierarchy of GATT attributes, described in <a href="#information-model"></a>.
</li>
</ul>
<p>
The UA SHOULD determine that
two <a>Bluetooth device</a>s are the <dfn>same device</dfn> if and only if
they have the same <a>Public Bluetooth Address</a>, <a>Static Address</a>,
<a>Private Address</a>, or <a>Identity Resolving Key</a>,
or if the <a>Resolvable Private Address Resolution Procedure</a> succeeds using
one device's IRK and the other's Resolvable <a>Private Address</a>.
However, because platform APIs don't document how they determine device identity,
the UA MAY use another procedure.
</p>
</section>
<section>
<h2>Per-origin Bluetooth device properties</h2>
<p>
For each origin, the UA MUST maintain an <dfn>allowed devices map</dfn>,
whose keys are the <a>Bluetooth device</a>s the origin is allowed to access,
and whose values are pairs of a <code>DOMString</code> <dfn>device id</dfn> and
an <dfn>allowed services list</dfn> consisting of UUIDs
for GATT Primary <a>Service</a>s the origin is allowed to access on the device.
</p>
<p>
The UA MAY remove devices from the <a>allowed devices map</a> at any time
based on signals from the user.
<span class="issue">
This needs a definition involving
removing <a>BluetoothDevice</a> instances from <a>device instance map</a>s
and clearing out their [[\representedDevice]] fields.
</span>
For example, if the user chooses not to remember access,
the UA might remove a device when the tab that was granted access to it is closed.
Or the UA might provide a revocation UI that allows the user
to explicitly remove a device even while a tab is actively using that device.
If a device is removed from this list while
a <a>Promise</a> is pending to do something with the device,
it MUST be treated the same as if the device moved out of Bluetooth range.
</p>
<p>
To <dfn>add an allowed <a>Bluetooth device</a></dfn> <var>device</var>
to an origin's <a>allowed devices map</a>
with an optional <var>allowed services</var> list of <a>UUID</a>s,
the UA MUST run the following steps.
</p>
<ol>
<li>
If <var>device</var> is the <a>same device</a> as
an existing key in the <a>allowed devices map</a>,
abort these steps.
</li>
<li>
Let <var>id</var> be a new <code>DOMString</code> that
isn't equal to any of the <a>device id</a>s in the origin's <a>allowed devices map</a>
and isn't equal to or derived from any of <var>device</var>'s
<a>Public Bluetooth Address</a>, <a>Static Address</a>,
or <a>Identity Resolving Key</a>.
<var>id</var> MUST, with very high probability,
be unequal to the <a>device id</a>s used for keys
in any other origin's <a>allowed devices map</a>
or previously in this origin's <a>allowed devices map</a>
that are the <a>same device</a> as <var>device</var>.
</li>
<li>
If <var>allowed services</var> wasn't provided, set it to an empty list.
</li>
<li>
Add a mapping from <var>device</var>
to a pair of <var>id</var> and <var>allowed services</var>
in the <a>allowed devices map</a>.
</li>
</ol>
</section>
<section dfn-for="BluetoothDevice">
<h2><dfn>BluetoothDevice</dfn></h2>
<p>
A <a>BluetoothDevice</a> instance represents a <a>Bluetooth device</a>
inside a particular <a>script execution environment</a>.
</p>
<pre class="idl">
// Allocation authorities for Vendor IDs:
enum VendorIDSource {
"bluetooth",
"usb"
};
interface BluetoothDevice {
readonly attribute DOMString instanceID;
readonly attribute DOMString? name;
readonly attribute BluetoothAdvertisingData adData;
readonly attribute unsigned long? deviceClass;
readonly attribute VendorIDSource? vendorIDSource;
readonly attribute unsigned long? vendorID;
readonly attribute unsigned long? productID;
readonly attribute unsigned long? productVersion;
readonly attribute boolean paired;
readonly attribute BluetoothGATTRemoteServer? gattServer;
readonly attribute UUID[] uuids;
Promise<BluetoothGATTRemoteServer> connectGATT();
};
BluetoothDevice implements EventTarget;
BluetoothDevice implements CharacteristicEventHandlers;
BluetoothDevice implements ServiceEventHandlers;
</pre>
<div class="note" title="BluetoothDevice attributes">
<p>
<dfn>instanceID</dfn> uniquely identifies a device to the extent that
the UA can determine that two Bluetooth connections are to the same device.
It is computed as the <a>device id</a> in <a>add an allowed Bluetooth device</a>.
This ID can't be used to match a device across origins
or after the user revokes and re-grants access to the device.
</p>
<p>
<dfn>name</dfn> is the human-readable name of the device.
</p>
<p>
<dfn>adData</dfn> contains the most recent advertising data received for this device.