Split usb_report_descriptor into usb_hid_{item,usage}

This is cleaner IMO.

* usb_hid_item deals with parsing the report descriptor.
* usb_hid_usage has tables that define each usage.
This commit is contained in:
David Hoppenbrouwers
2022-09-09 02:42:00 +02:00
parent 7f6eb97569
commit 2175e1ed74
11 changed files with 121 additions and 122 deletions

View File

@ -1,5 +1,6 @@
[workspace] [workspace]
members = [ members = [
"usb_report_descriptor", "usb_hid_item",
"usb_hid_usage",
"usb_request", "usb_request",
] ]

View File

@ -2,5 +2,6 @@
## Crates ## Crates
* [`usb_report_descriptor`](usb_report_descriptor) item parser for HID Report descriptors. * [`usb_hid_item`](usb_hid_item) HID item parser for report descriptors.
* [`usb_hid_usage`](usb_hid_usage) HID usage pages.
* [`usb_request`](usb_request) request formatter & descriptor parser. * [`usb_request`](usb_request) request formatter & descriptor parser.

View File

@ -1,5 +1,5 @@
[package] [package]
name = "usb_report_descriptor" name = "usb_hid_item"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -1,4 +1,4 @@
# Report descriptor item parser # HID report descriptor item parser
## Example ## Example

View File

