Skip to content

Commit

Permalink
libusb: fix HID Report descriptor request (signal11#444)
Browse files Browse the repository at this point in the history
- pass the actual HID Report descriptor size (from HID descriptor);
- interface_num has to be wIndex, and not as part of wValue for LIBUSB_DT_REPORT request;
  • Loading branch information
Youw authored Aug 16, 2022
1 parent 7793462 commit ea38f65
Showing 1 changed file with 71 additions and 7 deletions.
78 changes: 71 additions & 7 deletions libusb/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ struct hid_device_ {
/* The interface number of the HID */
int interface;

uint16_t report_descriptor_size;

/* Endpoint information */
int input_endpoint;
int output_endpoint;
Expand Down Expand Up @@ -565,13 +567,18 @@ int HID_API_EXPORT hid_exit(void)
/**
* Requires an opened device with *claimed interface*.
*/
static void fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num)
static void fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num, uint16_t report_descriptor_size)
{
unsigned char data[4096];
unsigned short page = 0, usage = 0;

/* Get the HID Report Descriptor. */
int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000);
if (report_descriptor_size > 4096)
report_descriptor_size = 4096;

/* Get the HID Report Descriptor.
See USB HID Specificatin, sectin 7.1.1
*/
int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), interface_num, data, report_descriptor_size, 5000);
if (res >= 0) {
/* Parse the usage and usage page
out of the report descriptor. */
Expand All @@ -585,7 +592,7 @@ static void fill_device_info_usage(struct hid_device_info *cur_dev, libusb_devic
}

#ifdef INVASIVE_GET_USAGE
static void invasive_fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num)
static void invasive_fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num, uint16_t report_descriptor_size)
{
int res = 0;

Expand All @@ -604,7 +611,7 @@ static void invasive_fill_device_info_usage(struct hid_device_info *cur_dev, lib

res = libusb_claim_interface(handle, interface_num);
if (res >= 0) {
fill_device_info_usage(cur_dev, handle, interface_num);
fill_device_info_usage(cur_dev, handle, interface_num, report_descriptor_size);

/* Release the interface */
res = libusb_release_interface(handle, interface_num);
Expand Down Expand Up @@ -662,6 +669,59 @@ static struct hid_device_info * create_device_info_for_device(libusb_device *dev
return cur_dev;
}

static uint16_t get_report_descriptor_size_from_interface_descriptors(const struct libusb_interface_descriptor *intf_desc)
{
int i = 0;
int found_hid_report_descriptor = 0;
uint16_t result = 4096;
const unsigned char *extra = intf_desc->extra;
int extra_length = intf_desc->extra_length;

/*
"extra" contains a HID descriptor
See section 6.2.1 of HID 1.1 specification.
*/

while (extra_length >= 2) { /* Descriptor header: bLength/bDescriptorType */
if (extra[1] == LIBUSB_DT_HID) { /* bDescriptorType */
if (extra_length < 6) {
LOG("Broken HID descriptor: not enough data\n");
break;
}
unsigned char bNumDescriptors = extra[5];
if (extra_length < (6 + 3 * bNumDescriptors)) {
LOG("Broken HID descriptor: not enough data for Report metadata\n");
break;
}
for (i = 0; i < bNumDescriptors; i++) {
if (extra[6 + 3 * i] == LIBUSB_DT_REPORT) {
result = (uint16_t)extra[6 + 3 * i + 2] << 8 | extra[6 + 3 * i + 1];
found_hid_report_descriptor = 1;
break;
}
}

if (!found_hid_report_descriptor) {
/* We expect to find exactly 1 HID descriptor (LIBUSB_DT_HID)
which should contain exactly one HID Report Descriptor metadata (LIBUSB_DT_REPORT). */
LOG("Broken HID descriptor: missing Report descriptor\n");
}
break;
}

if (extra[0] == 0) { /* bLength */
LOG("Broken HID Interface descriptors: zero-sized descriptor\n");
break;
}

/* Iterate over to the next Descriptor */
extra_length -= extra[0];
extra += extra[0];
}

return result;
}

struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
{
libusb_device **devs;
Expand Down Expand Up @@ -741,7 +801,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
field in the hid_device_info struct to distinguish
between interfaces. */
if (handle) {
invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber);
uint16_t report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc);

invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber, report_descriptor_size);
}
#endif /* INVASIVE_GET_USAGE */

Expand Down Expand Up @@ -1004,6 +1066,8 @@ static int hidapi_initialize_device(hid_device *dev, int config_number, const st
dev->config_number = config_number;
dev->interface = intf_desc->bInterfaceNumber;

dev->report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc);

dev->input_endpoint = 0;
dev->input_ep_max_packet_size = 0;
dev->output_endpoint = 0;
Expand Down Expand Up @@ -1522,7 +1586,7 @@ HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_devi
// device error already set by create_device_info_for_device, if any

if (dev->device_info) {
fill_device_info_usage(dev->device_info, dev->device_handle, dev->interface);
fill_device_info_usage(dev->device_info, dev->device_handle, dev->interface, dev->report_descriptor_size);
}
}

Expand Down

0 comments on commit ea38f65

Please sign in to comment.