From 2884690d6a5ef2dbac3029748b502615f9cf61e2 Mon Sep 17 00:00:00 2001 From: auxcorelabs Date: Fri, 17 Apr 2026 06:43:43 +0000 Subject: [PATCH 1/2] Add hid_set_input_report_buffer_size() API Exposes a new public function to resize the per-device input report queue. High-throughput HID devices (medical telemetry, high-poll-rate gaming peripherals, data acquisition hardware) emit bursts of input reports that exceed the current hardcoded queue sizes (30 on macOS and the libusb backend, 64 on Windows). When a burst exceeds the queue, reports are silently dropped with no error indication to the caller. This adds: - hid_set_input_report_buffer_size(dev, size) in hidapi.h - HID_API_MAX_INPUT_REPORT_BUFFER_SIZE (1024) cap to prevent unbounded memory growth Per-backend behavior: - macOS: resizes the userspace IOHIDQueue-fed report queue - Linux libusb: resizes the userspace report queue - Windows: wraps HidD_SetNumInputBuffers (parity with existing behavior) - Linux hidraw: no-op (kernel manages buffering) - NetBSD: no-op (kernel manages buffering) Defaults are unchanged, so existing callers are unaffected. Values outside [1, HID_API_MAX_INPUT_REPORT_BUFFER_SIZE] are rejected with -1. Thread-safe on macOS (dev->mutex) and libusb (dev->thread_state), matching the locks used by the respective report callbacks. Addresses the same need as closed issue #154 (HidD_SetNumInputBuffers exposure) and complements #725 (callback-based input API). --- hidapi/hidapi.h | 38 ++++++++++++++++++++++++++++++++++++++ libusb/hid.c | 23 ++++++++++++++++++++++- linux/hid.c | 18 ++++++++++++++++++ mac/hid.c | 21 ++++++++++++++++++++- netbsd/hid.c | 18 ++++++++++++++++++ windows/hid.c | 18 ++++++++++++++++++ 6 files changed, 134 insertions(+), 2 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index cbc3107dd..c3c2502de 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -423,6 +423,44 @@ extern "C" { */ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); + /** @brief Upper bound for hid_set_input_report_buffer_size(). + + Values passed above this limit are rejected by + hid_set_input_report_buffer_size(). Guards against + memory-exhaustion via unbounded input report queue growth. + */ + #define HID_API_MAX_INPUT_REPORT_BUFFER_SIZE 1024 + + /** @brief Set the size of the input report buffer/queue. + + Some HID devices emit input reports in bursts at rates + that exceed the default internal queue capacity, causing + silent report drops on macOS and the libusb Linux backend. + This function allows callers to resize the per-device + input report buffer. + + Defaults per backend: + - macOS: 30 reports + - Linux libusb: 30 reports + - Windows: 64 reports (via HidD_SetNumInputBuffers) + - Linux hidraw: kernel-managed, no userspace queue + - NetBSD: kernel-managed, no userspace queue + + Call after hid_open() and before the first hid_read() + to avoid losing reports buffered at open time. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param buffer_size The desired buffer size in reports. + Must be in range [1, HID_API_MAX_INPUT_REPORT_BUFFER_SIZE]. + + @returns + 0 on success, -1 on failure (invalid parameters or + backend-specific error). Call hid_error(dev) for + details where supported. + */ + int HID_API_EXPORT HID_API_CALL hid_set_input_report_buffer_size(hid_device *dev, int buffer_size); + /** @brief Send a Feature report to the device. Feature reports are sent over the Control endpoint as a diff --git a/libusb/hid.c b/libusb/hid.c index d2ceef5d3..8fc097a3a 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -110,6 +110,9 @@ struct hid_device_ { /* Whether blocking reads are used */ int blocking; /* boolean */ + /* Maximum number of input reports to queue before dropping oldest. */ + int input_report_buffer_size; + /* Read thread objects */ hidapi_thread_state thread_state; int shutdown_thread; @@ -143,6 +146,7 @@ static hid_device *new_hid_device(void) return NULL; dev->blocking = 1; + dev->input_report_buffer_size = 30; hidapi_thread_state_init(&dev->thread_state); @@ -985,7 +989,7 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer) /* Pop one off if we've reached 30 in the queue. This way we don't grow forever if the user never reads anything from the device. */ - if (num_queued > 30) { + if (num_queued > dev->input_report_buffer_size) { return_data(dev, NULL, 0); } } @@ -1574,6 +1578,23 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) } + +int HID_API_EXPORT hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + /* Note: libusb backend currently has no error reporting infrastructure + (hid_error returns a fixed string). This function returns -1 on + invalid arguments but cannot provide a descriptive error message + until the backend gains error registration. */ + if (!dev) + return -1; + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) + return -1; + hidapi_thread_mutex_lock(&dev->thread_state); + dev->input_report_buffer_size = buffer_size; + hidapi_thread_mutex_unlock(&dev->thread_state); + return 0; +} + int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) { dev->blocking = !nonblock; diff --git a/linux/hid.c b/linux/hid.c index a4dc26f4e..880526095 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -1199,6 +1199,24 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) return dev->last_read_error_str; } + +int HID_API_EXPORT hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + if (!dev) { + register_global_error("Device is NULL"); + return -1; + } + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { + register_error_str(&dev->last_error_str, "buffer_size out of range"); + return -1; + } + /* hidraw: kernel manages the input report buffer, no userspace queue + to resize. Accept the call to preserve a consistent cross-platform + API so callers do not need per-backend conditional code. */ + (void)buffer_size; + return 0; +} + int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) { /* Do all non-blocking in userspace using poll(), since it looks diff --git a/mac/hid.c b/mac/hid.c index a91bc1902..823624f96 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -127,6 +127,7 @@ struct hid_device_ { IOOptionBits open_options; int blocking; int disconnected; + int input_report_buffer_size; CFStringRef run_loop_mode; CFRunLoopRef run_loop; CFRunLoopSourceRef source; @@ -156,6 +157,7 @@ static hid_device *new_hid_device(void) dev->open_options = device_open_options; dev->blocking = 1; dev->disconnected = 0; + dev->input_report_buffer_size = 30; dev->run_loop_mode = NULL; dev->run_loop = NULL; dev->source = NULL; @@ -908,7 +910,7 @@ static void hid_report_callback(void *context, IOReturn result, void *sender, /* Pop one off if we've reached 30 in the queue. This way we don't grow forever if the user never reads anything from the device. */ - if (num_queued > 30) { + if (num_queued > dev->input_report_buffer_size) { return_data(dev, NULL, 0); } } @@ -1347,6 +1349,23 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) return dev->last_read_error_str; } + +int HID_API_EXPORT hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + if (!dev) { + register_global_error("Device is NULL"); + return -1; + } + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { + register_error_str(&dev->last_error_str, "buffer_size out of range"); + return -1; + } + pthread_mutex_lock(&dev->mutex); + dev->input_report_buffer_size = buffer_size; + pthread_mutex_unlock(&dev->mutex); + return 0; +} + int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) { /* All Nonblocking operation is handled by the library. */ diff --git a/netbsd/hid.c b/netbsd/hid.c index a9fca67c5..9fabef199 100644 --- a/netbsd/hid.c +++ b/netbsd/hid.c @@ -955,6 +955,24 @@ HID_API_EXPORT const wchar_t* HID_API_CALL hid_read_error(hid_device *dev) return dev->last_read_error_str; } + +int HID_API_EXPORT HID_API_CALL hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + if (!dev) { + register_global_error("Device is NULL"); + return -1; + } + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { + register_error_str(&dev->last_error_str, "buffer_size out of range"); + return -1; + } + /* NetBSD: kernel manages the input report buffer, no userspace queue + to resize. Accept the call to preserve a consistent cross-platform + API so callers do not need per-backend conditional code. */ + (void)buffer_size; + return 0; +} + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) { dev->blocking = !nonblock; diff --git a/windows/hid.c b/windows/hid.c index 1e27f10a4..39dc90da0 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1257,6 +1257,24 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) return dev->last_read_error_str; } + +int HID_API_EXPORT HID_API_CALL hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) +{ + if (!dev) { + register_global_error(L"Device is NULL"); + return -1; + } + if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { + register_string_error(dev, L"buffer_size out of range"); + return -1; + } + if (!HidD_SetNumInputBuffers(dev->device_handle, (ULONG)buffer_size)) { + register_winapi_error(dev, L"HidD_SetNumInputBuffers"); + return -1; + } + return 0; +} + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) { dev->blocking = !nonblock; From f376588a972884487ccb4b99b6cd18b6fe3e62fe Mon Sep 17 00:00:00 2001 From: auxcorelabs Date: Fri, 17 Apr 2026 16:10:11 +0000 Subject: [PATCH 2/2] Address review: remove NULL checks, clarify no-op backends Per maintainer feedback on PR #787: - Remove if (!dev) validation from all 5 backends. hidapi convention is that device functions trust the caller to pass a valid handle; only hid_close is permitted to accept NULL. - Reword the inline comment in linux/hid.c and netbsd/hid.c to lead with "No-op" so the caller-visible behavior is explicit at the implementation site. --- libusb/hid.c | 2 -- linux/hid.c | 9 +++------ mac/hid.c | 4 ---- netbsd/hid.c | 9 +++------ windows/hid.c | 4 ---- 5 files changed, 6 insertions(+), 22 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 8fc097a3a..b42f558e1 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -1585,8 +1585,6 @@ int HID_API_EXPORT hid_set_input_report_buffer_size(hid_device *dev, int buffer_ (hid_error returns a fixed string). This function returns -1 on invalid arguments but cannot provide a descriptive error message until the backend gains error registration. */ - if (!dev) - return -1; if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) return -1; hidapi_thread_mutex_lock(&dev->thread_state); diff --git a/linux/hid.c b/linux/hid.c index 880526095..549247bec 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -1202,16 +1202,13 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) int HID_API_EXPORT hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) { - if (!dev) { - register_global_error("Device is NULL"); - return -1; - } if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { register_error_str(&dev->last_error_str, "buffer_size out of range"); return -1; } - /* hidraw: kernel manages the input report buffer, no userspace queue - to resize. Accept the call to preserve a consistent cross-platform + /* No-op on Linux hidraw and BSD backends: the kernel manages input + report buffering and there is no userspace queue to resize. The + call is accepted (returns 0) to preserve a consistent cross-platform API so callers do not need per-backend conditional code. */ (void)buffer_size; return 0; diff --git a/mac/hid.c b/mac/hid.c index 823624f96..7002a7a3c 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -1352,10 +1352,6 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) int HID_API_EXPORT hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) { - if (!dev) { - register_global_error("Device is NULL"); - return -1; - } if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { register_error_str(&dev->last_error_str, "buffer_size out of range"); return -1; diff --git a/netbsd/hid.c b/netbsd/hid.c index 9fabef199..b970e724c 100644 --- a/netbsd/hid.c +++ b/netbsd/hid.c @@ -958,16 +958,13 @@ HID_API_EXPORT const wchar_t* HID_API_CALL hid_read_error(hid_device *dev) int HID_API_EXPORT HID_API_CALL hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) { - if (!dev) { - register_global_error("Device is NULL"); - return -1; - } if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { register_error_str(&dev->last_error_str, "buffer_size out of range"); return -1; } - /* NetBSD: kernel manages the input report buffer, no userspace queue - to resize. Accept the call to preserve a consistent cross-platform + /* No-op on Linux hidraw and BSD backends: the kernel manages input + report buffering and there is no userspace queue to resize. The + call is accepted (returns 0) to preserve a consistent cross-platform API so callers do not need per-backend conditional code. */ (void)buffer_size; return 0; diff --git a/windows/hid.c b/windows/hid.c index 39dc90da0..c96e2a30a 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1260,10 +1260,6 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) int HID_API_EXPORT HID_API_CALL hid_set_input_report_buffer_size(hid_device *dev, int buffer_size) { - if (!dev) { - register_global_error(L"Device is NULL"); - return -1; - } if (buffer_size <= 0 || buffer_size > HID_API_MAX_INPUT_REPORT_BUFFER_SIZE) { register_string_error(dev, L"buffer_size out of range"); return -1;