Skip to content

Commit

Permalink
Fetch Bluetooth LE HID device info from Windows devnode properties
Browse files Browse the repository at this point in the history
  • Loading branch information
DJm00n committed Sep 6, 2021
1 parent 8741697 commit 6009cf5
Showing 1 changed file with 194 additions and 18 deletions.
212 changes: 194 additions & 18 deletions windows/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
https://github.com/libusb/hidapi .
********************************************************/

#ifdef _MSC_VER
// Do not warn about mbsrtowcs and wcsncpy usage.
// https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <windows.h>

#ifndef _NTDEF_
Expand Down Expand Up @@ -135,6 +141,34 @@ static struct hid_api_version api_version = {

static HMODULE lib_handle = NULL;
static BOOLEAN initialized = FALSE;

typedef DWORD RETURN_TYPE;
typedef RETURN_TYPE CONFIGRET;
typedef DWORD DEVNODE, DEVINST;
typedef DEVNODE* PDEVNODE, * PDEVINST;
typedef WCHAR* DEVNODEID_W, * DEVINSTID_W;

#define CR_SUCCESS (0x00000000)
#define CR_BUFFER_SMALL (0x0000001A)

#define CM_LOCATE_DEVNODE_NORMAL 0x00000000

#define DEVPROP_TYPEMOD_LIST 0x00002000

#define DEVPROP_TYPE_STRING 0x00000012
#define DEVPROP_TYPE_STRING_LIST (DEVPROP_TYPE_STRING|DEVPROP_TYPEMOD_LIST)

typedef CONFIGRET(__stdcall* CM_Locate_DevNodeW_)(PDEVINST pdnDevInst, DEVINSTID_W pDeviceID, ULONG ulFlags);
typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags);
typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);
typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);

static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL;
static CM_Get_Parent_ CM_Get_Parent = NULL;
static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL;
static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL;

static HMODULE cfgmgr32_lib_handle = NULL;
#endif /* HIDAPI_USE_DDK */

struct hid_device_ {
Expand All @@ -151,6 +185,7 @@ struct hid_device_ {
char *read_buf;
OVERLAPPED ol;
OVERLAPPED write_ol;
struct hid_device_info* device_info;
};

static hid_device *new_hid_device()
Expand All @@ -171,6 +206,7 @@ static hid_device *new_hid_device()
dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);
memset(&dev->write_ol, 0, sizeof(dev->write_ol));
dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL);
dev->device_info = NULL;

return dev;
}
Expand All @@ -184,6 +220,7 @@ static void free_hid_device(hid_device *dev)
free(dev->write_buf);
free(dev->feature_buf);
free(dev->read_buf);
free(dev->device_info);
free(dev);
}

Expand Down Expand Up @@ -248,6 +285,23 @@ static int lookup_functions()
else
return -1;

cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll");
if (cfgmgr32_lib_handle) {
#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
#define RESOLVE(x) x = (x##_)GetProcAddress(cfgmgr32_lib_handle, #x);
RESOLVE(CM_Locate_DevNodeW);
RESOLVE(CM_Get_Parent);
RESOLVE(CM_Get_DevNode_PropertyW);
RESOLVE(CM_Get_Device_Interface_PropertyW);
#undef RESOLVE
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
}

return 0;
}
#endif
Expand Down Expand Up @@ -299,11 +353,135 @@ int HID_API_EXPORT hid_exit(void)
if (lib_handle)
FreeLibrary(lib_handle);
lib_handle = NULL;
if (cfgmgr32_lib_handle)
FreeLibrary(cfgmgr32_lib_handle);
cfgmgr32_lib_handle = NULL;
initialized = FALSE;
#endif
return 0;
}

static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node)
{
ULONG len;
CONFIGRET cr;
DEVPROPTYPE property_type;

static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac }, 10 }; // DEVPROP_TYPE_STRING
static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 1 }; // DEVPROP_TYPE_STRING
static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 4 }; // DEVPROP_TYPE_STRING

/* Manufacturer String */
len = 0;
cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, NULL, &len, 0);
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) {
free(dev->manufacturer_string);
dev->manufacturer_string = (wchar_t*)calloc(len, sizeof(BYTE));
CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, (PBYTE)dev->manufacturer_string, &len, 0);
}

