From 7d11f3fc6d6c89944342080152a902e90edf73af Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 22 Oct 2024 14:48:58 +0100 Subject: [PATCH] Decode HID descriptors. --- src/capture.rs | 42 +++++++++++- src/usb.rs | 80 ++++++++++++++++++++++- tests/emf2022-badge/devices-reference.txt | 8 ++- tests/mouse/devices-reference.txt | 8 ++- tests/split-enum/devices-reference.txt | 16 ++++- 5 files changed, 145 insertions(+), 9 deletions(-) diff --git a/src/capture.rs b/src/capture.rs index 00a2b756..a3bf48af 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -288,6 +288,10 @@ pub enum DeviceItemContent { Endpoint(ConfigNum, InterfaceKey, IfaceEpNum), EndpointDescriptor(EndpointDescriptor), EndpointDescriptorField(EndpointDescriptor, EndpointField), + HidDescriptor(HidDescriptor), + HidDescriptorField(HidDescriptor, HidField), + HidDescriptorList(HidDescriptor), + HidDescriptorEntry(HidDescriptor, HidField), OtherDescriptor(Descriptor, Option), } @@ -2055,8 +2059,12 @@ impl ItemSource for CaptureReader { 0 => InterfaceDescriptor(*if_desc), n if n < 1 + desc_count => { let desc = interface.other_descriptor(n - 1)?.clone(); - OtherDescriptor(desc, - Some(interface.descriptor.interface_class)) + if let Descriptor::Hid(hid_desc) = desc { + HidDescriptor(hid_desc) + } else { + OtherDescriptor(desc, + Some(interface.descriptor.interface_class)) + } }, n => { let ep_num = (n - 1 - desc_count).try_into()?; @@ -2085,6 +2093,20 @@ impl ItemSource for CaptureReader { EndpointDescriptor(desc) => EndpointDescriptorField(*desc, EndpointField(index.try_into()?)), + HidDescriptor(desc) => { + const N: usize = usb::HidDescriptor::NUM_FIELDS; + const LAST_FIELD: usize = N - 1; + match index.try_into()? { + 0..=LAST_FIELD => + HidDescriptorField(desc.clone(), + HidField(index.try_into()?)), + N => HidDescriptorList(desc.clone()), + _ => bail!("HID descriptor has no child with index {index}") + } + }, + HidDescriptorList(desc) => + HidDescriptorEntry(desc.clone(), + HidField(index.try_into()?)), _ => bail!("This device item type cannot have children") }; Ok(DeviceItem { @@ -2153,7 +2175,10 @@ impl ItemSource for CaptureReader { (Ongoing, usb::InterfaceDescriptor::NUM_FIELDS), EndpointDescriptor(_) => (Complete, usb::EndpointDescriptor::NUM_FIELDS), - + HidDescriptor(_) => + (Complete, usb::HidDescriptor::NUM_FIELDS + 1), + HidDescriptorList(desc) => + (Complete, desc.available_descriptors.len()), // Other types have no children. _ => (Complete, 0), } @@ -2228,6 +2253,17 @@ impl ItemSource for CaptureReader { EndpointDescriptor(_) => "Endpoint descriptor".to_string(), EndpointDescriptorField(desc, field) => desc.field_text(*field), + HidDescriptor(_) => "HID descriptor".to_string(), + HidDescriptorField(desc, field) => desc.field_text(*field), + HidDescriptorList(_) => "Available descriptors".to_string(), + HidDescriptorEntry(desc, field) => { + let (desc_type, length) = + desc.available_descriptors + .get(field.0 as usize) + .context("Not enough entries in descriptor list")?; + format!("{}, {} bytes", + desc_type.description_with_class(ClassId::HID), length) + }, OtherDescriptor(desc, class) => desc.description(*class), }) } diff --git a/src/usb.rs b/src/usb.rs index 8bd3f882..406b53b7 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -146,6 +146,7 @@ byte_type!(EndpointField); byte_type!(EndpointAddr); byte_type!(EndpointAttr); byte_type!(IfaceAssocField); +byte_type!(HidField); byte_type!(ClassId); byte_type!(SubclassId); byte_type!(ProtocolId); @@ -846,6 +847,59 @@ impl EndpointDescriptor { pub const NUM_FIELDS: usize = 6; } +#[derive(Clone, Debug)] +pub struct HidDescriptor { + pub length: u8, + pub descriptor_type: u8, + pub hid_version: BCDVersion, + pub country_code: u8, + pub available_descriptors: Vec<(DescriptorType, u16)> +} + +impl HidDescriptor { + pub fn from(bytes: &[u8]) -> Option { + // A valid HID descriptor has at least 9 bytes. + if bytes.len() < 9 { + return None + } + // It must list at least one descriptor. + let num_descriptors = bytes[5]; + if num_descriptors == 0 { + return None + } + // There must be bytes for the number of descriptors specified. + if bytes.len() != 6 + (num_descriptors * 3) as usize { + return None + } + Some(HidDescriptor { + length: bytes[0], + descriptor_type: bytes[1], + hid_version: pod_read_unaligned::(&bytes[2..4]), + country_code: bytes[4], + available_descriptors: bytes[6..] + .chunks(3) + .map(|bytes| ( + DescriptorType::from(bytes[0]), + u16::from_le_bytes([bytes[1], bytes[2]]))) + .collect() + }) + } + + pub fn field_text(&self, id: HidField) -> String { + match id.0 { + 0 => format!("Length: {} bytes", self.length), + 1 => format!("Type: 0x{:02X}", self.descriptor_type), + 2 => format!("HID Version: {}", self.hid_version), + 3 => format!("Country code: 0x{:02X}{}", self.country_code, + usb_ids::HidCountryCode::from_id(self.country_code) + .map_or_else(String::new, |c| format!(": {}", c.name()))), + i => format!("Error: Invalid field ID {i}") + } + } + + pub const NUM_FIELDS: usize = 4; +} + #[allow(dead_code)] #[derive(Clone, Debug)] pub enum Descriptor { @@ -854,6 +908,7 @@ pub enum Descriptor { InterfaceAssociation(InterfaceAssociationDescriptor), Interface(InterfaceDescriptor), Endpoint(EndpointDescriptor), + Hid(HidDescriptor), Other(DescriptorType, Vec), Truncated(DescriptorType, Vec), } @@ -868,6 +923,7 @@ impl Descriptor { Endpoint(_) => "Endpoint descriptor".to_string(), InterfaceAssociation(_) => "Interface association descriptor".to_string(), + Hid(_) => "HID descriptor".to_string(), Other(desc_type, bytes) => { let description = match class { Some(class) => desc_type.description_with_class(class), @@ -892,13 +948,15 @@ impl Descriptor { pub struct DescriptorIterator<'bytes> { bytes: &'bytes [u8], offset: usize, + class: Option, } impl<'bytes> DescriptorIterator<'bytes> { fn from(bytes: &'bytes [u8]) -> Self { DescriptorIterator { bytes, - offset: 0 + offset: 0, + class: None, } } @@ -906,6 +964,7 @@ impl<'bytes> DescriptorIterator<'bytes> { &mut self, desc_type: DescriptorType, desc_bytes: &[u8], + class: Option, ) -> Descriptor { // Decide how many bytes to decode. let bytes = match desc_type.expected_length() { @@ -935,6 +994,13 @@ impl<'bytes> DescriptorIterator<'bytes> { DescriptorType::InterfaceAssociation => Descriptor::InterfaceAssociation( pod_read_unaligned::(bytes)), + DescriptorType::Class(code) => match (class, code) { + (Some(ClassId::HID), 0x21) => match HidDescriptor::from(bytes) { + Some(hid_desc) => Descriptor::Hid(hid_desc), + None => Descriptor::Truncated(desc_type, bytes.to_vec()) + }, + _ => Descriptor::Other(desc_type, bytes.to_vec()) + }, _ => Descriptor::Other(desc_type, bytes.to_vec()) } } @@ -962,7 +1028,15 @@ impl Iterator for DescriptorIterator<'_> { } else { // This looks like a valid descriptor, decode it. let bytes = &remaining_bytes[0 .. desc_length]; - (self.decode_descriptor(desc_type, bytes), desc_length) + let descriptor = self.decode_descriptor( + desc_type, bytes, self.class); + // If this was an interface descriptor, subsequent + // descriptors will be interpreted in the context of + // this interface's class. + if let Descriptor::Interface(iface_desc) = descriptor { + self.class = Some(iface_desc.interface_class); + } + (descriptor, desc_length) } } }; @@ -1289,6 +1363,7 @@ pub mod prelude { InterfaceAssociationDescriptor, InterfaceDescriptor, EndpointDescriptor, + HidDescriptor, Configuration, Function, Interface, @@ -1307,6 +1382,7 @@ pub mod prelude { InterfaceField, EndpointNum, EndpointField, + HidField, UTF16ByteVec, }; } diff --git a/tests/emf2022-badge/devices-reference.txt b/tests/emf2022-badge/devices-reference.txt index 3e058df4..14ce524a 100644 --- a/tests/emf2022-badge/devices-reference.txt +++ b/tests/emf2022-badge/devices-reference.txt @@ -204,7 +204,13 @@ Subclass: 0x01: Boot Interface Subclass Protocol: 0x01: Keyboard Interface string: #5 'TiDAL badge' - HID descriptor, 9 bytes + HID descriptor + Length: 9 bytes + Type: 0x21 + HID Version: 1.11 + Country code: 0x00: Not supported + Available descriptors + HID report descriptor, 144 bytes Endpoint 3 IN (interrupt) Endpoint descriptor Length: 7 bytes diff --git a/tests/mouse/devices-reference.txt b/tests/mouse/devices-reference.txt index cb65b1d3..dc2f5440 100644 --- a/tests/mouse/devices-reference.txt +++ b/tests/mouse/devices-reference.txt @@ -34,7 +34,13 @@ Subclass: 0x01: Boot Interface Subclass Protocol: 0x02: Mouse Interface string: (none) - HID descriptor, 9 bytes + HID descriptor + Length: 9 bytes + Type: 0x21 + HID Version: 1.10 + Country code: 0x00: Not supported + Available descriptors + HID report descriptor, 75 bytes Endpoint 1 IN (interrupt) Endpoint descriptor Length: 7 bytes diff --git a/tests/split-enum/devices-reference.txt b/tests/split-enum/devices-reference.txt index 3037596e..d0f81322 100644 --- a/tests/split-enum/devices-reference.txt +++ b/tests/split-enum/devices-reference.txt @@ -36,7 +36,13 @@ Subclass: 0x01: Boot Interface Subclass Protocol: 0x01: Keyboard Interface string: (none) - HID descriptor, 9 bytes + HID descriptor + Length: 9 bytes + Type: 0x21 + HID Version: 1.00 + Country code: 0x00: Not supported + Available descriptors + HID report descriptor, 77 bytes Endpoint 1 IN (interrupt) Endpoint descriptor Length: 7 bytes @@ -56,7 +62,13 @@ Subclass: 0x01: Boot Interface Subclass Protocol: 0x02: Mouse Interface string: (none) - HID descriptor, 9 bytes + HID descriptor + Length: 9 bytes + Type: 0x21 + HID Version: 1.00 + Country code: 0x00: Not supported + Available descriptors + HID report descriptor, 91 bytes Endpoint 2 IN (interrupt) Endpoint descriptor Length: 7 bytes