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
43use std::io::Write;
44
45use hid_types::item::Item;
46
47use crate::item::{DecoderContext, decode_one};
48
49pub use crate::item::LengthError;
50
51pub mod item;
52
53/// Structured decode of an HID report descriptor.
54///
55/// An `ItemDecoder` is a decoder that returns a sequence of decoded [`Item`]s.
56pub struct ItemDecoder<'a> {
57 iter: std::iter::Copied<std::slice::Iter<'a, u8>>,
58 context: DecoderContext,
59}
60
61impl<'a> ItemDecoder<'a> {
62 /// Create a new [`ItemDecoder`].
63 pub fn new(bytes: &'a [u8]) -> Self {
64 let iter = bytes.iter().copied();
65 Self {
66 iter,
67 context: Default::default(),
68 }
69 }
70}
71
72impl<'a> Iterator for ItemDecoder<'a> {
73 type Item = Result<Item, LengthError>;
74
75 fn next(&mut self) -> Option<Self::Item> {
76 decode_one(&mut self.iter, &mut self.context)
77 .map(|item_maybe| item_maybe.map(|item| item.item))
78 }
79}
80
81/// Structured decode of an HID report descriptor.
82///
83/// Decode an HID descriptor to a list of [`Item`]s.
84/// This is a shorter way of writing `ItemDecoder::new(bytes).collect()`.
85pub fn decode_items(bytes: &[u8]) -> Result<Vec<Item>, LengthError> {
86 ItemDecoder::new(bytes).collect()
87}
88
89/// Decode an HID report descriptor to text, using the default settings.
90///
91/// This is a shorter way of writing `TextDecoder::new(writer).decode(bytes)`.
92pub fn decode<W>(writer: W, bytes: &[u8]) -> std::io::Result<()>
93where
94 W: Write,
95{
96 TextDecoder::new(writer).decode(bytes)
97}
98
99/// Settings that control how a `TextDecoder` displays its output.
100#[derive(Default, Debug, Clone)]
101struct OutputOptions {
102 /// Display the item tag types, i.e. Main, Global, Local.
103 pub display_tag_type: bool,
104 /// Display the bytes that make up each item.
105 pub display_raw_bytes: bool,
106}
107
108/// Decode an HID report descriptor to plain text.
109///
110#[derive(Clone)]
111pub struct TextDecoder<W> {
112 // FIXME: add a way to abstract over both std::io::Write (files, stdout) and std::fmt::Write (String).
113 writer: W,
114 output_options: OutputOptions,
115 context: DecoderContext,
116}
117
118impl<W> TextDecoder<W>
119where
120 W: Write,
121{
122 /// Create a new [`TextDecoder`], by specifying where the output will be written.
123 ///
124 /// # Example
125 /// ```rust
126 /// # use hid_decode::TextDecoder;
127 /// let mut decoder = TextDecoder::new(std::io::stdout());
128 /// ```
129 pub fn new(writer: W) -> Self {
130 Self {
131 writer,
132 output_options: Default::default(),
133 context: Default::default(),
134 }
135 }
136
137 /// Display the item tag types, i.e. Main, Global, Local.
138 pub fn display_tag_type(mut self) -> Self {
139 self.output_options.display_tag_type = true;
140 self
141 }
142
143 /// Display the raw bytes that make up each item.
144 pub fn display_raw_bytes(mut self) -> Self {
145 self.output_options.display_raw_bytes = true;
146 self
147 }
148
149 /// Decode HID descriptor bytes as text.
150 ///
151 /// This will consume the `Decoder`. If multiple descriptors will be decoded,
152 /// `clone()` the decoder _before_ calling `decode()`.
153 pub fn decode(mut self, bytes: &[u8]) -> std::io::Result<()> {
154 let mut iter = bytes.iter().copied();
155 loop {
156 match decode_one(&mut iter, &mut self.context) {
157 None => break,
158 Some(Ok(item)) => {
159 if self.output_options.display_raw_bytes {
160 let mut hex = String::new();
161 for byte in item.bytes {
162 use std::fmt::Write;
163 write!(hex, "{byte:02x} ").unwrap();
164 }
165 write!(self.writer, "{hex:9} ")?;
166 }
167 if self.output_options.display_tag_type {
168 write!(self.writer, "{:9}", item.item.type_note())?;
169 }
170 writeln!(self.writer, "{}", item.item)?;
171 }
172 Some(Err(_e)) => return Err(std::io::Error::other("decoding failed")),
173 }
174 }
175 Ok(())
176 }
177}