hid-decode 0.2.0

HID report descriptor decoding utilities
Documentation
//! Decode a single item inside an HID report descriptor.

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};
use tinyvec::TinyVec;

/// Context that can affect the way the decoder interprets subsequent descriptor items.
#[derive(Default, Debug, Clone)]
pub struct DecoderContext {
    /// The current usage page.
    usage_page: Option<UsagePage>,
}

/// A successfully decoded descriptor item.
pub struct DecodedItem {
    /// The decoded descriptor item.
    pub item: Item,
    /// The bytes this item was decoded from.
    pub bytes: TinyVec<[u8; 8]>,
}

impl DecodedItem {
    // FIXME: rename
    fn yes<T, E>(item: Item, iter: Recorder<T>) -> Result<Self, E> {
        Ok(Self {
            item,
            bytes: iter.take_bytes(),
        })
    }
}

/// Decode one item from its encoded bytes.
///
/// This will consume one or more bytes from the input iterator.
pub fn decode_one<Iter>(
    iter: Iter,
    context: &mut DecoderContext,
) -> Option<Result<DecodedItem, LengthError>>
where
    Iter: Iterator<Item = u8>,
{
    let mut iter = Recorder::new(iter);

    let Some(tag) = iter.next() else {
        // End of input iterator
        return None;
    };
    Some(decode_one_inner(iter, context, tag))
}

/// Decode one item from its tag and any data bytes that follow.
///
/// This will consume zero or more bytes from the input iterator.
fn decode_one_inner<I>(
    mut iter: Recorder<I>,
    context: &mut DecoderContext,
    tag: u8,
) -> Result<DecodedItem, LengthError>
where
    I: Iterator<Item = u8>,
{
    let tag = TagTypeSize::from_bits(tag);

    let more: TinyVec<[u8; 8]> = match tag.size() {
        Size::Short(size_bytes) => {
            let iter_remaining = (&mut iter).take(size_bytes);
            iter_remaining.collect()
        }
        Size::Long => {
            let (Some(size), Some(tag)) = (iter.next(), iter.next()) else {
                return Err(LengthError::Truncated);
            };
            let iter_payload = (&mut iter).take(usize::from(size));
            let data = iter_payload.collect::<Vec<_>>();
            let item = Item::Long(LongItem { tag, data });
            return DecodedItem::yes(item, iter);
        }
    };

    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()),
            };
            DecodedItem::yes(Item::Main(decoded), iter)
        }
        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);
                    context.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()),
            };
            DecodedItem::yes(Item::Global(decoded), iter)
        }
        TypeBits::Local => {
            let item = LocalItem::from(tag.tag());
            let decoded = match item {
                LocalItem::Usage => {
                    // If the payload is 1 or 2 bytes, we use the
                    // existing page number. If the payload is 4 bytes, it
                    // contains both the page number and page id.
                    match slice_to_u16(&more) {
                        Ok(page_id) => match context.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()),
            };
            DecodedItem::yes(Item::Local(decoded), iter)
        }
        TypeBits::Reserved => {
            let decoded = Item::Reserved(tag.ty().into_bits());
            DecodedItem::yes(decoded, iter)
        }
    }
}

/// An iterator that yields values from an inner iterator, but also collects everything that was yielded.
struct Recorder<Iter> {
    inner: Iter,
    bytes: TinyVec<[u8; 8]>,
}

impl<Iter> Recorder<Iter> {
    /// Create a new recording iterator.
    fn new(iter: Iter) -> Self {
        Self {
            inner: iter,
            bytes: TinyVec::new(),
        }
    }

    /// Take the recorded bytes, consuming the iterator.
    fn take_bytes(self) -> TinyVec<[u8; 8]> {
        self.bytes
    }
}

impl<Iter> Iterator for Recorder<Iter>
where
    Iter: Iterator<Item = u8>,
{
    type Item = u8;

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().inspect(|b| {
            self.bytes.push(*b);
        })
    }
}

/// An error that resulted from improper item encoding.
#[derive(Clone, Debug, thiserror::Error)]
pub enum LengthError {
    /// The data length was too big for this item.
    #[error("item data too big")]
    DataTooBig,
    /// A data length of zero is not allowed in this item.
    #[error("missing item data")]
    ZeroLength,
    /// Ran out of bytes trying to decode this item.
    #[error("item data truncated")]
    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 _error_traits() {
        use std::error::Error;
        use std::fmt::Display;
        let _err: Box<dyn Error> = Box::new(LengthError::DataTooBig);
        let _err: Box<dyn Display> = Box::new(LengthError::DataTooBig);
        assert_eq!(format!("{}", LengthError::Truncated), "item data truncated");
    }

    #[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();
    }
}