blob: 210a2228de239bb3fb820e137ec0b89d05bc06ae [file] [log] [blame]
// Copyright 2018 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 "device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/win/post_async_results.h"
#include "base/win/winrt_storage_util.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_gatt_discoverer_winrt.h"
#include "device/bluetooth/bluetooth_remote_gatt_descriptor_winrt.h"
#include "device/bluetooth/bluetooth_remote_gatt_service.h"
#include "device/bluetooth/bluetooth_remote_gatt_service_winrt.h"
#include "device/bluetooth/bluetooth_uuid.h"
#include "device/bluetooth/event_utils_winrt.h"
namespace device {
namespace {
using ABI::Windows::Devices::Bluetooth::BluetoothCacheMode_Uncached;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCharacteristicProperties;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattClientCharacteristicConfigurationDescriptorValue;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattClientCharacteristicConfigurationDescriptorValue_Indicate;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattClientCharacteristicConfigurationDescriptorValue_None;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattClientCharacteristicConfigurationDescriptorValue_Notify;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCommunicationStatus;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCommunicationStatus_Success;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattReadResult;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattWriteOption_WriteWithoutResponse;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattWriteOption_WriteWithResponse;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattWriteResult;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattCharacteristic;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattCharacteristic3;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattReadResult;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattReadResult2;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattValueChangedEventArgs;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattWriteResult;
using ABI::Windows::Foundation::IAsyncOperation;
using ABI::Windows::Storage::Streams::IBuffer;
using Microsoft::WRL::ComPtr;
} // namespace
// static
std::unique_ptr<BluetoothRemoteGattCharacteristicWinrt>
BluetoothRemoteGattCharacteristicWinrt::Create(
BluetoothRemoteGattService* service,
ComPtr<IGattCharacteristic> characteristic) {
DCHECK(characteristic);
GUID guid;
HRESULT hr = characteristic->get_Uuid(&guid);
if (FAILED(hr)) {
VLOG(2) << "Getting UUID failed: " << logging::SystemErrorCodeToString(hr);
return nullptr;
}
GattCharacteristicProperties properties;
hr = characteristic->get_CharacteristicProperties(&properties);
if (FAILED(hr)) {
VLOG(2) << "Getting Properties failed: "
<< logging::SystemErrorCodeToString(hr);
return nullptr;
}
uint16_t attribute_handle;
hr = characteristic->get_AttributeHandle(&attribute_handle);
if (FAILED(hr)) {
VLOG(2) << "Getting AttributeHandle failed: "
<< logging::SystemErrorCodeToString(hr);
return nullptr;
}
return base::WrapUnique(new BluetoothRemoteGattCharacteristicWinrt(
service, std::move(characteristic), BluetoothUUID(guid), properties,
attribute_handle));
}
BluetoothRemoteGattCharacteristicWinrt::
~BluetoothRemoteGattCharacteristicWinrt() {
if (pending_read_callbacks_) {
std::move(pending_read_callbacks_->error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
}
if (pending_write_callbacks_) {
std::move(pending_write_callbacks_->error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
}
if (value_changed_token_)
RemoveValueChangedHandler();
}
std::string BluetoothRemoteGattCharacteristicWinrt::GetIdentifier() const {
return identifier_;
}
BluetoothUUID BluetoothRemoteGattCharacteristicWinrt::GetUUID() const {
return uuid_;
}
BluetoothGattCharacteristic::Properties
BluetoothRemoteGattCharacteristicWinrt::GetProperties() const {
return properties_;
}
BluetoothGattCharacteristic::Permissions
BluetoothRemoteGattCharacteristicWinrt::GetPermissions() const {
NOTIMPLEMENTED();
return Permissions();
}
const std::vector<uint8_t>& BluetoothRemoteGattCharacteristicWinrt::GetValue()
const {
return value_;
}
BluetoothRemoteGattService* BluetoothRemoteGattCharacteristicWinrt::GetService()
const {
return service_;
}
void BluetoothRemoteGattCharacteristicWinrt::ReadRemoteCharacteristic(
ValueCallback callback,
ErrorCallback error_callback) {
if (!(GetProperties() & PROPERTY_READ)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_NOT_PERMITTED));
return;
}
if (pending_read_callbacks_ || pending_write_callbacks_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS));
return;
}
ComPtr<IAsyncOperation<GattReadResult*>> read_value_op;
HRESULT hr = characteristic_->ReadValueWithCacheModeAsync(
BluetoothCacheMode_Uncached, &read_value_op);
if (FAILED(hr)) {
VLOG(2) << "GattCharacteristic::ReadValueWithCacheModeAsync failed: "
<< logging::SystemErrorCodeToString(hr);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
hr = base::win::PostAsyncResults(
std::move(read_value_op),
base::BindOnce(&BluetoothRemoteGattCharacteristicWinrt::OnReadValue,
weak_ptr_factory_.GetWeakPtr()));
if (FAILED(hr)) {
VLOG(2) << "PostAsyncResults failed: "
<< logging::SystemErrorCodeToString(hr);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
pending_read_callbacks_ = std::make_unique<PendingReadCallbacks>(
std::move(callback), std::move(error_callback));
}
void BluetoothRemoteGattCharacteristicWinrt::WriteRemoteCharacteristic(
const std::vector<uint8_t>& value,
base::OnceClosure callback,
ErrorCallback error_callback) {
if (!(GetProperties() & PROPERTY_WRITE) &&
!(GetProperties() & PROPERTY_WRITE_WITHOUT_RESPONSE)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_NOT_PERMITTED));
return;
}
if (pending_read_callbacks_ || pending_write_callbacks_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS));
return;
}
ComPtr<IGattCharacteristic3> characteristic_3;
HRESULT hr = characteristic_.As(&characteristic_3);
if (FAILED(hr)) {
VLOG(2) << "As IGattCharacteristic3 failed: "
<< logging::SystemErrorCodeToString(hr);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
ComPtr<IBuffer> buffer;
hr = base::win::CreateIBufferFromData(value.data(), value.size(), &buffer);
if (FAILED(hr)) {
VLOG(2) << "base::win::CreateIBufferFromData failed: "
<< logging::SystemErrorCodeToString(hr);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
ComPtr<IAsyncOperation<GattWriteResult*>> write_value_op;
hr = characteristic_3->WriteValueWithResultAndOptionAsync(
buffer.Get(),
(GetProperties() & PROPERTY_WRITE) ? GattWriteOption_WriteWithResponse
: GattWriteOption_WriteWithoutResponse,
&write_value_op);
if (FAILED(hr)) {
VLOG(2) << "GattCharacteristic::WriteValueWithResultAndOptionAsync failed: "
<< logging::SystemErrorCodeToString(hr);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
hr = base::win::PostAsyncResults(
std::move(write_value_op),
base::BindOnce(&BluetoothRemoteGattCharacteristicWinrt::
OnWriteValueWithResultAndOption,
weak_ptr_factory_.GetWeakPtr()));
if (FAILED(hr)) {
VLOG(2) << "PostAsyncResults failed: "
<< logging::SystemErrorCodeToString(hr);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
pending_write_callbacks_ = std::make_unique<PendingWriteCallbacks>(
std::move(callback), std::move(error_callback));
}
void BluetoothRemoteGattCharacteristicWinrt::UpdateDescriptors(
BluetoothGattDiscovererWinrt* gatt_discoverer) {
const auto* gatt_descriptors =
gatt_discoverer->GetDescriptors(attribute_handle_);
DCHECK(gatt_descriptors);
// Instead of clearing out |descriptors_| and creating each descriptor
// from scratch, we create a new map and move already existing descriptors
// into it in order to preserve pointer stability.
DescriptorMap descriptors;
for (const auto& gatt_descriptor : *gatt_descriptors) {
auto descriptor =
BluetoothRemoteGattDescriptorWinrt::Create(this, gatt_descriptor.Get());
if (!descriptor)
continue;
std::string identifier = descriptor->GetIdentifier();
auto iter = descriptors_.find(identifier);
if (iter != descriptors_.end())
descriptors.emplace(std::move(*iter));
else
descriptors.emplace(std::move(identifier), std::move(descriptor));
}
std::swap(descriptors, descriptors_);
}
bool BluetoothRemoteGattCharacteristicWinrt::WriteWithoutResponse(
base::span<const uint8_t> value) {
if (!(GetProperties() & PROPERTY_WRITE_WITHOUT_RESPONSE))
return false;
if (pending_read_callbacks_ || pending_write_callbacks_)
return false;
ComPtr<IGattCharacteristic3> characteristic_3;
HRESULT hr = characteristic_.As(&characteristic_3);
if (FAILED(hr)) {
VLOG(2) << "As IGattCharacteristic3 failed: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
ComPtr<IBuffer> buffer;
hr = base::win::CreateIBufferFromData(value.data(), value.size(), &buffer);
if (FAILED(hr)) {
VLOG(2) << "base::win::CreateIBufferFromData failed: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
ComPtr<IAsyncOperation<GattWriteResult*>> write_value_op;
// Note: As we are ignoring the result WriteValueWithOptionAsync() would work
// as well, but re-using WriteValueWithResultAndOptionAsync() does simplify
// the testing code and there is no difference in production.
hr = characteristic_3->WriteValueWithResultAndOptionAsync(
buffer.Get(), GattWriteOption_WriteWithoutResponse, &write_value_op);
if (FAILED(hr)) {
VLOG(2) << "GattCharacteristic::WriteValueWithResultAndOptionAsync failed: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
// While we are ignoring the response, we still post the async_op in order to
// extend its lifetime until the operation has completed.
hr =
base::win::PostAsyncResults(std::move(write_value_op), base::DoNothing());
if (FAILED(hr)) {
VLOG(2) << "PostAsyncResults failed: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
return true;
}
IGattCharacteristic*
BluetoothRemoteGattCharacteristicWinrt::GetCharacteristicForTesting() {
return characteristic_.Get();
}
void BluetoothRemoteGattCharacteristicWinrt::SubscribeToNotifications(
BluetoothRemoteGattDescriptor* ccc_descriptor,
const base::Closure& callback,
ErrorCallback error_callback) {
value_changed_token_ = AddTypedEventHandler(
characteristic_.Get(), &IGattCharacteristic::add_ValueChanged,
base::BindRepeating(
&BluetoothRemoteGattCharacteristicWinrt::OnValueChanged,
weak_ptr_factory_.GetWeakPtr()));
if (!value_changed_token_) {
VLOG(2) << "Adding Value Changed Handler failed.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
WriteCccDescriptor(
(GetProperties() & PROPERTY_NOTIFY)
? GattClientCharacteristicConfigurationDescriptorValue_Notify
: GattClientCharacteristicConfigurationDescriptorValue_Indicate,
callback, std::move(error_callback));
}
void BluetoothRemoteGattCharacteristicWinrt::UnsubscribeFromNotifications(
BluetoothRemoteGattDescriptor* ccc_descriptor,
const base::Closure& callback,
ErrorCallback error_callback) {
auto repeating_error_callback =
base::AdaptCallbackForRepeating(std::move(error_callback));
base::OnceClosure success_callback = base::BindOnce(
[](base::WeakPtr<BluetoothRemoteGattCharacteristicWinrt> characteristic,
base::OnceClosure callback, ErrorCallback error_callback) {
if (characteristic && !characteristic->RemoveValueChangedHandler()) {
std::move(error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
return;
}
std::move(callback).Run();
},
weak_ptr_factory_.GetWeakPtr(), callback, repeating_error_callback);
WriteCccDescriptor(
GattClientCharacteristicConfigurationDescriptorValue_None,
// Wrap the success and error callbacks in a lambda, so that we can
// notify callers whether removing the event handler succeeded after
// the descriptor has been written to.
std::move(success_callback), repeating_error_callback);
}
BluetoothRemoteGattCharacteristicWinrt::PendingReadCallbacks::
PendingReadCallbacks(ValueCallback callback, ErrorCallback error_callback)
: callback(std::move(callback)),
error_callback(std::move(error_callback)) {}
BluetoothRemoteGattCharacteristicWinrt::PendingReadCallbacks::
~PendingReadCallbacks() = default;
BluetoothRemoteGattCharacteristicWinrt::PendingWriteCallbacks::
PendingWriteCallbacks(base::OnceClosure callback,
ErrorCallback error_callback)
: callback(std::move(callback)),
error_callback(std::move(error_callback)) {}
BluetoothRemoteGattCharacteristicWinrt::PendingWriteCallbacks::
~PendingWriteCallbacks() = default;
BluetoothRemoteGattCharacteristicWinrt::BluetoothRemoteGattCharacteristicWinrt(
BluetoothRemoteGattService* service,
ComPtr<IGattCharacteristic> characteristic,
BluetoothUUID uuid,
Properties properties,
uint16_t attribute_handle)
: service_(service),
characteristic_(std::move(characteristic)),
uuid_(std::move(uuid)),
properties_(properties),
attribute_handle_(attribute_handle),
identifier_(base::StringPrintf("%s/%s_%04x",
service_->GetIdentifier().c_str(),
uuid_.value().c_str(),
attribute_handle_)),
weak_ptr_factory_(this) {}
void BluetoothRemoteGattCharacteristicWinrt::WriteCccDescriptor(
GattClientCharacteristicConfigurationDescriptorValue value,
base::OnceClosure callback,
ErrorCallback error_callback) {
DCHECK(!pending_notification_callbacks_);
ComPtr<IGattCharacteristic3> characteristic_3;
HRESULT hr = characteristic_.As(&characteristic_3);
if (FAILED(hr)) {
VLOG(2) << "As IGattCharacteristic3 failed: "
<< logging::SystemErrorCodeToString(hr);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
ComPtr<IAsyncOperation<GattWriteResult*>> write_ccc_descriptor_op;
hr = characteristic_3
->WriteClientCharacteristicConfigurationDescriptorWithResultAsync(
value, &write_ccc_descriptor_op);
if (FAILED(hr)) {
VLOG(2) << "GattCharacteristic::"
"WriteClientCharacteristicConfigurationDescriptorWithResultAsync"
" failed: "
<< logging::SystemErrorCodeToString(hr);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
hr = base::win::PostAsyncResults(
std::move(write_ccc_descriptor_op),
base::BindOnce(
&BluetoothRemoteGattCharacteristicWinrt::OnWriteCccDescriptor,
weak_ptr_factory_.GetWeakPtr()));
if (FAILED(hr)) {
VLOG(2) << "PostAsyncResults failed: "
<< logging::SystemErrorCodeToString(hr);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(error_callback),
BluetoothRemoteGattService::GATT_ERROR_FAILED));
return;
}
pending_notification_callbacks_ =
std::make_unique<PendingNotificationCallbacks>(std::move(callback),
std::move(error_callback));
}
void BluetoothRemoteGattCharacteristicWinrt::OnReadValue(
ComPtr<IGattReadResult> read_result) {
DCHECK(pending_read_callbacks_);
auto pending_read_callbacks = std::move(pending_read_callbacks_);
if (!read_result) {
std::move(pending_read_callbacks->error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
return;
}
GattCommunicationStatus status;
HRESULT hr = read_result->get_Status(&status);
if (FAILED(hr)) {
VLOG(2) << "Getting GATT Communication Status failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(pending_read_callbacks->error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
return;
}
if (status != GattCommunicationStatus_Success) {
VLOG(2) << "Unexpected GattCommunicationStatus: " << status;
ComPtr<IGattReadResult2> read_result_2;
hr = read_result.As(&read_result_2);
if (FAILED(hr)) {
VLOG(2) << "As IGattReadResult2 failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(pending_read_callbacks->error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
return;
}
std::move(pending_read_callbacks->error_callback)
.Run(BluetoothRemoteGattServiceWinrt::GetGattErrorCode(
read_result_2.Get()));
return;
}
ComPtr<IBuffer> value;
hr = read_result->get_Value(&value);
if (FAILED(hr)) {
VLOG(2) << "Getting Characteristic Value failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(pending_read_callbacks->error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
return;
}
uint8_t* data = nullptr;
uint32_t length = 0;
hr = base::win::GetPointerToBufferData(value.Get(), &data, &length);
if (FAILED(hr)) {
VLOG(2) << "Getting Pointer To Buffer Data failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(pending_read_callbacks->error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
return;
}
value_.assign(data, data + length);
std::move(pending_read_callbacks->callback).Run(value_);
}
void BluetoothRemoteGattCharacteristicWinrt::OnWriteValueWithResultAndOption(
ComPtr<IGattWriteResult> write_result) {
DCHECK(pending_write_callbacks_);
OnWriteImpl(std::move(write_result), std::move(pending_write_callbacks_));
}
void BluetoothRemoteGattCharacteristicWinrt::OnWriteCccDescriptor(
ComPtr<IGattWriteResult> write_result) {
OnWriteImpl(std::move(write_result),
std::move(pending_notification_callbacks_));
}
void BluetoothRemoteGattCharacteristicWinrt::OnWriteImpl(
ComPtr<IGattWriteResult> write_result,
std::unique_ptr<PendingWriteCallbacks> callbacks) {
if (!write_result) {
std::move(callbacks->error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
return;
}
GattCommunicationStatus status;
HRESULT hr = write_result->get_Status(&status);
if (FAILED(hr)) {
VLOG(2) << "Getting GATT Communication Status failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callbacks->error_callback)
.Run(BluetoothGattService::GATT_ERROR_FAILED);
return;
}
if (status != GattCommunicationStatus_Success) {
VLOG(2) << "Unexpected GattCommunicationStatus: " << status;
std::move(callbacks->error_callback)
.Run(BluetoothRemoteGattServiceWinrt::GetGattErrorCode(
write_result.Get()));
return;
}
std::move(callbacks->callback).Run();
}
void BluetoothRemoteGattCharacteristicWinrt::OnValueChanged(
IGattCharacteristic* characteristic,
IGattValueChangedEventArgs* event_args) {
ComPtr<IBuffer> value;
HRESULT hr = event_args->get_CharacteristicValue(&value);
if (FAILED(hr)) {
VLOG(2) << "Getting Characteristic Value failed: "
<< logging::SystemErrorCodeToString(hr);
return;
}
uint8_t* data = nullptr;
uint32_t length = 0;
hr = base::win::GetPointerToBufferData(value.Get(), &data, &length);
if (FAILED(hr)) {
VLOG(2) << "Getting Pointer To Buffer Data failed: "
<< logging::SystemErrorCodeToString(hr);
return;
}
value_.assign(data, data + length);
service_->GetDevice()->GetAdapter()->NotifyGattCharacteristicValueChanged(
this, value_);
}
bool BluetoothRemoteGattCharacteristicWinrt::RemoveValueChangedHandler() {
DCHECK(value_changed_token_);
HRESULT hr = characteristic_->remove_ValueChanged(*value_changed_token_);
if (FAILED(hr)) {
VLOG(2) << "Removing the Value Changed Handler failed: "
<< logging::SystemErrorCodeToString(hr);
}
value_changed_token_.reset();
return SUCCEEDED(hr);
}
} // namespace device