Files
nearby/internal/platform/implementation/windows/bluetooth_adapter.cc
T
2025-11-20 13:49:49 -08:00

911 lines
34 KiB
C++

// Copyright 2020-2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/windows/bluetooth_adapter.h"
// clang-format off
#include <windows.h> // These two headers must be defined
#include <winioctl.h> // first and in this order, otherwise
// the MSVC compiler emits a warning
// 'CTL_CODE undefined. Include winioctl.h or wdm.h'
// See https://screenshot.googleplex.com/BYB9QnoHT6eP6oc
// clang-format on
#include <bthdef.h>
#include <bthioctl.h>
#include <cfgmgr32.h>
#include <devguid.h>
#include <initguid.h>
#include <objbase.h>
#include <setupapi.h>
#include <stdio.h>
#include <usbiodef.h>
#include <cstdint>
#include <cstring>
#include <exception>
#include <optional>
#include <string>
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "third_party/json/src/json.hpp"
#include "internal/platform/feature_flags.h"
#include "internal/platform/implementation/platform.h"
#include "internal/platform/implementation/windows/generated/winrt/Windows.Foundation.h"
#include "internal/platform/logging.h"
#include "internal/platform/mac_address.h"
typedef std::basic_string<TCHAR> tstring;
// IOCTL to get local radio information
#define BTH_GET_DEVICE_INFO_IOCTL 0x411008
#define REGISTRY_QUERY_FORMAT \
"SYSTEM\\ControlSet001\\Enum\\%s\\Device Parameters"
#define BLUETOOTH_RADIO_REGISTRY_NAME_KEY "Local Name"
namespace nearby {
namespace windows {
namespace {
struct LocalSettings {
std::string original_radio_name;
std::string nearby_radio_name;
};
using json = ::nlohmann::json;
constexpr absl::string_view kLocalSettingsFileName = "settings_file.json";
constexpr char kOriginalRadioName[] = "OriginalRadioName";
constexpr char kNearbyRadioName[] = "NearbyRadioName";
void to_json(json &json_output, const LocalSettings &local_settings) {
json_output = json{{kOriginalRadioName, local_settings.original_radio_name},
{kNearbyRadioName, local_settings.nearby_radio_name}};
}
void from_json(const json &json_input, LocalSettings &local_settings) {
json_input.at(kOriginalRadioName).get_to(local_settings.original_radio_name);
json_input.at(kNearbyRadioName).get_to(local_settings.nearby_radio_name);
}
} // namespace
constexpr uint8_t kAndroidDiscoverableBluetoothNameMaxLength = 37; // bytes
BluetoothAdapter::BluetoothAdapter() : windows_bluetooth_adapter_(nullptr) {
try {
windows_bluetooth_adapter_ =
winrt::Windows::Devices::Bluetooth::BluetoothAdapter::GetDefaultAsync()
.get();
if (windows_bluetooth_adapter_ == nullptr) {
LOG(ERROR) << __func__ << ": No Bluetooth adapter on this device.";
} else {
// Gets the radio represented by this Bluetooth adapter.
// https://docs.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothadapter.getradioasync?view=winrt-20348
windows_bluetooth_radio_ =
windows_bluetooth_adapter_.GetRadioAsync().get();
}
} catch (const winrt::hresult_error &error) {
LOG(ERROR) << __func__ << ": WinRT exception: " << error.code() << ": "
<< winrt::to_string(error.message());
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
}
}
// Synchronously sets the status of the BluetoothAdapter to 'status', and
// returns true if the operation was a success.
bool BluetoothAdapter::SetStatus(Status status) {
LOG(INFO) << __func__ << ": Set Bluetooth radio status to "
<< (status == Status::kEnabled ? "On" : "Off");
if (windows_bluetooth_radio_ == nullptr) {
LOG(ERROR) << __func__ << ": No Bluetooth radio on this device.";
return false;
}
auto radio_state = windows_bluetooth_radio_.State();
if (status == Status::kDisabled &&
(radio_state == RadioState::Unknown || radio_state == RadioState::Off ||
radio_state == RadioState::Disabled)) {
VLOG(1) << __func__
<< ": Skip set radio status kDisabled due to requested state is "
"already kDisabled.";
return true;
}
if (status == Status::kEnabled && radio_state == RadioState::On) {
VLOG(1) << __func__
<< ": Skip set radio status kEnabled due to requested state is "
"already kEnabled.";
return true;
}
if (!FeatureFlags::GetInstance().GetFlags().enable_set_radio_state) {
VLOG(1) << __func__
<< ": Attempt to set the radio state while "
"FeatureFlags::enable_set_radio_state is false.";
return false;
}
try {
// An asynchronous operation that attempts to set the state of the radio
// represented by this object.
// https://docs.microsoft.com/en-us/uwp/api/windows.devices.radios.radio.setstateasync?view=winrt-20348
if (status == Status::kDisabled) {
windows_bluetooth_radio_.SetStateAsync(RadioState::Off).get();
} else {
windows_bluetooth_radio_.SetStateAsync(RadioState::On).get();
}
} catch (const winrt::hresult_error &ex) {
LOG(ERROR) << __func__ << ": Failed to set Bluetooth radio state to "
<< (status == Status::kDisabled ? "kDisabled." : "kEnabled.")
<< "Exception: " << ex.code() << ": "
<< winrt::to_string(ex.message());
return false;
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
return false;
}
LOG(INFO) << __func__ << ": Successfully set the radio state to "
<< (status == Status::kDisabled ? "kDisabled." : "kEnabled.");
return true;
}
// Returns true if the BluetoothAdapter's current status is
// Status::Value::kEnabled.
bool BluetoothAdapter::IsEnabled() const {
if (windows_bluetooth_radio_ == nullptr) {
LOG(ERROR) << __func__ << ": No Bluetooth radio on this device.";
return false;
}
try {
// Gets the current state of the radio represented by this object.
// https://docs.microsoft.com/en-us/uwp/api/windows.devices.radios.radio.state?view=winrt-20348
return windows_bluetooth_radio_.State() == RadioState::On;
} catch (std::exception exception) {
LOG(ERROR) << __func__ << ": exception:" << exception.what();
return false;
} catch (const winrt::hresult_error &ex) {
LOG(ERROR) << __func__ << ": exception:" << ex.code() << ": "
<< winrt::to_string(ex.message());
return false;
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
return false;
}
}
// Returns true if the Bluetooth hardware supports Bluetooth 5.0 Extended
// Advertising
bool BluetoothAdapter::IsExtendedAdvertisingSupported() const {
if (windows_bluetooth_adapter_ == nullptr) {
LOG(ERROR) << __func__ << ": No Bluetooth adapter on this device.";
return false;
}
try {
// Indicates whether the adapter supports the 5.0 Extended Advertising
// format.
// https://docs.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothadapter.isextendedadvertisingsupported?view=winrt-22621
return windows_bluetooth_adapter_.IsExtendedAdvertisingSupported();
} catch (std::exception exception) {
LOG(ERROR) << __func__ << ": exception:" << exception.what();
return false;
} catch (const winrt::hresult_error &ex) {
LOG(ERROR) << __func__ << ": exception:" << ex.code() << ": "
<< winrt::to_string(ex.message());
return false;
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
return false;
}
}
// Returns true if the Bluetooth hardware supports BLE Central Role
bool BluetoothAdapter::IsCentralRoleSupported() const {
if (windows_bluetooth_adapter_ == nullptr) {
LOG(ERROR) << __func__ << ": No Bluetooth adapter on this device.";
return false;
}
try {
// Indicates whether the adapter supports the BLE Central Role
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothadapter.iscentralrolesupported?view=winrt-22621
return windows_bluetooth_adapter_.IsCentralRoleSupported();
} catch (std::exception exception) {
LOG(ERROR) << __func__ << ": exception:" << exception.what();
return false;
} catch (const winrt::hresult_error &ex) {
LOG(ERROR) << __func__ << ": exception:" << ex.code() << ": "
<< winrt::to_string(ex.message());
return false;
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
return false;
}
}
// Returns true if the Bluetooth hardware supports BLE Peripheral Role
bool BluetoothAdapter::IsPeripheralRoleSupported() const {
if (windows_bluetooth_adapter_ == nullptr) {
LOG(ERROR) << __func__ << ": No Bluetooth adapter on this device.";
return false;
}
try {
// Indicates whether the adapter supports the BLE Peripheral Role
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothadapter.isperipheralrolesupported?view=winrt-22621
return windows_bluetooth_adapter_.IsPeripheralRoleSupported();
} catch (std::exception exception) {
LOG(ERROR) << __func__ << ": exception:" << exception.what();
return false;
} catch (const winrt::hresult_error &ex) {
LOG(ERROR) << __func__ << ": exception:" << ex.code() << ": "
<< winrt::to_string(ex.message());
return false;
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
return false;
}
}
// Returns true if the Bluetooth hardware supports BLE
bool BluetoothAdapter::IsLowEnergySupported() const {
if (windows_bluetooth_adapter_ == nullptr) {
LOG(ERROR) << __func__ << ": No Bluetooth adapter on this device.";
return false;
}
try {
// Indicates whether the adapter supports BLE
// https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothadapter.islowenergysupported?view=winrt-22621
return windows_bluetooth_adapter_.IsLowEnergySupported();
} catch (std::exception exception) {
LOG(ERROR) << __func__ << ": exception:" << exception.what();
return false;
} catch (const winrt::hresult_error &ex) {
LOG(ERROR) << __func__ << ": exception:" << ex.code() << ": "
<< winrt::to_string(ex.message());
return false;
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
return false;
}
}
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode()
//
// Returns ScanMode::kUnknown on error.
BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const {
return scan_mode_;
}
// Synchronously sets the scan mode of the adapter, and returns true if the
// operation was a success.
bool BluetoothAdapter::SetScanMode(ScanMode scan_mode) {
scan_mode_ = scan_mode;
if (scan_mode_changed_ != nullptr) {
scan_mode_changed_(scan_mode);
}
return true;
}
void BluetoothAdapter::RestoreRadioNameIfNecessary() {
try {
std::string nearby_radio_name;
std::string current_radio_name = GetName();
std::string settings_path(kLocalSettingsFileName);
auto full_path =
nearby::api::ImplementationPlatform::GetAppDataPath(settings_path);
auto settings_file =
nearby::api::ImplementationPlatform::CreateInputFile(full_path, 0);
if (settings_file == nullptr) {
LOG(ERROR) << __func__ << ": Failed to create input file.";
return;
}
auto total_size = settings_file->GetTotalSize();
if (total_size == 0) {
LOG(WARNING) << __func__ << ": No data for local settings.";
return;
}
nearby::ExceptionOr<ByteArray> raw_local_settings;
raw_local_settings = settings_file->Read(total_size);
settings_file->Close();
if (!raw_local_settings.ok()) {
LOG(ERROR) << __func__ << ": Failed to read data file.";
return;
}
auto local_settings =
json::parse(raw_local_settings.GetResult().data(), nullptr, false);
if (local_settings.is_discarded()) {
LOG(ERROR) << __func__ << ": Invalid local settings data.";
return;
}
VLOG(1) << __func__ << ": loaded settings: " << local_settings.dump();
LocalSettings settings = local_settings.get<LocalSettings>();
if (current_radio_name == settings.nearby_radio_name) {
SetName(settings.original_radio_name,
/* persist= */ true);
}
} catch (const winrt::hresult_error &ex) {
LOG(ERROR) << __func__ << ": exception:" << ex.code() << ": "
<< winrt::to_string(ex.message());
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
}
}
void BluetoothAdapter::StoreRadioNames(absl::string_view original_radio_name,
absl::string_view nearby_radio_name) {
try {
if (original_radio_name.empty() || nearby_radio_name.empty()) {
LOG(ERROR) << __func__
<< ":Failed to save radio names due to invalid parameters.";
return;
}
std::string settings_path(kLocalSettingsFileName);
auto full_path =
nearby::api::ImplementationPlatform::GetAppDataPath(settings_path);
auto settings_file =
nearby::api::ImplementationPlatform::CreateOutputFile(full_path);
if (settings_file == nullptr) {
LOG(ERROR) << __func__ << ": Failed to create output file.";
return;
}
LocalSettings local_settings = {std::string(original_radio_name),
std::string(nearby_radio_name)};
json encoded_local_settings;
to_json(encoded_local_settings, local_settings);
VLOG(1) << __func__
<< ": saved settings: " << encoded_local_settings.dump();
ByteArray data(encoded_local_settings.dump());
settings_file->Write(data);
settings_file->Close();
} catch (const winrt::hresult_error &ex) {
LOG(ERROR) << __func__ << ": exception:" << ex.code() << ": "
<< winrt::to_string(ex.message());
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
}
}
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName()
// Returns an empty string on error
std::string BluetoothAdapter::GetName() const {
if (device_name_.has_value()) {
return *device_name_;
}
std::optional<std::string> adapter_instance_id =
GetGenericBluetoothAdapterInstanceID();
if (!adapter_instance_id.has_value()) {
LOG(ERROR) << __func__
<< ": Failed to get Generic Bluetooth Adapter InstanceID";
return std::string();
}
std::string instance_id = *adapter_instance_id;
// Change radio module local name in registry
HKEY hKey;
// Retrieve the size required
char empty[0];
size_t registry_query_size = absl::SNPrintF(empty, // output
0, // size
REGISTRY_QUERY_FORMAT, // format
instance_id.c_str()); // args
std::string local_name_key;
local_name_key.reserve(registry_query_size + 1);
absl::SNPrintF(local_name_key.data(), // output
registry_query_size + 1, // size
REGISTRY_QUERY_FORMAT, // format
instance_id.c_str()); // args
LSTATUS status;
// Opens the specified registry key.
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regopenkeyexa
status = RegOpenKeyExA(
HKEY_LOCAL_MACHINE, // A handle to an open registry key, using
// predefined key for local machine
local_name_key.c_str(), // The name of the registry subkey to be opened.
0L, // Specifies the option to apply when opening the key.
KEY_QUERY_VALUE, // A mask that specifies the desired access rights to
// the key to be opened.
&hKey); // A pointer to a variable that receives a handle to the opened
// key
std::string adapter_name;
if (status == ERROR_SUCCESS) {
adapter_name = GetNameFromRegistry(&hKey);
}
// Closes a handle to the specified registry key.
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regclosekey
RegCloseKey(hKey);
// The local name is not in the registry, return the machine name
if (adapter_name.empty()) {
return GetNameFromComputerName();
}
return adapter_name;
}
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String)
bool BluetoothAdapter::SetName(absl::string_view name) {
return SetName(name,
/* persist= */ true);
}
bool BluetoothAdapter::SetName(absl::string_view name, bool persist) {
if (!persist) {
StoreRadioNames(GetName(), name);
}
if (name.size() > 248 * sizeof(char)) {
LOG(ERROR) << __func__
<< ": Failed to set name for bluetooth adapter because "
"the name exceeded the 248 bytes limit for Windows.";
return false;
}
if (name.size() > kAndroidDiscoverableBluetoothNameMaxLength * sizeof(char)) {
LOG(ERROR) << __func__
<< ": Failed to set name for bluetooth adapter because "
"Android cannot discover Windows bluetooth device "
"name that exceeded the 37 bytes limit (11 "
"characters in EndpointInfo).";
device_name_ = std::string(name);
return true;
}
device_name_ = std::nullopt;
if (registry_bluetooth_adapter_name_ == name) {
VLOG(1) << __func__
<< ": Tried to set name for bluetooth adapter to the "
"same name again.";
return true;
}
std::optional<std::string> adapter_instance_id =
GetGenericBluetoothAdapterInstanceID();
if (!adapter_instance_id.has_value()) {
LOG(ERROR) << __func__
<< ": Failed to get Generic Bluetooth Adapter InstanceID";
return false;
}
std::string instance_id = *adapter_instance_id;
// defined in usbiodef.h
const GUID guid = GUID_DEVINTERFACE_USB_DEVICE;
// Conversion of a 16 byte guid to a format of
// "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" results in
// a required buffer of
// (32 digits + 4 '-'s + 2 braces + 1 '\0') * sizeof(OLECHAR) for
// a total of 39 OLECHARs.
#define OLE_GUID_STRING_BUFFER_SIZE 39
OLECHAR guid_ole_str[OLE_GUID_STRING_BUFFER_SIZE];
size_t guid_ole_str_size = OLE_GUID_STRING_BUFFER_SIZE;
// Converts a globally unique identifier (GUID) into a string of printable
// characters.
// https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-stringfromguid2
auto conversionResult =
StringFromGUID2(guid, guid_ole_str, guid_ole_str_size);
if (conversionResult == 0) {
LOG(ERROR) << __func__ << ": Failed to convert guid to string";
return false;
}
std::string guid_str;
int guid_str_size = 0;
BOOL defaultCharUsed;
guid_str_size = WideCharToMultiByte(
CP_UTF8, // Code page to use in performing the conversion.
0, // Flags indicating the conversion type.
guid_ole_str, // Pointer to the Unicode string to convert.
guid_ole_str_size, // Size, in characters, of the string indicated by
// lpWideCharStr.
nullptr, // Pointer to a buffer that receives the converted string.
0, // Size, in bytes, of the buffer indicated by lpMultiByteStr.
NULL, // Pointer to the character to use if a character cannot be
// represented in the specified code page.
&defaultCharUsed); // Pointer to a flag that indicates if the function
// has used a default character in the conversion.
if (guid_str_size == 0) {
process_error();
return false;
}
guid_str.reserve(guid_str_size);
// Maps a UTF-16 (wide character) string to a new character string. The new
// character string is not necessarily from a multibyte character set.
// https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
conversionResult = WideCharToMultiByte(
CP_UTF8, // Code page to use in performing the conversion.
0, // Flags indicating the conversion type.
guid_ole_str, // Pointer to the Unicode string to convert.
guid_ole_str_size, // Size, in characters, of the string indicated by
// lpWideCharStr.
guid_str
.data(), // Pointer to a buffer that receives the converted string.
guid_str_size, // Size, in bytes, of the buffer indicated by
// lpMultiByteStr.
NULL, // // Pointer to the character to use if a character cannot be
// represented in the specified code page.
&defaultCharUsed); // // Pointer to a flag that indicates if the
// function has used a default character in the
// conversion.
if (conversionResult == 0) {
process_error();
return false;
}
// Add 1 to length to get size (including null)
std::string instance_id_modified;
instance_id_modified.reserve((instance_id.size() + 1) * sizeof(char));
absl::SNPrintF(instance_id_modified.data(),
size_t((instance_id.size() + 1) * sizeof(char)), "%s",
instance_id.c_str());
// Convert the P&P instance id, to one that CreateFileA expects
find_and_replace(instance_id_modified.data(), "\\", "#");
char empty[0];
size_t file_name_size = absl::SNPrintF(
empty, 0, "\\\\.\\%s#%s", instance_id_modified.c_str(), guid_str.c_str());
std::string file_name;
file_name.reserve(file_name_size + 1);
// Add the guid to the P&P instance id forming the complete symbolic
// path to the device.
absl::SNPrintF(file_name.data(), file_name_size + 1, "\\\\.\\%s#%s",
instance_id_modified.c_str(), guid_str.c_str());
HANDLE hDevice;
// Creates or opens a file or I/O device.
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
hDevice = CreateFileA(
file_name.c_str(), // The name of the file or device to be created or
// opened.
GENERIC_WRITE, // The requested access to the file or device.
0, // The requested sharing mode of the file or device.
NULL, // A pointer to a SECURITY_ATTRIBUTES structure.
OPEN_EXISTING, // An action to take on a file or device that exists or
// does not exist.
0, // The file or device attributes and flags.
NULL); // A valid handle to a template file with the GENERIC_READ
// access right. This parameter can be NULL.
if (hDevice == INVALID_HANDLE_VALUE) {
LOG(ERROR) << __func__
<< ": Failed to open device. Error code: " << GetLastError();
return false;
}
// Change radio module local name in registry
HKEY hKey;
size_t buffer_size =
absl::SNPrintF(empty, 0, REGISTRY_QUERY_FORMAT, instance_id);
std::string local_name_key;
local_name_key.reserve(buffer_size + 1);
absl::SNPrintF(local_name_key.data(), buffer_size + 1, REGISTRY_QUERY_FORMAT,
instance_id.c_str());
LSTATUS status;
// Opens the specified registry key. Note that key names are not case
// sensitive.
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regopenkeyexa
status = RegOpenKeyExA(
HKEY_LOCAL_MACHINE, // A handle to an open registry key.
local_name_key.c_str(), // The name of the registry subkey to be opened.
0L, // Specifies the option to apply when opening the key.
KEY_SET_VALUE, // A mask that specifies the desired access rights to
// the key to be opened.
&hKey); // A pointer to a variable that receives a handle to the opened
// key.
if (status != ERROR_SUCCESS) {
LOG(ERROR) << __func__
<< ": Failed to open registry key. Error code: " << status;
return false;
}
if (name != "") {
// Sets the data and type of a specified value under a registry key.
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regsetvalueexa
status = RegSetValueExA(
hKey, // A handle to an open registry key
BLUETOOTH_RADIO_REGISTRY_NAME_KEY, // The name of the value to be
// set.
0, // This parameter is reserved and must be zero.
REG_BINARY, // The type of data pointed to by the lpData parameter.
(LPBYTE)std::string(name).c_str(), // The data to be stored.
strlen(std::string(name)
.c_str())); // The size of the information pointed
// to by the lpData parameter, in bytes.
} else {
// If we are told to set the key to "", we treat this as a reset
// If we delete the key value the OS will default to the system
// name. If we just set it to blank, it will show as blank in all
// system dialogs, this is likely undesireable
status = RegDeleteValueA(hKey, BLUETOOTH_RADIO_REGISTRY_NAME_KEY);
}
if (status != ERROR_SUCCESS) {
LOG(ERROR) << __func__
<< ": Failed to set/delete registry key. Error code: " << status;
return false;
}
// Closes a handle to the specified registry key.
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regclosekey
RegCloseKey(hKey);
// tells the control function to reset or reload or similar...
int32_t reload = 4;
// merely a placeholder
DWORD bytes = 0;
// Send radio module driver command to update device information
// Sends a control code directly to a specified device driver, causing the
// corresponding device to perform the corresponding operation.
// https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol
if (!DeviceIoControl(
hDevice, // A handle to the device on which the operation is to be
// performed.
BTH_GET_DEVICE_INFO_IOCTL, // The control code for the operation.
&reload, // A pointer to the input buffer that contains the data
// required to perform the operation.
sizeof(reload), // The size of the input buffer, in bytes.
NULL, // A pointer to the output buffer that is to receive the data
// returned by the operation.
0, // The size of the output buffer, in bytes.
&bytes, // A pointer to a variable that receives the size of the
// data stored in the output buffer, in bytes.
NULL)) { // A pointer to an OVERLAPPED structure.
LOG(ERROR) << __func__
<< ": Failed to update radio module local name. Error code: "
<< GetLastError();
return false;
}
registry_bluetooth_adapter_name_ = std::string(name);
return true;
}
void BluetoothAdapter::process_error() {
const char *errorResult = {};
int errorMessageID = GetLastError();
switch (errorMessageID) {
case ERROR_INSUFFICIENT_BUFFER:
errorResult =
"A supplied buffer size was not large enough, or it was "
"incorrectly set to NULL.";
break;
case ERROR_INVALID_FLAGS:
errorResult = "The values supplied for flags were not valid.";
break;
case ERROR_INVALID_PARAMETER:
errorResult = "Any of the parameter values was invalid.";
break;
case ERROR_NO_UNICODE_TRANSLATION:
errorResult = "Invalid Unicode was found in a string.";
break;
default:
errorResult = "Unknown error.";
break;
}
LOG(ERROR) << __func__ << ": Failed to convert guid to string " << errorResult
<< " Error code:" << errorMessageID;
}
void BluetoothAdapter::find_and_replace(char *source, const char *strFind,
const char *strReplace) const {
std::string s = std::string(source);
std::string f = std::string(strFind);
std::string r = std::string(strReplace);
size_t j;
for (; (j = s.find(f)) != std::string::npos;) {
s.replace(j, f.length(), r);
}
memcpy(source, s.c_str(), s.size());
}
std::optional<std::string>
BluetoothAdapter::GetGenericBluetoothAdapterInstanceID() const {
unsigned i;
CONFIGRET r;
HDEVINFO hDevInfo;
SP_DEVINFO_DATA DeviceInfoData;
char *deviceInstanceID = new char[MAX_DEVICE_ID_LEN];
// Find all bluetooth radio modules
// The SetupDiGetClassDevs function returns a handle to a device information
// set that contains requested device information elements for a local
// computer.
// https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetclassdevsa
hDevInfo =
SetupDiGetClassDevsA(&GUID_DEVCLASS_BLUETOOTH, NULL, NULL, DIGCF_PRESENT);
if (hDevInfo == INVALID_HANDLE_VALUE) {
LOG(ERROR) << __func__
<< ": Could not find BluetoothDevice on this machine";
return std::nullopt;
}
// Get first Generic Bluetooth Adapter InstanceID
for (i = 0;; i++) {
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
// The SetupDiEnumDeviceInfo function returns a SP_DEVINFO_DATA structure
// that specifies a device information element in a device information
// set.
// https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdienumdeviceinfo
if (!SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData)) break;
// The CM_Get_Device_ID function retrieves the device instance ID for a
// specified device instance on the local machine.
// https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_device_ida
r = CM_Get_Device_IDA(DeviceInfoData.DevInst, deviceInstanceID,
MAX_DEVICE_ID_LEN, 0);
if (r != CR_SUCCESS) continue;
// With Windows, a Bluetooth radio can be packaged as an external dongle
// or embedded inside a computer but it must be connected to one of the
// computer's USB ports.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/bluetooth/bluetooth-host-radio-support
if (strncmp("USB", deviceInstanceID, 3) == 0) {
SetupDiDestroyDeviceInfoList(hDevInfo);
return std::string(deviceInstanceID);
}
}
LOG(ERROR) << __func__ << ": Failed to get the generic bluetooth adapter id";
SetupDiDestroyDeviceInfoList(hDevInfo);
return std::nullopt;
}
// Returns BT MAC address assigned to this adapter.
MacAddress BluetoothAdapter::GetMacAddress() const {
if (windows_bluetooth_adapter_ == nullptr) {
LOG(ERROR) << __func__ << ": No Bluetooth adapter on this device.";
return MacAddress();
}
MacAddress mac_address;
try {
MacAddress::FromUint64(
windows_bluetooth_adapter_.BluetoothAddress(), mac_address);
} catch (std::exception exception) {
LOG(ERROR) << __func__ << ": exception:" << exception.what();
} catch (const winrt::hresult_error &ex) {
LOG(ERROR) << __func__ << ": exception:" << ex.code() << ": "
<< winrt::to_string(ex.message());
} catch (...) {
LOG(ERROR) << __func__ << ": unknown error.";
}
return mac_address;
}
std::string BluetoothAdapter::GetNameFromRegistry(PHKEY hKey) const {
DWORD local_name_size = 0;
DWORD value_type;
// Retrieves the size of the data for the specified value name associated
// with an open registry key.
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexa
LSTATUS status = RegQueryValueExA(
*hKey, // A handle to an open registry key.
BLUETOOTH_RADIO_REGISTRY_NAME_KEY, // The name of the registry
// value.
nullptr, // This parameter is reserved and must be NULL.
&value_type, // A pointer to a variable that receives a code
// indicating the type of data stored in the
// specified value.
nullptr, // null tells the function to just get the buffer size.
// value's data.
&local_name_size); // A pointer to a variable that specifies the
// size of the buffer pointed to by the lpData
// parameter, in bytes.
if (status != ERROR_SUCCESS) {
LOG(ERROR) << __func__
<< ": Failed to get the required size of the local name buffer: "
<< status;
return "";
}
unsigned char *local_name = new unsigned char[local_name_size];
memset(local_name, '\0', local_name_size);
status = RegQueryValueExA(
*hKey, // A handle to an open registry key.
BLUETOOTH_RADIO_REGISTRY_NAME_KEY, // The name of the registry
// value.
nullptr, // This parameter is reserved and must be NULL.
&value_type, // A pointer to a variable that receives a code
// indicating the type of data stored in the
// specified value.
local_name, // A pointer to a buffer that
// receives the value's data.
&local_name_size); // A pointer to a variable that specifies the
// size of the buffer pointed to by the lpData
// parameter, in bytes.
if (status == ERROR_SUCCESS) {
std::string local_name_return = std::string(
local_name, local_name + local_name_size / sizeof local_name[0]);
delete[] local_name;
return local_name_return;
}
delete[] local_name;
return "";
}
std::string BluetoothAdapter::GetNameFromComputerName() const {
DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
CHAR computer_name[MAX_COMPUTERNAME_LENGTH + 1];
if (GetComputerNameA(computer_name, &size)) {
return std::string(computer_name);
}
LOG(ERROR) << __func__ << ": Failed to get any computer name";
return "";
}
} // namespace windows
} // namespace nearby