Windows precompiled header support in GN.
[chromium-blink-merge.git] / device / bluetooth / bluetooth_low_energy_win.cc
blobebac47d0cdf2a7c7df2b029d2f9eb34cb43fa554
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"
13 namespace {
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 "
27 "address.";
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 {
34 public:
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; }
47 private:
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,
57 std::string* error) {
58 if (value.length() != 6 * 2) {
59 *error = kInvalidBluetoothAddress;
60 return false;
63 int buffer[6];
64 int result = sscanf_s(value.c_str(),
65 "%02X%02X%02X%02X%02X%02X",
66 &buffer[5],
67 &buffer[4],
68 &buffer[3],
69 &buffer[2],
70 &buffer[1],
71 &buffer[0]);
72 if (result != 6) {
73 *error = kInvalidBluetoothAddress;
74 return false;
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];
84 return true;
87 std::string FormatBluetoothError(const char* message, HRESULT hr) {
88 std::ostringstream string_stream;
89 string_stream << message;
90 if (FAILED(hr))
91 string_stream << logging::SystemErrorCodeToString(hr);
92 return string_stream.str();
95 bool CheckInsufficientBuffer(bool success,
96 const char* message,
97 std::string* error) {
98 if (success) {
99 *error = FormatBluetoothError(message, S_OK);
100 return false;
103 HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
104 if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
105 *error = FormatBluetoothError(message, hr);
106 return false;
109 return true;
112 bool CheckHResult(HRESULT hr, const char* message, std::string* error) {
113 if (FAILED(hr)) {
114 *error = FormatBluetoothError(message, hr);
115 return false;
118 return true;
121 bool CheckSuccess(bool success, const char* message, std::string* error) {
122 if (!success) {
123 CheckHResult(HRESULT_FROM_WIN32(GetLastError()), message, error);
124 return false;
127 return true;
130 bool CheckNoData(HRESULT hr, size_t length) {
131 if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
132 return true;
134 if (SUCCEEDED(hr) && length == 0)
135 return true;
137 return false;
140 bool CheckMoreData(HRESULT hr, const char* message, std::string* error) {
141 if (SUCCEEDED(hr)) {
142 *error = FormatBluetoothError(message, hr);
143 return false;
146 if (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA)) {
147 *error = FormatBluetoothError(message, hr);
148 return false;
151 return true;
154 bool CheckExpectedLength(size_t actual_length,
155 size_t expected_length,
156 const char* message,
157 std::string* error) {
158 if (actual_length != expected_length) {
159 *error = FormatBluetoothError(message, E_FAIL);
160 return false;
163 return true;
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(),
175 device_info_data,
176 &key,
177 &prop_type,
178 NULL,
180 &required_length,
182 if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
183 return false;
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(),
188 device_info_data,
189 &key,
190 &prop_type,
191 prop_value.get(),
192 actual_length,
193 &required_length,
195 if (!CheckSuccess(!!success, kDeviceInfoError, error))
196 return false;
197 if (!CheckExpectedLength(
198 actual_length, required_length, kDeviceInfoError, error)) {
199 return false;
202 (*value) = scoped_ptr<DevicePropertyValue>(
203 new DevicePropertyValue(prop_type, prop_value.Pass(), actual_length));
204 return true;
207 bool CollectBluetoothLowEnergyDeviceRegistryProperty(
208 const ScopedDeviceInfoSetHandle& device_info_handle,
209 PSP_DEVINFO_DATA device_info_data,
210 DWORD property_id,
211 scoped_ptr<DeviceRegistryPropertyValue>* value,
212 std::string* error) {
213 ULONG required_length = 0;
214 BOOL success = SetupDiGetDeviceRegistryProperty(device_info_handle.Get(),
215 device_info_data,
216 property_id,
217 NULL,
218 NULL,
220 &required_length);
221 if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
222 return false;
224 scoped_ptr<uint8_t[]> property_value(new uint8_t[required_length]);
225 ULONG actual_length = required_length;
226 DWORD property_type;
227 success = SetupDiGetDeviceRegistryProperty(device_info_handle.Get(),
228 device_info_data,
229 property_id,
230 &property_type,
231 property_value.get(),
232 actual_length,
233 &required_length);
234 if (!CheckSuccess(!!success, kDeviceInfoError, error))
235 return false;
236 if (!CheckExpectedLength(
237 actual_length, required_length, kDeviceInfoError, error)) {
238 return false;
241 (*value) = DeviceRegistryPropertyValue::Create(
242 property_type, property_value.Pass(), actual_length).Pass();
243 return true;
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))
255 return false;
257 scoped_ptr<WCHAR[]> instance_id(new WCHAR[required_length]);
258 ULONG actual_length = required_length;
259 success = SetupDiGetDeviceInstanceId(device_info_handle.Get(),
260 device_info_data,
261 instance_id.get(),
262 actual_length,
263 &required_length);
264 if (!CheckSuccess(!!success, kDeviceInfoError, error))
265 return false;
266 if (!CheckExpectedLength(
267 actual_length, required_length, kDeviceInfoError, error)) {
268 return false;
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());
276 return true;
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,
286 device_info_data,
287 SPDRP_FRIENDLYNAME,
288 &property_value,
289 error)) {
290 return false;
293 if (property_value->property_type() != REG_SZ) {
294 *error = kDeviceFriendlyNameError;
295 return false;
298 device_info->friendly_name = property_value->AsString();
299 return true;
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;
308 return false;
310 size_t end = instance_id.find("\\", start);
311 if (end == std::string::npos) {
312 *error = kDeviceAddressError;
313 return false;
316 start++;
317 std::string address = instance_id.substr(start, end - start);
318 if (!StringToBluetoothAddress(address, btha, error))
319 return false;
321 return true;
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,
345 device_info_data,
346 DEVPKEY_Device_DevNodeStatus,
347 &value,
348 error)) {
349 return false;
352 if (value->property_type() != DEVPROP_TYPE_UINT32) {
353 *error = kDeviceInfoError;
354 return false;
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;
363 return 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());
373 return false;
376 USHORT required_length;
377 HRESULT hr = BluetoothGATTGetServices(file.GetPlatformFile(),
379 NULL,
380 &required_length,
381 BLUETOOTH_GATT_FLAG_NONE);
382 if (CheckNoData(hr, required_length))
383 return true;
384 if (!CheckMoreData(hr, kDeviceInfoError, error))
385 return false;
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(),
391 actual_length,
392 gatt_services.get(),
393 &required_length,
394 BLUETOOTH_GATT_FLAG_NONE);
395 if (!CheckHResult(hr, kDeviceInfoError, error))
396 return false;
397 if (!CheckExpectedLength(
398 actual_length, required_length, kDeviceInfoError, error)) {
399 return false;
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);
410 return true;
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,
422 NULL,
424 &required_length,
425 NULL);
426 if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
427 return false;
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,
444 actual_length,
445 &required_length,
446 &device_info_data);
447 if (!CheckSuccess(!!success, kDeviceInfoError, error))
448 return false;
449 if (!CheckExpectedLength(
450 actual_length, required_length, kDeviceInfoError, error)) {
451 return false;
454 scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo> result(
455 new device::win::BluetoothLowEnergyDeviceInfo());
456 result->path =
457 base::FilePath(std::wstring(device_interface_detail_data->DevicePath));
458 if (!CollectBluetoothLowEnergyDeviceInstanceId(
459 device_info_handle, &device_info_data, result, error)) {
460 return false;
462 if (!CollectBluetoothLowEnergyDeviceFriendlyName(
463 device_info_handle, &device_info_data, result, error)) {
464 return false;
466 if (!CollectBluetoothLowEnergyDeviceAddress(
467 device_info_handle, &device_info_data, result, error)) {
468 return false;
470 if (!CollectBluetoothLowEnergyDeviceStatus(
471 device_info_handle, &device_info_data, result, error)) {
472 return false;
474 (*device_info) = result.Pass();
475 return true;
478 enum DeviceInfoResult { kOk, kError, kNoMoreDevices };
480 DeviceInfoResult EnumerateSingleBluetoothLowEnergyDevice(
481 const ScopedDeviceInfoSetHandle& device_info_handle,
482 DWORD device_index,
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(),
490 NULL,
491 &BluetoothInterfaceGUID,
492 device_index,
493 &device_interface_data);
494 if (!success) {
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);
500 return kError;
503 if (!CollectBluetoothLowEnergyDeviceInfo(
504 device_info_handle, &device_interface_data, device_info, error)) {
505 return kError;
508 return kOk;
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();
522 return S_OK;
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();
536 return S_OK;
539 } // namespace
541 namespace device {
542 namespace win {
544 // static
545 scoped_ptr<DeviceRegistryPropertyValue> DeviceRegistryPropertyValue::Create(
546 DWORD property_type,
547 scoped_ptr<uint8_t[]> value,
548 size_t value_size) {
549 switch (property_type) {
550 case REG_SZ: {
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;
557 break;
559 case REG_DWORD: {
560 CHECK_EQ(value_size, sizeof(DWORD));
561 break;
564 return scoped_ptr<DeviceRegistryPropertyValue>(
565 new DeviceRegistryPropertyValue(property_type, value.Pass(), value_size));
568 DeviceRegistryPropertyValue::DeviceRegistryPropertyValue(
569 DWORD property_type,
570 scoped_ptr<uint8_t[]> value,
571 size_t value_size)
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());
589 return *value;
592 DevicePropertyValue::DevicePropertyValue(DEVPROPTYPE property_type,
593 scoped_ptr<uint8_t[]> value,
594 size_t value_size)
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;
632 return false;
635 ScopedDeviceInfoSetHandle info_set_handle;
636 HRESULT hr = OpenBluetoothLowEnergyDevices(&info_set_handle);
637 if (FAILED(hr)) {
638 *error = FormatBluetoothError(kDeviceEnumError, hr);
639 return false;
642 for (DWORD i = 0;; ++i) {
643 scoped_ptr<BluetoothLowEnergyDeviceInfo> device_info;
644 DeviceInfoResult result = EnumerateSingleBluetoothLowEnergyDevice(
645 info_set_handle, i, &device_info, error);
646 switch (result) {
647 case kNoMoreDevices:
648 return true;
649 case kError:
650 return false;
651 case kOk:
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;
663 return false;
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);
676 } // namespace win
677 } // namespace device