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 13 namespace { 14 15 using device::win::DeviceRegistryPropertyValue; 16 using device::win::DevicePropertyValue; 17 using device::win::BluetoothLowEnergyDeviceInfo; 18 using device::win::BluetoothLowEnergyServiceInfo; 19 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."; 30 31 // Like ScopedHandle but for HDEVINFO. Only use this on HDEVINFO returned from 32 // SetupDiGetClassDevs. 33 class 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 51 typedef base::win::GenericScopedHandle<DeviceInfoSetTraits, 52 base::win::VerifierTraits> 53 ScopedDeviceInfoSetHandle; 54 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; 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 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(); 93 } 94 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; 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 112 bool 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 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; 125 } 126 127 return true; 128 } 129 130 bool 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 140 bool 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 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; 161 } 162 163 return true; 164 } 165 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, 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 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, 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 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, 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 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; 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 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; 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 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); 336 } 337 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; 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 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; 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 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, 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 478 enum DeviceInfoResult { kOk, kError, kNoMoreDevices }; 479 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, 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. 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()); 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. 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()); 533 } 534 535 (*handle) = result.Pass(); 536 return S_OK; 537 } 538 539 } // namespace 540 541 namespace device { 542 namespace win { 543 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; 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 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) { 575 } 576 577 DeviceRegistryPropertyValue::~DeviceRegistryPropertyValue() { 578 } 579 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); 584 } 585 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; 590 } 591 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) { 598 } 599 600 uint32_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 606 BluetoothLowEnergyServiceInfo::BluetoothLowEnergyServiceInfo() { 607 } 608 609 BluetoothLowEnergyServiceInfo::~BluetoothLowEnergyServiceInfo() { 610 } 611 612 BluetoothLowEnergyDeviceInfo::BluetoothLowEnergyDeviceInfo() 613 : visible(false), authenticated(false), connected(false) { 614 address.ullLong = BLUETOOTH_NULL_ADDRESS; 615 } 616 617 BluetoothLowEnergyDeviceInfo::~BluetoothLowEnergyDeviceInfo() { 618 } 619 620 bool IsBluetoothLowEnergySupported() { 621 return base::win::GetVersion() >= base::win::VERSION_WIN8; 622 } 623 624 bool 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 654 bool 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 666 bool 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