How to get HID Report Descriptor using Windows API

We are having some discussion in libusb-devel mailing list and github
hidapi project here.
https://github.com/libusb/hidapi/issues/249

Just wondering if the experts here have any other ideas. Thanks.

Best regards,
Xiaofan

On Sat, Jun 19, 2021 at 12:14 PM Tim Roberts wrote:

On 6/18/21 6:56 PM, Xiaofan Chen wrote:

Switch to another question. Hopefully you can shed some light on this as well. Thanks.
MacOS and Linux provide APIs for reading the HID Report descriptor of an HID device.
Therefore we can get the unmodified HID Report Descriptor on these operating systems.
Unfortunately the Windows HID subsystem doesn’t provide such an API.

Yes. This is a huge irritation to me. I have done two projects recently where I needed to
imitate another device. If I could have grabbed the original report descriptor, this task
would have been way easier. In both cases, I ended up hard-coding the report descriptor,
which is distasteful.

What will be the good approach to reconstruct the HID report descriptor?

I spent some time with this, and I don’t think it is possible. There’s just not enough information
in the “parsed descriptors” to do that.

MAYBE (I haven’t proven this to myself) you could use the same hub backdoor
that “usbview” uses, and fetch the report descriptor that way.

Turns out it is pretty tough.

The best result so far is to use an reverse engineering approach adopted by Chromium project.
https://github.com/libusb/hidapi/pull/306

Not really an expert, but assuming an Windows API would allow you to read the HID descriptor, that descriptor could be different from the original native device HID descriptor.

Any filter driver can modify the HID descriptor of a device. IMHO it’s best to write your own LowerLevel filter driver and capture the native HID descriptor. Of course you have to make sure that you are the lowest (or better only) LowerFilter driver in the stack otherwise you can’t be sure it has already been modified.

Hope this helps.

If USBView can do it from usermode then I’m obviously missing something here?

USBView doesn’t fetch the report descriptor.

USBView is an interesting beast. It doesn’t go through the normal device paths. Instead, it enumerates all of the USB hubs in the system, and talks directly to the hubs. There is an interesting set of backdoor hub requests to manipulate the hub’s ports, essentially bypassing the device. It can fetch descriptors, but unfortunately it can only fetch device-level descriptors (bmRequestType=0x80), and the report descriptor is an interface-specific request (bmRequeestType=0x81).

Even if we could fetch it, it’s still not trivial to map an abstract USB device to a hub and physical port number.

@pwilly said:
Not really an expert, but assuming an Windows API would allow you to read the HID descriptor, that descriptor could be different from the original native device HID descriptor.

Yes that is correct.

Any filter driver can modify the HID descriptor of a device. IMHO it’s best to write your own LowerLevel filter driver and capture the native HID descriptor. Of course you have to make sure that you are the lowest (or better only) LowerFilter driver in the stack otherwise you can’t be sure it has already been modified.

The idea here is to do the reconstruction as close as possible to the original HID descriptor without the help of extra driver like the lower filter driver. For HIDAPI’s purpose, actually only report ID, report count and report length are important.

If a different driver is possible, then actually it is possible to get the original HID descriptor, at least for USB HID device, for example, it is possible to use the WinUSB driver to replace the HID driver and then use control transfer to get the HID descriptor for each interface.

Still one question remains, why does Microsoft choose not to provide an API for getting the HID report descriptors?

It’s all about the abstraction. If people could get the report descriptor, they would tie their applications to USB, and they wouldn’t work with Bluetooth HID or whatever the next clever HID connection is. By enforcing the use of the abstraction, you allow the “bottom end” to be replaced by something very different.

In reality, I tend to think abstraction is not the issue here, probably some kind of backward compatibility issues. After all, the “reverse engineering” approach seems to work pretty well for devices with different transport (USB, Bluetooth including BLE, I2C, virtual) based on my tests, and most likely SPI as well (no devices to test).

Ref:

  1. https://github.com/libusb/hidapi/pull/306
  2. Windows HID report reconstructor from Google Chromiums WebHID implementation, which accesses the not official documented struct _HIDP_PREPARSED_DATA directly.
    https://chromium.googlesource.com/chromium/src/+/73fdaaf605bb60caf34d5f30bb84a417688aa528/services/device/hid/hid_preparsed_data.cc

Then if we look at the IOCTLs, actually there is an IOCTL (IOCTL_HID_GET_REPORT_DESCRIPTOR) for this purpose but it is not available to the user mode application. This IOCTL should be transport neutral.
https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/_hid/