@ -1,10 +1,6 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_std)]
pub mod usage;
pub use usage::{Usage, UsagePage};
use core::fmt; use core::fmt;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -16,7 +12,7 @@ pub enum Item<'a> {
Feature(MainFlags), Feature(MainFlags),
EndCollection, EndCollection,
UsagePage(UsagePage), UsagePage(u16),
LogicalMin(i32), LogicalMin(i32),
LogicalMax(i32), LogicalMax(i32),
PhysicalMin(i32), PhysicalMin(i32),
@ -29,9 +25,10 @@ pub enum Item<'a> {
Push, Push,
Pop, Pop,
Usage(Usage), Usage16(u16),
UsageMin(u32), Usage32(u16, u16),
UsageMax(u32), UsageMin(u16),
UsageMax(u16),
DesignatorIndex(u32), DesignatorIndex(u32),
DesignatorMin(u32), DesignatorMin(u32),
DesignatorMax(u32), DesignatorMax(u32),
@ -77,7 +74,7 @@ impl<'a> Item<'a> {
const STRING_MAX: u8 = 0x98; const STRING_MAX: u8 = 0x98;
const DELIMITER: u8 = 0xa8; const DELIMITER: u8 = 0xa8;
fn parse(data: &'a [u8], usage_page: u16) -> Result<(Self, &'a [u8]), ParseError> { fn parse(data: &'a [u8]) -> Result<(Self, &'a [u8]), ParseError> {
use ParseError::*; use ParseError::*;
let prefix = *data.get(0).ok_or(Truncated)?; let prefix = *data.get(0).ok_or(Truncated)?;
let (size, tag); let (size, tag);
@ -133,7 +130,7 @@ impl<'a> Item<'a> {
Self::FEATURE => Self::Feature(MainFlags(d32u()?)), Self::FEATURE => Self::Feature(MainFlags(d32u()?)),
Self::END_COLLECTION => d_empty(Self::EndCollection)?, Self::END_COLLECTION => d_empty(Self::EndCollection)?,
Self::USAGE_PAGE => Self::UsagePage(UsagePage::from_raw(d16u()?)), Self::USAGE_PAGE => Self::UsagePage(d16u()?),
Self::LOGI_MIN => Self::LogicalMin(d32i()?), Self::LOGI_MIN => Self::LogicalMin(d32i()?),
Self::LOGI_MAX => Self::LogicalMax(d32i()?), Self::LOGI_MAX => Self::LogicalMax(d32i()?),
Self::PHYS_MIN => Self::PhysicalMin(d32i()?), Self::PHYS_MIN => Self::PhysicalMin(d32i()?),
@ -146,20 +143,18 @@ impl<'a> Item<'a> {
Self::PUSH => Self::Push, Self::PUSH => Self::Push,
Self::POP => Self::Pop, Self::POP => Self::Pop,
Self::USAGE => Self::Usage(match d { Self::USAGE => match d {
&[] => Usage::from_raw(usage_page, 0), &[] => Self::Usage16(u16::from_le_bytes([0, 0])),
&[a] => Usage::from_raw(usage_page, u16::from_le_bytes([a, 0])), &[a] => Self::Usage16(u16::from_le_bytes([a, 0])),
&[a, b] => Usage::from_raw(usage_page, u16::from_le_bytes([a, b])), &[a, b] => Self::Usage16(u16::from_le_bytes([a, b])),
&[a, b, c] => { &[a, b, c] => Self::Usage32(u16::from_le_bytes([c, 0]), u16::from_le_bytes([a, b])),
Usage::from_raw(u16::from_le_bytes([c, 0]), u16::from_le_bytes([a, b]))
}
&[a, b, c, d] => { &[a, b, c, d] => {
Usage::from_raw(u16::from_le_bytes([c, d]), u16::from_le_bytes([a, b])) Self::Usage32(u16::from_le_bytes([c, d]), u16::from_le_bytes([a, b]))
} }
_ => return Err(UnexpectedData), _ => return Err(UnexpectedData),
}), },
Self::USAGE_MIN => Self::UsageMin(d32u()?), Self::USAGE_MIN => Self::UsageMin(d16u()?),
Self::USAGE_MAX => Self::UsageMax(d32u()?), Self::USAGE_MAX => Self::UsageMax(d16u()?),
Self::DESIGNATOR_INDEX => Self::DesignatorIndex(d32u()?), Self::DESIGNATOR_INDEX => Self::DesignatorIndex(d32u()?),
Self::DESIGNATOR_MIN => Self::DesignatorMin(d32u()?), Self::DESIGNATOR_MIN => Self::DesignatorMin(d32u()?),
Self::DESIGNATOR_MAX => Self::DesignatorMax(d32u()?), Self::DESIGNATOR_MAX => Self::DesignatorMax(d32u()?),
@ -250,7 +245,6 @@ pub enum ParseError {
pub struct Parser<'a> { pub struct Parser<'a> {
data: &'a [u8], data: &'a [u8],
usage_page: u16,
} }
impl<'a> Iterator for Parser<'a> { impl<'a> Iterator for Parser<'a> {
@ -259,21 +253,14 @@ impl<'a> Iterator for Parser<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
(!self.data.is_empty()).then(|| { (!self.data.is_empty()).then(|| {
let e; let e;
(e, self.data) = Item::parse(self.data, self.usage_page)?; (e, self.data) = Item::parse(self.data)?;
match &e {
Item::UsagePage(p) => self.usage_page = p.as_raw(),
_ => {}
}
Ok(e) Ok(e)
}) })
} }
} }
pub fn parse(data: &[u8]) -> Parser<'_> { pub fn parse(data: &[u8]) -> Parser<'_> {
Parser { Parser { data }
data,
usage_page: 0,
}
} }
#[cfg(test)] #[cfg(test)]
@ -298,12 +285,12 @@ mod tests {
fn qemu_usb_tablet() { fn qemu_usb_tablet() {
let mut it = parse(QEMU_USB_TABLET); let mut it = parse(QEMU_USB_TABLET);
let it = &mut it; let it = &mut it;
tk(it, Item::UsagePage(UsagePage::GenericDesktop)); tk(it, Item::UsagePage(0x1));
tk(it, Item::Usage(Usage::GenericDesktop(usage::generic_desktop::Usage::Mouse))); tk(it, Item::Usage16(0x2));
tk(it, Item::Collection(Collection::Application)); tk(it, Item::Collection(Collection::Application));
tk(it, Item::Usage(Usage::GenericDesktop(usage::generic_desktop::Usage::Pointer))); tk(it, Item::Usage16(0x1));
tk(it, Item::Collection(Collection::Physical)); tk(it, Item::Collection(Collection::Physical));
tk(it, Item::UsagePage(UsagePage::Button)); tk(it, Item::UsagePage(0x9));
tk(it, Item::UsageMin(1)); tk(it, Item::UsageMin(1));
tk(it, Item::UsageMax(3)); tk(it, Item::UsageMax(3));
tk(it, Item::LogicalMin(0)); tk(it, Item::LogicalMin(0));
@ -314,9 +301,9 @@ mod tests {
tk(it, Item::ReportCount(1)); tk(it, Item::ReportCount(1));
tk(it, Item::ReportSize(5)); tk(it, Item::ReportSize(5));
tk(it, Item::Input(MainFlags(0b1))); // constant tk(it, Item::Input(MainFlags(0b1))); // constant
tk(it, Item::UsagePage(UsagePage::GenericDesktop)); tk(it, Item::UsagePage(1));
tk(it, Item::Usage(Usage::GenericDesktop(usage::generic_desktop::Usage::X))); tk(it, Item::Usage16(0x30));
tk(it, Item::Usage(Usage::GenericDesktop(usage::generic_desktop::Usage::Y))); tk(it, Item::Usage16(0x31));
tk(it, Item::LogicalMin(0)); tk(it, Item::LogicalMin(0));
tk(it, Item::LogicalMax(0x7fff)); tk(it, Item::LogicalMax(0x7fff));
tk(it, Item::PhysicalMin(0)); tk(it, Item::PhysicalMin(0));
@ -324,8 +311,8 @@ mod tests {
tk(it, Item::ReportSize(16)); tk(it, Item::ReportSize(16));
tk(it, Item::ReportCount(2)); tk(it, Item::ReportCount(2));
tk(it, Item::Input(MainFlags(0b010))); // absolute, variable, data tk(it, Item::Input(MainFlags(0b010))); // absolute, variable, data
tk(it, Item::UsagePage(UsagePage::GenericDesktop)); tk(it, Item::UsagePage(1));
tk(it, Item::Usage(Usage::GenericDesktop(usage::generic_desktop::Usage::Wheel))); tk(it, Item::Usage16(0x38));
tk(it, Item::LogicalMin(-0x7f)); tk(it, Item::LogicalMin(-0x7f));
tk(it, Item::LogicalMax(0x7f)); tk(it, Item::LogicalMax(0x7f));
tk(it, Item::PhysicalMin(0)); tk(it, Item::PhysicalMin(0));

8
usb_hid_usage/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "usb_hid_usage"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,17 @@
use core::num::NonZeroU16;
pub const PAGE: u16 = 0x09;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Usage {
NoButton,
Button(NonZeroU16),
}
impl TryFrom<u16> for Usage {
type Error = super::UnknownUsage;
fn try_from(n: u16) -> Result<Self, Self::Error> {
Ok(NonZeroU16::new(n).map_or(Self::NoButton, Self::Button))
}
}

63
usb_hid_usage/src/lib.rs Normal file
View File

@ -0,0 +1,63 @@
//!
//! ## References
//!
//! * <https://www.usb.org/sites/default/files/hut1_3_0.pdf>
#![no_std]
macro_rules! usage {
{ [$page:literal] $($i:literal $v:ident)* } => {
pub const PAGE: u16 = $page;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Usage {
$($v,)*
}
impl TryFrom<u16> for Usage {
type Error = super::UnknownUsage;
fn try_from(n: u16) -> Result<Self, Self::Error> {
Ok(match n {
$($i => Self::$v,)*
_ => return Err(super::UnknownUsage),
})
}
}
};
}
macro_rules! page {
{ $($v:ident $m:ident)* } => {
$(pub mod $m;)*
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum UsagePage {
$($v,)*
}
impl TryFrom<u16> for UsagePage {
type Error = UnknownPage;
fn try_from(n: u16) -> Result<Self, Self::Error> {
Ok(match n {
$($m::PAGE => Self::$v,)*
_ => return Err(UnknownPage),
})
}
}
};
}
page! {
GenericDesktop generic_desktop
Button button
}
#[derive(Debug)]
pub struct UnknownUsage;
#[derive(Debug)]
pub struct UnknownPage;

View File

@ -1,3 +0,0 @@
usage! {
[0x09]
}

View File

@ -1,75 +0,0 @@
//! ## References
//!
//! * <https://www.usb.org/sites/default/files/hut1_3_0.pdf>
macro_rules! usage {
{ [$page:literal] $($i:literal $v:ident)* } => {
pub const PAGE: u16 = $page;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Usage {
$($v,)*
Unknown(u16),
}
pub(crate) fn from_raw(raw: u16) -> Usage {
use Usage::*;
match raw {
$($i => $v,)*
r => Unknown(r),
}
}
};
}
pub mod generic_desktop;
pub mod button;
macro_rules! page {
{ $($v:ident $m:ident)* } => {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum UsagePage {
$($v,)*
Unknown(u16),
}
impl UsagePage {
pub(crate) fn from_raw(raw: u16) -> Self {
match raw {
$($m::PAGE => Self::$v,)*
r => Self::Unknown(r),
}
}
pub(crate) fn as_raw(&self) -> u16 {
match self {
$(Self::$v => $m::PAGE,)*
Self::Unknown(n) => *n,
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Usage {
$($v($m::Usage),)*
Unknown(u16, u16),
}
impl Usage {
pub(crate) fn from_raw(page: u16, id: u16) -> Self {
match page {
$($m::PAGE => Self::$v($m::from_raw(id)),)*
_ => Self::Unknown(page, id),
}
}
}
};
}
page! {
GenericDesktop generic_desktop
Button button
}