#![warn(clippy::print_stderr, clippy::print_stdout, clippy::dbg_macro)]
#![warn(clippy::todo)]
#![warn(missing_docs)]
use std::io::Write;
use hid_types::encoding::{Size, TagTypeSize, TypeBits};
use hid_types::hid::{CollectionType, InputFlags, IoFlags, OutputFeatureFlags, Unit};
use hid_types::id::tag::{GlobalItem, LocalItem, MainItem};
use hid_types::id::usage::UsagePage;
use hid_types::item::usage::{ExtendedUsage, Usage};
use hid_types::item::{Global, Item, Local, LongItem, Main};
pub fn decode<W, I>(writer: W, bytes: I) -> std::io::Result<()>
where
W: Write,
I: IntoIterator<Item = u8>,
{
decode_with_options(writer, bytes, Verbosity::default())
}
pub fn decode_with_options<W, I>(
mut writer: W,
bytes: I,
verbosity: Verbosity,
) -> std::io::Result<()>
where
W: Write,
I: IntoIterator<Item = u8>,
{
let mut iter = bytes.into_iter();
let mut decoder = DecoderContext::new(verbosity);
loop {
match decoder.decode_one(&mut iter) {
Ok(Some(item)) => {
if decoder.verbosity.display_tag_type {
write!(writer, "{:9}", item.type_note())?;
}
writeln!(writer, "{item}")?;
}
Ok(None) => break,
Err(_e) => return Err(std::io::Error::other("decoding failed")),
}
}
Ok(())
}
#[derive(Clone, Default)]
pub struct Verbosity {
pub display_tag_type: bool,
}
#[derive(Default)]
pub struct DecoderContext {
verbosity: Verbosity,
usage_page: Option<UsagePage>,
}
impl DecoderContext {
fn new(verbosity: Verbosity) -> Self {
Self {
verbosity,
..Default::default()
}
}
pub fn decode_one<Iter>(&mut self, iter: &mut Iter) -> Result<Option<Item>, LengthError>
where
Iter: Iterator<Item = u8>,
{
let Some(tag) = iter.next() else {
return Ok(None);
};
let tag = TagTypeSize::from_bits(tag);
let more = match tag.size() {
Size::Short(size_bytes) => {
let iter_remaining = iter.take(size_bytes);
iter_remaining.collect::<Vec<_>>()
}
Size::Long => {
let (Some(size), Some(tag)) = (iter.next(), iter.next()) else {
return Err(LengthError::Truncated);
};
let iter_payload = iter.take(usize::from(size));
let data = iter_payload.collect::<Vec<_>>();
let item = Item::Long(LongItem { tag, data });
return Ok(Some(item));
}
};
match tag.ty() {
TypeBits::Main => {
let item = MainItem::from(tag.tag());
let decoded = match item {
MainItem::Input => {
let data = slice_to_u32(&more)?;
let flags = InputFlags(IoFlags::from(data));
Main::Input(flags)
}
MainItem::Output => {
let data = slice_to_u32(&more)?;
let flags = OutputFeatureFlags(IoFlags::from(data));
Main::Output(flags)
}
MainItem::Feature => {
let data = slice_to_u32(&more)?;
let flags = OutputFeatureFlags(IoFlags::from(data));
Main::Feature(flags)
}
MainItem::Collection => {
assert_eq!(more.len(), 1);
let coll_type = CollectionType::from_integer(more[0]);
Main::Collection(coll_type)
}
MainItem::EndCollection => {
assert!(more.is_empty());
Main::EndCollection
}
MainItem::Reserved => Main::Reserved(tag.tag()),
};
Ok(Some(Item::Main(decoded)))
}
TypeBits::Global => {
let item = GlobalItem::from(tag.tag());
let decoded = match item {
GlobalItem::UsagePage => {
let page_number = slice_to_u16(&more)?;
let usage_page = UsagePage::from_integer(page_number);
self.usage_page = Some(usage_page);
Global::UsagePage(usage_page)
}
GlobalItem::LogicalMinimum => {
let num = slice_to_i32(&more)?;
Global::LogicalMinimum(num)
}
GlobalItem::LogicalMaximum => {
let num = slice_to_i32(&more)?;
Global::LogicalMaximum(num)
}
GlobalItem::PhysicalMinimum => {
let num = slice_to_i32(&more)?;
Global::PhysicalMinimum(num)
}
GlobalItem::PhysicalMaximum => {
let num = slice_to_i32(&more)?;
Global::PhysicalMaximum(num)
}
GlobalItem::UnitExponent => {
let num = slice_to_i32(&more)?;
Global::UnitExponent(num)
}
GlobalItem::ReportSize => {
let num = slice_to_u32(&more)?;
Global::ReportSize(num)
}
GlobalItem::ReportId => {
let num = slice_to_u32(&more)?;
Global::ReportId(num)
}
GlobalItem::ReportCount => {
let num = slice_to_u32(&more)?;
Global::ReportCount(num)
}
GlobalItem::Unit => {
let num = slice_to_u32(&more)?;
let unit = Unit::from_integer(num);
Global::Unit(unit)
}
GlobalItem::Push => Global::Push,
GlobalItem::Pop => Global::Pop,
GlobalItem::Reserved => Global::Reserved(tag.tag()),
};
Ok(Some(Item::Global(decoded)))
}
TypeBits::Local => {
let item = LocalItem::from(tag.tag());
let decoded = match item {
LocalItem::Usage => {
match slice_to_u16(&more) {
Ok(page_id) => match self.usage_page {
Some(page) => Local::Usage(Usage::new(page, page_id)),
None => Local::Usage(Usage::without_page(page_id)),
},
Err(LengthError::DataTooBig) => {
let value = slice_to_u32(&more)?;
Local::ExtendedUsage(ExtendedUsage::from_u32(value))
}
Err(e) => return Err(e),
}
}
LocalItem::UsageMinimum => {
let num = slice_to_u32(&more)?;
Local::UsageMinimum(num)
}
LocalItem::UsageMaximum => {
let num = slice_to_u32(&more)?;
Local::UsageMaximum(num)
}
LocalItem::DesignatorIndex => {
let num = slice_to_u32(&more)?;
Local::DesignatorIndex(num)
}
LocalItem::DesignatorMinimum => {
let num = slice_to_u32(&more)?;
Local::DesignatorMinimum(num)
}
LocalItem::DesignatorMaximum => {
let num = slice_to_u32(&more)?;
Local::DesignatorMaximum(num)
}
LocalItem::StringIndex => {
let num = slice_to_u32(&more)?;
Local::StringIndex(num)
}
LocalItem::StringMinimum => {
let num = slice_to_u32(&more)?;
Local::StringMinimum(num)
}
LocalItem::StringMaximum => {
let num = slice_to_u32(&more)?;
Local::StringMaximum(num)
}
LocalItem::Delimiter => {
let num = slice_to_u8(&more)?;
let param = match num {
0 => false,
1 => true,
_ => return Err(LengthError::DataTooBig),
};
Local::Delimiter(param)
}
LocalItem::Reserved => Local::Reserved(tag.tag()),
};
Ok(Some(Item::Local(decoded)))
}
TypeBits::Reserved => {
let decoded = Item::Reserved(tag.ty().into_bits());
Ok(Some(decoded))
}
}
}
}
#[derive(Clone, Debug)]
pub enum LengthError {
DataTooBig,
ZeroLength,
Truncated,
}
fn slice_to_u8(slice: &[u8]) -> Result<u8, LengthError> {
match slice.len() {
0 => Err(LengthError::ZeroLength),
1 => Ok(slice[0]),
_ => Err(LengthError::DataTooBig),
}
}
fn slice_to_u16(slice: &[u8]) -> Result<u16, LengthError> {
match slice.len() {
0 => Err(LengthError::ZeroLength),
1 => Ok(slice[0] as u16),
2 => {
let ar: &[u8; 2] = slice.as_array().unwrap();
Ok(u16::from_le_bytes(*ar))
}
_ => Err(LengthError::DataTooBig),
}
}
fn slice_to_u32(slice: &[u8]) -> Result<u32, LengthError> {
match slice.len() {
0 => Err(LengthError::ZeroLength),
1 => Ok(slice[0] as u32),
2 => {
let ar: &[u8; 2] = slice.as_array().unwrap();
let value = u16::from_le_bytes(*ar);
Ok(value as u32)
}
3 => Err(LengthError::Truncated),
4 => {
let ar: &[u8; 4] = slice.as_array().unwrap();
Ok(u32::from_le_bytes(*ar))
}
_ => Err(LengthError::DataTooBig),
}
}
fn slice_to_i32(slice: &[u8]) -> Result<i32, LengthError> {
match slice.len() {
0 => Err(LengthError::ZeroLength),
1 => Ok(slice[0] as i8 as i32),
2 => {
let ar: &[u8; 2] = slice.as_array().unwrap();
let value = i16::from_le_bytes(*ar);
Ok(value as i32)
}
3 => Err(LengthError::Truncated),
4 => {
let ar: &[u8; 4] = slice.as_array().unwrap();
Ok(i32::from_le_bytes(*ar))
}
_ => Err(LengthError::DataTooBig),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_integer_decoding() {
assert_eq!(slice_to_u8(&[0xFF]).unwrap(), 0xFF);
slice_to_u8(&[]).unwrap_err();
slice_to_u8(&[0xFF, 0xFF]).unwrap_err();
assert_eq!(slice_to_u16(&[0xFF]).unwrap(), 0xFF);
assert_eq!(slice_to_u16(&[0xFF, 0xFF]).unwrap(), 0xFFFF);
assert_eq!(slice_to_u16(&[1, 2]).unwrap(), 0x0201);
slice_to_u16(&[]).unwrap_err();
slice_to_u16(&[0xFF; 3]).unwrap_err();
slice_to_u16(&[0xFF; 4]).unwrap_err();
assert_eq!(slice_to_u32(&[0xFF]).unwrap(), 0xFF);
assert_eq!(slice_to_u32(&[0xFF, 0xFF]).unwrap(), 0xFFFF);
assert_eq!(slice_to_u32(&[1, 2]).unwrap(), 0x0201);
assert_eq!(slice_to_u32(&[0xFF; 4]).unwrap(), 0xFFFFFFFF);
assert_eq!(slice_to_u32(&[1, 2, 3, 4]).unwrap(), 0x04030201);
slice_to_u32(&[]).unwrap_err();
slice_to_u32(&[0xFF; 3]).unwrap_err();
slice_to_u32(&[0xFF; 5]).unwrap_err();
assert_eq!(slice_to_i32(&[0]).unwrap(), 0);
assert_eq!(slice_to_i32(&[0, 0]).unwrap(), 0);
assert_eq!(slice_to_i32(&[1, 2]).unwrap(), 0x0201);
assert_eq!(slice_to_i32(&[0; 4]).unwrap(), 0);
assert_eq!(slice_to_i32(&[1, 2, 3, 4]).unwrap(), 0x04030201);
assert_eq!(slice_to_i32(&[0xFF]).unwrap(), -1);
assert_eq!(slice_to_i32(&[0xFF, 0xFF]).unwrap(), -1);
assert_eq!(slice_to_i32(&[0xFF; 4]).unwrap(), -1);
slice_to_i32(&[]).unwrap_err();
slice_to_i32(&[0xFF; 3]).unwrap_err();
slice_to_i32(&[0xFF; 5]).unwrap_err();
}
}