use crate::error::{HidError, HidResult};
macro_rules! item_tags {
(
$(#[$enum_meta:meta])*
pub enum $name:ident {
$( $(#[$variant_meta:meta])* $variant:ident = $code:literal, )*
}
) => {
$(#[$enum_meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum $name {
$( $(#[$variant_meta])* $variant, )*
}
impl $name {
pub const fn code(self) -> u8 {
match self {
$( $name::$variant => $code, )*
}
}
pub const fn from_code(code: u8) -> Option<Self> {
match code {
$( $code => Some($name::$variant), )*
_ => None,
}
}
}
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ItemType {
Main,
Global,
Local,
Reserved,
}
impl ItemType {
pub const fn code(self) -> u8 {
match self {
ItemType::Main => 0,
ItemType::Global => 1,
ItemType::Local => 2,
ItemType::Reserved => 3,
}
}
pub const fn from_code(code: u8) -> Self {
match code & 0x3 {
0 => ItemType::Main,
1 => ItemType::Global,
2 => ItemType::Local,
_ => ItemType::Reserved,
}
}
}
item_tags! {
pub enum MainTag {
Input = 0b1000,
Output = 0b1001,
Feature = 0b1011,
Collection = 0b1010,
EndCollection = 0b1100,
}
}
item_tags! {
pub enum GlobalTag {
UsagePage = 0x0,
LogicalMinimum = 0x1,
LogicalMaximum = 0x2,
PhysicalMinimum = 0x3,
PhysicalMaximum = 0x4,
UnitExponent = 0x5,
Unit = 0x6,
ReportSize = 0x7,
ReportId = 0x8,
ReportCount = 0x9,
Push = 0xA,
Pop = 0xB,
}
}
item_tags! {
pub enum LocalTag {
Usage = 0x0,
UsageMinimum = 0x1,
UsageMaximum = 0x2,
DesignatorIndex = 0x3,
DesignatorMinimum = 0x4,
DesignatorMaximum = 0x5,
StringIndex = 0x7,
StringMinimum = 0x8,
StringMaximum = 0x9,
Delimiter = 0xA,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RawItem<'a> {
pub item_type: ItemType,
pub tag: u8,
pub data: &'a [u8],
pub long: bool,
}
impl<'a> RawItem<'a> {
pub fn unsigned(&self) -> u32 {
let mut v: u32 = 0;
for (i, b) in self.data.iter().take(4).enumerate() {
v |= (*b as u32) << (8 * i);
}
v
}
pub fn signed(&self) -> i32 {
match self.data.len() {
0 => 0,
1 => self.data[0] as i8 as i32,
2 => i16::from_le_bytes([self.data[0], self.data[1]]) as i32,
_ => self.unsigned() as i32,
}
}
pub fn main_tag(&self) -> Option<MainTag> {
self.typed_tag(ItemType::Main, MainTag::from_code)
}
pub fn global_tag(&self) -> Option<GlobalTag> {
self.typed_tag(ItemType::Global, GlobalTag::from_code)
}
pub fn local_tag(&self) -> Option<LocalTag> {
self.typed_tag(ItemType::Local, LocalTag::from_code)
}
fn typed_tag<T>(&self, want: ItemType, decode: fn(u8) -> Option<T>) -> Option<T> {
if self.long || self.item_type != want {
return None;
}
decode(self.tag)
}
}
#[derive(Debug, Clone)]
pub struct Items<'a> {
rest: &'a [u8],
failed: bool,
}
impl<'a> Items<'a> {
pub fn new(descriptor: &'a [u8]) -> Self {
Items {
rest: descriptor,
failed: false,
}
}
}
impl<'a> Iterator for Items<'a> {
type Item = HidResult<RawItem<'a>>;
fn next(&mut self) -> Option<Self::Item> {
if self.failed || self.rest.is_empty() {
return None;
}
let prefix = self.rest[0];
if prefix == 0xFE {
if self.rest.len() < 3 {
self.failed = true;
return Some(Err(HidError::Parse {
message: "truncated long item header".into(),
}));
}
let size = self.rest[1] as usize;
let tag = self.rest[2];
if self.rest.len() < 3 + size {
self.failed = true;
return Some(Err(HidError::Parse {
message: "truncated long item payload".into(),
}));
}
let data = &self.rest[3..3 + size];
self.rest = &self.rest[3 + size..];
return Some(Ok(RawItem {
item_type: ItemType::Reserved,
tag,
data,
long: true,
}));
}
let size = match prefix & 0x03 {
3 => 4,
n => n as usize,
};
let item_type = ItemType::from_code(prefix >> 2);
let tag = prefix >> 4;
if self.rest.len() < 1 + size {
self.failed = true;
return Some(Err(HidError::Parse {
message: "truncated short item payload".into(),
}));
}
let data = &self.rest[1..1 + size];
self.rest = &self.rest[1 + size..];
Some(Ok(RawItem {
item_type,
tag,
data,
long: false,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lexes_short_items() {
let bytes = [0x05, 0x01, 0x09, 0x02, 0xA1, 0x01];
let items: Vec<_> = Items::new(&bytes).collect::<Result<_, _>>().unwrap();
assert_eq!(items.len(), 3);
assert_eq!(items[0].global_tag(), Some(GlobalTag::UsagePage));
assert_eq!(items[0].unsigned(), 0x01);
assert_eq!(items[1].local_tag(), Some(LocalTag::Usage));
assert_eq!(items[2].main_tag(), Some(MainTag::Collection));
}
#[test]
fn sign_extends() {
let bytes = [0x15, 0x81];
let item = Items::new(&bytes).next().unwrap().unwrap();
assert_eq!(item.signed(), -127);
assert_eq!(item.unsigned(), 0x81);
}
#[test]
fn four_byte_size_code() {
let bytes = [0x17, 0x01, 0x00, 0x00, 0x80];
let item = Items::new(&bytes).next().unwrap().unwrap();
assert_eq!(item.data.len(), 4);
assert_eq!(item.signed(), -2147483647);
}
#[test]
fn tag_codes_round_trip() {
for tag in [
MainTag::Input,
MainTag::Output,
MainTag::Feature,
MainTag::Collection,
MainTag::EndCollection,
] {
assert_eq!(MainTag::from_code(tag.code()), Some(tag));
}
for tag in [
GlobalTag::UsagePage,
GlobalTag::LogicalMinimum,
GlobalTag::LogicalMaximum,
GlobalTag::PhysicalMinimum,
GlobalTag::PhysicalMaximum,
GlobalTag::UnitExponent,
GlobalTag::Unit,
GlobalTag::ReportSize,
GlobalTag::ReportId,
GlobalTag::ReportCount,
GlobalTag::Push,
GlobalTag::Pop,
] {
assert_eq!(GlobalTag::from_code(tag.code()), Some(tag));
}
for tag in [
LocalTag::Usage,
LocalTag::UsageMinimum,
LocalTag::UsageMaximum,
LocalTag::DesignatorIndex,
LocalTag::DesignatorMinimum,
LocalTag::DesignatorMaximum,
LocalTag::StringIndex,
LocalTag::StringMinimum,
LocalTag::StringMaximum,
LocalTag::Delimiter,
] {
assert_eq!(LocalTag::from_code(tag.code()), Some(tag));
}
assert_eq!(LocalTag::from_code(0x6), None);
assert_eq!(GlobalTag::from_code(0xC), None);
assert_eq!(MainTag::from_code(0b1101), None);
for ty in [
ItemType::Main,
ItemType::Global,
ItemType::Local,
ItemType::Reserved,
] {
assert_eq!(ItemType::from_code(ty.code()), ty);
}
}
#[test]
fn truncated_input_errors() {
let bytes = [0x05]; let mut it = Items::new(&bytes);
assert!(it.next().unwrap().is_err());
assert!(it.next().is_none());
}
}