// 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.
#include "device/bluetooth/bluetooth_low_energy_win.h"
#include "base/files/file.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
namespace {
using device::win::DeviceRegistryPropertyValue;
using device::win::DevicePropertyValue;
using device::win::BluetoothLowEnergyDeviceInfo;
using device::win::BluetoothLowEnergyServiceInfo;
const char kPlatformNotSupported[] =
"Bluetooth Low energy is only supported on Windows 8 and later.";
const char kDeviceEnumError[] = "Error enumerating Bluetooth LE devices.";
const char kDeviceInfoError[] =
"Error retrieving Bluetooth LE device information.";
const char kDeviceAddressError[] =
"Device instance ID value does not seem to contain a Bluetooth Adapter "
"address.";
const char kDeviceFriendlyNameError[] = "Device name is not valid.";
const char kInvalidBluetoothAddress[] = "Bluetooth address format is invalid.";
// Like ScopedHandle but for HDEVINFO. Only use this on HDEVINFO returned from
// SetupDiGetClassDevs.
class DeviceInfoSetTraits {
public:
typedef HDEVINFO Handle;
static bool CloseHandle(HDEVINFO handle) {
return ::SetupDiDestroyDeviceInfoList(handle) != FALSE;
}
static bool IsHandleValid(HDEVINFO handle) {
return handle != INVALID_HANDLE_VALUE;
}
static HDEVINFO NullHandle() { return INVALID_HANDLE_VALUE; }
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(DeviceInfoSetTraits);
};
typedef base::win::GenericScopedHandle<DeviceInfoSetTraits,
base::win::VerifierTraits>
ScopedDeviceInfoSetHandle;
bool StringToBluetoothAddress(const std::string& value,
BLUETOOTH_ADDRESS* btha,
std::string* error) {
if (value.length() != 6 * 2) {
*error = kInvalidBluetoothAddress;
return false;
}
int buffer[6];
int result = sscanf_s(value.c_str(),
"%02X%02X%02X%02X%02X%02X",
&buffer[5],
&buffer[4],
&buffer[3],
&buffer[2],
&buffer[1],
&buffer[0]);
if (result != 6) {
*error = kInvalidBluetoothAddress;
return false;
}
ZeroMemory(btha, sizeof(*btha));
btha->rgBytes[0] = buffer[0];
btha->rgBytes[1] = buffer[1];
btha->rgBytes[2] = buffer[2];
btha->rgBytes[3] = buffer[3];
btha->rgBytes[4] = buffer[4];
btha->rgBytes[5] = buffer[5];
return true;
}
std::string FormatBluetoothError(const char* message, HRESULT hr) {
std::ostringstream string_stream;
string_stream << message;
if (FAILED(hr))
string_stream << logging::SystemErrorCodeToString(hr);
return string_stream.str();
}
bool CheckInsufficientBuffer(bool success,
const char* message,
std::string* error) {
if (success) {
*error = FormatBluetoothError(message, S_OK);
return false;
}
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
*error = FormatBluetoothError(message, hr);
return false;
}
return true;
}
bool CheckHResult(HRESULT hr, const char* message, std::string* error) {
if (FAILED(hr)) {
*error = FormatBluetoothError(message, hr);
return false;
}
return true;
}
bool CheckSuccess(bool success, const char* message, std::string* error) {
if (!success) {
CheckHResult(HRESULT_FROM_WIN32(GetLastError()), message, error);
return false;
}
return true;
}
bool CheckNoData(HRESULT hr, size_t length) {
if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
return true;
if (SUCCEEDED(hr) && length == 0)
return true;
return false;
}
bool CheckMoreData(HRESULT hr, const char* message, std::string* error) {
if (SUCCEEDED(hr)) {
*error = FormatBluetoothError(message, hr);
return false;
}
if (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA)) {
*error = FormatBluetoothError(message, hr);
return false;
}
return true;
}
bool CheckExpectedLength(size_t actual_length,
size_t expected_length,
const char* message,
std::string* error) {
if (actual_length != expected_length) {
*error = FormatBluetoothError(message, E_FAIL);
return false;
}
return true;
}
bool CollectBluetoothLowEnergyDeviceProperty(
const ScopedDeviceInfoSetHandle& device_info_handle,
PSP_DEVINFO_DATA device_info_data,
const DEVPROPKEY& key,
scoped_ptr<DevicePropertyValue>* value,
std::string* error) {
DWORD required_length;
DEVPROPTYPE prop_type;
BOOL success = SetupDiGetDeviceProperty(device_info_handle,
device_info_data,
&key,
&prop_type,
NULL,
0,
&required_length,
0);
if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
return false;
scoped_ptr<uint8_t[]> prop_value(new uint8_t[required_length]);
DWORD actual_length = required_length;
success = SetupDiGetDeviceProperty(device_info_handle,
device_info_data,
&key,
&prop_type,
prop_value.get(),
actual_length,
&required_length,
0);
if (!CheckSuccess(!!success, kDeviceInfoError, error))
return false;
if (!CheckExpectedLength(
actual_length, required_length, kDeviceInfoError, error)) {
return false;
}
(*value) = scoped_ptr<DevicePropertyValue>(
new DevicePropertyValue(prop_type, prop_value.Pass(), actual_length));
return true;
}
bool CollectBluetoothLowEnergyDeviceRegistryProperty(
const ScopedDeviceInfoSetHandle& device_info_handle,
PSP_DEVINFO_DATA device_info_data,
DWORD property_id,
scoped_ptr<DeviceRegistryPropertyValue>* value,
std::string* error) {
ULONG required_length = 0;
BOOL success = SetupDiGetDeviceRegistryProperty(device_info_handle,
device_info_data,
property_id,
NULL,
NULL,
0,
&required_length);
if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
return false;
scoped_ptr<uint8_t[]> property_value(new uint8_t[required_length]);
ULONG actual_length = required_length;
DWORD property_type;
success = SetupDiGetDeviceRegistryProperty(device_info_handle,
device_info_data,
property_id,
&property_type,
property_value.get(),
actual_length,
&required_length);
if (!CheckSuccess(!!success, kDeviceInfoError, error))
return false;
if (!CheckExpectedLength(
actual_length, required_length, kDeviceInfoError, error)) {
return false;
}
(*value) = DeviceRegistryPropertyValue::Create(
property_type, property_value.Pass(), actual_length).Pass();
return true;
}
bool CollectBluetoothLowEnergyDeviceInstanceId(
const ScopedDeviceInfoSetHandle& device_info_handle,
PSP_DEVINFO_DATA device_info_data,
scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info,
std::string* error) {
ULONG required_length = 0;
BOOL success = SetupDiGetDeviceInstanceId(
device_info_handle, device_info_data, NULL, 0, &required_length);
if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
return false;
scoped_ptr<WCHAR[]> instance_id(new WCHAR[required_length]);
ULONG actual_length = required_length;
success = SetupDiGetDeviceInstanceId(device_info_handle,
device_info_data,
instance_id.get(),
actual_length,
&required_length);
if (!CheckSuccess(!!success, kDeviceInfoError, error))
return false;
if (!CheckExpectedLength(
actual_length, required_length, kDeviceInfoError, error)) {
return false;
}
if (actual_length >= 1) {
// Ensure string is zero terminated.
instance_id.get()[actual_length - 1] = 0;
device_info->id = base::SysWideToUTF8(instance_id.get());
}
return true;
}
bool CollectBluetoothLowEnergyDeviceFriendlyName(
const ScopedDeviceInfoSetHandle& device_info_handle,
PSP_DEVINFO_DATA device_info_data,
scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info,
std::string* error) {
scoped_ptr<DeviceRegistryPropertyValue> property_value;
if (!CollectBluetoothLowEnergyDeviceRegistryProperty(device_info_handle,
device_info_data,
SPDRP_FRIENDLYNAME,
&property_value,
error)) {
return false;
}
if (property_value->property_type() != REG_SZ) {
*error = kDeviceFriendlyNameError;
return false;
}
device_info->friendly_name = property_value->AsString();
return true;
}
bool ExtractBluetoothAddressFromDeviceInstanceId(const std::string& instance_id,
BLUETOOTH_ADDRESS* btha,
std::string* error) {
size_t start = instance_id.find("_");
if (start == std::string::npos) {
*error = kDeviceAddressError;
return false;
}
size_t end = instance_id.find("\\", start);
if (end == std::string::npos) {
*error = kDeviceAddressError;
return false;
}
start++;
std::string address = instance_id.substr(start, end - start);
if (!StringToBluetoothAddress(address, btha, error))
return false;
return true;
}
bool CollectBluetoothLowEnergyDeviceAddress(
const ScopedDeviceInfoSetHandle& device_info_handle,
PSP_DEVINFO_DATA device_info_data,
scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info,
std::string* error) {
// TODO(rpaquay): We exctract the bluetooth device address from the device
// instance ID string, as we did not find a more formal API for retrieving the
// bluetooth address of a Bluetooth Low Energy device.
// An Bluetooth device instance ID has the following format (under Win8+):
// BTHLE\DEV_BC6A29AB5FB0\8&31038925&0&BC6A29AB5FB0
return ExtractBluetoothAddressFromDeviceInstanceId(
device_info->id, &device_info->address, error);
}
bool CollectBluetoothLowEnergyDeviceStatus(
const ScopedDeviceInfoSetHandle& device_info_handle,
PSP_DEVINFO_DATA device_info_data,
scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info,
std::string* error) {
scoped_ptr<DevicePropertyValue> value;
if (!CollectBluetoothLowEnergyDeviceProperty(device_info_handle,
device_info_data,
DEVPKEY_Device_DevNodeStatus,
&value,
error)) {
return false;
}
if (value->property_type() != DEVPROP_TYPE_UINT32) {
*error = kDeviceInfoError;
return false;
}
device_info->connected = !(value->AsUint32() & DN_DEVICE_DISCONNECTED);
// Windows 8 exposes BLE devices only if they are visible and paired. This
// might change in the future if Windows offers a public API for discovering
// and pairing BLE devices.
device_info->visible = true;
device_info->authenticated = true;
return true;
}
bool CollectBluetoothLowEnergyDeviceServices(
const base::FilePath& device_path,
ScopedVector<BluetoothLowEnergyServiceInfo>* services,
std::string* error) {
base::File file(device_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
*error = file.ErrorToString(file.error_details());
return false;
}
USHORT required_length;
HRESULT hr = BluetoothGATTGetServices(file.GetPlatformFile(),
0,
NULL,
&required_length,
BLUETOOTH_GATT_FLAG_NONE);
if (CheckNoData(hr, required_length))
return true;
if (!CheckMoreData(hr, kDeviceInfoError, error))
return false;
scoped_ptr<BTH_LE_GATT_SERVICE[]> gatt_services(
new BTH_LE_GATT_SERVICE[required_length]);
USHORT actual_length = required_length;
hr = BluetoothGATTGetServices(file.GetPlatformFile(),
actual_length,
gatt_services.get(),
&required_length,
BLUETOOTH_GATT_FLAG_NONE);
if (!CheckHResult(hr, kDeviceInfoError, error))
return false;
if (!CheckExpectedLength(
actual_length, required_length, kDeviceInfoError, error)) {
return false;
}
for (USHORT i = 0; i < actual_length; ++i) {
BTH_LE_GATT_SERVICE& gatt_service(gatt_services.get()[i]);
BluetoothLowEnergyServiceInfo* service_info =
new BluetoothLowEnergyServiceInfo();
service_info->uuid = gatt_service.ServiceUuid;
services->push_back(service_info);
}
return true;
}
bool CollectBluetoothLowEnergyDeviceInfo(
const ScopedDeviceInfoSetHandle& device_info_handle,
PSP_DEVICE_INTERFACE_DATA device_interface_data,
scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>* device_info,
std::string* error) {
// Retrieve required # of bytes for interface details
ULONG required_length = 0;
BOOL success = SetupDiGetDeviceInterfaceDetail(device_info_handle,
device_interface_data,
NULL,
0,
&required_length,
NULL);
if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
return false;
scoped_ptr<uint8_t[]> interface_data(new uint8_t[required_length]);
ZeroMemory(interface_data.get(), required_length);
PSP_DEVICE_INTERFACE_DETAIL_DATA device_interface_detail_data =
reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(interface_data.get());
device_interface_detail_data->cbSize =
sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
SP_DEVINFO_DATA device_info_data = {0};
device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
ULONG actual_length = required_length;
success = SetupDiGetDeviceInterfaceDetail(device_info_handle,
device_interface_data,
device_interface_detail_data,
actual_length,
&required_length,
&device_info_data);
if (!CheckSuccess(!!success, kDeviceInfoError, error))
return false;
if (!CheckExpectedLength(
actual_length, required_length, kDeviceInfoError, error)) {
return false;
}
scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo> result(
new device::win::BluetoothLowEnergyDeviceInfo());
result->path =
base::FilePath(std::wstring(device_interface_detail_data->DevicePath));
if (!CollectBluetoothLowEnergyDeviceInstanceId(
device_info_handle, &device_info_data, result, error)) {
return false;
}
if (!CollectBluetoothLowEnergyDeviceFriendlyName(
device_info_handle, &device_info_data, result, error)) {
return false;
}
if (!CollectBluetoothLowEnergyDeviceAddress(
device_info_handle, &device_info_data, result, error)) {
return false;
}
if (!CollectBluetoothLowEnergyDeviceStatus(
device_info_handle, &device_info_data, result, error)) {
return false;
}
(*device_info) = result.Pass();
return true;
}
enum DeviceInfoResult { kOk, kError, kNoMoreDevices };
DeviceInfoResult EnumerateSingleBluetoothLowEnergyDevice(
const ScopedDeviceInfoSetHandle& device_info_handle,
DWORD device_index,
scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>* device_info,
std::string* error) {
// Enumerate device of BLUETOOTHLE_DEVICE interface class
GUID BluetoothInterfaceGUID = GUID_BLUETOOTHLE_DEVICE_INTERFACE;
SP_DEVICE_INTERFACE_DATA device_interface_data = {0};
device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
BOOL success = ::SetupDiEnumDeviceInterfaces(device_info_handle,
NULL,
&BluetoothInterfaceGUID,
device_index,
&device_interface_data);
if (!success) {
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
if (hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) {
return kNoMoreDevices;
}
*error = FormatBluetoothError(kDeviceInfoError, hr);
return kError;
}
if (!CollectBluetoothLowEnergyDeviceInfo(
device_info_handle, &device_interface_data, device_info, error)) {
return kError;
}
return kOk;
}
// Opens a Device Info Set that can be used to enumerate Bluetooth LE devices
// present on the machine.
HRESULT OpenBluetoothLowEnergyDevices(ScopedDeviceInfoSetHandle* handle) {
GUID BluetoothClassGUID = GUID_BLUETOOTHLE_DEVICE_INTERFACE;
ScopedDeviceInfoSetHandle result(SetupDiGetClassDevs(
&BluetoothClassGUID, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
if (!result.IsValid()) {
return HRESULT_FROM_WIN32(::GetLastError());
}
(*handle) = result.Pass();
return S_OK;
}
// Opens a Device Info Set that can be used to enumerate Bluetooth LE devices
// exposing a service GUID.
HRESULT OpenBluetoothLowEnergyService(const GUID& service_guid,
ScopedDeviceInfoSetHandle* handle) {
ScopedDeviceInfoSetHandle result(SetupDiGetClassDevs(
&service_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
if (!result.IsValid()) {
return HRESULT_FROM_WIN32(::GetLastError());
}
(*handle) = result.Pass();
return S_OK;
}
} // namespace
namespace device {
namespace win {
// static
scoped_ptr<DeviceRegistryPropertyValue> DeviceRegistryPropertyValue::Create(
DWORD property_type,
scoped_ptr<uint8_t[]> value,
size_t value_size) {
switch (property_type) {
case REG_SZ: {
// Ensure string is zero terminated.
size_t character_size = value_size / sizeof(WCHAR);
CHECK_EQ(character_size * sizeof(WCHAR), value_size);
CHECK_GE(character_size, 1u);
WCHAR* value_string = reinterpret_cast<WCHAR*>(value.get());
value_string[character_size - 1] = 0;
break;
}
case REG_DWORD: {
CHECK_EQ(value_size, sizeof(DWORD));
break;
}
}
return scoped_ptr<DeviceRegistryPropertyValue>(
new DeviceRegistryPropertyValue(property_type, value.Pass(), value_size));
}
DeviceRegistryPropertyValue::DeviceRegistryPropertyValue(
DWORD property_type,
scoped_ptr<uint8_t[]> value,
size_t value_size)
: property_type_(property_type),
value_(value.Pass()),
value_size_(value_size) {
}
DeviceRegistryPropertyValue::~DeviceRegistryPropertyValue() {
}
std::string DeviceRegistryPropertyValue::AsString() const {
CHECK_EQ(property_type_, static_cast<DWORD>(REG_SZ));
WCHAR* value_string = reinterpret_cast<WCHAR*>(value_.get());
return base::SysWideToUTF8(value_string);
}
DWORD DeviceRegistryPropertyValue::AsDWORD() const {
CHECK_EQ(property_type_, static_cast<DWORD>(REG_DWORD));
DWORD* value = reinterpret_cast<DWORD*>(value_.get());
return *value;
}
DevicePropertyValue::DevicePropertyValue(DEVPROPTYPE property_type,
scoped_ptr<uint8_t[]> value,
size_t value_size)
: property_type_(property_type),
value_(value.Pass()),
value_size_(value_size) {
}
uint32_t DevicePropertyValue::AsUint32() const {
CHECK_EQ(property_type_, static_cast<DEVPROPTYPE>(DEVPROP_TYPE_UINT32));
CHECK_EQ(value_size_, sizeof(uint32_t));
return *reinterpret_cast<uint32_t*>(value_.get());
}
BluetoothLowEnergyServiceInfo::BluetoothLowEnergyServiceInfo() {
}
BluetoothLowEnergyServiceInfo::~BluetoothLowEnergyServiceInfo() {
}
BluetoothLowEnergyDeviceInfo::BluetoothLowEnergyDeviceInfo()
: visible(false), authenticated(false), connected(false) {
address.ullLong = BLUETOOTH_NULL_ADDRESS;
}
BluetoothLowEnergyDeviceInfo::~BluetoothLowEnergyDeviceInfo() {
}
bool IsBluetoothLowEnergySupported() {
return base::win::GetVersion() >= base::win::VERSION_WIN8;
}
bool EnumerateKnownBluetoothLowEnergyDevices(
ScopedVector<BluetoothLowEnergyDeviceInfo>* devices,
std::string* error) {
if (!IsBluetoothLowEnergySupported()) {
*error = kPlatformNotSupported;
return false;
}
ScopedDeviceInfoSetHandle info_set_handle;
HRESULT hr = OpenBluetoothLowEnergyDevices(&info_set_handle);
if (FAILED(hr)) {
*error = FormatBluetoothError(kDeviceEnumError, hr);
return false;
}
for (DWORD i = 0;; ++i) {
scoped_ptr<BluetoothLowEnergyDeviceInfo> device_info;
DeviceInfoResult result = EnumerateSingleBluetoothLowEnergyDevice(
info_set_handle, i, &device_info, error);
switch (result) {
case kNoMoreDevices:
return true;
case kError:
return false;
case kOk:
devices->push_back(device_info.release());
}
}
}
bool EnumerateKnownBluetoothLowEnergyServices(
const base::FilePath& device_path,
ScopedVector<BluetoothLowEnergyServiceInfo>* services,
std::string* error) {
if (!IsBluetoothLowEnergySupported()) {
*error = kPlatformNotSupported;
return false;
}
return CollectBluetoothLowEnergyDeviceServices(device_path, services, error);
}
bool ExtractBluetoothAddressFromDeviceInstanceIdForTesting(
const std::string& instance_id,
BLUETOOTH_ADDRESS* btha,
std::string* error) {
return ExtractBluetoothAddressFromDeviceInstanceId(instance_id, btha, error);
}
} // namespace win
} // namespace device