1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 // When a device can't be found in the BluetoothAdapter, that generally
7 // indicates that it's gone out of range. We reject with a NetworkError in that
9 // https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothdevice-connectgatt
11 #include "content/browser/bluetooth/bluetooth_dispatcher_host.h"
13 #include "base/bind.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/thread_task_runner_handle.h"
17 #include "content/browser/bad_message.h"
18 #include "content/browser/bluetooth/bluetooth_metrics.h"
19 #include "content/browser/bluetooth/first_device_bluetooth_chooser.h"
20 #include "content/browser/frame_host/render_frame_host_impl.h"
21 #include "content/common/bluetooth/bluetooth_messages.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_delegate.h"
24 #include "device/bluetooth/bluetooth_adapter.h"
25 #include "device/bluetooth/bluetooth_adapter_factory.h"
26 #include "device/bluetooth/bluetooth_device.h"
27 #include "device/bluetooth/bluetooth_discovery_session.h"
28 #include "device/bluetooth/bluetooth_gatt_characteristic.h"
29 #include "device/bluetooth/bluetooth_gatt_service.h"
31 using blink::WebBluetoothError
;
32 using device::BluetoothAdapter
;
33 using device::BluetoothAdapterFactory
;
34 using device::BluetoothGattCharacteristic
;
35 using device::BluetoothGattService
;
36 using device::BluetoothUUID
;
42 // TODO(ortuno): Once we have a chooser for scanning, a way to control that
43 // chooser from tests, and the right callback for discovered services we should
44 // delete these constants.
45 // https://crbug.com/436280 and https://crbug.com/484504
46 const int kDelayTime
= 5; // 5 seconds for scanning and discovering
47 const int kTestingDelayTime
= 0; // No need to wait during tests
50 // https://webbluetoothchrome.github.io/web-bluetooth/#dfn-matches-a-filter
51 bool MatchesFilter(const std::set
<BluetoothUUID
>& device_uuids
,
52 const content::BluetoothScanFilter
& filter
) {
53 if (filter
.services
.empty())
55 for (const BluetoothUUID
& service
: filter
.services
) {
56 if (!ContainsKey(device_uuids
, service
)) {
63 bool MatchesFilters(const device::BluetoothDevice
& device
,
64 const std::vector
<content::BluetoothScanFilter
>& filters
) {
65 const std::vector
<BluetoothUUID
>& device_uuid_list
= device
.GetUUIDs();
66 const std::set
<BluetoothUUID
> device_uuids(device_uuid_list
.begin(),
67 device_uuid_list
.end());
68 for (const content::BluetoothScanFilter
& filter
: filters
) {
69 if (MatchesFilter(device_uuids
, filter
)) {
76 WebBluetoothError
TranslateConnectError(
77 device::BluetoothDevice::ConnectErrorCode error_code
) {
79 case device::BluetoothDevice::ERROR_UNKNOWN
:
80 RecordConnectGATTOutcome(UMAConnectGATTOutcome::UNKNOWN
);
81 return WebBluetoothError::ConnectUnknownError
;
82 case device::BluetoothDevice::ERROR_INPROGRESS
:
83 RecordConnectGATTOutcome(UMAConnectGATTOutcome::IN_PROGRESS
);
84 return WebBluetoothError::ConnectAlreadyInProgress
;
85 case device::BluetoothDevice::ERROR_FAILED
:
86 RecordConnectGATTOutcome(UMAConnectGATTOutcome::FAILED
);
87 return WebBluetoothError::ConnectUnknownFailure
;
88 case device::BluetoothDevice::ERROR_AUTH_FAILED
:
89 RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_FAILED
);
90 return WebBluetoothError::ConnectAuthFailed
;
91 case device::BluetoothDevice::ERROR_AUTH_CANCELED
:
92 RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_CANCELED
);
93 return WebBluetoothError::ConnectAuthCanceled
;
94 case device::BluetoothDevice::ERROR_AUTH_REJECTED
:
95 RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_REJECTED
);
96 return WebBluetoothError::ConnectAuthRejected
;
97 case device::BluetoothDevice::ERROR_AUTH_TIMEOUT
:
98 RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_TIMEOUT
);
99 return WebBluetoothError::ConnectAuthTimeout
;
100 case device::BluetoothDevice::ERROR_UNSUPPORTED_DEVICE
:
101 RecordConnectGATTOutcome(UMAConnectGATTOutcome::UNSUPPORTED_DEVICE
);
102 return WebBluetoothError::ConnectUnsupportedDevice
;
105 return WebBluetoothError::UntranslatedConnectErrorCode
;
108 blink::WebBluetoothError
TranslateGATTError(
109 BluetoothGattService::GattErrorCode error_code
,
110 UMAGATTOperation operation
) {
111 switch (error_code
) {
112 case BluetoothGattService::GATT_ERROR_UNKNOWN
:
113 RecordGATTOperationOutcome(operation
, UMAGATTOperationOutcome::UNKNOWN
);
114 return blink::WebBluetoothError::GATTUnknownError
;
115 case BluetoothGattService::GATT_ERROR_FAILED
:
116 RecordGATTOperationOutcome(operation
, UMAGATTOperationOutcome::FAILED
);
117 return blink::WebBluetoothError::GATTUnknownFailure
;
118 case BluetoothGattService::GATT_ERROR_IN_PROGRESS
:
119 RecordGATTOperationOutcome(operation
,
120 UMAGATTOperationOutcome::IN_PROGRESS
);
121 return blink::WebBluetoothError::GATTOperationInProgress
;
122 case BluetoothGattService::GATT_ERROR_INVALID_LENGTH
:
123 RecordGATTOperationOutcome(operation
,
124 UMAGATTOperationOutcome::INVALID_LENGTH
);
125 return blink::WebBluetoothError::GATTInvalidAttributeLength
;
126 case BluetoothGattService::GATT_ERROR_NOT_PERMITTED
:
127 RecordGATTOperationOutcome(operation
,
128 UMAGATTOperationOutcome::NOT_PERMITTED
);
129 return blink::WebBluetoothError::GATTNotPermitted
;
130 case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED
:
131 RecordGATTOperationOutcome(operation
,
132 UMAGATTOperationOutcome::NOT_AUTHORIZED
);
133 return blink::WebBluetoothError::GATTNotAuthorized
;
134 case BluetoothGattService::GATT_ERROR_NOT_PAIRED
:
135 RecordGATTOperationOutcome(operation
,
136 UMAGATTOperationOutcome::NOT_PAIRED
);
137 return blink::WebBluetoothError::GATTNotPaired
;
138 case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED
:
139 RecordGATTOperationOutcome(operation
,
140 UMAGATTOperationOutcome::NOT_SUPPORTED
);
141 return blink::WebBluetoothError::GATTNotSupported
;
144 return blink::WebBluetoothError::GATTUntranslatedErrorCode
;
147 void StopDiscoverySession(
148 scoped_ptr
<device::BluetoothDiscoverySession
> discovery_session
) {
149 // Nothing goes wrong if the discovery session fails to stop, and we don't
150 // need to wait for it before letting the user's script proceed, so we ignore
152 discovery_session
->Stop(base::Bind(&base::DoNothing
),
153 base::Bind(&base::DoNothing
));
158 BluetoothDispatcherHost::BluetoothDispatcherHost(int render_process_id
)
159 : BrowserMessageFilter(BluetoothMsgStart
),
160 render_process_id_(render_process_id
),
161 current_delay_time_(kDelayTime
),
162 discovery_session_timer_(
164 // TODO(jyasskin): Add a way for tests to control the dialog
165 // directly, and change this to a reasonable discovery timeout.
166 base::TimeDelta::FromSecondsD(current_delay_time_
),
167 base::Bind(&BluetoothDispatcherHost::StopDeviceDiscovery
,
168 // base::Timer guarantees it won't call back after its
169 // destructor starts.
170 base::Unretained(this)),
171 /*is_repeating=*/false),
172 weak_ptr_factory_(this) {
173 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
174 if (BluetoothAdapterFactory::IsBluetoothAdapterAvailable())
175 BluetoothAdapterFactory::GetAdapter(
176 base::Bind(&BluetoothDispatcherHost::set_adapter
,
177 weak_ptr_factory_
.GetWeakPtr()));
180 void BluetoothDispatcherHost::OnDestruct() const {
181 // See class comment: UI Thread Note.
182 BrowserThread::DeleteOnUIThread::Destruct(this);
185 void BluetoothDispatcherHost::OverrideThreadForMessage(
186 const IPC::Message
& message
,
187 content::BrowserThread::ID
* thread
) {
188 // See class comment: UI Thread Note.
189 *thread
= BrowserThread::UI
;
192 bool BluetoothDispatcherHost::OnMessageReceived(const IPC::Message
& message
) {
193 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
195 IPC_BEGIN_MESSAGE_MAP(BluetoothDispatcherHost
, message
)
196 IPC_MESSAGE_HANDLER(BluetoothHostMsg_RequestDevice
, OnRequestDevice
)
197 IPC_MESSAGE_HANDLER(BluetoothHostMsg_ConnectGATT
, OnConnectGATT
)
198 IPC_MESSAGE_HANDLER(BluetoothHostMsg_GetPrimaryService
, OnGetPrimaryService
)
199 IPC_MESSAGE_HANDLER(BluetoothHostMsg_GetCharacteristic
, OnGetCharacteristic
)
200 IPC_MESSAGE_HANDLER(BluetoothHostMsg_ReadValue
, OnReadValue
)
201 IPC_MESSAGE_HANDLER(BluetoothHostMsg_WriteValue
, OnWriteValue
)
202 IPC_MESSAGE_UNHANDLED(handled
= false)
203 IPC_END_MESSAGE_MAP()
207 void BluetoothDispatcherHost::SetBluetoothAdapterForTesting(
208 scoped_refptr
<device::BluetoothAdapter
> mock_adapter
) {
209 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
210 current_delay_time_
= kTestingDelayTime
;
211 // Reset the discovery session timer to use the new delay time.
212 discovery_session_timer_
.Start(
213 FROM_HERE
, base::TimeDelta::FromSecondsD(current_delay_time_
),
214 base::Bind(&BluetoothDispatcherHost::StopDeviceDiscovery
,
215 // base::Timer guarantees it won't call back after its
216 // destructor starts.
217 base::Unretained(this)));
218 set_adapter(mock_adapter
.Pass());
221 BluetoothDispatcherHost::~BluetoothDispatcherHost() {
222 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
223 // Clear adapter, releasing observer references.
224 set_adapter(scoped_refptr
<device::BluetoothAdapter
>());
227 // Stores information associated with an in-progress requestDevice call. This
228 // will include the state of the active chooser dialog in a future patch.
229 struct BluetoothDispatcherHost::RequestDeviceSession
{
231 RequestDeviceSession(int thread_id
,
233 const std::vector
<BluetoothScanFilter
>& filters
,
234 const std::vector
<BluetoothUUID
>& optional_services
)
235 : thread_id(thread_id
),
236 request_id(request_id
),
238 optional_services(optional_services
) {}
240 void AddFilteredDevice(const device::BluetoothDevice
& device
) {
241 if (chooser
&& MatchesFilters(device
, filters
)) {
242 chooser
->AddDevice(device
.GetAddress(), device
.GetName());
247 const int request_id
;
248 const std::vector
<BluetoothScanFilter
> filters
;
249 const std::vector
<BluetoothUUID
> optional_services
;
250 scoped_ptr
<BluetoothChooser
> chooser
;
251 scoped_ptr
<device::BluetoothDiscoverySession
> discovery_session
;
254 void BluetoothDispatcherHost::set_adapter(
255 scoped_refptr
<device::BluetoothAdapter
> adapter
) {
256 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
258 adapter_
->RemoveObserver(this);
261 adapter_
->AddObserver(this);
264 void BluetoothDispatcherHost::StopDeviceDiscovery() {
265 for (IDMap
<RequestDeviceSession
, IDMapOwnPointer
>::iterator
iter(
266 &request_device_sessions_
);
267 !iter
.IsAtEnd(); iter
.Advance()) {
268 RequestDeviceSession
* session
= iter
.GetCurrentValue();
269 if (session
->discovery_session
) {
270 StopDiscoverySession(session
->discovery_session
.Pass());
272 if (session
->chooser
) {
273 session
->chooser
->ShowDiscoveryState(
274 BluetoothChooser::DiscoveryState::IDLE
);
279 void BluetoothDispatcherHost::AdapterPoweredChanged(
280 device::BluetoothAdapter
* adapter
,
282 const BluetoothChooser::AdapterPresence presence
=
283 powered
? BluetoothChooser::AdapterPresence::POWERED_ON
284 : BluetoothChooser::AdapterPresence::POWERED_OFF
;
285 for (IDMap
<RequestDeviceSession
, IDMapOwnPointer
>::iterator
iter(
286 &request_device_sessions_
);
287 !iter
.IsAtEnd(); iter
.Advance()) {
288 RequestDeviceSession
* session
= iter
.GetCurrentValue();
289 if (session
->chooser
)
290 session
->chooser
->SetAdapterPresence(presence
);
294 void BluetoothDispatcherHost::DeviceAdded(device::BluetoothAdapter
* adapter
,
295 device::BluetoothDevice
* device
) {
296 VLOG(1) << "Adding device to all choosers: " << device
->GetAddress();
297 for (IDMap
<RequestDeviceSession
, IDMapOwnPointer
>::iterator
iter(
298 &request_device_sessions_
);
299 !iter
.IsAtEnd(); iter
.Advance()) {
300 RequestDeviceSession
* session
= iter
.GetCurrentValue();
301 session
->AddFilteredDevice(*device
);
305 void BluetoothDispatcherHost::DeviceRemoved(device::BluetoothAdapter
* adapter
,
306 device::BluetoothDevice
* device
) {
307 VLOG(1) << "Marking device removed on all choosers: " << device
->GetAddress();
308 for (IDMap
<RequestDeviceSession
, IDMapOwnPointer
>::iterator
iter(
309 &request_device_sessions_
);
310 !iter
.IsAtEnd(); iter
.Advance()) {
311 RequestDeviceSession
* session
= iter
.GetCurrentValue();
312 if (session
->chooser
) {
313 session
->chooser
->RemoveDevice(device
->GetAddress());
318 static scoped_ptr
<device::BluetoothDiscoveryFilter
> ComputeScanFilter(
319 const std::vector
<BluetoothScanFilter
>& filters
) {
320 std::set
<BluetoothUUID
> services
;
321 for (const BluetoothScanFilter
& filter
: filters
) {
322 services
.insert(filter
.services
.begin(), filter
.services
.end());
324 scoped_ptr
<device::BluetoothDiscoveryFilter
> discovery_filter(
325 new device::BluetoothDiscoveryFilter(
326 device::BluetoothDiscoveryFilter::TRANSPORT_DUAL
));
327 for (const BluetoothUUID
& service
: services
) {
328 discovery_filter
->AddUUID(service
);
330 return discovery_filter
.Pass();
333 void BluetoothDispatcherHost::OnRequestDevice(
336 int frame_routing_id
,
337 const std::vector
<BluetoothScanFilter
>& filters
,
338 const std::vector
<BluetoothUUID
>& optional_services
) {
339 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
340 RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::REQUEST_DEVICE
);
341 RecordRequestDeviceArguments(filters
, optional_services
);
343 VLOG(1) << "requestDevice called with the following filters: ";
344 for (const BluetoothScanFilter
& filter
: filters
) {
346 for (const BluetoothUUID
& service
: filter
.services
)
347 VLOG(1) << "\t\t" << service
.value();
351 VLOG(1) << "requestDevice called with the following optional services: ";
352 for (const BluetoothUUID
& service
: optional_services
)
353 VLOG(1) << "\t" << service
.value();
355 RenderFrameHostImpl
* render_frame_host
=
356 RenderFrameHostImpl::FromID(render_process_id_
, frame_routing_id
);
358 if (!render_frame_host
) {
360 << "Got a requestDevice IPC without a matching RenderFrameHost: "
361 << render_process_id_
<< ", " << frame_routing_id
;
362 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_RENDER_FRAME
);
363 Send(new BluetoothMsg_RequestDeviceError(
364 thread_id
, request_id
, WebBluetoothError::RequestDeviceWithoutFrame
));
369 VLOG(1) << "No BluetoothAdapter. Can't serve requestDevice.";
370 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_BLUETOOTH_ADAPTER
);
371 Send(new BluetoothMsg_RequestDeviceError(
372 thread_id
, request_id
, WebBluetoothError::NoBluetoothAdapter
));
376 if (!adapter_
->IsPresent()) {
377 VLOG(1) << "Bluetooth Adapter not present. Can't serve requestDevice.";
378 RecordRequestDeviceOutcome(
379 UMARequestDeviceOutcome::BLUETOOTH_ADAPTER_NOT_PRESENT
);
380 Send(new BluetoothMsg_RequestDeviceError(
381 thread_id
, request_id
, WebBluetoothError::NoBluetoothAdapter
));
385 // Create storage for the information that backs the chooser, and show the
387 RequestDeviceSession
* const session
= new RequestDeviceSession(
388 thread_id
, request_id
, filters
, optional_services
);
389 int chooser_id
= request_device_sessions_
.Add(session
);
391 BluetoothChooser::EventHandler chooser_event_handler
=
392 base::Bind(&BluetoothDispatcherHost::OnBluetoothChooserEvent
,
393 weak_ptr_factory_
.GetWeakPtr(), chooser_id
);
394 if (WebContents
* web_contents
=
395 WebContents::FromRenderFrameHost(render_frame_host
)) {
396 if (WebContentsDelegate
* delegate
= web_contents
->GetDelegate()) {
397 session
->chooser
= delegate
->RunBluetoothChooser(
398 web_contents
, chooser_event_handler
,
399 render_frame_host
->GetLastCommittedURL().GetOrigin());
402 if (!session
->chooser
) {
404 << "No Bluetooth chooser implementation; falling back to first device.";
405 session
->chooser
.reset(
406 new FirstDeviceBluetoothChooser(chooser_event_handler
));
409 // Populate the initial list of devices.
410 VLOG(1) << "Populating " << adapter_
->GetDevices().size()
411 << " devices in chooser " << chooser_id
;
412 for (const device::BluetoothDevice
* device
: adapter_
->GetDevices()) {
413 VLOG(1) << "\t" << device
->GetAddress();
414 session
->AddFilteredDevice(*device
);
417 if (!session
->chooser
) {
418 // If the dialog's closing, no need to do any of the rest of this.
422 if (!adapter_
->IsPowered()) {
423 session
->chooser
->SetAdapterPresence(
424 BluetoothChooser::AdapterPresence::POWERED_OFF
);
428 // Redundant with the chooser's default; just to be clear:
429 session
->chooser
->ShowDiscoveryState(
430 BluetoothChooser::DiscoveryState::DISCOVERING
);
431 adapter_
->StartDiscoverySessionWithFilter(
432 ComputeScanFilter(filters
),
433 base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStarted
,
434 weak_ptr_factory_
.GetWeakPtr(), chooser_id
),
435 base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStartedError
,
436 weak_ptr_factory_
.GetWeakPtr(), chooser_id
));
439 void BluetoothDispatcherHost::OnConnectGATT(
442 const std::string
& device_instance_id
) {
443 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
444 RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::CONNECT_GATT
);
445 const base::TimeTicks start_time
= base::TimeTicks::Now();
447 // TODO(ortuno): Right now it's pointless to check if the domain has access to
448 // the device, because any domain can connect to any device. But once
449 // permissions are implemented we should check that the domain has access to
450 // the device. https://crbug.com/484745
451 device::BluetoothDevice
* device
= adapter_
->GetDevice(device_instance_id
);
452 if (device
== nullptr) { // See "NETWORK_ERROR Note" above.
453 RecordConnectGATTOutcome(UMAConnectGATTOutcome::NO_DEVICE
);
454 Send(new BluetoothMsg_ConnectGATTError(
455 thread_id
, request_id
, WebBluetoothError::DeviceNoLongerInRange
));
458 device
->CreateGattConnection(
459 base::Bind(&BluetoothDispatcherHost::OnGATTConnectionCreated
,
460 weak_ptr_factory_
.GetWeakPtr(), thread_id
, request_id
,
461 device_instance_id
, start_time
),
462 base::Bind(&BluetoothDispatcherHost::OnCreateGATTConnectionError
,
463 weak_ptr_factory_
.GetWeakPtr(), thread_id
, request_id
,
464 device_instance_id
, start_time
));
467 void BluetoothDispatcherHost::OnGetPrimaryService(
470 const std::string
& device_instance_id
,
471 const std::string
& service_uuid
) {
472 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
473 RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::GET_PRIMARY_SERVICE
);
474 RecordGetPrimaryServiceService(BluetoothUUID(service_uuid
));
476 // TODO(ortuno): Check if device_instance_id is in "allowed devices"
477 // https://crbug.com/493459
478 // TODO(ortuno): Check if service_uuid is in "allowed services"
479 // https://crbug.com/493460
480 // For now just wait a fixed time and call OnServiceDiscovered.
481 // TODO(ortuno): Use callback once it's implemented http://crbug.com/484504
482 BrowserThread::PostDelayedTask(
483 BrowserThread::UI
, FROM_HERE
,
484 base::Bind(&BluetoothDispatcherHost::OnServicesDiscovered
,
485 weak_ptr_factory_
.GetWeakPtr(), thread_id
, request_id
,
486 device_instance_id
, service_uuid
),
487 base::TimeDelta::FromSeconds(current_delay_time_
));
490 void BluetoothDispatcherHost::OnGetCharacteristic(
493 const std::string
& service_instance_id
,
494 const std::string
& characteristic_uuid
) {
495 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
496 RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::GET_CHARACTERISTIC
);
497 RecordGetCharacteristicCharacteristic(characteristic_uuid
);
499 auto device_iter
= service_to_device_
.find(service_instance_id
);
500 // A service_instance_id not in the map implies a hostile renderer
501 // because a renderer obtains the service id from this class and
502 // it will be added to the map at that time.
503 if (device_iter
== service_to_device_
.end()) {
505 bad_message::ReceivedBadMessage(this, bad_message::BDH_INVALID_SERVICE_ID
);
509 // TODO(ortuno): Check if domain has access to device.
510 // https://crbug.com/493459
511 device::BluetoothDevice
* device
=
512 adapter_
->GetDevice(device_iter
->second
/* device_instance_id */);
514 if (device
== nullptr) { // See "NETWORK_ERROR Note" above.
515 RecordGetCharacteristicOutcome(UMAGetCharacteristicOutcome::NO_DEVICE
);
516 Send(new BluetoothMsg_GetCharacteristicError(
517 thread_id
, request_id
, WebBluetoothError::DeviceNoLongerInRange
));
521 // TODO(ortuno): Check if domain has access to service
522 // http://crbug.com/493460
523 device::BluetoothGattService
* service
=
524 device
->GetGattService(service_instance_id
);
526 RecordGetCharacteristicOutcome(UMAGetCharacteristicOutcome::NO_SERVICE
);
527 Send(new BluetoothMsg_GetCharacteristicError(
528 thread_id
, request_id
, WebBluetoothError::ServiceNoLongerExists
));
532 for (BluetoothGattCharacteristic
* characteristic
:
533 service
->GetCharacteristics()) {
534 if (characteristic
->GetUUID().canonical_value() == characteristic_uuid
) {
535 const std::string
& characteristic_instance_id
=
536 characteristic
->GetIdentifier();
538 auto insert_result
= characteristic_to_service_
.insert(
539 make_pair(characteristic_instance_id
, service_instance_id
));
541 // If value is already in map, DCHECK it's valid.
542 if (!insert_result
.second
)
543 DCHECK(insert_result
.first
->second
== service_instance_id
);
545 RecordGetCharacteristicOutcome(UMAGetCharacteristicOutcome::SUCCESS
);
546 // TODO(ortuno): Use generated instance ID instead.
547 // https://crbug.com/495379
548 Send(new BluetoothMsg_GetCharacteristicSuccess(
549 thread_id
, request_id
, characteristic_instance_id
));
553 RecordGetCharacteristicOutcome(UMAGetCharacteristicOutcome::NOT_FOUND
);
554 Send(new BluetoothMsg_GetCharacteristicError(
555 thread_id
, request_id
, WebBluetoothError::CharacteristicNotFound
));
558 void BluetoothDispatcherHost::OnReadValue(
561 const std::string
& characteristic_instance_id
) {
562 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
563 RecordWebBluetoothFunctionCall(
564 UMAWebBluetoothFunction::CHARACTERISTIC_READ_VALUE
);
566 auto characteristic_iter
=
567 characteristic_to_service_
.find(characteristic_instance_id
);
568 // A characteristic_instance_id not in the map implies a hostile renderer
569 // because a renderer obtains the characteristic id from this class and
570 // it will be added to the map at that time.
571 if (characteristic_iter
== characteristic_to_service_
.end()) {
573 bad_message::ReceivedBadMessage(this,
574 bad_message::BDH_INVALID_CHARACTERISTIC_ID
);
577 const std::string
& service_instance_id
= characteristic_iter
->second
;
579 auto device_iter
= service_to_device_
.find(service_instance_id
);
581 CHECK(device_iter
!= service_to_device_
.end());
583 device::BluetoothDevice
* device
=
584 adapter_
->GetDevice(device_iter
->second
/* device_instance_id */);
585 if (device
== nullptr) { // See "NETWORK_ERROR Note" above.
586 RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome::NO_DEVICE
);
587 Send(new BluetoothMsg_ReadCharacteristicValueError(
588 thread_id
, request_id
, WebBluetoothError::DeviceNoLongerInRange
));
592 BluetoothGattService
* service
= device
->GetGattService(service_instance_id
);
593 if (service
== nullptr) {
594 RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome::NO_SERVICE
);
595 Send(new BluetoothMsg_ReadCharacteristicValueError(
596 thread_id
, request_id
, WebBluetoothError::ServiceNoLongerExists
));
600 BluetoothGattCharacteristic
* characteristic
=
601 service
->GetCharacteristic(characteristic_instance_id
);
602 if (characteristic
== nullptr) {
603 RecordCharacteristicReadValueOutcome(
604 UMAGATTOperationOutcome::NO_CHARACTERISTIC
);
605 Send(new BluetoothMsg_ReadCharacteristicValueError(
606 thread_id
, request_id
,
607 WebBluetoothError::CharacteristicNoLongerExists
));
611 characteristic
->ReadRemoteCharacteristic(
612 base::Bind(&BluetoothDispatcherHost::OnCharacteristicValueRead
,
613 weak_ptr_factory_
.GetWeakPtr(), thread_id
, request_id
),
614 base::Bind(&BluetoothDispatcherHost::OnCharacteristicReadValueError
,
615 weak_ptr_factory_
.GetWeakPtr(), thread_id
, request_id
));
618 void BluetoothDispatcherHost::OnWriteValue(
621 const std::string
& characteristic_instance_id
,
622 const std::vector
<uint8_t>& value
) {
623 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
624 RecordWebBluetoothFunctionCall(
625 UMAWebBluetoothFunction::CHARACTERISTIC_WRITE_VALUE
);
627 // Length check per step 3 of writeValue algorithm:
628 // https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothgattcharacteristic-writevalue
629 // We perform the length check on the renderer side. So if we
630 // get a value with length > 512, we can assume it's a hostile
631 // renderer and kill it.
632 if (value
.size() > 512) {
633 bad_message::ReceivedBadMessage(
634 this, bad_message::BDH_INVALID_WRITE_VALUE_LENGTH
);
638 auto characteristic_iter
=
639 characteristic_to_service_
.find(characteristic_instance_id
);
640 // A characteristic_instance_id not in the map implies a hostile renderer
641 // because a renderer obtains the characteristic id from this class and
642 // it will be added to the map at that time.
643 if (characteristic_iter
== characteristic_to_service_
.end()) {
644 bad_message::ReceivedBadMessage(this,
645 bad_message::BDH_INVALID_CHARACTERISTIC_ID
);
648 const std::string
& service_instance_id
= characteristic_iter
->second
;
650 auto device_iter
= service_to_device_
.find(service_instance_id
);
652 CHECK(device_iter
!= service_to_device_
.end());
654 device::BluetoothDevice
* device
=
655 adapter_
->GetDevice(device_iter
->second
/* device_instance_id */);
656 if (device
== nullptr) { // See "NETWORK_ERROR Note" above.
657 RecordCharacteristicWriteValueOutcome(UMAGATTOperationOutcome::NO_DEVICE
);
658 Send(new BluetoothMsg_WriteCharacteristicValueError(
659 thread_id
, request_id
, WebBluetoothError::DeviceNoLongerInRange
));
663 BluetoothGattService
* service
= device
->GetGattService(service_instance_id
);
664 if (service
== nullptr) {
665 RecordCharacteristicWriteValueOutcome(UMAGATTOperationOutcome::NO_SERVICE
);
666 Send(new BluetoothMsg_WriteCharacteristicValueError(
667 thread_id
, request_id
, WebBluetoothError::ServiceNoLongerExists
));
671 BluetoothGattCharacteristic
* characteristic
=
672 service
->GetCharacteristic(characteristic_instance_id
);
673 if (characteristic
== nullptr) {
674 RecordCharacteristicWriteValueOutcome(
675 UMAGATTOperationOutcome::NO_CHARACTERISTIC
);
676 Send(new BluetoothMsg_WriteCharacteristicValueError(
677 thread_id
, request_id
,
678 WebBluetoothError::CharacteristicNoLongerExists
));
681 characteristic
->WriteRemoteCharacteristic(
682 value
, base::Bind(&BluetoothDispatcherHost::OnWriteValueSuccess
,
683 weak_ptr_factory_
.GetWeakPtr(), thread_id
, request_id
),
684 base::Bind(&BluetoothDispatcherHost::OnWriteValueFailed
,
685 weak_ptr_factory_
.GetWeakPtr(), thread_id
, request_id
));
688 void BluetoothDispatcherHost::OnDiscoverySessionStarted(
690 scoped_ptr
<device::BluetoothDiscoverySession
> discovery_session
) {
691 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
692 VLOG(1) << "Started discovery session for " << chooser_id
;
693 if (RequestDeviceSession
* session
=
694 request_device_sessions_
.Lookup(chooser_id
)) {
695 session
->discovery_session
= discovery_session
.Pass();
697 // Arrange to stop discovery later.
698 discovery_session_timer_
.Reset();
700 VLOG(1) << "Chooser " << chooser_id
701 << " was closed before the session finished starting. Stopping.";
702 StopDiscoverySession(discovery_session
.Pass());
706 void BluetoothDispatcherHost::OnDiscoverySessionStartedError(int chooser_id
) {
707 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
708 VLOG(1) << "Failed to start discovery session for " << chooser_id
;
709 if (RequestDeviceSession
* session
=
710 request_device_sessions_
.Lookup(chooser_id
)) {
711 if (session
->chooser
&& !session
->discovery_session
) {
712 session
->chooser
->ShowDiscoveryState(
713 BluetoothChooser::DiscoveryState::FAILED_TO_START
);
716 // Ignore discovery session start errors when the dialog was already closed by
717 // the time they happen.
720 void BluetoothDispatcherHost::OnBluetoothChooserEvent(
722 BluetoothChooser::Event event
,
723 const std::string
& device_id
) {
725 case BluetoothChooser::Event::RESCAN
:
726 // TODO(jyasskin): Implement starting a new Bluetooth discovery session.
729 case BluetoothChooser::Event::CANCELLED
:
730 case BluetoothChooser::Event::SELECTED
: {
731 RequestDeviceSession
* session
=
732 request_device_sessions_
.Lookup(chooser_id
);
733 DCHECK(session
) << "Shouldn't close the dialog twice.";
734 CHECK(session
->chooser
) << "Shouldn't close the dialog twice.";
736 // Synchronously ensure nothing else calls into the chooser after it has
737 // asked to be closed.
738 session
->chooser
.reset();
740 // Yield to the event loop to make sure we don't destroy the session
741 // within a BluetoothDispatcherHost stack frame.
742 if (!base::ThreadTaskRunnerHandle::Get()->PostTask(
744 base::Bind(&BluetoothDispatcherHost::FinishClosingChooser
,
745 weak_ptr_factory_
.GetWeakPtr(), chooser_id
, event
,
747 LOG(WARNING
) << "No TaskRunner; not closing requestDevice dialog.";
751 case BluetoothChooser::Event::SHOW_OVERVIEW_HELP
:
752 ShowBluetoothOverviewLink();
754 case BluetoothChooser::Event::SHOW_PAIRING_HELP
:
755 ShowBluetoothPairingLink();
757 case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP
:
758 ShowBluetoothAdapterOffLink();
763 void BluetoothDispatcherHost::FinishClosingChooser(
765 BluetoothChooser::Event event
,
766 const std::string
& device_id
) {
767 RequestDeviceSession
* session
= request_device_sessions_
.Lookup(chooser_id
);
768 DCHECK(session
) << "Session removed unexpectedly.";
770 if (event
== BluetoothChooser::Event::CANCELLED
) {
771 RecordRequestDeviceOutcome(
772 UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_CANCELLED
);
773 VLOG(1) << "Bluetooth chooser cancelled";
774 Send(new BluetoothMsg_RequestDeviceError(
775 session
->thread_id
, session
->request_id
,
776 WebBluetoothError::ChooserCancelled
));
777 request_device_sessions_
.Remove(chooser_id
);
780 DCHECK_EQ(static_cast<int>(event
),
781 static_cast<int>(BluetoothChooser::Event::SELECTED
));
783 const device::BluetoothDevice
* const device
= adapter_
->GetDevice(device_id
);
784 if (device
== nullptr) {
785 VLOG(1) << "Device " << device_id
<< " no longer in adapter";
786 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::CHOSEN_DEVICE_VANISHED
);
787 Send(new BluetoothMsg_RequestDeviceError(
788 session
->thread_id
, session
->request_id
,
789 WebBluetoothError::ChosenDeviceVanished
));
790 request_device_sessions_
.Remove(chooser_id
);
794 VLOG(1) << "Device: " << device
->GetName();
795 VLOG(1) << "UUIDs: ";
796 for (BluetoothUUID uuid
: device
->GetUUIDs())
797 VLOG(1) << "\t" << uuid
.canonical_value();
799 content::BluetoothDevice
device_ipc(
800 device
->GetAddress(), // instance_id
801 device
->GetName(), // name
802 device
->GetBluetoothClass(), // device_class
803 device
->GetVendorIDSource(), // vendor_id_source
804 device
->GetVendorID(), // vendor_id
805 device
->GetProductID(), // product_id
806 device
->GetDeviceID(), // product_version
807 device
->IsPaired(), // paired
808 content::BluetoothDevice::UUIDsFromBluetoothUUIDs(
809 device
->GetUUIDs())); // uuids
810 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::SUCCESS
);
811 Send(new BluetoothMsg_RequestDeviceSuccess(session
->thread_id
,
812 session
->request_id
, device_ipc
));
813 request_device_sessions_
.Remove(chooser_id
);
816 void BluetoothDispatcherHost::OnGATTConnectionCreated(
819 const std::string
& device_instance_id
,
820 base::TimeTicks start_time
,
821 scoped_ptr
<device::BluetoothGattConnection
> connection
) {
822 // TODO(ortuno): Save the BluetoothGattConnection so we can disconnect
824 RecordConnectGATTTimeSuccess(base::TimeTicks::Now() - start_time
);
825 RecordConnectGATTOutcome(UMAConnectGATTOutcome::SUCCESS
);
826 Send(new BluetoothMsg_ConnectGATTSuccess(thread_id
, request_id
,
827 device_instance_id
));
830 void BluetoothDispatcherHost::OnCreateGATTConnectionError(
833 const std::string
& device_instance_id
,
834 base::TimeTicks start_time
,
835 device::BluetoothDevice::ConnectErrorCode error_code
) {
836 // There was an error creating the ATT Bearer so we reject with
838 // https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothdevice-connectgatt
839 RecordConnectGATTTimeFailed(base::TimeTicks::Now() - start_time
);
840 // RecordConnectGATTOutcome is called by TranslateConnectError.
841 Send(new BluetoothMsg_ConnectGATTError(thread_id
, request_id
,
842 TranslateConnectError(error_code
)));
845 void BluetoothDispatcherHost::OnServicesDiscovered(
848 const std::string
& device_instance_id
,
849 const std::string
& service_uuid
) {
850 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
852 device::BluetoothDevice
* device
= adapter_
->GetDevice(device_instance_id
);
853 if (device
== nullptr) { // See "NETWORK_ERROR Note" above.
854 RecordGetPrimaryServiceOutcome(UMAGetPrimaryServiceOutcome::NO_DEVICE
);
855 Send(new BluetoothMsg_GetPrimaryServiceError(
856 thread_id
, request_id
, WebBluetoothError::DeviceNoLongerInRange
));
859 for (BluetoothGattService
* service
: device
->GetGattServices()) {
860 if (service
->GetUUID().canonical_value() == service_uuid
) {
861 // TODO(ortuno): Use generated instance ID instead.
862 // https://crbug.com/495379
863 const std::string
& service_identifier
= service
->GetIdentifier();
864 auto insert_result
= service_to_device_
.insert(
865 make_pair(service_identifier
, device_instance_id
));
867 // If a value is already in map, DCHECK it's valid.
868 if (!insert_result
.second
)
869 DCHECK(insert_result
.first
->second
== device_instance_id
);
871 RecordGetPrimaryServiceOutcome(UMAGetPrimaryServiceOutcome::SUCCESS
);
872 Send(new BluetoothMsg_GetPrimaryServiceSuccess(thread_id
, request_id
,
873 service_identifier
));
877 RecordGetPrimaryServiceOutcome(UMAGetPrimaryServiceOutcome::NOT_FOUND
);
878 Send(new BluetoothMsg_GetPrimaryServiceError(
879 thread_id
, request_id
, WebBluetoothError::ServiceNotFound
));
882 void BluetoothDispatcherHost::OnCharacteristicValueRead(
885 const std::vector
<uint8
>& value
) {
886 RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome::SUCCESS
);
887 Send(new BluetoothMsg_ReadCharacteristicValueSuccess(thread_id
, request_id
,
891 void BluetoothDispatcherHost::OnCharacteristicReadValueError(
894 device::BluetoothGattService::GattErrorCode error_code
) {
895 // TranslateGATTError calls RecordGATTOperationOutcome.
896 Send(new BluetoothMsg_ReadCharacteristicValueError(
897 thread_id
, request_id
,
898 TranslateGATTError(error_code
, UMAGATTOperation::CHARACTERISTIC_READ
)));
901 void BluetoothDispatcherHost::OnWriteValueSuccess(int thread_id
,
903 RecordCharacteristicWriteValueOutcome(UMAGATTOperationOutcome::SUCCESS
);
904 Send(new BluetoothMsg_WriteCharacteristicValueSuccess(thread_id
, request_id
));
907 void BluetoothDispatcherHost::OnWriteValueFailed(
910 device::BluetoothGattService::GattErrorCode error_code
) {
911 // TranslateGATTError calls RecordGATTOperationOutcome.
912 Send(new BluetoothMsg_WriteCharacteristicValueError(
913 thread_id
, request_id
,
914 TranslateGATTError(error_code
, UMAGATTOperation::CHARACTERISTIC_WRITE
)));
917 void BluetoothDispatcherHost::ShowBluetoothOverviewLink() {
921 void BluetoothDispatcherHost::ShowBluetoothPairingLink() {
925 void BluetoothDispatcherHost::ShowBluetoothAdapterOffLink() {
929 } // namespace content