blob: 85bf81e00038f5b12484100403ac1c7e100a936e [file] [log] [blame]
// 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_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/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;
}
} // 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.
if (BluetoothAdapterFactory::IsBluetoothAdapterAvailable())
BluetoothAdapterFactory::GetAdapter(
base::Bind(&BluetoothDispatcherHost::set_adapter,
weak_ptr_factory_.GetWeakPtr()));
}
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_ConnectGATT, OnConnectGATT)
IPC_MESSAGE_HANDLER(BluetoothHostMsg_GetPrimaryService, OnGetPrimaryService)
IPC_MESSAGE_HANDLER(BluetoothHostMsg_GetCharacteristic, OnGetCharacteristic)
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();
connections_.clear();
}
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,
url::Origin origin,
const std::vector<BluetoothScanFilter>& filters,
const std::vector<BluetoothUUID>& optional_services)
: thread_id(thread_id),
request_id(request_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 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);
connections_.clear();
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();
if (session->chooser)
session->chooser->SetAdapterPresence(presence);
}
}
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);
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();
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(render_process_id_, frame_routing_id);
if (!render_frame_host) {
DLOG(WARNING)
<< "Got a requestDevice IPC without a matching RenderFrameHost: "
<< render_process_id_ << ", " << frame_routing_id;
RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_RENDER_FRAME);
Send(new BluetoothMsg_RequestDeviceError(
thread_id, request_id, WebBluetoothError::RequestDeviceWithoutFrame));
return;
}
if (!adapter_) {
VLOG(1) << "No BluetoothAdapter. Can't serve requestDevice.";
RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_BLUETOOTH_ADAPTER);
Send(new BluetoothMsg_RequestDeviceError(
thread_id, request_id, WebBluetoothError::NoBluetoothAdapter));
return;
}
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;
}
// Create storage for the information that backs the chooser, and show the
// chooser.
RequestDeviceSession* const session = new RequestDeviceSession(
thread_id, request_id, render_frame_host->GetLastCommittedOrigin(),
filters, optional_services);
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 (WebContents* web_contents =
WebContents::FromRenderFrameHost(render_frame_host)) {
if (WebContentsDelegate* delegate = web_contents->GetDelegate()) {
session->chooser = delegate->RunBluetoothChooser(
web_contents, chooser_event_handler,
// TODO(ortuno): Replace with GetLastCommittedOrigin.
// http://crbug.com/577451
render_frame_host->GetLastCommittedURL().GetOrigin());
}
}
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::OnConnectGATT(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_ConnectGATTError(thread_id, request_id,
query_result.GetWebError()));
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::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));
// TODO(ortuno): Check if service_uuid is in "allowed services"
// https://crbug.com/493460
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);
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::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;
}
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;
}
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::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);
break;
case BluetoothChooser::Event::DENIED_PERMISSION:
case BluetoothChooser::Event::CANCELLED:
case BluetoothChooser::Event::SELECTED: {
// 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.";
}
break;
}
case BluetoothChooser::Event::SHOW_OVERVIEW_HELP:
ShowBluetoothOverviewLink();
break;
case BluetoothChooser::Event::SHOW_PAIRING_HELP:
ShowBluetoothPairingLink();
break;
case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP:
ShowBluetoothAdapterOffLink();
break;
case BluetoothChooser::Event::SHOW_NEED_LOCATION_HELP:
ShowNeedLocationLink();
break;
}
}
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::CANCELLED) {
RecordRequestDeviceOutcome(
UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_CANCELLED);
VLOG(1) << "Bluetooth chooser cancelled";
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;
}
VLOG(1) << "Device: " << device->GetName();
VLOG(1) << "UUIDs: ";
for (BluetoothUUID uuid : device->GetUUIDs())
VLOG(1) << "\t" << uuid.canonical_value();
const std::string& device_id_for_origin = allowed_devices_map_.AddDevice(
session->origin, device->GetAddress(), session->filters,
session->optional_services);
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(
device->GetUUIDs())); // 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);
connections_.push_back(std::move(connection));
RecordConnectGATTTimeSuccess(base::TimeTicks::Now() - start_time);
RecordConnectGATTOutcome(UMAConnectGATTOutcome::SUCCESS);
Send(new BluetoothMsg_ConnectGATTSuccess(thread_id, request_id, device_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_ConnectGATTError(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;
}
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;
}
void BluetoothDispatcherHost::ShowBluetoothOverviewLink() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
NOTIMPLEMENTED();
}
void BluetoothDispatcherHost::ShowBluetoothPairingLink() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
NOTIMPLEMENTED();
}
void BluetoothDispatcherHost::ShowBluetoothAdapterOffLink() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
NOTIMPLEMENTED();
}
void BluetoothDispatcherHost::ShowNeedLocationLink() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
NOTIMPLEMENTED();
}
} // namespace content