AprilNEA/OpenLogi · error · io::Error
unsupported HID++ report id
Error message
unsupported HID++ report id {:#04x} What it means
On Windows, the composite HID channel routes each outgoing report by its first byte (the report id) to either the short or the long HID++ endpoint via `endpoint_for_report_id`. If the report id is unknown to that mapping, no endpoint is selected and an `io::ErrorKind::Unsupported` error carrying this message is returned from `write_report`. It is a programming/protocol error rather than a runtime device condition.
Solutions
- Prefix the payload with the correct HID++ report id: 0x10 for short (7-byte) or 0x11 for long (20-byte) reports.
- Verify the buffer is non-empty and its length matches the report id before writing.
- Use the crate's HID++ report constructors instead of hand-built byte arrays.
- Log the full report (`{:#04x}` shows the offending id) and compare against `endpoint_for_report_id`'s supported set.
Example fix
// before — raw payload without a HID++ report id let mut report = vec![0x00; 7]; report[1..].copy_from_slice(&payload); backend.write_report(&report).await?; // error: unsupported HID++ report id 0x00 // after — proper short HID++ report let mut report = vec![0x10; 7]; report[1..].copy_from_slice(&payload); backend.write_report(&report).await?;
Defensive patterns
Strategy: validation
Validate before calling
const SHORT_REPORT: u8 = 0x10;
const LONG_REPORT: u8 = 0x11;
fn assert_hidpp_report(report: &[u8]) -> std::io::Result<()> {
match report.first() {
Some(&SHORT_REPORT) if report.len() == 7 => Ok(()),
Some(&LONG_REPORT) if report.len() == 20 => Ok(()),
other => Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
format!("bad HID++ report id/length: {:?} len={}", other, report.len()),
)),
}
} Type guard
fn is_short_hidpp(report: &[u8]) -> bool { report.first() == Some(&0x10) && report.len() == 7 }
fn is_long_hidpp(report: &[u8]) -> bool { report.first() == Some(&0x11) && report.len() == 20 } Prevention
- Always build reports via the hidpp crate's constructors, never raw byte arrays.
- Validate report id (0x10/0x11) and length before every write in debug builds.
- Guard against empty buffers — an empty first byte renders as 0x00 in this error.
When it happens
Trigger: Calling `write_report` with a buffer whose first byte is not a recognized HID++ report id (neither the short 0x10 nor long 0x11 report, or any id missing from `endpoint_for_report_id`); also triggered when the buffer is empty (`src.first()` is `None`, falling into the `_ => None` arm with report id shown as 0x00).
Common situations: Constructing a raw HID++ packet by hand with a wrong report id; passing a vendor/custom feature report to the HID++ channel; a protocol-layer regression sending zero-length buffers; copying report formats from non-HID++ (consumer-page) devices.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- SmartShift sensitivity write not applied: requested
- SmartShift mode changed unexpectedly: was
- SmartShift toggle had no effect: still
- wheel resolution write not applied: requested
- wheel reporting target is not native after write
AI-assisted analysis of AprilNEA/OpenLogi@e846e6f4b4 (2026-09-13).
Data as JSON: /api/errors/58bf45ea0481dfad.
Report an issue: GitHub.
Appendix: source
Thrown at crates/openlogi-hid/src/transport/windows.rs:265
fn vendor_id(&self) -> u16 {
self.info.vendor_id
}
fn product_id(&self) -> u16 {
self.info.product_id
}
async fn write_report(&self, src: &[u8]) -> Result<usize, Box<dyn Error + Send + Sync>> {
if !self.device_io.allows_io() {
return Err(super::device_io_error());
}
let endpoint = match src.first().copied().and_then(endpoint_for_report_id) {
Some(ReportEndpoint::Short) => self.short.as_ref(),
Some(ReportEndpoint::Long) => Some(&self.long),
_ => None,
}
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::Unsupported,
format!(
"unsupported HID++ report id {:#04x}",
src.first().copied().unwrap_or_default()
),
)
})?;
if !self.device_io.allows_io() {
return Err(super::device_io_error());
}
let result = endpoint.write_report(src).await;
if let Err(e) = &result
&& is_permanent_disconnect(e.as_ref())
{
self.mark_disconnected();
}
resultView on GitHub (pinned to e846e6f4b4)