use crate::error::{HidError, HidResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ItemType {
Main,
Global,
Local,
Reserved,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MainTag {
Input,
Output,
Feature,
Collection,
EndCollection,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlobalTag {
UsagePage,
LogicalMinimum,
LogicalMaximum,
PhysicalMinimum,
PhysicalMaximum,
UnitExponent,
Unit,
ReportSize,
ReportId,
ReportCount,
Push,
Pop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LocalTag {
Usage,
UsageMinimum,
UsageMaximum,
DesignatorIndex,
DesignatorMinimum,
DesignatorMaximum,
StringIndex,
StringMinimum,
StringMaximum,
Delimiter,
}
#[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> {
if self.long || self.item_type != ItemType::Main {
return None;
}
Some(match self.tag {
0b1000 => MainTag::Input,
0b1001 => MainTag::Output,
0b1011 => MainTag::Feature,
0b1010 => MainTag::Collection,
0b1100 => MainTag::EndCollection,
_ => return None,
})
}
pub fn global_tag(&self) -> Option<GlobalTag> {
if self.long || self.item_type != ItemType::Global {
return None;
}
Some(match self.tag {
0x0 => GlobalTag::UsagePage,
0x1 => GlobalTag::LogicalMinimum,
0x2 => GlobalTag::LogicalMaximum,
0x3 => GlobalTag::PhysicalMinimum,
0x4 => GlobalTag::PhysicalMaximum,
0x5 => GlobalTag::UnitExponent,
0x6 => GlobalTag::Unit,
0x7 => GlobalTag::ReportSize,
0x8 => GlobalTag::ReportId,
0x9 => GlobalTag::ReportCount,
0xA => GlobalTag::Push,
0xB => GlobalTag::Pop,
_ => return None,
})
}
pub fn local_tag(&self) -> Option<LocalTag> {
if self.long || self.item_type != ItemType::Local {
return None;
}
Some(match self.tag {
0x0 => LocalTag::Usage,
0x1 => LocalTag::UsageMinimum,
0x2 => LocalTag::UsageMaximum,
0x3 => LocalTag::DesignatorIndex,
0x4 => LocalTag::DesignatorMinimum,
0x5 => LocalTag::DesignatorMaximum,
0x7 => LocalTag::StringIndex,
0x8 => LocalTag::StringMinimum,
0x9 => LocalTag::StringMaximum,
0xA => LocalTag::Delimiter,
_ => return None,
})
}
}
#[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 = match (prefix >> 2) & 0x03 {
0 => ItemType::Main,
1 => ItemType::Global,
2 => ItemType::Local,
_ => ItemType::Reserved,
};
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 truncated_input_errors() {
let bytes = [0x05]; let mut it = Items::new(&bytes);
assert!(it.next().unwrap().is_err());
assert!(it.next().is_none());
}
}