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

  1. Prefix the payload with the correct HID++ report id: 0x10 for short (7-byte) or 0x11 for long (20-byte) reports.
  2. Verify the buffer is non-empty and its length matches the report id before writing.
  3. Use the crate's HID++ report constructors instead of hand-built byte arrays.
  4. 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

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


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();
        }
        result

View on GitHub (pinned to e846e6f4b4)