Skip to main content

hid_decode/
lib.rs

1//! # HID report descriptor decoding utilities
2//!
3//! This library can perform text or structured decoding of USB HID report descriptors.
4//! For text decoding, use [`decode()`] or [`TextDecoder`].
5//! For structured decoding, use [`decode_items()`] or [`ItemDecoder`].
6//!
7//! The decoders don't understand the meaning of HID report descriptors, so they can't be used
8//! to automatically determine the size and layout of HID reports.
9//!
10//! See [Device Class Definition for Human Interface Devices](https://www.usb.org/document-library/device-class-definition-hid-111)
11//! for a detailed description of HID report descriptors.
12//!
13//! # Example
14//! ```rust
15//! # // This descriptor was generated by an example in the `hid-descriptor` crate sources.
16//! # static GAMEPAD_DESCRIPTOR: &[u8] = include_bytes!("../tests/gamepad.bin");
17//! let mut output = Vec::new();
18//! hid_decode::decode(&mut output, &GAMEPAD_DESCRIPTOR).expect("write error");
19//! let output = String::try_from(output).expect("non-UTF8 output");
20//! assert_eq!(
21//!     output.lines().collect::<Vec<_>>(),
22//!     [
23//!         "Usage Page: GenericDesktop",
24//!         "Usage: Gamepad",
25//!         "Collection: Application",
26//!         "Usage Page: Button",
27//!         "Usage Minimum: 1",
28//!         "Usage Maximum: 8",
29//!         "Logical Minimum: 0",
30//!         "Logical Maximum: 1",
31//!         "Report Size: 1",
32//!         "Report Count: 8",
33//!         "Input: Data Variable Absolute No-Wrap Linear Preferred-State No-Null-Position Bit-Field",
34//!         "EndCollection",
35//!     ]
36//! );
37//! ```
38
39#![warn(clippy::print_stderr, clippy::print_stdout, clippy::dbg_macro)]
40#![warn(clippy::todo)]
41#![warn(missing_docs)]
42
43// Re-export everything from the `hid_types` crate, so that callers
44// only need to track one direct dependency.
45pub use hid_types;
46
47use std::io::Write;
48
49use hid_types::item::Item;
50
51use crate::item::{DecoderContext, decode_one};
52
53#[doc(inline)]
54pub use crate::item::LengthError;
55
56pub mod item;
57
58/// Structured decode of an HID report descriptor.
59///
60/// An `ItemDecoder` is a decoder that returns a sequence of decoded [`Item`]s.
61pub struct ItemDecoder<'a> {
62    iter: std::iter::Copied<std::slice::Iter<'a, u8>>,
63    context: DecoderContext,
64}
65
66impl<'a> ItemDecoder<'a> {
67    /// Create a new [`ItemDecoder`].
68    pub fn new(bytes: &'a [u8]) -> Self {
69        let iter = bytes.iter().copied();
70        Self {
71            iter,
72            context: Default::default(),
73        }
74    }
75
76    /// Don't allow zero-length data on descriptor items with values.
77    pub fn reject_zero_length_values(mut self) -> Self {
78        self.context.reject_zero_length_values = true;
79        self
80    }
81}
82
83impl<'a> Iterator for ItemDecoder<'a> {
84    type Item = Result<Item, LengthError>;
85
86    fn next(&mut self) -> Option<Self::Item> {
87        decode_one(&mut self.iter, &mut self.context)
88            .map(|item_maybe| item_maybe.map(|item| item.item))
89    }
90}
91
92/// Structured decode of an HID report descriptor.
93///
94/// Decode an HID descriptor to a list of [`Item`]s.
95/// This is a shorter way of writing `ItemDecoder::new(bytes).collect()`.
96pub fn decode_items(bytes: &[u8]) -> Result<Vec<Item>, LengthError> {
97    ItemDecoder::new(bytes).collect()
98}
99
100/// Decode an HID report descriptor to text, using the default settings.
101///
102/// This is a shorter way of writing `TextDecoder::new(writer).decode(bytes)`.
103pub fn decode<W>(writer: W, bytes: &[u8]) -> std::io::Result<()>
104where
105    W: Write,
106{
107    TextDecoder::new(writer).decode(bytes)
108}
109
110/// Settings that control how a `TextDecoder` displays its output.
111#[derive(Default, Debug, Clone)]
112struct OutputOptions {
113    /// Display the item tag types, i.e. Main, Global, Local.
114    pub display_tag_type: bool,
115    /// Display the bytes that make up each item.
116    pub display_raw_bytes: bool,
117}
118
119/// Decode an HID report descriptor to plain text.
120///
121#[derive(Clone)]
122pub struct TextDecoder<W> {
123    // FIXME: add a way to abstract over both std::io::Write (files, stdout) and std::fmt::Write (String).
124    writer: W,
125    output_options: OutputOptions,
126    context: DecoderContext,
127}
128
129impl<W> TextDecoder<W>
130where
131    W: Write,
132{
133    /// Create a new [`TextDecoder`], by specifying where the output will be written.
134    ///
135    /// # Example
136    /// ```rust
137    /// # use hid_decode::TextDecoder;
138    /// let mut decoder = TextDecoder::new(std::io::stdout());
139    /// ```
140    pub fn new(writer: W) -> Self {
141        Self {
142            writer,
143            output_options: Default::default(),
144            context: Default::default(),
145        }
146    }
147
148    /// Display the item tag types, i.e. Main, Global, Local.
149    pub fn display_tag_type(mut self) -> Self {
150        self.output_options.display_tag_type = true;
151        self
152    }
153
154    /// Display the raw bytes that make up each item.
155    pub fn display_raw_bytes(mut self) -> Self {
156        self.output_options.display_raw_bytes = true;
157        self
158    }
159
160    /// Don't allow zero-length data on descriptor items with values.
161    pub fn reject_zero_length_values(mut self) -> Self {
162        self.context.reject_zero_length_values = true;
163        self
164    }
165
166    /// Decode HID descriptor bytes as text.
167    ///
168    /// This will consume the `Decoder`. If multiple descriptors will be decoded,
169    /// `clone()` the decoder _before_ calling `decode()`.
170    pub fn decode(mut self, bytes: &[u8]) -> std::io::Result<()> {
171        let mut iter = bytes.iter().copied();
172        loop {
173            match decode_one(&mut iter, &mut self.context) {
174                None => break,
175                Some(Ok(item)) => {
176                    if self.output_options.display_raw_bytes {
177                        let mut hex = String::new();
178                        for byte in item.bytes {
179                            use std::fmt::Write;
180                            write!(hex, "{byte:02x} ").unwrap();
181                        }
182                        write!(self.writer, "{hex:9} ")?;
183                    }
184                    if self.output_options.display_tag_type {
185                        write!(self.writer, "{:9}", item.item.type_note())?;
186                    }
187                    writeln!(self.writer, "{}", item.item)?;
188                }
189                Some(Err(e)) => return Err(std::io::Error::other(e)),
190            }
191        }
192        Ok(())
193    }
194}