1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
|
// Copyright 2015 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.
#include "modules/bluetooth/Bluetooth.h"
#include "bindings/core/v8/CallbackPromiseAdapter.h"
#include "bindings/core/v8/ScriptPromise.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/dom/DOMException.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/ExecutionContext.h"
#include "core/frame/LocalFrame.h"
#include "modules/bluetooth/BluetoothDevice.h"
#include "modules/bluetooth/BluetoothError.h"
#include "modules/bluetooth/BluetoothRemoteGATTCharacteristic.h"
#include "modules/bluetooth/BluetoothUUID.h"
#include "modules/bluetooth/RequestDeviceOptions.h"
#include "platform/UserGestureIndicator.h"
#include "public/platform/InterfaceProvider.h"
#include <memory>
#include <utility>
namespace blink {
namespace {
// A name coming from an adv packet is max 29 bytes (adv packet max size
// 31 bytes - 2 byte length field), but the name can also be acquired via
// gap.device_name, so it is limited to the max EIR packet size of 240 bytes.
// See Core Spec 5.0, vol 3, C, 8.1.2.
const size_t kMaxFilterNameLength = 240;
const char kFilterNameTooLong[] =
"A 'name' or 'namePrefix' longer than 240 bytes results in no devices "
"being found, because a device can't acquire a name longer than 240 bytes.";
// Per the Bluetooth Spec: The name is a user-friendly name associated with the
// device and consists of a maximum of 248 bytes coded according to the UTF-8
// standard.
const size_t kMaxDeviceNameLength = 248;
const char kDeviceNameTooLong[] =
"A device name can't be longer than 248 bytes.";
} // namespace
static void canonicalizeFilter(
const BluetoothScanFilterInit& filter,
mojom::blink::WebBluetoothScanFilterPtr& canonicalizedFilter,
ExceptionState& exceptionState) {
if (!(filter.hasServices() || filter.hasName() || filter.hasNamePrefix())) {
exceptionState.throwTypeError(
"A filter must restrict the devices in some way.");
return;
}
if (filter.hasServices()) {
if (filter.services().size() == 0) {
exceptionState.throwTypeError(
"'services', if present, must contain at least one service.");
return;
}
canonicalizedFilter->services.emplace();
for (const StringOrUnsignedLong& service : filter.services()) {
const String& validatedService =
BluetoothUUID::getService(service, exceptionState);
if (exceptionState.hadException())
return;
canonicalizedFilter->services->push_back(validatedService);
}
}
if (filter.hasName()) {
size_t nameLength = filter.name().utf8().length();
if (nameLength > kMaxDeviceNameLength) {
exceptionState.throwTypeError(kDeviceNameTooLong);
return;
}
if (nameLength > kMaxFilterNameLength) {
exceptionState.throwDOMException(NotFoundError, kFilterNameTooLong);
return;
}
canonicalizedFilter->name = filter.name();
}
if (filter.hasNamePrefix()) {
size_t namePrefixLength = filter.namePrefix().utf8().length();
if (namePrefixLength > kMaxDeviceNameLength) {
exceptionState.throwTypeError(kDeviceNameTooLong);
return;
}
if (namePrefixLength > kMaxFilterNameLength) {
exceptionState.throwDOMException(NotFoundError, kFilterNameTooLong);
return;
}
if (filter.namePrefix().length() == 0) {
exceptionState.throwTypeError(
"'namePrefix', if present, must me non-empty.");
return;
}
canonicalizedFilter->name_prefix = filter.namePrefix();
}
}
static void convertRequestDeviceOptions(
const RequestDeviceOptions& options,
mojom::blink::WebBluetoothRequestDeviceOptionsPtr& result,
ExceptionState& exceptionState) {
if (!(options.hasFilters() ^ options.acceptAllDevices())) {
exceptionState.throwTypeError(
"Either 'filters' should be present or 'acceptAllDevices' should be "
"true, but not both.");
return;
}
result->accept_all_devices = options.acceptAllDevices();
if (options.hasFilters()) {
if (options.filters().isEmpty()) {
exceptionState.throwTypeError(
"'filters' member must be non-empty to find any devices.");
return;
}
result->filters.emplace();
for (const BluetoothScanFilterInit& filter : options.filters()) {
auto canonicalizedFilter = mojom::blink::WebBluetoothScanFilter::New();
canonicalizeFilter(filter, canonicalizedFilter, exceptionState);
if (exceptionState.hadException())
return;
result->filters.value().push_back(std::move(canonicalizedFilter));
}
}
if (options.hasOptionalServices()) {
for (const StringOrUnsignedLong& optionalService :
options.optionalServices()) {
const String& validatedOptionalService =
BluetoothUUID::getService(optionalService, exceptionState);
if (exceptionState.hadException())
return;
result->optional_services.push_back(validatedOptionalService);
}
}
}
void Bluetooth::dispose() {
// The pipe to this object must be closed when is marked unreachable to
// prevent messages from being dispatched before lazy sweeping.
if (m_clientBinding.is_bound())
m_clientBinding.Close();
}
void Bluetooth::RequestDeviceCallback(
ScriptPromiseResolver* resolver,
mojom::blink::WebBluetoothResult result,
mojom::blink::WebBluetoothDevicePtr device) {
if (!resolver->getExecutionContext() ||
resolver->getExecutionContext()->isContextDestroyed())
return;
if (result == mojom::blink::WebBluetoothResult::SUCCESS) {
BluetoothDevice* bluetoothDevice =
getBluetoothDeviceRepresentingDevice(std::move(device), resolver);
resolver->resolve(bluetoothDevice);
} else {
resolver->reject(BluetoothError::take(resolver, result));
}
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
ScriptPromise Bluetooth::requestDevice(ScriptState* scriptState,
const RequestDeviceOptions& options,
ExceptionState& exceptionState) {
ExecutionContext* context = scriptState->getExecutionContext();
// If the incumbent settings object is not a secure context, reject promise
// with a SecurityError and abort these steps.
String errorMessage;
if (!context->isSecureContext(errorMessage)) {
return ScriptPromise::rejectWithDOMException(
scriptState, DOMException::create(SecurityError, errorMessage));
}
// If the algorithm is not allowed to show a popup, reject promise with a
// SecurityError and abort these steps.
if (!UserGestureIndicator::consumeUserGesture()) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(
SecurityError,
"Must be handling a user gesture to show a permission request."));
}
if (!m_service) {
InterfaceProvider* interfaceProvider = nullptr;
ExecutionContext* executionContext = scriptState->getExecutionContext();
if (executionContext->isDocument()) {
Document* document = toDocument(executionContext);
if (document->frame())
interfaceProvider = document->frame()->interfaceProvider();
}
if (interfaceProvider)
interfaceProvider->getInterface(mojo::MakeRequest(&m_service));
if (m_service) {
// Create an associated interface ptr and pass it to the
// WebBluetoothService so that it can send us events without us
// prompting.
mojom::blink::WebBluetoothServiceClientAssociatedPtrInfo ptrInfo;
m_clientBinding.Bind(&ptrInfo, m_service.associated_group());
m_service->SetClient(std::move(ptrInfo));
}
}
if (!m_service) {
return ScriptPromise::rejectWithDOMException(
scriptState, DOMException::create(NotSupportedError));
}
// In order to convert the arguments from service names and aliases to just
// UUIDs, do the following substeps:
auto deviceOptions = mojom::blink::WebBluetoothRequestDeviceOptions::New();
convertRequestDeviceOptions(options, deviceOptions, exceptionState);
if (exceptionState.hadException())
return exceptionState.reject(scriptState);
// Subsequent steps are handled in the browser process.
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
ScriptPromise promise = resolver->promise();
service()->RequestDevice(
std::move(deviceOptions),
convertToBaseCallback(WTF::bind(&Bluetooth::RequestDeviceCallback,
wrapPersistent(this),
wrapPersistent(resolver))));
return promise;
}
void Bluetooth::addDevice(const String& deviceId, BluetoothDevice* device) {
m_connectedDevices.add(deviceId, device);
}
void Bluetooth::removeDevice(const String& deviceId) {
m_connectedDevices.remove(deviceId);
}
void Bluetooth::registerCharacteristicObject(
const String& characteristicInstanceId,
BluetoothRemoteGATTCharacteristic* characteristic) {
m_activeCharacteristics.add(characteristicInstanceId, characteristic);
}
void Bluetooth::characteristicObjectRemoved(
const String& characteristicInstanceId) {
m_activeCharacteristics.remove(characteristicInstanceId);
}
DEFINE_TRACE(Bluetooth) {
visitor->trace(m_deviceInstanceMap);
visitor->trace(m_activeCharacteristics);
visitor->trace(m_connectedDevices);
}
Bluetooth::Bluetooth() : m_clientBinding(this) {}
void Bluetooth::RemoteCharacteristicValueChanged(
const WTF::String& characteristicInstanceId,
const WTF::Vector<uint8_t>& value) {
BluetoothRemoteGATTCharacteristic* characteristic =
m_activeCharacteristics.get(characteristicInstanceId);
if (characteristic)
characteristic->dispatchCharacteristicValueChanged(value);
}
void Bluetooth::GattServerDisconnected(const WTF::String& deviceId) {
BluetoothDevice* device = m_connectedDevices.get(deviceId);
if (device) {
// Remove device from the map before calling dispatchGattServerDisconnected
// to avoid removing a device the gattserverdisconnected event handler might
// have re-connected.
m_connectedDevices.remove(deviceId);
device->dispatchGattServerDisconnected();
}
}
BluetoothDevice* Bluetooth::getBluetoothDeviceRepresentingDevice(
mojom::blink::WebBluetoothDevicePtr devicePtr,
ScriptPromiseResolver* resolver) {
WTF::String id = devicePtr->id;
BluetoothDevice* device = m_deviceInstanceMap.get(id);
if (!device) {
device = BluetoothDevice::take(resolver, std::move(devicePtr), this);
auto result = m_deviceInstanceMap.add(id, device);
DCHECK(result.isNewEntry);
}
return device;
}
} // namespace blink
|