| // Copyright 2014 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | // ID Not In Map Note: | 
 | // A service, characteristic, or descriptor ID not in the corresponding | 
 | // BluetoothDispatcherHost map [service_to_device_, characteristic_to_service_, | 
 | // descriptor_to_characteristic_] implies a hostile renderer because a renderer | 
 | // obtains the corresponding ID from this class and it will be added to the map | 
 | // at that time. | 
 |  | 
 | #include "content/browser/bluetooth/bluetooth_dispatcher_host.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/single_thread_task_runner.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/thread_task_runner_handle.h" | 
 | #include "content/browser/bad_message.h" | 
 | #include "content/browser/bluetooth/bluetooth_blacklist.h" | 
 | #include "content/browser/bluetooth/bluetooth_metrics.h" | 
 | #include "content/browser/bluetooth/first_device_bluetooth_chooser.h" | 
 | #include "content/browser/frame_host/render_frame_host_impl.h" | 
 | #include "content/common/bluetooth/bluetooth_messages.h" | 
 | #include "content/public/browser/content_browser_client.h" | 
 | #include "content/public/browser/web_contents.h" | 
 | #include "content/public/browser/web_contents_delegate.h" | 
 | #include "device/bluetooth/bluetooth_adapter.h" | 
 | #include "device/bluetooth/bluetooth_adapter_factory.h" | 
 | #include "device/bluetooth/bluetooth_device.h" | 
 | #include "device/bluetooth/bluetooth_discovery_session.h" | 
 | #include "device/bluetooth/bluetooth_gatt_characteristic.h" | 
 | #include "device/bluetooth/bluetooth_gatt_service.h" | 
 |  | 
 | using blink::WebBluetoothError; | 
 | using device::BluetoothAdapter; | 
 | using device::BluetoothAdapterFactory; | 
 | using device::BluetoothGattCharacteristic; | 
 | using device::BluetoothGattService; | 
 | using device::BluetoothUUID; | 
 |  | 
 | namespace content { | 
 |  | 
 | namespace { | 
 |  | 
 | // TODO(ortuno): Once we have a chooser for scanning, a way to control that | 
 | // chooser from tests, and the right callback for discovered services we should | 
 | // delete these constants. | 
 | // https://crbug.com/436280 and https://crbug.com/484504 | 
 | const int kDelayTime = 5;         // 5 seconds for scanning and discovering | 
 | const int kTestingDelayTime = 0;  // No need to wait during tests | 
 | const size_t kMaxLengthForDeviceName = | 
 |     29;  // max length of device name in filter. | 
 |  | 
 | bool IsEmptyOrInvalidFilter(const content::BluetoothScanFilter& filter) { | 
 |   return filter.name.empty() && filter.namePrefix.empty() && | 
 |          filter.services.empty() && | 
 |          filter.name.length() > kMaxLengthForDeviceName && | 
 |          filter.namePrefix.length() > kMaxLengthForDeviceName; | 
 | } | 
 |  | 
 | bool HasEmptyOrInvalidFilter( | 
 |     const std::vector<content::BluetoothScanFilter>& filters) { | 
 |   return filters.empty() | 
 |              ? true | 
 |              : filters.end() != std::find_if(filters.begin(), filters.end(), | 
 |                                              IsEmptyOrInvalidFilter); | 
 | } | 
 |  | 
 | // Defined at | 
 | // https://webbluetoothchrome.github.io/web-bluetooth/#dfn-matches-a-filter | 
 | bool MatchesFilter(const device::BluetoothDevice& device, | 
 |                    const content::BluetoothScanFilter& filter) { | 
 |   DCHECK(!IsEmptyOrInvalidFilter(filter)); | 
 |  | 
 |   const std::string device_name = base::UTF16ToUTF8(device.GetName()); | 
 |  | 
 |   if (!filter.name.empty() && (device_name != filter.name)) { | 
 |       return false; | 
 |   } | 
 |  | 
 |   if (!filter.namePrefix.empty() && | 
 |       (!base::StartsWith(device_name, filter.namePrefix, | 
 |                          base::CompareCase::SENSITIVE))) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (!filter.services.empty()) { | 
 |     const auto& device_uuid_list = device.GetUUIDs(); | 
 |     const std::set<BluetoothUUID> device_uuids(device_uuid_list.begin(), | 
 |                                                device_uuid_list.end()); | 
 |     for (const auto& service : filter.services) { | 
 |       if (!ContainsKey(device_uuids, service)) { | 
 |         return false; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | bool MatchesFilters(const device::BluetoothDevice& device, | 
 |                     const std::vector<content::BluetoothScanFilter>& filters) { | 
 |   DCHECK(!HasEmptyOrInvalidFilter(filters)); | 
 |   for (const content::BluetoothScanFilter& filter : filters) { | 
 |     if (MatchesFilter(device, filter)) { | 
 |       return true; | 
 |     } | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | WebBluetoothError TranslateConnectError( | 
 |     device::BluetoothDevice::ConnectErrorCode error_code) { | 
 |   switch (error_code) { | 
 |     case device::BluetoothDevice::ERROR_UNKNOWN: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::UNKNOWN); | 
 |       return WebBluetoothError::ConnectUnknownError; | 
 |     case device::BluetoothDevice::ERROR_INPROGRESS: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::IN_PROGRESS); | 
 |       return WebBluetoothError::ConnectAlreadyInProgress; | 
 |     case device::BluetoothDevice::ERROR_FAILED: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::FAILED); | 
 |       return WebBluetoothError::ConnectUnknownFailure; | 
 |     case device::BluetoothDevice::ERROR_AUTH_FAILED: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_FAILED); | 
 |       return WebBluetoothError::ConnectAuthFailed; | 
 |     case device::BluetoothDevice::ERROR_AUTH_CANCELED: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_CANCELED); | 
 |       return WebBluetoothError::ConnectAuthCanceled; | 
 |     case device::BluetoothDevice::ERROR_AUTH_REJECTED: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_REJECTED); | 
 |       return WebBluetoothError::ConnectAuthRejected; | 
 |     case device::BluetoothDevice::ERROR_AUTH_TIMEOUT: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_TIMEOUT); | 
 |       return WebBluetoothError::ConnectAuthTimeout; | 
 |     case device::BluetoothDevice::ERROR_UNSUPPORTED_DEVICE: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::UNSUPPORTED_DEVICE); | 
 |       return WebBluetoothError::ConnectUnsupportedDevice; | 
 |     case device::BluetoothDevice::ERROR_ATTRIBUTE_LENGTH_INVALID: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::ATTRIBUTE_LENGTH_INVALID); | 
 |       return WebBluetoothError::ConnectAttributeLengthInvalid; | 
 |     case device::BluetoothDevice::ERROR_CONNECTION_CONGESTED: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::CONNECTION_CONGESTED); | 
 |       return WebBluetoothError::ConnectConnectionCongested; | 
 |     case device::BluetoothDevice::ERROR_INSUFFICIENT_ENCRYPTION: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::INSUFFICIENT_ENCRYPTION); | 
 |       return WebBluetoothError::ConnectInsufficientEncryption; | 
 |     case device::BluetoothDevice::ERROR_OFFSET_INVALID: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::OFFSET_INVALID); | 
 |       return WebBluetoothError::ConnectOffsetInvalid; | 
 |     case device::BluetoothDevice::ERROR_READ_NOT_PERMITTED: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::READ_NOT_PERMITTED); | 
 |       return WebBluetoothError::ConnectReadNotPermitted; | 
 |     case device::BluetoothDevice::ERROR_REQUEST_NOT_SUPPORTED: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::REQUEST_NOT_SUPPORTED); | 
 |       return WebBluetoothError::ConnectRequestNotSupported; | 
 |     case device::BluetoothDevice::ERROR_WRITE_NOT_PERMITTED: | 
 |       RecordConnectGATTOutcome(UMAConnectGATTOutcome::WRITE_NOT_PERMITTED); | 
 |       return WebBluetoothError::ConnectWriteNotPermitted; | 
 |     case device::BluetoothDevice::NUM_CONNECT_ERROR_CODES: | 
 |       NOTREACHED(); | 
 |       return WebBluetoothError::UntranslatedConnectErrorCode; | 
 |   } | 
 |   NOTREACHED(); | 
 |   return WebBluetoothError::UntranslatedConnectErrorCode; | 
 | } | 
 |  | 
 | blink::WebBluetoothError TranslateGATTError( | 
 |     BluetoothGattService::GattErrorCode error_code, | 
 |     UMAGATTOperation operation) { | 
 |   switch (error_code) { | 
 |     case BluetoothGattService::GATT_ERROR_UNKNOWN: | 
 |       RecordGATTOperationOutcome(operation, UMAGATTOperationOutcome::UNKNOWN); | 
 |       return blink::WebBluetoothError::GATTUnknownError; | 
 |     case BluetoothGattService::GATT_ERROR_FAILED: | 
 |       RecordGATTOperationOutcome(operation, UMAGATTOperationOutcome::FAILED); | 
 |       return blink::WebBluetoothError::GATTUnknownFailure; | 
 |     case BluetoothGattService::GATT_ERROR_IN_PROGRESS: | 
 |       RecordGATTOperationOutcome(operation, | 
 |                                  UMAGATTOperationOutcome::IN_PROGRESS); | 
 |       return blink::WebBluetoothError::GATTOperationInProgress; | 
 |     case BluetoothGattService::GATT_ERROR_INVALID_LENGTH: | 
 |       RecordGATTOperationOutcome(operation, | 
 |                                  UMAGATTOperationOutcome::INVALID_LENGTH); | 
 |       return blink::WebBluetoothError::GATTInvalidAttributeLength; | 
 |     case BluetoothGattService::GATT_ERROR_NOT_PERMITTED: | 
 |       RecordGATTOperationOutcome(operation, | 
 |                                  UMAGATTOperationOutcome::NOT_PERMITTED); | 
 |       return blink::WebBluetoothError::GATTNotPermitted; | 
 |     case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED: | 
 |       RecordGATTOperationOutcome(operation, | 
 |                                  UMAGATTOperationOutcome::NOT_AUTHORIZED); | 
 |       return blink::WebBluetoothError::GATTNotAuthorized; | 
 |     case BluetoothGattService::GATT_ERROR_NOT_PAIRED: | 
 |       RecordGATTOperationOutcome(operation, | 
 |                                  UMAGATTOperationOutcome::NOT_PAIRED); | 
 |       return blink::WebBluetoothError::GATTNotPaired; | 
 |     case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED: | 
 |       RecordGATTOperationOutcome(operation, | 
 |                                  UMAGATTOperationOutcome::NOT_SUPPORTED); | 
 |       return blink::WebBluetoothError::GATTNotSupported; | 
 |   } | 
 |   NOTREACHED(); | 
 |   return blink::WebBluetoothError::GATTUntranslatedErrorCode; | 
 | } | 
 |  | 
 | void StopDiscoverySession( | 
 |     scoped_ptr<device::BluetoothDiscoverySession> discovery_session) { | 
 |   // Nothing goes wrong if the discovery session fails to stop, and we don't | 
 |   // need to wait for it before letting the user's script proceed, so we ignore | 
 |   // the results here. | 
 |   discovery_session->Stop(base::Bind(&base::DoNothing), | 
 |                           base::Bind(&base::DoNothing)); | 
 | } | 
 |  | 
 | // TODO(ortuno): This should really be a BluetoothDevice method. | 
 | // Replace when implemented. http://crbug.com/552022 | 
 | std::vector<BluetoothGattService*> GetPrimaryServicesByUUID( | 
 |     device::BluetoothDevice* device, | 
 |     const std::string& service_uuid) { | 
 |   std::vector<BluetoothGattService*> services; | 
 |   VLOG(1) << "Looking for service: " << service_uuid; | 
 |   for (BluetoothGattService* service : device->GetGattServices()) { | 
 |     VLOG(1) << "Service in cache: " << service->GetUUID().canonical_value(); | 
 |     if (service->GetUUID().canonical_value() == service_uuid && | 
 |         service->IsPrimary()) { | 
 |       services.push_back(service); | 
 |     } | 
 |   } | 
 |   return services; | 
 | } | 
 |  | 
 | UMARequestDeviceOutcome OutcomeFromChooserEvent(BluetoothChooser::Event event) { | 
 |   switch (event) { | 
 |     case BluetoothChooser::Event::DENIED_PERMISSION: | 
 |       return UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_DENIED_PERMISSION; | 
 |     case BluetoothChooser::Event::CANCELLED: | 
 |       return UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_CANCELLED; | 
 |     case BluetoothChooser::Event::SHOW_OVERVIEW_HELP: | 
 |       return UMARequestDeviceOutcome::BLUETOOTH_OVERVIEW_HELP_LINK_PRESSED; | 
 |     case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP: | 
 |       return UMARequestDeviceOutcome::ADAPTER_OFF_HELP_LINK_PRESSED; | 
 |     case BluetoothChooser::Event::SHOW_NEED_LOCATION_HELP: | 
 |       return UMARequestDeviceOutcome::NEED_LOCATION_HELP_LINK_PRESSED; | 
 |     case BluetoothChooser::Event::SELECTED: | 
 |       // We can't know if we are going to send a success message yet because | 
 |       // the device could have vanished. This event should be histogramed | 
 |       // manually after checking if the device is still around. | 
 |       NOTREACHED(); | 
 |       return UMARequestDeviceOutcome::SUCCESS; | 
 |     case BluetoothChooser::Event::RESCAN: | 
 |       // Rescanning doesn't result in a IPC message for the request being sent | 
 |       // so no need to histogram it. | 
 |       NOTREACHED(); | 
 |       return UMARequestDeviceOutcome::SUCCESS; | 
 |   } | 
 |   NOTREACHED(); | 
 |   return UMARequestDeviceOutcome::SUCCESS; | 
 | } | 
 |  | 
 | }  //  namespace | 
 |  | 
 | BluetoothDispatcherHost::BluetoothDispatcherHost(int render_process_id) | 
 |     : BrowserMessageFilter(BluetoothMsgStart), | 
 |       render_process_id_(render_process_id), | 
 |       current_delay_time_(kDelayTime), | 
 |       discovery_session_timer_( | 
 |           FROM_HERE, | 
 |           // TODO(jyasskin): Add a way for tests to control the dialog | 
 |           // directly, and change this to a reasonable discovery timeout. | 
 |           base::TimeDelta::FromSecondsD(current_delay_time_), | 
 |           base::Bind(&BluetoothDispatcherHost::StopDeviceDiscovery, | 
 |                      // base::Timer guarantees it won't call back after its | 
 |                      // destructor starts. | 
 |                      base::Unretained(this)), | 
 |           /*is_repeating=*/false), | 
 |       weak_ptr_factory_(this) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |  | 
 |   // Bind all future weak pointers to the UI thread. | 
 |   weak_ptr_on_ui_thread_ = weak_ptr_factory_.GetWeakPtr(); | 
 |   weak_ptr_on_ui_thread_.get();  // Associates with UI thread. | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnDestruct() const { | 
 |   // See class comment: UI Thread Note. | 
 |   BrowserThread::DeleteOnUIThread::Destruct(this); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OverrideThreadForMessage( | 
 |     const IPC::Message& message, | 
 |     content::BrowserThread::ID* thread) { | 
 |   // See class comment: UI Thread Note. | 
 |   *thread = BrowserThread::UI; | 
 | } | 
 |  | 
 | bool BluetoothDispatcherHost::OnMessageReceived(const IPC::Message& message) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   bool handled = true; | 
 |   IPC_BEGIN_MESSAGE_MAP(BluetoothDispatcherHost, message) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_RequestDevice, OnRequestDevice) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_GATTServerConnect, OnGATTServerConnect) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_GATTServerDisconnect, | 
 |                       OnGATTServerDisconnect) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_GetPrimaryService, OnGetPrimaryService) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_GetCharacteristic, OnGetCharacteristic) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_GetCharacteristics, OnGetCharacteristics) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_ReadValue, OnReadValue) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_WriteValue, OnWriteValue) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_StartNotifications, OnStartNotifications) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_StopNotifications, OnStopNotifications) | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_RegisterCharacteristic, | 
 |                       OnRegisterCharacteristicObject); | 
 |   IPC_MESSAGE_HANDLER(BluetoothHostMsg_UnregisterCharacteristic, | 
 |                       OnUnregisterCharacteristicObject); | 
 |   IPC_MESSAGE_UNHANDLED(handled = false) | 
 |   IPC_END_MESSAGE_MAP() | 
 |   return handled; | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::SetBluetoothAdapterForTesting( | 
 |     scoped_refptr<device::BluetoothAdapter> mock_adapter) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |  | 
 |   if (mock_adapter.get()) { | 
 |     current_delay_time_ = kTestingDelayTime; | 
 |     // Reset the discovery session timer to use the new delay time. | 
 |     discovery_session_timer_.Start( | 
 |         FROM_HERE, base::TimeDelta::FromSecondsD(current_delay_time_), | 
 |         base::Bind(&BluetoothDispatcherHost::StopDeviceDiscovery, | 
 |                    // base::Timer guarantees it won't call back after its | 
 |                    // destructor starts. | 
 |                    base::Unretained(this))); | 
 |   } else { | 
 |     // The following data structures are used to store pending operations. | 
 |     // They should never contain elements at the end of a test. | 
 |     DCHECK(request_device_sessions_.IsEmpty()); | 
 |     DCHECK(pending_primary_services_requests_.empty()); | 
 |  | 
 |     // The following data structures are cleaned up when a | 
 |     // device/service/characteristic is removed. | 
 |     // Since this can happen after the test is done and the cleanup function is | 
 |     // called, we clean them here. | 
 |     service_to_device_.clear(); | 
 |     characteristic_to_service_.clear(); | 
 |     characteristic_id_to_notify_session_.clear(); | 
 |     active_characteristic_threads_.clear(); | 
 |     device_id_to_connection_map_.clear(); | 
 |     allowed_devices_map_ = BluetoothAllowedDevicesMap(); | 
 |   } | 
 |  | 
 |   set_adapter(std::move(mock_adapter)); | 
 | } | 
 |  | 
 | BluetoothDispatcherHost::~BluetoothDispatcherHost() { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   // Clear adapter, releasing observer references. | 
 |   set_adapter(scoped_refptr<device::BluetoothAdapter>()); | 
 | } | 
 |  | 
 | // Stores information associated with an in-progress requestDevice call. This | 
 | // will include the state of the active chooser dialog in a future patch. | 
 | struct BluetoothDispatcherHost::RequestDeviceSession { | 
 |  public: | 
 |   RequestDeviceSession(int thread_id, | 
 |                        int request_id, | 
 |                        int frame_routing_id, | 
 |                        url::Origin origin, | 
 |                        const std::vector<BluetoothScanFilter>& filters, | 
 |                        const std::vector<BluetoothUUID>& optional_services) | 
 |       : thread_id(thread_id), | 
 |         request_id(request_id), | 
 |         frame_routing_id(frame_routing_id), | 
 |         origin(origin), | 
 |         filters(filters), | 
 |         optional_services(optional_services) {} | 
 |  | 
 |   void AddFilteredDevice(const device::BluetoothDevice& device) { | 
 |     if (chooser && MatchesFilters(device, filters)) { | 
 |       chooser->AddDevice(device.GetAddress(), device.GetName()); | 
 |     } | 
 |   } | 
 |  | 
 |   scoped_ptr<device::BluetoothDiscoveryFilter> ComputeScanFilter() const { | 
 |     std::set<BluetoothUUID> services; | 
 |     for (const BluetoothScanFilter& filter : filters) { | 
 |       services.insert(filter.services.begin(), filter.services.end()); | 
 |     } | 
 |     scoped_ptr<device::BluetoothDiscoveryFilter> discovery_filter( | 
 |         new device::BluetoothDiscoveryFilter( | 
 |             device::BluetoothDiscoveryFilter::TRANSPORT_DUAL)); | 
 |     for (const BluetoothUUID& service : services) { | 
 |       discovery_filter->AddUUID(service); | 
 |     } | 
 |     return discovery_filter; | 
 |   } | 
 |  | 
 |   const int thread_id; | 
 |   const int request_id; | 
 |   const int frame_routing_id; | 
 |   const url::Origin origin; | 
 |   const std::vector<BluetoothScanFilter> filters; | 
 |   const std::vector<BluetoothUUID> optional_services; | 
 |   scoped_ptr<BluetoothChooser> chooser; | 
 |   scoped_ptr<device::BluetoothDiscoverySession> discovery_session; | 
 | }; | 
 |  | 
 | struct BluetoothDispatcherHost::CacheQueryResult { | 
 |   CacheQueryResult() | 
 |       : device(nullptr), | 
 |         service(nullptr), | 
 |         characteristic(nullptr), | 
 |         outcome(CacheQueryOutcome::SUCCESS) {} | 
 |   CacheQueryResult(CacheQueryOutcome outcome) | 
 |       : device(nullptr), | 
 |         service(nullptr), | 
 |         characteristic(nullptr), | 
 |         outcome(outcome) {} | 
 |   ~CacheQueryResult() {} | 
 |   WebBluetoothError GetWebError() const { | 
 |     switch (outcome) { | 
 |       case CacheQueryOutcome::SUCCESS: | 
 |       case CacheQueryOutcome::BAD_RENDERER: | 
 |         NOTREACHED(); | 
 |         return WebBluetoothError::DeviceNoLongerInRange; | 
 |       case CacheQueryOutcome::NO_DEVICE: | 
 |         return WebBluetoothError::DeviceNoLongerInRange; | 
 |       case CacheQueryOutcome::NO_SERVICE: | 
 |         return WebBluetoothError::ServiceNoLongerExists; | 
 |       case CacheQueryOutcome::NO_CHARACTERISTIC: | 
 |         return WebBluetoothError::CharacteristicNoLongerExists; | 
 |     } | 
 |     NOTREACHED(); | 
 |     return WebBluetoothError::DeviceNoLongerInRange; | 
 |   } | 
 |  | 
 |   device::BluetoothDevice* device; | 
 |   device::BluetoothGattService* service; | 
 |   device::BluetoothGattCharacteristic* characteristic; | 
 |   CacheQueryOutcome outcome; | 
 | }; | 
 |  | 
 | struct BluetoothDispatcherHost::PrimaryServicesRequest { | 
 |   enum CallingFunction { GET_PRIMARY_SERVICE, GET_PRIMARY_SERVICES }; | 
 |  | 
 |   PrimaryServicesRequest(int thread_id, | 
 |                          int request_id, | 
 |                          const std::string& service_uuid, | 
 |                          PrimaryServicesRequest::CallingFunction func) | 
 |       : thread_id(thread_id), | 
 |         request_id(request_id), | 
 |         service_uuid(service_uuid), | 
 |         func(func) {} | 
 |   ~PrimaryServicesRequest() {} | 
 |  | 
 |   int thread_id; | 
 |   int request_id; | 
 |   std::string service_uuid; | 
 |   CallingFunction func; | 
 | }; | 
 |  | 
 | void BluetoothDispatcherHost::set_adapter( | 
 |     scoped_refptr<device::BluetoothAdapter> adapter) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   if (adapter_.get()) | 
 |     adapter_->RemoveObserver(this); | 
 |   adapter_ = adapter; | 
 |   if (adapter_.get()) | 
 |     adapter_->AddObserver(this); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::StartDeviceDiscovery( | 
 |     RequestDeviceSession* session, | 
 |     int chooser_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   if (session->discovery_session) { | 
 |     // Already running; just increase the timeout. | 
 |     discovery_session_timer_.Reset(); | 
 |   } else { | 
 |     session->chooser->ShowDiscoveryState( | 
 |         BluetoothChooser::DiscoveryState::DISCOVERING); | 
 |     adapter_->StartDiscoverySessionWithFilter( | 
 |         session->ComputeScanFilter(), | 
 |         base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStarted, | 
 |                    weak_ptr_on_ui_thread_, chooser_id), | 
 |         base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStartedError, | 
 |                    weak_ptr_on_ui_thread_, chooser_id)); | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::StopDeviceDiscovery() { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( | 
 |            &request_device_sessions_); | 
 |        !iter.IsAtEnd(); iter.Advance()) { | 
 |     RequestDeviceSession* session = iter.GetCurrentValue(); | 
 |     if (session->discovery_session) { | 
 |       StopDiscoverySession(std::move(session->discovery_session)); | 
 |     } | 
 |     if (session->chooser) { | 
 |       session->chooser->ShowDiscoveryState( | 
 |           BluetoothChooser::DiscoveryState::IDLE); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::AdapterPoweredChanged( | 
 |     device::BluetoothAdapter* adapter, | 
 |     bool powered) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   const BluetoothChooser::AdapterPresence presence = | 
 |       powered ? BluetoothChooser::AdapterPresence::POWERED_ON | 
 |               : BluetoothChooser::AdapterPresence::POWERED_OFF; | 
 |   for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( | 
 |            &request_device_sessions_); | 
 |        !iter.IsAtEnd(); iter.Advance()) { | 
 |     RequestDeviceSession* session = iter.GetCurrentValue(); | 
 |  | 
 |     // Stop ongoing discovery session if power is off. | 
 |     if (!powered && session->discovery_session) { | 
 |       StopDiscoverySession(std::move(session->discovery_session)); | 
 |     } | 
 |  | 
 |     if (session->chooser) | 
 |       session->chooser->SetAdapterPresence(presence); | 
 |   } | 
 |  | 
 |   // Stop the timer so that we don't change the state of the chooser | 
 |   // when timer expires. | 
 |   if (!powered) { | 
 |     discovery_session_timer_.Stop(); | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::DeviceAdded(device::BluetoothAdapter* adapter, | 
 |                                           device::BluetoothDevice* device) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   VLOG(1) << "Adding device to all choosers: " << device->GetAddress(); | 
 |   for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( | 
 |            &request_device_sessions_); | 
 |        !iter.IsAtEnd(); iter.Advance()) { | 
 |     RequestDeviceSession* session = iter.GetCurrentValue(); | 
 |     session->AddFilteredDevice(*device); | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::DeviceRemoved(device::BluetoothAdapter* adapter, | 
 |                                             device::BluetoothDevice* device) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   VLOG(1) << "Marking device removed on all choosers: " << device->GetAddress(); | 
 |   for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( | 
 |            &request_device_sessions_); | 
 |        !iter.IsAtEnd(); iter.Advance()) { | 
 |     RequestDeviceSession* session = iter.GetCurrentValue(); | 
 |     if (session->chooser) { | 
 |       session->chooser->RemoveDevice(device->GetAddress()); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::GattServicesDiscovered( | 
 |     device::BluetoothAdapter* adapter, | 
 |     device::BluetoothDevice* device) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   const std::string& device_address = device->GetAddress(); | 
 |   VLOG(1) << "Services discovered for device: " << device_address; | 
 |  | 
 |   auto iter = pending_primary_services_requests_.find(device_address); | 
 |   if (iter == pending_primary_services_requests_.end()) { | 
 |     return; | 
 |   } | 
 |   std::vector<PrimaryServicesRequest> requests; | 
 |   requests.swap(iter->second); | 
 |   pending_primary_services_requests_.erase(iter); | 
 |  | 
 |   for (const PrimaryServicesRequest& request : requests) { | 
 |     std::vector<BluetoothGattService*> services = | 
 |         GetPrimaryServicesByUUID(device, request.service_uuid); | 
 |     switch (request.func) { | 
 |       case PrimaryServicesRequest::GET_PRIMARY_SERVICE: | 
 |         if (!services.empty()) { | 
 |           AddToServicesMapAndSendGetPrimaryServiceSuccess( | 
 |               *services[0], request.thread_id, request.request_id); | 
 |         } else { | 
 |           VLOG(1) << "No service found"; | 
 |           RecordGetPrimaryServiceOutcome( | 
 |               UMAGetPrimaryServiceOutcome::NOT_FOUND); | 
 |           Send(new BluetoothMsg_GetPrimaryServiceError( | 
 |               request.thread_id, request.request_id, | 
 |               WebBluetoothError::ServiceNotFound)); | 
 |         } | 
 |         break; | 
 |       case PrimaryServicesRequest::GET_PRIMARY_SERVICES: | 
 |         NOTIMPLEMENTED(); | 
 |         break; | 
 |     } | 
 |   } | 
 |   DCHECK(!ContainsKey(pending_primary_services_requests_, device_address)) | 
 |       << "Sending get-service responses unexpectedly queued another request."; | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::GattCharacteristicValueChanged( | 
 |     device::BluetoothAdapter* adapter, | 
 |     device::BluetoothGattCharacteristic* characteristic, | 
 |     const std::vector<uint8_t>& value) { | 
 |   VLOG(1) << "Characteristic updated: " << characteristic->GetIdentifier(); | 
 |   auto iter = | 
 |       active_characteristic_threads_.find(characteristic->GetIdentifier()); | 
 |  | 
 |   if (iter == active_characteristic_threads_.end()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   for (int thread_id : iter->second) { | 
 |     // Yield to the event loop so that the event gets dispatched after the | 
 |     // readValue promise resolves. | 
 |     // TODO(ortuno): Make sure the order of fulfulling promises and triggering | 
 |     // events matches the spec and that events don't get lost. | 
 |     // https://crbug.com/543882 | 
 |     if (!base::ThreadTaskRunnerHandle::Get()->PostTask( | 
 |             FROM_HERE, | 
 |             base::Bind(&BluetoothDispatcherHost::NotifyActiveCharacteristic, | 
 |                        weak_ptr_on_ui_thread_, thread_id, | 
 |                        characteristic->GetIdentifier(), value))) { | 
 |       LOG(WARNING) << "No TaskRunner."; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::NotifyActiveCharacteristic( | 
 |     int thread_id, | 
 |     const std::string& characteristic_instance_id, | 
 |     const std::vector<uint8_t>& value) { | 
 |   Send(new BluetoothMsg_CharacteristicValueChanged( | 
 |       thread_id, characteristic_instance_id, value)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnRequestDevice( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::vector<BluetoothScanFilter>& filters, | 
 |     const std::vector<BluetoothUUID>& optional_services) { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::REQUEST_DEVICE); | 
 |   if (!adapter_.get()) { | 
 |     if (BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) { | 
 |       BluetoothAdapterFactory::GetAdapter(base::Bind( | 
 |           &BluetoothDispatcherHost::OnGetAdapter, weak_ptr_on_ui_thread_, | 
 |           base::Bind(&BluetoothDispatcherHost::OnRequestDeviceImpl, | 
 |                      weak_ptr_on_ui_thread_, thread_id, request_id, | 
 |                      frame_routing_id, filters, optional_services))); | 
 |       return; | 
 |     } | 
 |     RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_BLUETOOTH_ADAPTER); | 
 |     Send(new BluetoothMsg_RequestDeviceError( | 
 |         thread_id, request_id, WebBluetoothError::NoBluetoothAdapter)); | 
 |     return; | 
 |   } | 
 |   OnRequestDeviceImpl(thread_id, request_id, frame_routing_id, filters, | 
 |                       optional_services); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnGATTServerConnect( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::string& device_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::CONNECT_GATT); | 
 |   const base::TimeTicks start_time = base::TimeTicks::Now(); | 
 |  | 
 |   const CacheQueryResult query_result = | 
 |       QueryCacheForDevice(GetOrigin(frame_routing_id), device_id); | 
 |  | 
 |   if (query_result.outcome != CacheQueryOutcome::SUCCESS) { | 
 |     RecordConnectGATTOutcome(query_result.outcome); | 
 |     Send(new BluetoothMsg_GATTServerConnectError(thread_id, request_id, | 
 |                                                  query_result.GetWebError())); | 
 |     return; | 
 |   } | 
 |  | 
 |   // If we are already connected no need to connect again. | 
 |   auto connection_iter = device_id_to_connection_map_.find(device_id); | 
 |   if (connection_iter != device_id_to_connection_map_.end()) { | 
 |     if (connection_iter->second->IsConnected()) { | 
 |       VLOG(1) << "Already connected."; | 
 |       Send(new BluetoothMsg_GATTServerConnectSuccess(thread_id, request_id)); | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   query_result.device->CreateGattConnection( | 
 |       base::Bind(&BluetoothDispatcherHost::OnGATTConnectionCreated, | 
 |                  weak_ptr_on_ui_thread_, thread_id, request_id, device_id, | 
 |                  start_time), | 
 |       base::Bind(&BluetoothDispatcherHost::OnCreateGATTConnectionError, | 
 |                  weak_ptr_on_ui_thread_, thread_id, request_id, device_id, | 
 |                  start_time)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnGATTServerDisconnect( | 
 |     int thread_id, | 
 |     int frame_routing_id, | 
 |     const std::string& device_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall( | 
 |       UMAWebBluetoothFunction::REMOTE_GATT_SERVER_DISCONNECT); | 
 |  | 
 |   // Make sure the origin is allowed to access the device. We perform this check | 
 |   // in case a hostile renderer is trying to disconnect a device that the | 
 |   // renderer is not allowed to access. | 
 |   if (allowed_devices_map_.GetDeviceAddress(GetOrigin(frame_routing_id), | 
 |                                             device_id) | 
 |           .empty()) { | 
 |     bad_message::ReceivedBadMessage( | 
 |         this, bad_message::BDH_DEVICE_NOT_ALLOWED_FOR_ORIGIN); | 
 |     return; | 
 |   } | 
 |  | 
 |   // The last BluetoothGattConnection for a device closes the connection when | 
 |   // it's destroyed. | 
 |   if (device_id_to_connection_map_.erase(device_id)) { | 
 |     VLOG(1) << "Disconnecting device: " << device_id; | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnGetPrimaryService( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::string& device_id, | 
 |     const std::string& service_uuid) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::GET_PRIMARY_SERVICE); | 
 |   RecordGetPrimaryServiceService(BluetoothUUID(service_uuid)); | 
 |  | 
 |   if (!allowed_devices_map_.IsOriginAllowedToAccessService( | 
 |           GetOrigin(frame_routing_id), device_id, service_uuid)) { | 
 |     Send(new BluetoothMsg_GetPrimaryServiceError( | 
 |         thread_id, request_id, WebBluetoothError::NotAllowedToAccessService)); | 
 |     return; | 
 |   } | 
 |  | 
 |   const CacheQueryResult query_result = | 
 |       QueryCacheForDevice(GetOrigin(frame_routing_id), device_id); | 
 |  | 
 |   if (query_result.outcome != CacheQueryOutcome::SUCCESS) { | 
 |     RecordGetPrimaryServiceOutcome(query_result.outcome); | 
 |     Send(new BluetoothMsg_GetPrimaryServiceError(thread_id, request_id, | 
 |                                                  query_result.GetWebError())); | 
 |     return; | 
 |   } | 
 |  | 
 |   // There are four possibilities here: | 
 |   // 1. Services not discovered and service present in |device|: Send back the | 
 |   //    service to the renderer. | 
 |   // 2. Services discovered and service present in |device|: Send back the | 
 |   //    service to the renderer. | 
 |   // 3. Services discovered and service not present in |device|: Send back not | 
 |   //    found error. | 
 |   // 4. Services not discovered and service not present in |device|: Add request | 
 |   //    to map of pending getPrimaryService requests. | 
 |  | 
 |   std::vector<BluetoothGattService*> services = | 
 |       GetPrimaryServicesByUUID(query_result.device, service_uuid); | 
 |  | 
 |   // 1. & 2. | 
 |   if (!services.empty()) { | 
 |     VLOG(1) << "Service found in device."; | 
 |     const BluetoothGattService& service = *services[0]; | 
 |     DCHECK(service.IsPrimary()); | 
 |     AddToServicesMapAndSendGetPrimaryServiceSuccess(service, thread_id, | 
 |                                                     request_id); | 
 |     return; | 
 |   } | 
 |  | 
 |   // 3. | 
 |   if (query_result.device->IsGattServicesDiscoveryComplete()) { | 
 |     VLOG(1) << "Service not found in device."; | 
 |     RecordGetPrimaryServiceOutcome(UMAGetPrimaryServiceOutcome::NOT_FOUND); | 
 |     Send(new BluetoothMsg_GetPrimaryServiceError( | 
 |         thread_id, request_id, WebBluetoothError::ServiceNotFound)); | 
 |     return; | 
 |   } | 
 |  | 
 |   VLOG(1) << "Adding service request to pending requests."; | 
 |   // 4. | 
 |   AddToPendingPrimaryServicesRequest( | 
 |       query_result.device->GetAddress(), | 
 |       PrimaryServicesRequest(thread_id, request_id, service_uuid, | 
 |                              PrimaryServicesRequest::GET_PRIMARY_SERVICE)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnGetCharacteristic( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::string& service_instance_id, | 
 |     const std::string& characteristic_uuid) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::GET_CHARACTERISTIC); | 
 |   RecordGetCharacteristicCharacteristic(characteristic_uuid); | 
 |  | 
 |   // Check Blacklist for characteristic_uuid. | 
 |   if (BluetoothBlacklist::Get().IsExcluded( | 
 |           BluetoothUUID(characteristic_uuid))) { | 
 |     RecordGetCharacteristicOutcome(UMAGetCharacteristicOutcome::BLACKLISTED); | 
 |     Send(new BluetoothMsg_GetCharacteristicError( | 
 |         thread_id, request_id, | 
 |         WebBluetoothError::BlacklistedCharacteristicUUID)); | 
 |     return; | 
 |   } | 
 |  | 
 |   const CacheQueryResult query_result = | 
 |       QueryCacheForService(GetOrigin(frame_routing_id), service_instance_id); | 
 |  | 
 |   if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (query_result.outcome != CacheQueryOutcome::SUCCESS) { | 
 |     RecordGetCharacteristicOutcome(query_result.outcome); | 
 |     Send(new BluetoothMsg_GetCharacteristicError(thread_id, request_id, | 
 |                                                  query_result.GetWebError())); | 
 |     return; | 
 |   } | 
 |  | 
 |   for (BluetoothGattCharacteristic* characteristic : | 
 |        query_result.service->GetCharacteristics()) { | 
 |     if (characteristic->GetUUID().canonical_value() == characteristic_uuid) { | 
 |       const std::string& characteristic_instance_id = | 
 |           characteristic->GetIdentifier(); | 
 |  | 
 |       auto insert_result = characteristic_to_service_.insert( | 
 |           make_pair(characteristic_instance_id, service_instance_id)); | 
 |  | 
 |       // If  value is already in map, DCHECK it's valid. | 
 |       if (!insert_result.second) | 
 |         DCHECK(insert_result.first->second == service_instance_id); | 
 |  | 
 |       RecordGetCharacteristicOutcome(UMAGetCharacteristicOutcome::SUCCESS); | 
 |       // TODO(ortuno): Use generated instance ID instead. | 
 |       // https://crbug.com/495379 | 
 |       Send(new BluetoothMsg_GetCharacteristicSuccess( | 
 |           thread_id, request_id, characteristic_instance_id, | 
 |           static_cast<uint32_t>(characteristic->GetProperties()))); | 
 |       return; | 
 |     } | 
 |   } | 
 |   RecordGetCharacteristicOutcome(UMAGetCharacteristicOutcome::NOT_FOUND); | 
 |   Send(new BluetoothMsg_GetCharacteristicError( | 
 |       thread_id, request_id, WebBluetoothError::CharacteristicNotFound)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnGetCharacteristics( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::string& service_instance_id, | 
 |     const std::string& characteristics_uuid) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall( | 
 |       UMAWebBluetoothFunction::SERVICE_GET_CHARACTERISTICS); | 
 |   RecordGetCharacteristicsCharacteristic(characteristics_uuid); | 
 |  | 
 |   // Check Blacklist for characteristics_uuid. | 
 |   if (!characteristics_uuid.empty() && | 
 |       BluetoothBlacklist::Get().IsExcluded( | 
 |           BluetoothUUID(characteristics_uuid))) { | 
 |     RecordGetCharacteristicsOutcome(UMAGetCharacteristicOutcome::BLACKLISTED); | 
 |     Send(new BluetoothMsg_GetCharacteristicsError( | 
 |         thread_id, request_id, | 
 |         WebBluetoothError::BlacklistedCharacteristicUUID)); | 
 |     return; | 
 |   } | 
 |  | 
 |   const CacheQueryResult query_result = | 
 |       QueryCacheForService(GetOrigin(frame_routing_id), service_instance_id); | 
 |  | 
 |   if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (query_result.outcome != CacheQueryOutcome::SUCCESS) { | 
 |     RecordGetCharacteristicsOutcome(query_result.outcome); | 
 |     Send(new BluetoothMsg_GetCharacteristicsError(thread_id, request_id, | 
 |                                                   query_result.GetWebError())); | 
 |     return; | 
 |   } | 
 |  | 
 |   std::vector<std::string> characteristics_instance_ids; | 
 |   std::vector<std::string> characteristics_uuids; | 
 |   std::vector<uint32_t> characteristics_properties; | 
 |  | 
 |   for (BluetoothGattCharacteristic* characteristic : | 
 |        query_result.service->GetCharacteristics()) { | 
 |     if (!BluetoothBlacklist::Get().IsExcluded(characteristic->GetUUID()) && | 
 |         (characteristics_uuid.empty() || | 
 |          characteristics_uuid == characteristic->GetUUID().canonical_value())) { | 
 |       const std::string& characteristic_instance_id = | 
 |           characteristic->GetIdentifier(); | 
 |  | 
 |       characteristics_instance_ids.push_back(characteristic_instance_id); | 
 |       characteristics_uuids.push_back( | 
 |           characteristic->GetUUID().canonical_value()); | 
 |       characteristics_properties.push_back( | 
 |           static_cast<uint32_t>(characteristic->GetProperties())); | 
 |  | 
 |       auto insert_result = characteristic_to_service_.insert( | 
 |           make_pair(characteristic_instance_id, service_instance_id)); | 
 |  | 
 |       // If  value is already in map, DCHECK it's valid. | 
 |       if (!insert_result.second) | 
 |         DCHECK(insert_result.first->second == service_instance_id); | 
 |     } | 
 |   } | 
 |  | 
 |   if (!characteristics_instance_ids.empty()) { | 
 |     RecordGetCharacteristicsOutcome(UMAGetCharacteristicOutcome::SUCCESS); | 
 |     Send(new BluetoothMsg_GetCharacteristicsSuccess( | 
 |         thread_id, request_id, characteristics_instance_ids, | 
 |         characteristics_uuids, characteristics_properties)); | 
 |     return; | 
 |   } | 
 |   RecordGetCharacteristicsOutcome( | 
 |       characteristics_uuid.empty() | 
 |           ? UMAGetCharacteristicOutcome::NO_CHARACTERISTICS | 
 |           : UMAGetCharacteristicOutcome::NOT_FOUND); | 
 |   Send(new BluetoothMsg_GetCharacteristicsError( | 
 |       thread_id, request_id, characteristics_uuid.empty() | 
 |                                  ? WebBluetoothError::NoCharacteristicsFound | 
 |                                  : WebBluetoothError::CharacteristicNotFound)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnReadValue( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::string& characteristic_instance_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall( | 
 |       UMAWebBluetoothFunction::CHARACTERISTIC_READ_VALUE); | 
 |  | 
 |   const CacheQueryResult query_result = QueryCacheForCharacteristic( | 
 |       GetOrigin(frame_routing_id), characteristic_instance_id); | 
 |  | 
 |   if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (query_result.outcome != CacheQueryOutcome::SUCCESS) { | 
 |     RecordCharacteristicReadValueOutcome(query_result.outcome); | 
 |     Send(new BluetoothMsg_ReadCharacteristicValueError( | 
 |         thread_id, request_id, query_result.GetWebError())); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (BluetoothBlacklist::Get().IsExcludedFromReads( | 
 |           query_result.characteristic->GetUUID())) { | 
 |     RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome::BLACKLISTED); | 
 |     Send(new BluetoothMsg_ReadCharacteristicValueError( | 
 |         thread_id, request_id, WebBluetoothError::BlacklistedRead)); | 
 |     return; | 
 |   } | 
 |  | 
 |   query_result.characteristic->ReadRemoteCharacteristic( | 
 |       base::Bind(&BluetoothDispatcherHost::OnCharacteristicValueRead, | 
 |                  weak_ptr_on_ui_thread_, thread_id, request_id), | 
 |       base::Bind(&BluetoothDispatcherHost::OnCharacteristicReadValueError, | 
 |                  weak_ptr_on_ui_thread_, thread_id, request_id)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnWriteValue( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::string& characteristic_instance_id, | 
 |     const std::vector<uint8_t>& value) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall( | 
 |       UMAWebBluetoothFunction::CHARACTERISTIC_WRITE_VALUE); | 
 |  | 
 |   // Length check per step 3 of writeValue algorithm: | 
 |   // https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothgattcharacteristic-writevalue | 
 |   // We perform the length check on the renderer side. So if we | 
 |   // get a value with length > 512, we can assume it's a hostile | 
 |   // renderer and kill it. | 
 |   if (value.size() > 512) { | 
 |     bad_message::ReceivedBadMessage( | 
 |         this, bad_message::BDH_INVALID_WRITE_VALUE_LENGTH); | 
 |     return; | 
 |   } | 
 |  | 
 |   const CacheQueryResult query_result = QueryCacheForCharacteristic( | 
 |       GetOrigin(frame_routing_id), characteristic_instance_id); | 
 |  | 
 |   if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (query_result.outcome != CacheQueryOutcome::SUCCESS) { | 
 |     RecordCharacteristicWriteValueOutcome(query_result.outcome); | 
 |     Send(new BluetoothMsg_WriteCharacteristicValueError( | 
 |         thread_id, request_id, query_result.GetWebError())); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (BluetoothBlacklist::Get().IsExcludedFromWrites( | 
 |           query_result.characteristic->GetUUID())) { | 
 |     RecordCharacteristicWriteValueOutcome(UMAGATTOperationOutcome::BLACKLISTED); | 
 |     Send(new BluetoothMsg_WriteCharacteristicValueError( | 
 |         thread_id, request_id, WebBluetoothError::BlacklistedWrite)); | 
 |     return; | 
 |   } | 
 |  | 
 |   query_result.characteristic->WriteRemoteCharacteristic( | 
 |       value, base::Bind(&BluetoothDispatcherHost::OnWriteValueSuccess, | 
 |                         weak_ptr_on_ui_thread_, thread_id, request_id), | 
 |       base::Bind(&BluetoothDispatcherHost::OnWriteValueFailed, | 
 |                  weak_ptr_on_ui_thread_, thread_id, request_id)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnStartNotifications( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::string& characteristic_instance_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall( | 
 |       UMAWebBluetoothFunction::CHARACTERISTIC_START_NOTIFICATIONS); | 
 |  | 
 |   // BluetoothDispatcher will never send a request for a characteristic | 
 |   // already subscribed to notifications. | 
 |   if (characteristic_id_to_notify_session_.find(characteristic_instance_id) != | 
 |       characteristic_id_to_notify_session_.end()) { | 
 |     bad_message::ReceivedBadMessage( | 
 |         this, bad_message::BDH_CHARACTERISTIC_ALREADY_SUBSCRIBED); | 
 |     return; | 
 |   } | 
 |  | 
 |   // TODO(ortuno): Check if notify/indicate bit is set. | 
 |   // http://crbug.com/538869 | 
 |  | 
 |   const CacheQueryResult query_result = QueryCacheForCharacteristic( | 
 |       GetOrigin(frame_routing_id), characteristic_instance_id); | 
 |  | 
 |   if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (query_result.outcome != CacheQueryOutcome::SUCCESS) { | 
 |     RecordStartNotificationsOutcome(query_result.outcome); | 
 |     Send(new BluetoothMsg_StartNotificationsError(thread_id, request_id, | 
 |                                                   query_result.GetWebError())); | 
 |     return; | 
 |   } | 
 |  | 
 |   query_result.characteristic->StartNotifySession( | 
 |       base::Bind(&BluetoothDispatcherHost::OnStartNotifySessionSuccess, | 
 |                  weak_ptr_factory_.GetWeakPtr(), thread_id, request_id), | 
 |       base::Bind(&BluetoothDispatcherHost::OnStartNotifySessionFailed, | 
 |                  weak_ptr_factory_.GetWeakPtr(), thread_id, request_id)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnStopNotifications( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::string& characteristic_instance_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall( | 
 |       UMAWebBluetoothFunction::CHARACTERISTIC_STOP_NOTIFICATIONS); | 
 |  | 
 |   // Check the origin is allowed to access the device. We perform this check in | 
 |   // case a hostile renderer is trying to stop notifications for a device | 
 |   // that the renderer is not allowed to access. | 
 |   if (!CanFrameAccessCharacteristicInstance(frame_routing_id, | 
 |                                             characteristic_instance_id)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   auto notify_session_iter = | 
 |       characteristic_id_to_notify_session_.find(characteristic_instance_id); | 
 |   if (notify_session_iter == characteristic_id_to_notify_session_.end()) { | 
 |     Send(new BluetoothMsg_StopNotificationsSuccess(thread_id, request_id)); | 
 |     return; | 
 |   } | 
 |   notify_session_iter->second->Stop( | 
 |       base::Bind(&BluetoothDispatcherHost::OnStopNotifySession, | 
 |                  weak_ptr_factory_.GetWeakPtr(), thread_id, request_id, | 
 |                  characteristic_instance_id)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnRegisterCharacteristicObject( | 
 |     int thread_id, | 
 |     int frame_routing_id, | 
 |     const std::string& characteristic_instance_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   // Make sure the origin is allowed to access the device. | 
 |   if (!CanFrameAccessCharacteristicInstance(frame_routing_id, | 
 |                                             characteristic_instance_id)) { | 
 |     return; | 
 |   } | 
 |   active_characteristic_threads_[characteristic_instance_id].insert(thread_id); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnUnregisterCharacteristicObject( | 
 |     int thread_id, | 
 |     int frame_routing_id, | 
 |     const std::string& characteristic_instance_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   auto active_iter = | 
 |       active_characteristic_threads_.find(characteristic_instance_id); | 
 |   if (active_iter == active_characteristic_threads_.end()) { | 
 |     return; | 
 |   } | 
 |   std::set<int>& thread_ids_set = active_iter->second; | 
 |   thread_ids_set.erase(thread_id); | 
 |   if (thread_ids_set.empty()) { | 
 |     active_characteristic_threads_.erase(active_iter); | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnGetAdapter( | 
 |     base::Closure continuation, | 
 |     scoped_refptr<device::BluetoothAdapter> adapter) { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |   set_adapter(adapter); | 
 |   continuation.Run(); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnRequestDeviceImpl( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     int frame_routing_id, | 
 |     const std::vector<BluetoothScanFilter>& filters, | 
 |     const std::vector<BluetoothUUID>& optional_services) { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |   RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::REQUEST_DEVICE); | 
 |   RecordRequestDeviceArguments(filters, optional_services); | 
 |  | 
 |   VLOG(1) << "requestDevice called with the following filters: "; | 
 |   for (const BluetoothScanFilter& filter : filters) { | 
 |     VLOG(1) << "Name: " << filter.name; | 
 |     VLOG(1) << "Name Prefix: " << filter.namePrefix; | 
 |     VLOG(1) << "Services:"; | 
 |     VLOG(1) << "\t["; | 
 |     for (const BluetoothUUID& service : filter.services) | 
 |       VLOG(1) << "\t\t" << service.value(); | 
 |     VLOG(1) << "\t]"; | 
 |   } | 
 |  | 
 |   VLOG(1) << "requestDevice called with the following optional services: "; | 
 |   for (const BluetoothUUID& service : optional_services) | 
 |     VLOG(1) << "\t" << service.value(); | 
 |  | 
 |   // Check blacklist to reject invalid filters and adjust optional_services. | 
 |   if (BluetoothBlacklist::Get().IsExcluded(filters)) { | 
 |     RecordRequestDeviceOutcome( | 
 |         UMARequestDeviceOutcome::BLACKLISTED_SERVICE_IN_FILTER); | 
 |     Send(new BluetoothMsg_RequestDeviceError( | 
 |         thread_id, request_id, | 
 |         WebBluetoothError::RequestDeviceWithBlacklistedUUID)); | 
 |     return; | 
 |   } | 
 |   std::vector<BluetoothUUID> optional_services_blacklist_filtered( | 
 |       optional_services); | 
 |   BluetoothBlacklist::Get().RemoveExcludedUuids( | 
 |       &optional_services_blacklist_filtered); | 
 |  | 
 |   RenderFrameHostImpl* render_frame_host = | 
 |       RenderFrameHostImpl::FromID(render_process_id_, frame_routing_id); | 
 |   WebContents* web_contents = | 
 |       WebContents::FromRenderFrameHost(render_frame_host); | 
 |  | 
 |   if (!render_frame_host || !web_contents) { | 
 |     DLOG(WARNING) << "Got a requestDevice IPC without a matching " | 
 |                   << "RenderFrameHost or WebContents: " << render_process_id_ | 
 |                   << ", " << frame_routing_id; | 
 |     RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_RENDER_FRAME); | 
 |     Send(new BluetoothMsg_RequestDeviceError( | 
 |         thread_id, request_id, WebBluetoothError::RequestDeviceWithoutFrame)); | 
 |     return; | 
 |   } | 
 |  | 
 |   const url::Origin requesting_origin = | 
 |       render_frame_host->GetLastCommittedOrigin(); | 
 |   const url::Origin embedding_origin = | 
 |       web_contents->GetMainFrame()->GetLastCommittedOrigin(); | 
 |  | 
 |   if (requesting_origin.unique()) { | 
 |     VLOG(1) << "Request device with unique origin."; | 
 |     Send(new BluetoothMsg_RequestDeviceError( | 
 |         thread_id, request_id, | 
 |         WebBluetoothError::RequestDeviceWithUniqueOrigin)); | 
 |     return; | 
 |   } | 
 |  | 
 |   DCHECK(adapter_.get()); | 
 |  | 
 |   if (!adapter_->IsPresent()) { | 
 |     VLOG(1) << "Bluetooth Adapter not present. Can't serve requestDevice."; | 
 |     RecordRequestDeviceOutcome( | 
 |         UMARequestDeviceOutcome::BLUETOOTH_ADAPTER_NOT_PRESENT); | 
 |     Send(new BluetoothMsg_RequestDeviceError( | 
 |         thread_id, request_id, WebBluetoothError::NoBluetoothAdapter)); | 
 |     return; | 
 |   } | 
 |  | 
 |   // The renderer should never send empty filters. | 
 |   if (HasEmptyOrInvalidFilter(filters)) { | 
 |     bad_message::ReceivedBadMessage(this, | 
 |                                     bad_message::BDH_EMPTY_OR_INVALID_FILTERS); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!GetContentClient()->browser()->AllowWebBluetooth( | 
 |           web_contents->GetBrowserContext(), requesting_origin, | 
 |           embedding_origin)) { | 
 |     RecordRequestDeviceOutcome( | 
 |         UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_GLOBALLY_DISABLED); | 
 |     Send(new BluetoothMsg_RequestDeviceError( | 
 |         thread_id, request_id, WebBluetoothError::ChooserDisabled)); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Create storage for the information that backs the chooser, and show the | 
 |   // chooser. | 
 |   RequestDeviceSession* const session = new RequestDeviceSession( | 
 |       thread_id, request_id, frame_routing_id, requesting_origin, filters, | 
 |       optional_services_blacklist_filtered); | 
 |   int chooser_id = request_device_sessions_.Add(session); | 
 |  | 
 |   BluetoothChooser::EventHandler chooser_event_handler = | 
 |       base::Bind(&BluetoothDispatcherHost::OnBluetoothChooserEvent, | 
 |                  weak_ptr_on_ui_thread_, chooser_id); | 
 |   if (WebContentsDelegate* delegate = web_contents->GetDelegate()) { | 
 |     session->chooser = | 
 |         delegate->RunBluetoothChooser(render_frame_host, chooser_event_handler); | 
 |   } | 
 |   if (!session->chooser) { | 
 |     LOG(WARNING) | 
 |         << "No Bluetooth chooser implementation; falling back to first device."; | 
 |     session->chooser.reset( | 
 |         new FirstDeviceBluetoothChooser(chooser_event_handler)); | 
 |   } | 
 |  | 
 |   if (!session->chooser->CanAskForScanningPermission()) { | 
 |     VLOG(1) << "Closing immediately because Chooser cannot obtain permission."; | 
 |     OnBluetoothChooserEvent(chooser_id, | 
 |                             BluetoothChooser::Event::DENIED_PERMISSION, ""); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Populate the initial list of devices. | 
 |   VLOG(1) << "Populating " << adapter_->GetDevices().size() | 
 |           << " devices in chooser " << chooser_id; | 
 |   for (const device::BluetoothDevice* device : adapter_->GetDevices()) { | 
 |     VLOG(1) << "\t" << device->GetAddress(); | 
 |     session->AddFilteredDevice(*device); | 
 |   } | 
 |  | 
 |   if (!session->chooser) { | 
 |     // If the dialog's closing, no need to do any of the rest of this. | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!adapter_->IsPowered()) { | 
 |     session->chooser->SetAdapterPresence( | 
 |         BluetoothChooser::AdapterPresence::POWERED_OFF); | 
 |     return; | 
 |   } | 
 |  | 
 |   StartDeviceDiscovery(session, chooser_id); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnDiscoverySessionStarted( | 
 |     int chooser_id, | 
 |     scoped_ptr<device::BluetoothDiscoverySession> discovery_session) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   VLOG(1) << "Started discovery session for " << chooser_id; | 
 |   if (RequestDeviceSession* session = | 
 |           request_device_sessions_.Lookup(chooser_id)) { | 
 |     session->discovery_session = std::move(discovery_session); | 
 |  | 
 |     // Arrange to stop discovery later. | 
 |     discovery_session_timer_.Reset(); | 
 |   } else { | 
 |     VLOG(1) << "Chooser " << chooser_id | 
 |             << " was closed before the session finished starting. Stopping."; | 
 |     StopDiscoverySession(std::move(discovery_session)); | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnDiscoverySessionStartedError(int chooser_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   VLOG(1) << "Failed to start discovery session for " << chooser_id; | 
 |   if (RequestDeviceSession* session = | 
 |           request_device_sessions_.Lookup(chooser_id)) { | 
 |     if (session->chooser && !session->discovery_session) { | 
 |       session->chooser->ShowDiscoveryState( | 
 |           BluetoothChooser::DiscoveryState::FAILED_TO_START); | 
 |     } | 
 |   } | 
 |   // Ignore discovery session start errors when the dialog was already closed by | 
 |   // the time they happen. | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnBluetoothChooserEvent( | 
 |     int chooser_id, | 
 |     BluetoothChooser::Event event, | 
 |     const std::string& device_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RequestDeviceSession* session = request_device_sessions_.Lookup(chooser_id); | 
 |   DCHECK(session) << "Shouldn't receive an event (" << static_cast<int>(event) | 
 |                   << ") from a closed chooser."; | 
 |   CHECK(session->chooser) << "Shouldn't receive an event (" | 
 |                           << static_cast<int>(event) | 
 |                           << ") from a closed chooser."; | 
 |   switch (event) { | 
 |     case BluetoothChooser::Event::RESCAN: | 
 |       StartDeviceDiscovery(session, chooser_id); | 
 |       // No need to close the chooser so we return. | 
 |       return; | 
 |     case BluetoothChooser::Event::DENIED_PERMISSION: | 
 |     case BluetoothChooser::Event::CANCELLED: | 
 |     case BluetoothChooser::Event::SELECTED: | 
 |       break; | 
 |     case BluetoothChooser::Event::SHOW_OVERVIEW_HELP: | 
 |       VLOG(1) << "Overview Help link pressed."; | 
 |       break; | 
 |     case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP: | 
 |       VLOG(1) << "Adapter Off Help link pressed."; | 
 |       break; | 
 |     case BluetoothChooser::Event::SHOW_NEED_LOCATION_HELP: | 
 |       VLOG(1) << "Need Location Help link pressed."; | 
 |       break; | 
 |   } | 
 |  | 
 |   // Synchronously ensure nothing else calls into the chooser after it has | 
 |   // asked to be closed. | 
 |   session->chooser.reset(); | 
 |  | 
 |   // Yield to the event loop to make sure we don't destroy the session | 
 |   // within a BluetoothDispatcherHost stack frame. | 
 |   if (!base::ThreadTaskRunnerHandle::Get()->PostTask( | 
 |           FROM_HERE, | 
 |           base::Bind(&BluetoothDispatcherHost::FinishClosingChooser, | 
 |                      weak_ptr_on_ui_thread_, chooser_id, event, device_id))) { | 
 |     LOG(WARNING) << "No TaskRunner; not closing requestDevice dialog."; | 
 |   } | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::FinishClosingChooser( | 
 |     int chooser_id, | 
 |     BluetoothChooser::Event event, | 
 |     const std::string& device_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RequestDeviceSession* session = request_device_sessions_.Lookup(chooser_id); | 
 |   DCHECK(session) << "Session removed unexpectedly."; | 
 |  | 
 |   if ((event != BluetoothChooser::Event::DENIED_PERMISSION) && | 
 |       (event != BluetoothChooser::Event::SELECTED)) { | 
 |     RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event)); | 
 |     Send(new BluetoothMsg_RequestDeviceError( | 
 |         session->thread_id, session->request_id, | 
 |         WebBluetoothError::ChooserCancelled)); | 
 |     request_device_sessions_.Remove(chooser_id); | 
 |     return; | 
 |   } | 
 |   if (event == BluetoothChooser::Event::DENIED_PERMISSION) { | 
 |     RecordRequestDeviceOutcome( | 
 |         UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_DENIED_PERMISSION); | 
 |     VLOG(1) << "Bluetooth chooser denied permission"; | 
 |     Send(new BluetoothMsg_RequestDeviceError( | 
 |         session->thread_id, session->request_id, | 
 |         WebBluetoothError::ChooserDeniedPermission)); | 
 |     request_device_sessions_.Remove(chooser_id); | 
 |     return; | 
 |   } | 
 |   DCHECK_EQ(static_cast<int>(event), | 
 |             static_cast<int>(BluetoothChooser::Event::SELECTED)); | 
 |  | 
 |   // |device_id| is the Device Address that RequestDeviceSession passed to | 
 |   // chooser->AddDevice(). | 
 |   const device::BluetoothDevice* const device = adapter_->GetDevice(device_id); | 
 |   if (device == nullptr) { | 
 |     VLOG(1) << "Device " << device_id << " no longer in adapter"; | 
 |     RecordRequestDeviceOutcome(UMARequestDeviceOutcome::CHOSEN_DEVICE_VANISHED); | 
 |     Send(new BluetoothMsg_RequestDeviceError( | 
 |         session->thread_id, session->request_id, | 
 |         WebBluetoothError::ChosenDeviceVanished)); | 
 |     request_device_sessions_.Remove(chooser_id); | 
 |     return; | 
 |   } | 
 |  | 
 |   const std::string& device_id_for_origin = allowed_devices_map_.AddDevice( | 
 |       session->origin, device->GetAddress(), session->filters, | 
 |       session->optional_services); | 
 |  | 
 |   VLOG(1) << "Device: " << device->GetName(); | 
 |   VLOG(1) << "UUIDs: "; | 
 |  | 
 |   device::BluetoothDevice::UUIDList filtered_uuids; | 
 |   for (BluetoothUUID uuid : device->GetUUIDs()) { | 
 |     if (allowed_devices_map_.IsOriginAllowedToAccessService( | 
 |             session->origin, device_id_for_origin, uuid.canonical_value())) { | 
 |       VLOG(1) << "\t Allowed: " << uuid.canonical_value(); | 
 |       filtered_uuids.push_back(uuid); | 
 |     } else { | 
 |       VLOG(1) << "\t Not Allowed: " << uuid.canonical_value(); | 
 |     } | 
 |   } | 
 |  | 
 |   content::BluetoothDevice device_ipc( | 
 |       device_id_for_origin,  // id | 
 |       device->GetName(),     // name | 
 |       content::BluetoothDevice::ValidatePower( | 
 |           device->GetInquiryTxPower()),  // tx_power | 
 |       content::BluetoothDevice::ValidatePower( | 
 |           device->GetInquiryRSSI()),  // rssi | 
 |       device->GetBluetoothClass(),    // device_class | 
 |       device->GetVendorIDSource(),    // vendor_id_source | 
 |       device->GetVendorID(),          // vendor_id | 
 |       device->GetProductID(),         // product_id | 
 |       device->GetDeviceID(),          // product_version | 
 |       content::BluetoothDevice::UUIDsFromBluetoothUUIDs( | 
 |           filtered_uuids));  // uuids | 
 |   RecordRequestDeviceOutcome(UMARequestDeviceOutcome::SUCCESS); | 
 |   Send(new BluetoothMsg_RequestDeviceSuccess(session->thread_id, | 
 |                                              session->request_id, device_ipc)); | 
 |   request_device_sessions_.Remove(chooser_id); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnGATTConnectionCreated( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     const std::string& device_id, | 
 |     base::TimeTicks start_time, | 
 |     scoped_ptr<device::BluetoothGattConnection> connection) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   device_id_to_connection_map_[device_id] = std::move(connection); | 
 |   RecordConnectGATTTimeSuccess(base::TimeTicks::Now() - start_time); | 
 |   RecordConnectGATTOutcome(UMAConnectGATTOutcome::SUCCESS); | 
 |   Send(new BluetoothMsg_GATTServerConnectSuccess(thread_id, request_id)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnCreateGATTConnectionError( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     const std::string& device_id, | 
 |     base::TimeTicks start_time, | 
 |     device::BluetoothDevice::ConnectErrorCode error_code) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   // There was an error creating the ATT Bearer so we reject with | 
 |   // NetworkError. | 
 |   // https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothdevice-connectgatt | 
 |   RecordConnectGATTTimeFailed(base::TimeTicks::Now() - start_time); | 
 |   // RecordConnectGATTOutcome is called by TranslateConnectError. | 
 |   Send(new BluetoothMsg_GATTServerConnectError( | 
 |       thread_id, request_id, TranslateConnectError(error_code))); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::AddToServicesMapAndSendGetPrimaryServiceSuccess( | 
 |     const device::BluetoothGattService& service, | 
 |     int thread_id, | 
 |     int request_id) { | 
 |   const std::string& service_identifier = service.GetIdentifier(); | 
 |   const std::string& device_address = service.GetDevice()->GetAddress(); | 
 |   auto insert_result = | 
 |       service_to_device_.insert(make_pair(service_identifier, device_address)); | 
 |  | 
 |   // If a value is already in map, DCHECK it's valid. | 
 |   if (!insert_result.second) | 
 |     DCHECK_EQ(insert_result.first->second, device_address); | 
 |  | 
 |   RecordGetPrimaryServiceOutcome(UMAGetPrimaryServiceOutcome::SUCCESS); | 
 |   Send(new BluetoothMsg_GetPrimaryServiceSuccess(thread_id, request_id, | 
 |                                                  service_identifier)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnCharacteristicValueRead( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     const std::vector<uint8_t>& value) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome::SUCCESS); | 
 |   Send(new BluetoothMsg_ReadCharacteristicValueSuccess(thread_id, request_id, | 
 |                                                        value)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnCharacteristicReadValueError( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     device::BluetoothGattService::GattErrorCode error_code) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   // TranslateGATTError calls RecordGATTOperationOutcome. | 
 |   Send(new BluetoothMsg_ReadCharacteristicValueError( | 
 |       thread_id, request_id, | 
 |       TranslateGATTError(error_code, UMAGATTOperation::CHARACTERISTIC_READ))); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnWriteValueSuccess(int thread_id, | 
 |                                                   int request_id) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   RecordCharacteristicWriteValueOutcome(UMAGATTOperationOutcome::SUCCESS); | 
 |   Send(new BluetoothMsg_WriteCharacteristicValueSuccess(thread_id, request_id)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnWriteValueFailed( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     device::BluetoothGattService::GattErrorCode error_code) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   // TranslateGATTError calls RecordGATTOperationOutcome. | 
 |   Send(new BluetoothMsg_WriteCharacteristicValueError( | 
 |       thread_id, request_id, | 
 |       TranslateGATTError(error_code, UMAGATTOperation::CHARACTERISTIC_WRITE))); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnStartNotifySessionSuccess( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     scoped_ptr<device::BluetoothGattNotifySession> notify_session) { | 
 |   RecordStartNotificationsOutcome(UMAGATTOperationOutcome::SUCCESS); | 
 |  | 
 |   // Copy Characteristic Instance ID before passing scoped pointer because | 
 |   // compilers may evaluate arguments in any order. | 
 |   const std::string characteristic_instance_id = | 
 |       notify_session->GetCharacteristicIdentifier(); | 
 |   characteristic_id_to_notify_session_.insert( | 
 |       std::make_pair(characteristic_instance_id, std::move(notify_session))); | 
 |  | 
 |   Send(new BluetoothMsg_StartNotificationsSuccess(thread_id, request_id)); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnStartNotifySessionFailed( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     device::BluetoothGattService::GattErrorCode error_code) { | 
 |   // TranslateGATTError calls RecordGATTOperationOutcome. | 
 |   Send(new BluetoothMsg_StartNotificationsError( | 
 |       thread_id, request_id, | 
 |       TranslateGATTError(error_code, UMAGATTOperation::START_NOTIFICATIONS))); | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::OnStopNotifySession( | 
 |     int thread_id, | 
 |     int request_id, | 
 |     const std::string& characteristic_instance_id) { | 
 |   characteristic_id_to_notify_session_.erase(characteristic_instance_id); | 
 |   Send(new BluetoothMsg_StopNotificationsSuccess(thread_id, request_id)); | 
 | } | 
 |  | 
 | BluetoothDispatcherHost::CacheQueryResult | 
 | BluetoothDispatcherHost::QueryCacheForDevice(const url::Origin& origin, | 
 |                                              const std::string& device_id) { | 
 |   const std::string& device_address = | 
 |       allowed_devices_map_.GetDeviceAddress(origin, device_id); | 
 |   if (device_address.empty()) { | 
 |     bad_message::ReceivedBadMessage( | 
 |         this, bad_message::BDH_DEVICE_NOT_ALLOWED_FOR_ORIGIN); | 
 |     return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); | 
 |   } | 
 |  | 
 |   CacheQueryResult result; | 
 |   result.device = adapter_->GetDevice(device_address); | 
 |  | 
 |   // When a device can't be found in the BluetoothAdapter, that generally | 
 |   // indicates that it's gone out of range. We reject with a NetworkError in | 
 |   // that case. | 
 |   // https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothdevice-connectgatt | 
 |   if (result.device == nullptr) { | 
 |     result.outcome = CacheQueryOutcome::NO_DEVICE; | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | BluetoothDispatcherHost::CacheQueryResult | 
 | BluetoothDispatcherHost::QueryCacheForService( | 
 |     const url::Origin& origin, | 
 |     const std::string& service_instance_id) { | 
 |   auto device_iter = service_to_device_.find(service_instance_id); | 
 |  | 
 |   // Kill the renderer, see "ID Not In Map Note" above. | 
 |   if (device_iter == service_to_device_.end()) { | 
 |     bad_message::ReceivedBadMessage(this, bad_message::BDH_INVALID_SERVICE_ID); | 
 |     return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); | 
 |   } | 
 |  | 
 |   const std::string& device_id = | 
 |       allowed_devices_map_.GetDeviceId(origin, device_iter->second); | 
 |   // Kill the renderer if the origin is not allowed to access the device. | 
 |   if (device_id.empty()) { | 
 |     bad_message::ReceivedBadMessage( | 
 |         this, bad_message::BDH_DEVICE_NOT_ALLOWED_FOR_ORIGIN); | 
 |     return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); | 
 |   } | 
 |  | 
 |   CacheQueryResult result = QueryCacheForDevice(origin, device_id); | 
 |  | 
 |   if (result.outcome != CacheQueryOutcome::SUCCESS) { | 
 |     return result; | 
 |   } | 
 |  | 
 |   result.service = result.device->GetGattService(service_instance_id); | 
 |   if (result.service == nullptr) { | 
 |     result.outcome = CacheQueryOutcome::NO_SERVICE; | 
 |   } else if (!allowed_devices_map_.IsOriginAllowedToAccessService( | 
 |                  origin, device_id, | 
 |                  result.service->GetUUID().canonical_value())) { | 
 |     bad_message::ReceivedBadMessage( | 
 |         this, bad_message::BDH_SERVICE_NOT_ALLOWED_FOR_ORIGIN); | 
 |     return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | BluetoothDispatcherHost::CacheQueryResult | 
 | BluetoothDispatcherHost::QueryCacheForCharacteristic( | 
 |     const url::Origin& origin, | 
 |     const std::string& characteristic_instance_id) { | 
 |   auto characteristic_iter = | 
 |       characteristic_to_service_.find(characteristic_instance_id); | 
 |  | 
 |   // Kill the renderer, see "ID Not In Map Note" above. | 
 |   if (characteristic_iter == characteristic_to_service_.end()) { | 
 |     bad_message::ReceivedBadMessage(this, | 
 |                                     bad_message::BDH_INVALID_CHARACTERISTIC_ID); | 
 |     return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); | 
 |   } | 
 |  | 
 |   CacheQueryResult result = | 
 |       QueryCacheForService(origin, characteristic_iter->second); | 
 |   if (result.outcome != CacheQueryOutcome::SUCCESS) { | 
 |     return result; | 
 |   } | 
 |  | 
 |   result.characteristic = | 
 |       result.service->GetCharacteristic(characteristic_instance_id); | 
 |  | 
 |   if (result.characteristic == nullptr) { | 
 |     result.outcome = CacheQueryOutcome::NO_CHARACTERISTIC; | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | void BluetoothDispatcherHost::AddToPendingPrimaryServicesRequest( | 
 |     const std::string& device_address, | 
 |     const PrimaryServicesRequest& request) { | 
 |   pending_primary_services_requests_[device_address].push_back(request); | 
 | } | 
 |  | 
 | url::Origin BluetoothDispatcherHost::GetOrigin(int frame_routing_id) { | 
 |   return RenderFrameHostImpl::FromID(render_process_id_, frame_routing_id) | 
 |       ->GetLastCommittedOrigin(); | 
 | } | 
 |  | 
 | bool BluetoothDispatcherHost::CanFrameAccessCharacteristicInstance( | 
 |     int frame_routing_id, | 
 |     const std::string& characteristic_instance_id) { | 
 |   return QueryCacheForCharacteristic(GetOrigin(frame_routing_id), | 
 |                                      characteristic_instance_id) | 
 |              .outcome != CacheQueryOutcome::BAD_RENDERER; | 
 | } | 
 |  | 
 | }  // namespace content |