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.
4
5#include "device/bluetooth/bluetooth_low_energy_win.h"
6
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"
12
13namespace {
14
15using device::win::DeviceRegistryPropertyValue;
16using device::win::DevicePropertyValue;
17using device::win::BluetoothLowEnergyDeviceInfo;
18using device::win::BluetoothLowEnergyServiceInfo;
19
20const char kPlatformNotSupported[] =
21    "Bluetooth Low energy is only supported on Windows 8 and later.";
22const char kDeviceEnumError[] = "Error enumerating Bluetooth LE devices.";
23const char kDeviceInfoError[] =
24    "Error retrieving Bluetooth LE device information.";
25const char kDeviceAddressError[] =
26    "Device instance ID value does not seem to contain a Bluetooth Adapter "
27    "address.";
28const char kDeviceFriendlyNameError[] = "Device name is not valid.";
29const char kInvalidBluetoothAddress[] = "Bluetooth address format is invalid.";
30
31// Like ScopedHandle but for HDEVINFO.  Only use this on HDEVINFO returned from
32// SetupDiGetClassDevs.
33class DeviceInfoSetTraits {
34 public:
35  typedef HDEVINFO Handle;
36
37  static bool CloseHandle(HDEVINFO handle) {
38    return ::SetupDiDestroyDeviceInfoList(handle) != FALSE;
39  }
40
41  static bool IsHandleValid(HDEVINFO handle) {
42    return handle != INVALID_HANDLE_VALUE;
43  }
44
45  static HDEVINFO NullHandle() { return INVALID_HANDLE_VALUE; }
46
47 private:
48  DISALLOW_IMPLICIT_CONSTRUCTORS(DeviceInfoSetTraits);
49};
50
51typedef base::win::GenericScopedHandle<DeviceInfoSetTraits,
52                                       base::win::VerifierTraits>
53    ScopedDeviceInfoSetHandle;
54
55bool 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;
61  }
62
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;
75  }
76
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;
85}
86
87std::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();
93}
94
95bool CheckInsufficientBuffer(bool success,
96                             const char* message,
97                             std::string* error) {
98  if (success) {
99    *error = FormatBluetoothError(message, S_OK);
100    return false;
101  }
102
103  HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
104  if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
105    *error = FormatBluetoothError(message, hr);
106    return false;
107  }
108
109  return true;
110}
111
112bool CheckHResult(HRESULT hr, const char* message, std::string* error) {
113  if (FAILED(hr)) {
114    *error = FormatBluetoothError(message, hr);
115    return false;
116  }
117
118  return true;
119}
120
121bool CheckSuccess(bool success, const char* message, std::string* error) {
122  if (!success) {
123    CheckHResult(HRESULT_FROM_WIN32(GetLastError()), message, error);
124    return false;
125  }
126
127  return true;
128}
129
130bool CheckNoData(HRESULT hr, size_t length) {
131  if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
132    return true;
133
134  if (SUCCEEDED(hr) && length == 0)
135    return true;
136
137  return false;
138}
139
140bool CheckMoreData(HRESULT hr, const char* message, std::string* error) {
141  if (SUCCEEDED(hr)) {
142    *error = FormatBluetoothError(message, hr);
143    return false;
144  }
145
146  if (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA)) {
147    *error = FormatBluetoothError(message, hr);
148    return false;
149  }
150
151  return true;
152}
153
154bool 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;
161  }
162
163  return true;
164}
165
166bool 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,
175                                          device_info_data,
176                                          &key,
177                                          &prop_type,
178                                          NULL,
179                                          0,
180                                          &required_length,
181                                          0);
182  if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
183    return false;
184
185  scoped_ptr<uint8_t[]> prop_value(new uint8_t[required_length]);
186  DWORD actual_length = required_length;
187  success = SetupDiGetDeviceProperty(device_info_handle,
188                                     device_info_data,
189                                     &key,
190                                     &prop_type,
191                                     prop_value.get(),
192                                     actual_length,
193                                     &required_length,
194                                     0);
195  if (!CheckSuccess(!!success, kDeviceInfoError, error))
196    return false;
197  if (!CheckExpectedLength(
198          actual_length, required_length, kDeviceInfoError, error)) {
199    return false;
200  }
201
202  (*value) = scoped_ptr<DevicePropertyValue>(
203      new DevicePropertyValue(prop_type, prop_value.Pass(), actual_length));
204  return true;
205}
206
207bool 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,
215                                                  device_info_data,
216                                                  property_id,
217                                                  NULL,
218                                                  NULL,
219                                                  0,
220                                                  &required_length);
221  if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
222    return false;
223
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,
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;
239  }
240
241  (*value) = DeviceRegistryPropertyValue::Create(
242                 property_type, property_value.Pass(), actual_length).Pass();
243  return true;
244}
245
246bool 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, device_info_data, NULL, 0, &required_length);
254  if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
255    return false;
256
257  scoped_ptr<WCHAR[]> instance_id(new WCHAR[required_length]);
258  ULONG actual_length = required_length;
259  success = SetupDiGetDeviceInstanceId(device_info_handle,
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;
269  }
270
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());
275  }
276  return true;
277}
278
279bool 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;
291  }
292
293  if (property_value->property_type() != REG_SZ) {
294    *error = kDeviceFriendlyNameError;
295    return false;
296  }
297
298  device_info->friendly_name = property_value->AsString();
299  return true;
300}
301
302bool 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;
309  }
310  size_t end = instance_id.find("\\", start);
311  if (end == std::string::npos) {
312    *error = kDeviceAddressError;
313    return false;
314  }
315
316  start++;
317  std::string address = instance_id.substr(start, end - start);
318  if (!StringToBluetoothAddress(address, btha, error))
319    return false;
320
321  return true;
322}
323
324bool 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);
336}
337
338bool 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;
350  }
351
352  if (value->property_type() != DEVPROP_TYPE_UINT32) {
353    *error = kDeviceInfoError;
354    return false;
355  }
356
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;
364}
365
366bool 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;
374  }
375
376  USHORT required_length;
377  HRESULT hr = BluetoothGATTGetServices(file.GetPlatformFile(),
378                                        0,
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;
386
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;
400  }
401
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);
408  }
409
410  return true;
411}
412
413bool 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,
421                                                 device_interface_data,
422                                                 NULL,
423                                                 0,
424                                                 &required_length,
425                                                 NULL);
426  if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error))
427    return false;
428
429  scoped_ptr<uint8_t[]> interface_data(new uint8_t[required_length]);
430  ZeroMemory(interface_data.get(), required_length);
431
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);
436
437  SP_DEVINFO_DATA device_info_data = {0};
438  device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
439
440  ULONG actual_length = required_length;
441  success = SetupDiGetDeviceInterfaceDetail(device_info_handle,
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;
452  }
453
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;
461  }
462  if (!CollectBluetoothLowEnergyDeviceFriendlyName(
463          device_info_handle, &device_info_data, result, error)) {
464    return false;
465  }
466  if (!CollectBluetoothLowEnergyDeviceAddress(
467          device_info_handle, &device_info_data, result, error)) {
468    return false;
469  }
470  if (!CollectBluetoothLowEnergyDeviceStatus(
471          device_info_handle, &device_info_data, result, error)) {
472    return false;
473  }
474  (*device_info) = result.Pass();
475  return true;
476}
477
478enum DeviceInfoResult { kOk, kError, kNoMoreDevices };
479
480DeviceInfoResult 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,
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;
498    }
499    *error = FormatBluetoothError(kDeviceInfoError, hr);
500    return kError;
501  }
502
503  if (!CollectBluetoothLowEnergyDeviceInfo(
504          device_info_handle, &device_interface_data, device_info, error)) {
505    return kError;
506  }
507
508  return kOk;
509}
510
511// Opens a Device Info Set that can be used to enumerate Bluetooth LE devices
512// present on the machine.
513HRESULT 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());
519  }
520
521  (*handle) = result.Pass();
522  return S_OK;
523}
524
525// Opens a Device Info Set that can be used to enumerate Bluetooth LE devices
526// exposing a service GUID.
527HRESULT 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());
533  }
534
535  (*handle) = result.Pass();
536  return S_OK;
537}
538
539}  // namespace
540
541namespace device {
542namespace win {
543
544// static
545scoped_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;
558    }
559    case REG_DWORD: {
560      CHECK_EQ(value_size, sizeof(DWORD));
561      break;
562    }
563  }
564  return scoped_ptr<DeviceRegistryPropertyValue>(
565      new DeviceRegistryPropertyValue(property_type, value.Pass(), value_size));
566}
567
568DeviceRegistryPropertyValue::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) {
575}
576
577DeviceRegistryPropertyValue::~DeviceRegistryPropertyValue() {
578}
579
580std::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);
584}
585
586DWORD DeviceRegistryPropertyValue::AsDWORD() const {
587  CHECK_EQ(property_type_, static_cast<DWORD>(REG_DWORD));
588  DWORD* value = reinterpret_cast<DWORD*>(value_.get());
589  return *value;
590}
591
592DevicePropertyValue::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) {
598}
599
600uint32_t DevicePropertyValue::AsUint32() const {
601  CHECK_EQ(property_type_, static_cast<DEVPROPTYPE>(DEVPROP_TYPE_UINT32));
602  CHECK_EQ(value_size_, sizeof(uint32_t));
603  return *reinterpret_cast<uint32_t*>(value_.get());
604}
605
606BluetoothLowEnergyServiceInfo::BluetoothLowEnergyServiceInfo() {
607}
608
609BluetoothLowEnergyServiceInfo::~BluetoothLowEnergyServiceInfo() {
610}
611
612BluetoothLowEnergyDeviceInfo::BluetoothLowEnergyDeviceInfo()
613    : visible(false), authenticated(false), connected(false) {
614  address.ullLong = BLUETOOTH_NULL_ADDRESS;
615}
616
617BluetoothLowEnergyDeviceInfo::~BluetoothLowEnergyDeviceInfo() {
618}
619
620bool IsBluetoothLowEnergySupported() {
621  return base::win::GetVersion() >= base::win::VERSION_WIN8;
622}
623
624bool EnumerateKnownBluetoothLowEnergyDevices(
625    ScopedVector<BluetoothLowEnergyDeviceInfo>* devices,
626    std::string* error) {
627  if (!IsBluetoothLowEnergySupported()) {
628    *error = kPlatformNotSupported;
629    return false;
630  }
631
632  ScopedDeviceInfoSetHandle info_set_handle;
633  HRESULT hr = OpenBluetoothLowEnergyDevices(&info_set_handle);
634  if (FAILED(hr)) {
635    *error = FormatBluetoothError(kDeviceEnumError, hr);
636    return false;
637  }
638
639  for (DWORD i = 0;; ++i) {
640    scoped_ptr<BluetoothLowEnergyDeviceInfo> device_info;
641    DeviceInfoResult result = EnumerateSingleBluetoothLowEnergyDevice(
642        info_set_handle, i, &device_info, error);
643    switch (result) {
644      case kNoMoreDevices:
645        return true;
646      case kError:
647        return false;
648      case kOk:
649        devices->push_back(device_info.release());
650    }
651  }
652}
653
654bool EnumerateKnownBluetoothLowEnergyServices(
655    const base::FilePath& device_path,
656    ScopedVector<BluetoothLowEnergyServiceInfo>* services,
657    std::string* error) {
658  if (!IsBluetoothLowEnergySupported()) {
659    *error = kPlatformNotSupported;
660    return false;
661  }
662
663  return CollectBluetoothLowEnergyDeviceServices(device_path, services, error);
664}
665
666bool ExtractBluetoothAddressFromDeviceInstanceIdForTesting(
667    const std::string& instance_id,
668    BLUETOOTH_ADDRESS* btha,
669    std::string* error) {
670  return ExtractBluetoothAddressFromDeviceInstanceId(instance_id, btha, error);
671}
672
673}  // namespace win
674}  // namespace device
675