1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "device/bluetooth/bluetooth_low_energy_win.h"
7 #include "base/files/file.h"
8 #include "base/logging.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "base/win/scoped_handle.h"
11 #include "base/win/windows_version.h"
15 using device::win::DeviceRegistryPropertyValue
;
16 using device::win::DevicePropertyValue
;
17 using device::win::BluetoothLowEnergyDeviceInfo
;
18 using device::win::BluetoothLowEnergyServiceInfo
;
20 const char kPlatformNotSupported
[] =
21 "Bluetooth Low energy is only supported on Windows 8 and later.";
22 const char kDeviceEnumError
[] = "Error enumerating Bluetooth LE devices.";
23 const char kDeviceInfoError
[] =
24 "Error retrieving Bluetooth LE device information.";
25 const char kDeviceAddressError
[] =
26 "Device instance ID value does not seem to contain a Bluetooth Adapter "
28 const char kDeviceFriendlyNameError
[] = "Device name is not valid.";
29 const char kInvalidBluetoothAddress
[] = "Bluetooth address format is invalid.";
31 // Like ScopedHandle but for HDEVINFO. Only use this on HDEVINFO returned from
32 // SetupDiGetClassDevs.
33 class DeviceInfoSetTraits
{
35 typedef HDEVINFO Handle
;
37 static bool CloseHandle(HDEVINFO handle
) {
38 return ::SetupDiDestroyDeviceInfoList(handle
) != FALSE
;
41 static bool IsHandleValid(HDEVINFO handle
) {
42 return handle
!= INVALID_HANDLE_VALUE
;
45 static HDEVINFO
NullHandle() { return INVALID_HANDLE_VALUE
; }
48 DISALLOW_IMPLICIT_CONSTRUCTORS(DeviceInfoSetTraits
);
51 typedef base::win::GenericScopedHandle
<DeviceInfoSetTraits
,
52 base::win::DummyVerifierTraits
>
53 ScopedDeviceInfoSetHandle
;
55 bool StringToBluetoothAddress(const std::string
& value
,
56 BLUETOOTH_ADDRESS
* btha
,
58 if (value
.length() != 6 * 2) {
59 *error
= kInvalidBluetoothAddress
;
64 int result
= sscanf_s(value
.c_str(),
65 "%02X%02X%02X%02X%02X%02X",
73 *error
= kInvalidBluetoothAddress
;
77 ZeroMemory(btha
, sizeof(*btha
));
78 btha
->rgBytes
[0] = buffer
[0];
79 btha
->rgBytes
[1] = buffer
[1];
80 btha
->rgBytes
[2] = buffer
[2];
81 btha
->rgBytes
[3] = buffer
[3];
82 btha
->rgBytes
[4] = buffer
[4];
83 btha
->rgBytes
[5] = buffer
[5];
87 std::string
FormatBluetoothError(const char* message
, HRESULT hr
) {
88 std::ostringstream string_stream
;
89 string_stream
<< message
;
91 string_stream
<< logging::SystemErrorCodeToString(hr
);
92 return string_stream
.str();
95 bool CheckInsufficientBuffer(bool success
,
99 *error
= FormatBluetoothError(message
, S_OK
);
103 HRESULT hr
= HRESULT_FROM_WIN32(GetLastError());
104 if (hr
!= HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER
)) {
105 *error
= FormatBluetoothError(message
, hr
);
112 bool CheckHResult(HRESULT hr
, const char* message
, std::string
* error
) {
114 *error
= FormatBluetoothError(message
, hr
);
121 bool CheckSuccess(bool success
, const char* message
, std::string
* error
) {
123 CheckHResult(HRESULT_FROM_WIN32(GetLastError()), message
, error
);
130 bool CheckNoData(HRESULT hr
, size_t length
) {
131 if (hr
== HRESULT_FROM_WIN32(ERROR_NOT_FOUND
))
134 if (SUCCEEDED(hr
) && length
== 0)
140 bool CheckMoreData(HRESULT hr
, const char* message
, std::string
* error
) {
142 *error
= FormatBluetoothError(message
, hr
);
146 if (hr
!= HRESULT_FROM_WIN32(ERROR_MORE_DATA
)) {
147 *error
= FormatBluetoothError(message
, hr
);
154 bool CheckExpectedLength(size_t actual_length
,
155 size_t expected_length
,
157 std::string
* error
) {
158 if (actual_length
!= expected_length
) {
159 *error
= FormatBluetoothError(message
, E_FAIL
);
166 bool CollectBluetoothLowEnergyDeviceProperty(
167 const ScopedDeviceInfoSetHandle
& device_info_handle
,
168 PSP_DEVINFO_DATA device_info_data
,
169 const DEVPROPKEY
& key
,
170 scoped_ptr
<DevicePropertyValue
>* value
,
171 std::string
* error
) {
172 DWORD required_length
;
173 DEVPROPTYPE prop_type
;
174 BOOL success
= SetupDiGetDeviceProperty(device_info_handle
.Get(),
182 if (!CheckInsufficientBuffer(!!success
, kDeviceInfoError
, error
))
185 scoped_ptr
<uint8_t[]> prop_value(new uint8_t[required_length
]);
186 DWORD actual_length
= required_length
;
187 success
= SetupDiGetDeviceProperty(device_info_handle
.Get(),
195 if (!CheckSuccess(!!success
, kDeviceInfoError
, error
))
197 if (!CheckExpectedLength(
198 actual_length
, required_length
, kDeviceInfoError
, error
)) {
202 (*value
) = scoped_ptr
<DevicePropertyValue
>(
203 new DevicePropertyValue(prop_type
, prop_value
.Pass(), actual_length
));
207 bool CollectBluetoothLowEnergyDeviceRegistryProperty(
208 const ScopedDeviceInfoSetHandle
& device_info_handle
,
209 PSP_DEVINFO_DATA device_info_data
,
211 scoped_ptr
<DeviceRegistryPropertyValue
>* value
,
212 std::string
* error
) {
213 ULONG required_length
= 0;
214 BOOL success
= SetupDiGetDeviceRegistryProperty(device_info_handle
.Get(),
221 if (!CheckInsufficientBuffer(!!success
, kDeviceInfoError
, error
))
224 scoped_ptr
<uint8_t[]> property_value(new uint8_t[required_length
]);
225 ULONG actual_length
= required_length
;
227 success
= SetupDiGetDeviceRegistryProperty(device_info_handle
.Get(),
231 property_value
.get(),
234 if (!CheckSuccess(!!success
, kDeviceInfoError
, error
))
236 if (!CheckExpectedLength(
237 actual_length
, required_length
, kDeviceInfoError
, error
)) {
241 (*value
) = DeviceRegistryPropertyValue::Create(
242 property_type
, property_value
.Pass(), actual_length
).Pass();
246 bool CollectBluetoothLowEnergyDeviceInstanceId(
247 const ScopedDeviceInfoSetHandle
& device_info_handle
,
248 PSP_DEVINFO_DATA device_info_data
,
249 scoped_ptr
<device::win::BluetoothLowEnergyDeviceInfo
>& device_info
,
250 std::string
* error
) {
251 ULONG required_length
= 0;
252 BOOL success
= SetupDiGetDeviceInstanceId(
253 device_info_handle
.Get(), device_info_data
, NULL
, 0, &required_length
);
254 if (!CheckInsufficientBuffer(!!success
, kDeviceInfoError
, error
))
257 scoped_ptr
<WCHAR
[]> instance_id(new WCHAR
[required_length
]);
258 ULONG actual_length
= required_length
;
259 success
= SetupDiGetDeviceInstanceId(device_info_handle
.Get(),
264 if (!CheckSuccess(!!success
, kDeviceInfoError
, error
))
266 if (!CheckExpectedLength(
267 actual_length
, required_length
, kDeviceInfoError
, error
)) {
271 if (actual_length
>= 1) {
272 // Ensure string is zero terminated.
273 instance_id
.get()[actual_length
- 1] = 0;
274 device_info
->id
= base::SysWideToUTF8(instance_id
.get());
279 bool CollectBluetoothLowEnergyDeviceFriendlyName(
280 const ScopedDeviceInfoSetHandle
& device_info_handle
,
281 PSP_DEVINFO_DATA device_info_data
,
282 scoped_ptr
<device::win::BluetoothLowEnergyDeviceInfo
>& device_info
,
283 std::string
* error
) {
284 scoped_ptr
<DeviceRegistryPropertyValue
> property_value
;
285 if (!CollectBluetoothLowEnergyDeviceRegistryProperty(device_info_handle
,
293 if (property_value
->property_type() != REG_SZ
) {
294 *error
= kDeviceFriendlyNameError
;
298 device_info
->friendly_name
= property_value
->AsString();
302 bool ExtractBluetoothAddressFromDeviceInstanceId(const std::string
& instance_id
,
303 BLUETOOTH_ADDRESS
* btha
,
304 std::string
* error
) {
305 size_t start
= instance_id
.find("_");
306 if (start
== std::string::npos
) {
307 *error
= kDeviceAddressError
;
310 size_t end
= instance_id
.find("\\", start
);
311 if (end
== std::string::npos
) {
312 *error
= kDeviceAddressError
;
317 std::string address
= instance_id
.substr(start
, end
- start
);
318 if (!StringToBluetoothAddress(address
, btha
, error
))
324 bool CollectBluetoothLowEnergyDeviceAddress(
325 const ScopedDeviceInfoSetHandle
& device_info_handle
,
326 PSP_DEVINFO_DATA device_info_data
,
327 scoped_ptr
<device::win::BluetoothLowEnergyDeviceInfo
>& device_info
,
328 std::string
* error
) {
329 // TODO(rpaquay): We exctract the bluetooth device address from the device
330 // instance ID string, as we did not find a more formal API for retrieving the
331 // bluetooth address of a Bluetooth Low Energy device.
332 // An Bluetooth device instance ID has the following format (under Win8+):
333 // BTHLE\DEV_BC6A29AB5FB0\8&31038925&0&BC6A29AB5FB0
334 return ExtractBluetoothAddressFromDeviceInstanceId(
335 device_info
->id
, &device_info
->address
, error
);
338 bool CollectBluetoothLowEnergyDeviceStatus(
339 const ScopedDeviceInfoSetHandle
& device_info_handle
,
340 PSP_DEVINFO_DATA device_info_data
,
341 scoped_ptr
<device::win::BluetoothLowEnergyDeviceInfo
>& device_info
,
342 std::string
* error
) {
343 scoped_ptr
<DevicePropertyValue
> value
;
344 if (!CollectBluetoothLowEnergyDeviceProperty(device_info_handle
,
346 DEVPKEY_Device_DevNodeStatus
,
352 if (value
->property_type() != DEVPROP_TYPE_UINT32
) {
353 *error
= kDeviceInfoError
;
357 device_info
->connected
= !(value
->AsUint32() & DN_DEVICE_DISCONNECTED
);
358 // Windows 8 exposes BLE devices only if they are visible and paired. This
359 // might change in the future if Windows offers a public API for discovering
360 // and pairing BLE devices.
361 device_info
->visible
= true;
362 device_info
->authenticated
= true;
366 bool CollectBluetoothLowEnergyDeviceServices(
367 const base::FilePath
& device_path
,
368 ScopedVector
<BluetoothLowEnergyServiceInfo
>* services
,
369 std::string
* error
) {
370 base::File
file(device_path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
371 if (!file
.IsValid()) {
372 *error
= file
.ErrorToString(file
.error_details());
376 USHORT required_length
;
377 HRESULT hr
= BluetoothGATTGetServices(file
.GetPlatformFile(),
381 BLUETOOTH_GATT_FLAG_NONE
);
382 if (CheckNoData(hr
, required_length
))
384 if (!CheckMoreData(hr
, kDeviceInfoError
, error
))
387 scoped_ptr
<BTH_LE_GATT_SERVICE
[]> gatt_services(
388 new BTH_LE_GATT_SERVICE
[required_length
]);
389 USHORT actual_length
= required_length
;
390 hr
= BluetoothGATTGetServices(file
.GetPlatformFile(),
394 BLUETOOTH_GATT_FLAG_NONE
);
395 if (!CheckHResult(hr
, kDeviceInfoError
, error
))
397 if (!CheckExpectedLength(
398 actual_length
, required_length
, kDeviceInfoError
, error
)) {
402 for (USHORT i
= 0; i
< actual_length
; ++i
) {
403 BTH_LE_GATT_SERVICE
& gatt_service(gatt_services
.get()[i
]);
404 BluetoothLowEnergyServiceInfo
* service_info
=
405 new BluetoothLowEnergyServiceInfo();
406 service_info
->uuid
= gatt_service
.ServiceUuid
;
407 services
->push_back(service_info
);
413 bool CollectBluetoothLowEnergyDeviceInfo(
414 const ScopedDeviceInfoSetHandle
& device_info_handle
,
415 PSP_DEVICE_INTERFACE_DATA device_interface_data
,
416 scoped_ptr
<device::win::BluetoothLowEnergyDeviceInfo
>* device_info
,
417 std::string
* error
) {
418 // Retrieve required # of bytes for interface details
419 ULONG required_length
= 0;
420 BOOL success
= SetupDiGetDeviceInterfaceDetail(device_info_handle
.Get(),
421 device_interface_data
,
426 if (!CheckInsufficientBuffer(!!success
, kDeviceInfoError
, error
))
429 scoped_ptr
<uint8_t[]> interface_data(new uint8_t[required_length
]);
430 ZeroMemory(interface_data
.get(), required_length
);
432 PSP_DEVICE_INTERFACE_DETAIL_DATA device_interface_detail_data
=
433 reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA
>(interface_data
.get());
434 device_interface_detail_data
->cbSize
=
435 sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA
);
437 SP_DEVINFO_DATA device_info_data
= {0};
438 device_info_data
.cbSize
= sizeof(SP_DEVINFO_DATA
);
440 ULONG actual_length
= required_length
;
441 success
= SetupDiGetDeviceInterfaceDetail(device_info_handle
.Get(),
442 device_interface_data
,
443 device_interface_detail_data
,
447 if (!CheckSuccess(!!success
, kDeviceInfoError
, error
))
449 if (!CheckExpectedLength(
450 actual_length
, required_length
, kDeviceInfoError
, error
)) {
454 scoped_ptr
<device::win::BluetoothLowEnergyDeviceInfo
> result(
455 new device::win::BluetoothLowEnergyDeviceInfo());
457 base::FilePath(std::wstring(device_interface_detail_data
->DevicePath
));
458 if (!CollectBluetoothLowEnergyDeviceInstanceId(
459 device_info_handle
, &device_info_data
, result
, error
)) {
462 if (!CollectBluetoothLowEnergyDeviceFriendlyName(
463 device_info_handle
, &device_info_data
, result
, error
)) {
466 if (!CollectBluetoothLowEnergyDeviceAddress(
467 device_info_handle
, &device_info_data
, result
, error
)) {
470 if (!CollectBluetoothLowEnergyDeviceStatus(
471 device_info_handle
, &device_info_data
, result
, error
)) {
474 (*device_info
) = result
.Pass();
478 enum DeviceInfoResult
{ kOk
, kError
, kNoMoreDevices
};
480 DeviceInfoResult
EnumerateSingleBluetoothLowEnergyDevice(
481 const ScopedDeviceInfoSetHandle
& device_info_handle
,
483 scoped_ptr
<device::win::BluetoothLowEnergyDeviceInfo
>* device_info
,
484 std::string
* error
) {
485 // Enumerate device of BLUETOOTHLE_DEVICE interface class
486 GUID BluetoothInterfaceGUID
= GUID_BLUETOOTHLE_DEVICE_INTERFACE
;
487 SP_DEVICE_INTERFACE_DATA device_interface_data
= {0};
488 device_interface_data
.cbSize
= sizeof(SP_DEVICE_INTERFACE_DATA
);
489 BOOL success
= ::SetupDiEnumDeviceInterfaces(device_info_handle
.Get(),
491 &BluetoothInterfaceGUID
,
493 &device_interface_data
);
495 HRESULT hr
= HRESULT_FROM_WIN32(GetLastError());
496 if (hr
== HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS
)) {
497 return kNoMoreDevices
;
499 *error
= FormatBluetoothError(kDeviceInfoError
, hr
);
503 if (!CollectBluetoothLowEnergyDeviceInfo(
504 device_info_handle
, &device_interface_data
, device_info
, error
)) {
511 // Opens a Device Info Set that can be used to enumerate Bluetooth LE devices
512 // present on the machine.
513 HRESULT
OpenBluetoothLowEnergyDevices(ScopedDeviceInfoSetHandle
* handle
) {
514 GUID BluetoothClassGUID
= GUID_BLUETOOTHLE_DEVICE_INTERFACE
;
515 ScopedDeviceInfoSetHandle
result(SetupDiGetClassDevs(
516 &BluetoothClassGUID
, NULL
, NULL
, DIGCF_PRESENT
| DIGCF_DEVICEINTERFACE
));
517 if (!result
.IsValid()) {
518 return HRESULT_FROM_WIN32(::GetLastError());
521 (*handle
) = result
.Pass();
525 // Opens a Device Info Set that can be used to enumerate Bluetooth LE devices
526 // exposing a service GUID.
527 HRESULT
OpenBluetoothLowEnergyService(const GUID
& service_guid
,
528 ScopedDeviceInfoSetHandle
* handle
) {
529 ScopedDeviceInfoSetHandle
result(SetupDiGetClassDevs(
530 &service_guid
, NULL
, NULL
, DIGCF_PRESENT
| DIGCF_DEVICEINTERFACE
));
531 if (!result
.IsValid()) {
532 return HRESULT_FROM_WIN32(::GetLastError());
535 (*handle
) = result
.Pass();
545 scoped_ptr
<DeviceRegistryPropertyValue
> DeviceRegistryPropertyValue::Create(
547 scoped_ptr
<uint8_t[]> value
,
549 switch (property_type
) {
551 // Ensure string is zero terminated.
552 size_t character_size
= value_size
/ sizeof(WCHAR
);
553 CHECK_EQ(character_size
* sizeof(WCHAR
), value_size
);
554 CHECK_GE(character_size
, 1u);
555 WCHAR
* value_string
= reinterpret_cast<WCHAR
*>(value
.get());
556 value_string
[character_size
- 1] = 0;
560 CHECK_EQ(value_size
, sizeof(DWORD
));
564 return scoped_ptr
<DeviceRegistryPropertyValue
>(
565 new DeviceRegistryPropertyValue(property_type
, value
.Pass(), value_size
));
568 DeviceRegistryPropertyValue::DeviceRegistryPropertyValue(
570 scoped_ptr
<uint8_t[]> value
,
572 : property_type_(property_type
),
573 value_(value
.Pass()),
574 value_size_(value_size
) {
577 DeviceRegistryPropertyValue::~DeviceRegistryPropertyValue() {
580 std::string
DeviceRegistryPropertyValue::AsString() const {
581 CHECK_EQ(property_type_
, static_cast<DWORD
>(REG_SZ
));
582 WCHAR
* value_string
= reinterpret_cast<WCHAR
*>(value_
.get());
583 return base::SysWideToUTF8(value_string
);
586 DWORD
DeviceRegistryPropertyValue::AsDWORD() const {
587 CHECK_EQ(property_type_
, static_cast<DWORD
>(REG_DWORD
));
588 DWORD
* value
= reinterpret_cast<DWORD
*>(value_
.get());
592 DevicePropertyValue::DevicePropertyValue(DEVPROPTYPE property_type
,
593 scoped_ptr
<uint8_t[]> value
,
595 : property_type_(property_type
),
596 value_(value
.Pass()),
597 value_size_(value_size
) {
600 DevicePropertyValue::~DevicePropertyValue() {
603 uint32_t DevicePropertyValue::AsUint32() const {
604 CHECK_EQ(property_type_
, static_cast<DEVPROPTYPE
>(DEVPROP_TYPE_UINT32
));
605 CHECK_EQ(value_size_
, sizeof(uint32_t));
606 return *reinterpret_cast<uint32_t*>(value_
.get());
609 BluetoothLowEnergyServiceInfo::BluetoothLowEnergyServiceInfo() {
612 BluetoothLowEnergyServiceInfo::~BluetoothLowEnergyServiceInfo() {
615 BluetoothLowEnergyDeviceInfo::BluetoothLowEnergyDeviceInfo()
616 : visible(false), authenticated(false), connected(false) {
617 address
.ullLong
= BLUETOOTH_NULL_ADDRESS
;
620 BluetoothLowEnergyDeviceInfo::~BluetoothLowEnergyDeviceInfo() {
623 bool IsBluetoothLowEnergySupported() {
624 return base::win::GetVersion() >= base::win::VERSION_WIN8
;
627 bool EnumerateKnownBluetoothLowEnergyDevices(
628 ScopedVector
<BluetoothLowEnergyDeviceInfo
>* devices
,
629 std::string
* error
) {
630 if (!IsBluetoothLowEnergySupported()) {
631 *error
= kPlatformNotSupported
;
635 ScopedDeviceInfoSetHandle info_set_handle
;
636 HRESULT hr
= OpenBluetoothLowEnergyDevices(&info_set_handle
);
638 *error
= FormatBluetoothError(kDeviceEnumError
, hr
);
642 for (DWORD i
= 0;; ++i
) {
643 scoped_ptr
<BluetoothLowEnergyDeviceInfo
> device_info
;
644 DeviceInfoResult result
= EnumerateSingleBluetoothLowEnergyDevice(
645 info_set_handle
, i
, &device_info
, error
);
652 devices
->push_back(device_info
.Pass());
657 bool EnumerateKnownBluetoothLowEnergyServices(
658 const base::FilePath
& device_path
,
659 ScopedVector
<BluetoothLowEnergyServiceInfo
>* services
,
660 std::string
* error
) {
661 if (!IsBluetoothLowEnergySupported()) {
662 *error
= kPlatformNotSupported
;
666 return CollectBluetoothLowEnergyDeviceServices(device_path
, services
, error
);
669 bool ExtractBluetoothAddressFromDeviceInstanceIdForTesting(
670 const std::string
& instance_id
,
671 BLUETOOTH_ADDRESS
* btha
,
672 std::string
* error
) {
673 return ExtractBluetoothAddressFromDeviceInstanceId(instance_id
, btha
, error
);
677 } // namespace device