// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/bluetooth/bluetooth_gatt_discoverer_winrt.h"
#include <windows.foundation.collections.h>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/win/post_async_results.h"
#include "components/device_event_log/device_event_log.h"
#include "device/bluetooth/bluetooth_remote_gatt_service_winrt.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
namespace device {
namespace {
using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice;
using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice3;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCharacteristic;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCharacteristicsResult;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCommunicationStatus;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCommunicationStatus_AccessDenied;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCommunicationStatus_Success;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattDescriptor;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattDescriptorsResult;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattDeviceService;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattDeviceServicesResult;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattOpenStatus;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattOpenStatus_AlreadyOpened;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattOpenStatus_Success;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattSharingMode;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattSharingMode_SharedReadAndWrite;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattCharacteristic3;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattCharacteristicsResult;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattDescriptorsResult;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattDeviceService;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattDeviceService3;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
IGattDeviceServicesResult;
using ABI::Windows::Foundation::IAsyncOperation;
using ABI::Windows::Foundation::IReference;
using ABI::Windows::Foundation::Collections::IVectorView;
using Microsoft::WRL::ComPtr;
std::string GattCommunicationStatusToString(GattCommunicationStatus status) {
switch (status) {
case GattCommunicationStatus_Success:
return "Success";
case ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCommunicationStatus_Unreachable:
return "Unreachable";
case ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCommunicationStatus_ProtocolError:
return "ProtocolError";
case GattCommunicationStatus_AccessDenied:
return "AccessDenied";
default:
return base::StringPrintf("Unknown (%d)", status);
}
}
std::string GattOpenStatusToString(GattOpenStatus status) {
switch (status) {
case ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattOpenStatus_Unspecified:
return "Unspecified";
case GattOpenStatus_Success:
return "Success";
case GattOpenStatus_AlreadyOpened:
return "AlreadyOpened";
case ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattOpenStatus_NotFound:
return "NotFound";
case ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattOpenStatus_SharingViolation:
return "SharingViolation";
case ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattOpenStatus_AccessDenied:
return "AccessDenied";
default:
return base::StringPrintf("Unknown (%d)", status);
}
}
template <typename IGattResult>
bool CheckCommunicationStatus(IGattResult* gatt_result,
bool allow_access_denied = false) {
if (!gatt_result) {
BLUETOOTH_LOG(DEBUG) << "Getting GATT Results failed.";
return false;
}
GattCommunicationStatus status;
HRESULT hr = gatt_result->get_Status(&status);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Getting GATT Communication Status failed: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
if (status != GattCommunicationStatus_Success) {
if (status == GattCommunicationStatus_AccessDenied) {
BLUETOOTH_LOG(DEBUG) << "GATT access denied error";
} else {
BLUETOOTH_LOG(DEBUG) << "Unexpected GattCommunicationStatus: "
<< GattCommunicationStatusToString(status);
}
BLUETOOTH_LOG(DEBUG)
<< "GATT Error Code: "
<< static_cast<int>(
BluetoothRemoteGattServiceWinrt::GetGattErrorCode(gatt_result));
}
return status == GattCommunicationStatus_Success ||
(allow_access_denied &&
status == GattCommunicationStatus_AccessDenied);
}
template <typename T, typename I>
bool GetAsVector(IVectorView<T*>* view, std::vector<ComPtr<I>>* vector) {
unsigned size;
HRESULT hr = view->get_Size(&size);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Getting Size failed: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
vector->reserve(size);
for (unsigned i = 0; i < size; ++i) {
ComPtr<I> entry;
hr = view->GetAt(i, &entry);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "GetAt(" << i << ") failed: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
vector->push_back(std::move(entry));
}
return true;
}
} // namespace
BluetoothGattDiscovererWinrt::BluetoothGattDiscovererWinrt(
ComPtr<IBluetoothLEDevice> ble_device,
std::optional<BluetoothUUID> service_uuid)
: ble_device_(std::move(ble_device)),
service_uuid_(std::move(service_uuid)) {}
BluetoothGattDiscovererWinrt::~BluetoothGattDiscovererWinrt() = default;
void BluetoothGattDiscovererWinrt::StartGattDiscovery(
GattDiscoveryCallback callback) {
callback_ = std::move(callback);
ComPtr<IBluetoothLEDevice3> ble_device_3;
HRESULT hr = ble_device_.As(&ble_device_3);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Obtaining IBluetoothLEDevice3 failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
ComPtr<IAsyncOperation<GattDeviceServicesResult*>> get_gatt_services_op;
if (service_uuid_.has_value()) {
hr = ble_device_3->GetGattServicesForUuidAsync(
BluetoothUUID::GetCanonicalValueAsGUID(
service_uuid_->canonical_value()),
&get_gatt_services_op);
} else {
hr = ble_device_3->GetGattServicesAsync(&get_gatt_services_op);
}
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "BluetoothLEDevice::GetGattServicesAsync failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
hr = base::win::PostAsyncResults(
std::move(get_gatt_services_op),
base::BindOnce(&BluetoothGattDiscovererWinrt::OnGetGattServices,
weak_ptr_factory_.GetWeakPtr()));
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "PostAsyncResults failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
}
}
const BluetoothGattDiscovererWinrt::GattServiceList&
BluetoothGattDiscovererWinrt::GetGattServices() const {
return gatt_services_;
}
const BluetoothGattDiscovererWinrt::GattCharacteristicList*
BluetoothGattDiscovererWinrt::GetCharacteristics(
uint16_t service_attribute_handle) const {
auto iter = service_to_characteristics_map_.find(service_attribute_handle);
return iter != service_to_characteristics_map_.end() ? &iter->second
: nullptr;
}
const BluetoothGattDiscovererWinrt::GattDescriptorList*
BluetoothGattDiscovererWinrt::GetDescriptors(
uint16_t characteristic_attribute_handle) const {
auto iter =
characteristic_to_descriptors_map_.find(characteristic_attribute_handle);
return iter != characteristic_to_descriptors_map_.end() ? &iter->second
: nullptr;
}
void BluetoothGattDiscovererWinrt::OnGetGattServices(
ComPtr<IGattDeviceServicesResult> services_result) {
if (!CheckCommunicationStatus(services_result.Get())) {
BLUETOOTH_LOG(DEBUG) << "Failed to get GATT services.";
std::move(callback_).Run(false);
return;
}
ComPtr<IVectorView<GattDeviceService*>> services;
HRESULT hr = services_result->get_Services(&services);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Getting GATT Services failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
if (!GetAsVector(services.Get(), &gatt_services_)) {
std::move(callback_).Run(false);
return;
}
num_services_ = gatt_services_.size();
for (const auto& gatt_service : gatt_services_) {
uint16_t service_attribute_handle;
hr = gatt_service->get_AttributeHandle(&service_attribute_handle);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Getting AttributeHandle failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
ComPtr<IGattDeviceService3> gatt_service_3;
hr = gatt_service.As(&gatt_service_3);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Obtaining IGattDeviceService3 failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
ComPtr<IAsyncOperation<GattOpenStatus>> open_op;
hr =
gatt_service_3->OpenAsync(GattSharingMode_SharedReadAndWrite, &open_op);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "GattDeviceService::OpenAsync() failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
}
hr = base::win::PostAsyncResults(
std::move(open_op),
base::BindOnce(&BluetoothGattDiscovererWinrt::OnServiceOpen,
weak_ptr_factory_.GetWeakPtr(),
std::move(gatt_service_3), service_attribute_handle));
}
RunCallbackIfDone();
}
void BluetoothGattDiscovererWinrt::OnServiceOpen(
ComPtr<IGattDeviceService3> gatt_service_3,
uint16_t service_attribute_handle,
GattOpenStatus status) {
if (status != GattOpenStatus_Success &&
status != GattOpenStatus_AlreadyOpened) {
BLUETOOTH_LOG(DEBUG) << "Ignoring failure to open service "
<< service_attribute_handle << ": "
<< GattOpenStatusToString(status);
// Enumerate no characteristics on services the browser is unable to access.
service_to_characteristics_map_.insert({service_attribute_handle, {}});
RunCallbackIfDone();
return;
}
ComPtr<IAsyncOperation<GattCharacteristicsResult*>> get_characteristics_op;
HRESULT hr = gatt_service_3->GetCharacteristicsAsync(&get_characteristics_op);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG)
<< "GattDeviceService::GetCharacteristicsAsync() failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
hr = base::win::PostAsyncResults(
std::move(get_characteristics_op),
base::BindOnce(&BluetoothGattDiscovererWinrt::OnGetCharacteristics,
weak_ptr_factory_.GetWeakPtr(), service_attribute_handle));
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "PostAsyncResults failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
}
}
void BluetoothGattDiscovererWinrt::OnGetCharacteristics(
uint16_t service_attribute_handle,
ComPtr<IGattCharacteristicsResult> characteristics_result) {
// A few GATT services like HID over GATT (short UUID 0x1812) are protected
// by the OS, leading to an access denied error.
if (!CheckCommunicationStatus(characteristics_result.Get(),
/*allow_access_denied=*/true)) {
BLUETOOTH_LOG(DEBUG) << "Failed to get characteristics for service "
<< service_attribute_handle << ".";
std::move(callback_).Run(false);
return;
}
ComPtr<IVectorView<GattCharacteristic*>> characteristics;
HRESULT hr = characteristics_result->get_Characteristics(&characteristics);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Getting Characteristics failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
DCHECK(!base::Contains(service_to_characteristics_map_,
service_attribute_handle));
auto& characteristics_list =
service_to_characteristics_map_[service_attribute_handle];
if (!GetAsVector(characteristics.Get(), &characteristics_list)) {
std::move(callback_).Run(false);
return;
}
num_characteristics_ += characteristics_list.size();
for (const auto& gatt_characteristic : characteristics_list) {
uint16_t characteristic_attribute_handle;
hr = gatt_characteristic->get_AttributeHandle(
&characteristic_attribute_handle);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Getting AttributeHandle failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
ComPtr<IGattCharacteristic3> gatt_characteristic_3;
hr = gatt_characteristic.As(&gatt_characteristic_3);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Obtaining IGattCharacteristic3 failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
ComPtr<IAsyncOperation<GattDescriptorsResult*>> get_descriptors_op;
hr = gatt_characteristic_3->GetDescriptorsAsync(&get_descriptors_op);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG)
<< "GattCharacteristic::GetDescriptorsAsync() failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
hr = base::win::PostAsyncResults(
std::move(get_descriptors_op),
base::BindOnce(&BluetoothGattDiscovererWinrt::OnGetDescriptors,
weak_ptr_factory_.GetWeakPtr(),
characteristic_attribute_handle));
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "PostAsyncResults failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
}
}
RunCallbackIfDone();
}
void BluetoothGattDiscovererWinrt::OnGetDescriptors(
uint16_t characteristic_attribute_handle,
ComPtr<IGattDescriptorsResult> descriptors_result) {
if (!CheckCommunicationStatus(descriptors_result.Get())) {
BLUETOOTH_LOG(DEBUG) << "Failed to get descriptors for characteristic "
<< characteristic_attribute_handle << ".";
std::move(callback_).Run(false);
return;
}
ComPtr<IVectorView<GattDescriptor*>> descriptors;
HRESULT hr = descriptors_result->get_Descriptors(&descriptors);
if (FAILED(hr)) {
BLUETOOTH_LOG(DEBUG) << "Getting descriptors failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(false);
return;
}
DCHECK(!base::Contains(characteristic_to_descriptors_map_,
characteristic_attribute_handle));
if (!GetAsVector(descriptors.Get(), &characteristic_to_descriptors_map_
[characteristic_attribute_handle])) {
std::move(callback_).Run(false);
return;
}
RunCallbackIfDone();
}
void BluetoothGattDiscovererWinrt::RunCallbackIfDone() {
DCHECK(callback_);
if (service_to_characteristics_map_.size() == num_services_ &&
characteristic_to_descriptors_map_.size() == num_characteristics_) {
std::move(callback_).Run(true);
}
}
} // namespace device