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:
@ -1,5 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"usb_report_descriptor",
|
"usb_hid_item",
|
||||||
|
"usb_hid_usage",
|
||||||
"usb_request",
|
"usb_request",
|
||||||
]
|
]
|
||||||
|
@ -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.
|
||||||
|
@ -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"
|
@ -1,4 +1,4 @@
|
|||||||
# Report descriptor item parser
|
# HID report descriptor item parser
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
@ -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
8
usb_hid_usage/Cargo.toml
Normal 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]
|
17
usb_hid_usage/src/button.rs
Normal file
17
usb_hid_usage/src/button.rs
Normal 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
63
usb_hid_usage/src/lib.rs
Normal 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;
|
@ -1,3 +0,0 @@
|
|||||||
usage! {
|
|
||||||
[0x09]
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
Reference in New Issue
Block a user