blob: cb108ad89ef2e69b9dc3eb67fb5924ef07a602ed [file] [log] [blame]
// 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/BluetoothRemoteGATTCharacteristic.h"
#include "bindings/core/v8/ScriptPromise.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/dom/DOMException.h"
#include "core/events/Event.h"
#include "core/inspector/ConsoleMessage.h"
#include "modules/bluetooth/Bluetooth.h"
#include "modules/bluetooth/BluetoothCharacteristicProperties.h"
#include "modules/bluetooth/BluetoothDevice.h"
#include "modules/bluetooth/BluetoothError.h"
#include "modules/bluetooth/BluetoothRemoteGATTDescriptor.h"
#include "modules/bluetooth/BluetoothRemoteGATTService.h"
#include "modules/bluetooth/BluetoothRemoteGATTUtils.h"
#include "modules/bluetooth/BluetoothUUID.h"
#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
#include <memory>
#include <utility>
namespace blink {
BluetoothRemoteGATTCharacteristic::BluetoothRemoteGATTCharacteristic(
ExecutionContext* context,
mojom::blink::WebBluetoothRemoteGATTCharacteristicPtr characteristic,
BluetoothRemoteGATTService* service,
BluetoothDevice* device)
: ContextLifecycleObserver(context),
characteristic_(std::move(characteristic)),
service_(service),
device_(device) {
properties_ =
BluetoothCharacteristicProperties::Create(characteristic_->properties);
}
BluetoothRemoteGATTCharacteristic* BluetoothRemoteGATTCharacteristic::Create(
ExecutionContext* context,
mojom::blink::WebBluetoothRemoteGATTCharacteristicPtr characteristic,
BluetoothRemoteGATTService* service,
BluetoothDevice* device) {
return new BluetoothRemoteGATTCharacteristic(
context, std::move(characteristic), service, device);
}
void BluetoothRemoteGATTCharacteristic::SetValue(DOMDataView* dom_data_view) {
value_ = dom_data_view;
}
void BluetoothRemoteGATTCharacteristic::RemoteCharacteristicValueChanged(
const Vector<uint8_t>& value) {
if (!GetGatt()->connected())
return;
this->SetValue(BluetoothRemoteGATTUtils::ConvertWTFVectorToDataView(value));
DispatchEvent(Event::Create(EventTypeNames::characteristicvaluechanged));
}
void BluetoothRemoteGATTCharacteristic::ContextDestroyed(ExecutionContext*) {
Dispose();
}
void BluetoothRemoteGATTCharacteristic::Dispose() {
client_bindings_.CloseAllBindings();
}
const WTF::AtomicString& BluetoothRemoteGATTCharacteristic::InterfaceName()
const {
return EventTargetNames::BluetoothRemoteGATTCharacteristic;
}
ExecutionContext* BluetoothRemoteGATTCharacteristic::GetExecutionContext()
const {
return ContextLifecycleObserver::GetExecutionContext();
}
void BluetoothRemoteGATTCharacteristic::AddedEventListener(
const AtomicString& event_type,
RegisteredEventListener& registered_listener) {
EventTargetWithInlineData::AddedEventListener(event_type,
registered_listener);
}
void BluetoothRemoteGATTCharacteristic::ReadValueCallback(
ScriptPromiseResolver* resolver,
mojom::blink::WebBluetoothResult result,
const Optional<Vector<uint8_t>>& value) {
if (!resolver->GetExecutionContext() ||
resolver->GetExecutionContext()->IsContextDestroyed())
return;
// If the device is disconnected, reject.
if (!GetGatt()->RemoveFromActiveAlgorithms(resolver)) {
resolver->Reject(
BluetoothError::CreateNotConnectedException(BluetoothOperation::kGATT));
return;
}
if (result == mojom::blink::WebBluetoothResult::SUCCESS) {
DCHECK(value);
DOMDataView* dom_data_view =
BluetoothRemoteGATTUtils::ConvertWTFVectorToDataView(value.value());
SetValue(dom_data_view);
DispatchEvent(Event::Create(EventTypeNames::characteristicvaluechanged));
resolver->Resolve(dom_data_view);
} else {
resolver->Reject(BluetoothError::CreateDOMException(result));
}
}
ScriptPromise BluetoothRemoteGATTCharacteristic::readValue(
ScriptState* script_state) {
if (!GetGatt()->connected()) {
return ScriptPromise::RejectWithDOMException(
script_state,
BluetoothError::CreateNotConnectedException(BluetoothOperation::kGATT));
}
if (!GetGatt()->device()->IsValidCharacteristic(
characteristic_->instance_id)) {
return ScriptPromise::RejectWithDOMException(
script_state, CreateInvalidCharacteristicError());
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
GetGatt()->AddToActiveAlgorithms(resolver);
mojom::blink::WebBluetoothService* service =
device_->GetBluetooth()->Service();
service->RemoteCharacteristicReadValue(
characteristic_->instance_id,
ConvertToBaseCallback(
WTF::Bind(&BluetoothRemoteGATTCharacteristic::ReadValueCallback,
WrapPersistent(this), WrapPersistent(resolver))));
return promise;
}
void BluetoothRemoteGATTCharacteristic::WriteValueCallback(
ScriptPromiseResolver* resolver,
const Vector<uint8_t>& value,
mojom::blink::WebBluetoothResult result) {
if (!resolver->GetExecutionContext() ||
resolver->GetExecutionContext()->IsContextDestroyed())
return;
// If the device is disconnected, reject.
if (!GetGatt()->RemoveFromActiveAlgorithms(resolver)) {
resolver->Reject(
BluetoothError::CreateNotConnectedException(BluetoothOperation::kGATT));
return;
}
if (result == mojom::blink::WebBluetoothResult::SUCCESS) {
SetValue(BluetoothRemoteGATTUtils::ConvertWTFVectorToDataView(value));
resolver->Resolve();
} else {
resolver->Reject(BluetoothError::CreateDOMException(result));
}
}
ScriptPromise BluetoothRemoteGATTCharacteristic::writeValue(
ScriptState* script_state,
const DOMArrayPiece& value) {
if (!GetGatt()->connected()) {
return ScriptPromise::RejectWithDOMException(
script_state,
BluetoothError::CreateNotConnectedException(BluetoothOperation::kGATT));
}
if (!GetGatt()->device()->IsValidCharacteristic(
characteristic_->instance_id)) {
return ScriptPromise::RejectWithDOMException(
script_state, CreateInvalidCharacteristicError());
}
// Partial implementation of writeValue algorithm:
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
// If bytes is more than 512 bytes long (the maximum length of an attribute
// value, per Long Attribute Values) return a promise rejected with an
// InvalidModificationError and abort.
if (value.ByteLength() > 512)
return ScriptPromise::RejectWithDOMException(
script_state, DOMException::Create(kInvalidModificationError,
"Value can't exceed 512 bytes."));
// Let valueVector be a copy of the bytes held by value.
Vector<uint8_t> value_vector;
value_vector.Append(value.Bytes(), value.ByteLength());
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
GetGatt()->AddToActiveAlgorithms(resolver);
mojom::blink::WebBluetoothService* service =
device_->GetBluetooth()->Service();
service->RemoteCharacteristicWriteValue(
characteristic_->instance_id, value_vector,
ConvertToBaseCallback(WTF::Bind(
&BluetoothRemoteGATTCharacteristic::WriteValueCallback,
WrapPersistent(this), WrapPersistent(resolver), value_vector)));
return promise;
}
void BluetoothRemoteGATTCharacteristic::NotificationsCallback(
ScriptPromiseResolver* resolver,
mojom::blink::WebBluetoothResult result) {
if (!resolver->GetExecutionContext() ||
resolver->GetExecutionContext()->IsContextDestroyed())
return;
// If the device is disconnected, reject.
if (!GetGatt()->RemoveFromActiveAlgorithms(resolver)) {
resolver->Reject(
BluetoothError::CreateNotConnectedException(BluetoothOperation::kGATT));
return;
}
if (result == mojom::blink::WebBluetoothResult::SUCCESS) {
resolver->Resolve(this);
} else {
resolver->Reject(BluetoothError::CreateDOMException(result));
}
}
ScriptPromise BluetoothRemoteGATTCharacteristic::startNotifications(
ScriptState* script_state) {
if (!GetGatt()->connected()) {
return ScriptPromise::RejectWithDOMException(
script_state,
BluetoothError::CreateNotConnectedException(BluetoothOperation::kGATT));
}
if (!GetGatt()->device()->IsValidCharacteristic(
characteristic_->instance_id)) {
return ScriptPromise::RejectWithDOMException(
script_state, CreateInvalidCharacteristicError());
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
GetGatt()->AddToActiveAlgorithms(resolver);
mojom::blink::WebBluetoothService* service =
device_->GetBluetooth()->Service();
mojom::blink::WebBluetoothCharacteristicClientAssociatedPtrInfo ptr_info;
auto request = mojo::MakeRequest(&ptr_info);
client_bindings_.AddBinding(this, std::move(request));
service->RemoteCharacteristicStartNotifications(
characteristic_->instance_id, std::move(ptr_info),
ConvertToBaseCallback(
WTF::Bind(&BluetoothRemoteGATTCharacteristic::NotificationsCallback,
WrapPersistent(this), WrapPersistent(resolver))));
return promise;
}
ScriptPromise BluetoothRemoteGATTCharacteristic::stopNotifications(
ScriptState* script_state) {
if (!GetGatt()->connected()) {
return ScriptPromise::RejectWithDOMException(
script_state,
BluetoothError::CreateNotConnectedException(BluetoothOperation::kGATT));
}
if (!GetGatt()->device()->IsValidCharacteristic(
characteristic_->instance_id)) {
return ScriptPromise::RejectWithDOMException(
script_state, CreateInvalidCharacteristicError());
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
GetGatt()->AddToActiveAlgorithms(resolver);
mojom::blink::WebBluetoothService* service =
device_->GetBluetooth()->Service();
service->RemoteCharacteristicStopNotifications(
characteristic_->instance_id,
ConvertToBaseCallback(
WTF::Bind(&BluetoothRemoteGATTCharacteristic::NotificationsCallback,
WrapPersistent(this), WrapPersistent(resolver),
mojom::blink::WebBluetoothResult::SUCCESS)));
return promise;
}
ScriptPromise BluetoothRemoteGATTCharacteristic::getDescriptor(
ScriptState* script_state,
const StringOrUnsignedLong& descriptor_uuid,
ExceptionState& exception_state) {
String descriptor =
BluetoothUUID::getDescriptor(descriptor_uuid, exception_state);
if (exception_state.HadException())
return exception_state.Reject(script_state);
return GetDescriptorsImpl(script_state,
mojom::blink::WebBluetoothGATTQueryQuantity::SINGLE,
descriptor);
}
ScriptPromise BluetoothRemoteGATTCharacteristic::getDescriptors(
ScriptState* script_state,
ExceptionState&) {
return GetDescriptorsImpl(
script_state, mojom::blink::WebBluetoothGATTQueryQuantity::MULTIPLE);
}
ScriptPromise BluetoothRemoteGATTCharacteristic::getDescriptors(
ScriptState* script_state,
const StringOrUnsignedLong& descriptor_uuid,
ExceptionState& exception_state) {
String descriptor =
BluetoothUUID::getDescriptor(descriptor_uuid, exception_state);
if (exception_state.HadException())
return exception_state.Reject(script_state);
return GetDescriptorsImpl(
script_state, mojom::blink::WebBluetoothGATTQueryQuantity::MULTIPLE,
descriptor);
}
ScriptPromise BluetoothRemoteGATTCharacteristic::GetDescriptorsImpl(
ScriptState* script_state,
mojom::blink::WebBluetoothGATTQueryQuantity quantity,
const String& descriptors_uuid) {
if (!GetGatt()->connected()) {
return ScriptPromise::RejectWithDOMException(
script_state, BluetoothError::CreateNotConnectedException(
BluetoothOperation::kDescriptorsRetrieval));
}
if (!GetGatt()->device()->IsValidCharacteristic(
characteristic_->instance_id)) {
return ScriptPromise::RejectWithDOMException(
script_state, CreateInvalidCharacteristicError());
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
GetGatt()->AddToActiveAlgorithms(resolver);
mojom::blink::WebBluetoothService* service =
device_->GetBluetooth()->Service();
service->RemoteCharacteristicGetDescriptors(
characteristic_->instance_id, quantity, descriptors_uuid,
ConvertToBaseCallback(WTF::Bind(
&BluetoothRemoteGATTCharacteristic::GetDescriptorsCallback,
WrapPersistent(this), descriptors_uuid, characteristic_->instance_id,
quantity, WrapPersistent(resolver))));
return promise;
}
// Callback that allows us to resolve the promise with a single descriptor
// or with a vector owning the descriptors.
void BluetoothRemoteGATTCharacteristic::GetDescriptorsCallback(
const String& requested_descriptor_uuid,
const String& characteristic_instance_id,
mojom::blink::WebBluetoothGATTQueryQuantity quantity,
ScriptPromiseResolver* resolver,
mojom::blink::WebBluetoothResult result,
Optional<Vector<mojom::blink::WebBluetoothRemoteGATTDescriptorPtr>>
descriptors) {
if (!resolver->GetExecutionContext() ||
resolver->GetExecutionContext()->IsContextDestroyed())
return;
// If the device is disconnected, reject.
if (!service_->device()->gatt()->RemoveFromActiveAlgorithms(resolver)) {
resolver->Reject(BluetoothError::CreateNotConnectedException(
BluetoothOperation::kDescriptorsRetrieval));
return;
}
if (result == mojom::blink::WebBluetoothResult::SUCCESS) {
DCHECK(descriptors);
if (quantity == mojom::blink::WebBluetoothGATTQueryQuantity::SINGLE) {
DCHECK_EQ(1u, descriptors->size());
resolver->Resolve(
service_->device()->GetOrCreateBluetoothRemoteGATTDescriptor(
std::move(descriptors.value()[0]), this));
return;
}
HeapVector<Member<BluetoothRemoteGATTDescriptor>> gatt_descriptors;
gatt_descriptors.ReserveInitialCapacity(descriptors->size());
for (auto& descriptor : descriptors.value()) {
gatt_descriptors.push_back(
service_->device()->GetOrCreateBluetoothRemoteGATTDescriptor(
std::move(descriptor), this));
}
resolver->Resolve(gatt_descriptors);
} else {
if (result == mojom::blink::WebBluetoothResult::DESCRIPTOR_NOT_FOUND) {
resolver->Reject(BluetoothError::CreateDOMException(
BluetoothErrorCode::kDescriptorNotFound,
"No Descriptors matching UUID " + requested_descriptor_uuid +
" found in Characteristic with UUID " + uuid() + "."));
} else {
resolver->Reject(BluetoothError::CreateDOMException(result));
}
}
}
DOMException*
BluetoothRemoteGATTCharacteristic::CreateInvalidCharacteristicError() {
return BluetoothError::CreateDOMException(
BluetoothErrorCode::kInvalidCharacteristic,
"Characteristic with UUID " + uuid() +
" is no longer valid. Remember to retrieve the characteristic again "
"after reconnecting.");
}
DEFINE_TRACE(BluetoothRemoteGATTCharacteristic) {
visitor->Trace(service_);
visitor->Trace(properties_);
visitor->Trace(value_);
visitor->Trace(device_);
EventTargetWithInlineData::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
}
} // namespace blink