/* Serial Number String (MAC Address) */
len = 0;
cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, NULL, &len, 0);
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) {
free(dev->serial_number);
dev->serial_number = (wchar_t*)calloc(len, sizeof(BYTE));
CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, (PBYTE)dev->serial_number, &len, 0);
}

/* Get devnode grandparent to reach out Bluetooth LE device node */
cr = CM_Get_Parent(&dev_node, dev_node, 0);
if (cr != CR_SUCCESS)
return;

/* Product String */
len = 0;
cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, NULL, &len, 0);
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) {
free(dev->product_string);
dev->product_string = (wchar_t*)calloc(len, sizeof(BYTE));
CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, (PBYTE)dev->product_string, &len, 0);
}
}

static void hid_internal_get_info(struct hid_device_info* dev)
{
char *tmp = NULL;
wchar_t *interface_path = NULL, *device_id = NULL, *compatible_ids = NULL;
mbstate_t state;
ULONG len;
CONFIGRET cr;
DEVPROPTYPE property_type;
DEVINST dev_node;

static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57 }, 256 }; // DEVPROP_TYPE_STRING
static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0}, 4 }; // DEVPROP_TYPE_STRING_LIST

if (!CM_Get_Device_Interface_PropertyW ||
!CM_Locate_DevNodeW ||
!CM_Get_Parent ||
!CM_Get_DevNode_PropertyW)
goto end;

tmp = dev->path;

len = (ULONG)strlen(tmp);
interface_path = (wchar_t*)calloc(len + 1, sizeof(wchar_t));
memset(&state, 0, sizeof(state));

if (mbsrtowcs(interface_path, &tmp, len, &state) == (size_t)-1)
goto end;

/* Get the device id from interface path */
len = 0;
cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, NULL, &len, 0);
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) {
device_id = (wchar_t*)calloc(len, sizeof(BYTE));
cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, (PBYTE)device_id, &len, 0);
}
if (cr != CR_SUCCESS)
goto end;

/* Open devnode from device id */
cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL);
if (cr != CR_SUCCESS)
goto end;

/* Get devnode parent */
cr = CM_Get_Parent(&dev_node, dev_node, 0);
if (cr != CR_SUCCESS)
goto end;

/* Get the compatible ids from parent devnode */
len = 0;
cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, NULL, &len, 0);
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING_LIST) {
compatible_ids = (wchar_t*)calloc(len, sizeof(BYTE));
cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, (PBYTE)compatible_ids, &len, 0);
}
if (cr != CR_SUCCESS)
goto end;

/* Now we can parse parent's compatible IDs to find out the device bus type */
for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) {
/* Normalize to upper case */
for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p);

/* Bluetooth LE devices */
if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) {
/* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices
Request this info via dev node properties instead.
https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html */
hid_internal_get_ble_info(dev, dev_node);
break;
}
}
end:
free(interface_path);
free(device_id);
free(compatible_ids);
}

static struct hid_device_info *hid_get_device_info(const char *path, HANDLE handle)
{
struct hid_device_info *dev = NULL; /* return object */
Expand Down Expand Up @@ -390,6 +568,8 @@ static struct hid_device_info *hid_get_device_info(const char *path, HANDLE hand
}
}

hid_internal_get_info(dev);

return dev;
}

Expand Down Expand Up @@ -650,6 +830,8 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)

dev->read_buf = (char*) malloc(dev->input_report_length);

dev->device_info = hid_get_device_info(path, dev->device_handle);

return dev;

err_pp_data:
Expand Down Expand Up @@ -948,39 +1130,33 @@ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)

int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
{
BOOL res;

res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
if (!res) {
register_error(dev, "HidD_GetManufacturerString");
if (!dev->device_info || !string || !maxlen)
return -1;
}

wcsncpy(string, dev->device_info->manufacturer_string, maxlen);
string[maxlen] = L'\0';

return 0;
}

int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
{
BOOL res;

res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
if (!res) {
register_error(dev, "HidD_GetProductString");
if (!dev->device_info || !string || !maxlen)
return -1;
}

wcsncpy(string, dev->device_info->product_string, maxlen);
string[maxlen] = L'\0';

return 0;
}

int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
{
BOOL res;

res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
if (!res) {
register_error(dev, "HidD_GetSerialNumberString");
if (!dev->device_info || !string || !maxlen)
return -1;
}

wcsncpy(string, dev->device_info->serial_number, maxlen);
string[maxlen] = L'\0';

return 0;
}
Expand Down

0 comments on commit 6009cf5

Please sign in to comment.