hid-decode 0.2.0

HID report descriptor decoding utilities
Documentation
//! # HID report descriptor decoding utilities
//!
//! This library can perform text or structured decoding of USB HID report descriptors.
//! For text decoding, use [`decode()`] or [`TextDecoder`].
//! For structured decoding, use [`decode_items()`] or [`ItemDecoder`].
//!
//! The decoders don't understand the meaning of HID report descriptors, so they can't be used
//! to automatically determine the size and layout of HID reports.
//!
//! See [Device Class Definition for Human Interface Devices](https://www.usb.org/document-library/device-class-definition-hid-111)
//! for a detailed description of HID report descriptors.
//!
//! # Example
//! ```rust
//! # // This descriptor was generated by an example in the `hid-descriptor` crate sources.
//! # static GAMEPAD_DESCRIPTOR: &[u8] = include_bytes!("../tests/gamepad.bin");
//! let mut output = Vec::new();
//! hid_decode::decode(&mut output, &GAMEPAD_DESCRIPTOR).expect("write error");
//! let output = String::try_from(output).expect("non-UTF8 output");
//! assert_eq!(
//!     output.lines().collect::<Vec<_>>(),
//!     [
//!         "Usage Page: GenericDesktop",
//!         "Usage: Gamepad",
//!         "Collection: Application",
//!         "Usage Page: Button",
//!         "Usage Minimum: 1",
//!         "Usage Maximum: 8",
//!         "Logical Minimum: 0",
//!         "Logical Maximum: 1",
//!         "Report Size: 1",
//!         "Report Count: 8",
//!         "Input: Data Variable Absolute No-Wrap Linear Preferred-State No-Null-Position Bit-Field",
//!         "EndCollection",
//!     ]
//! );
//! ```

#![warn(clippy::print_stderr, clippy::print_stdout, clippy::dbg_macro)]
#![warn(clippy::todo)]
#![warn(missing_docs)]

use std::io::Write;

use hid_types::item::Item;

use crate::item::{DecoderContext, decode_one};

pub use crate::item::LengthError;

pub mod item;

/// Structured decode of an HID report descriptor.
///
/// An `ItemDecoder` is a decoder that returns a sequence of decoded [`Item`]s.
pub struct ItemDecoder<'a> {
    iter: std::iter::Copied<std::slice::Iter<'a, u8>>,
    context: DecoderContext,
}

impl<'a> ItemDecoder<'a> {
    /// Create a new [`ItemDecoder`].
    pub fn new(bytes: &'a [u8]) -> Self {
        let iter = bytes.iter().copied();
        Self {
            iter,
            context: Default::default(),
        }
    }
}

impl<'a> Iterator for ItemDecoder<'a> {
    type Item = Result<Item, LengthError>;

    fn next(&mut self) -> Option<Self::Item> {
        decode_one(&mut self.iter, &mut self.context)
            .map(|item_maybe| item_maybe.map(|item| item.item))
    }
}

/// Structured decode of an HID report descriptor.
///
/// Decode an HID descriptor to a list of [`Item`]s.
/// This is a shorter way of writing `ItemDecoder::new(bytes).collect()`.
pub fn decode_items(bytes: &[u8]) -> Result<Vec<Item>, LengthError> {
    ItemDecoder::new(bytes).collect()
}

/// Decode an HID report descriptor to text, using the default settings.
///
/// This is a shorter way of writing `TextDecoder::new(writer).decode(bytes)`.
pub fn decode<W>(writer: W, bytes: &[u8]) -> std::io::Result<()>
where
    W: Write,
{
    TextDecoder::new(writer).decode(bytes)
}

/// Settings that control how a `TextDecoder` displays its output.
#[derive(Default, Debug, Clone)]
struct OutputOptions {
    /// Display the item tag types, i.e. Main, Global, Local.
    pub display_tag_type: bool,
    /// Display the bytes that make up each item.
    pub display_raw_bytes: bool,
}

/// Decode an HID report descriptor to plain text.
///
#[derive(Clone)]
pub struct TextDecoder<W> {
    // FIXME: add a way to abstract over both std::io::Write (files, stdout) and std::fmt::Write (String).
    writer: W,
    output_options: OutputOptions,
    context: DecoderContext,
}

impl<W> TextDecoder<W>
where
    W: Write,
{
    /// Create a new [`TextDecoder`], by specifying where the output will be written.
    ///
    /// # Example
    /// ```rust
    /// # use hid_decode::TextDecoder;
    /// let mut decoder = TextDecoder::new(std::io::stdout());
    /// ```
    pub fn new(writer: W) -> Self {
        Self {
            writer,
            output_options: Default::default(),
            context: Default::default(),
        }
    }

    /// Display the item tag types, i.e. Main, Global, Local.
    pub fn display_tag_type(mut self) -> Self {
        self.output_options.display_tag_type = true;
        self
    }

    /// Display the raw bytes that make up each item.
    pub fn display_raw_bytes(mut self) -> Self {
        self.output_options.display_raw_bytes = true;
        self
    }

    /// Decode HID descriptor bytes as text.
    ///
    /// This will consume the `Decoder`. If multiple descriptors will be decoded,
    /// `clone()` the decoder _before_ calling `decode()`.
    pub fn decode(mut self, bytes: &[u8]) -> std::io::Result<()> {
        let mut iter = bytes.iter().copied();
        loop {
            match decode_one(&mut iter, &mut self.context) {
                None => break,
                Some(Ok(item)) => {
                    if self.output_options.display_raw_bytes {
                        let mut hex = String::new();
                        for byte in item.bytes {
                            use std::fmt::Write;
                            write!(hex, "{byte:02x} ").unwrap();
                        }
                        write!(self.writer, "{hex:9} ")?;
                    }
                    if self.output_options.display_tag_type {
                        write!(self.writer, "{:9}", item.item.type_note())?;
                    }
                    writeln!(self.writer, "{}", item.item)?;
                }
                Some(Err(_e)) => return Err(std::io::Error::other("decoding failed")),
            }
        }
        Ok(())
    }
}