Skip to content

Commit

Permalink
Decode HID descriptors.
Browse files Browse the repository at this point in the history
  • Loading branch information
martinling committed Oct 22, 2024
1 parent 51ec581 commit b9bf1d3
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 7 deletions.
33 changes: 32 additions & 1 deletion src/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClassId>),
}

Expand Down Expand Up @@ -2036,6 +2040,19 @@ impl ItemSource<DeviceItem, DeviceViewMode> for CaptureReader {
EndpointDescriptor(desc) =>
EndpointDescriptorField(*desc,
EndpointField(index.try_into()?)),
HidDescriptor(desc) => {
const N: usize = usb::HidDescriptor::NUM_FIELDS;
match index.try_into()? {
0..=(N - 1) =>
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 {
Expand Down Expand Up @@ -2104,7 +2121,10 @@ impl ItemSource<DeviceItem, DeviceViewMode> 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),
}
Expand Down Expand Up @@ -2179,6 +2199,17 @@ impl ItemSource<DeviceItem, DeviceViewMode> 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),
})
}
Expand Down
80 changes: 78 additions & 2 deletions src/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<HidDescriptor> {
// 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::<BCDVersion>(&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 {
Expand All @@ -854,6 +908,7 @@ pub enum Descriptor {
InterfaceAssociation(InterfaceAssociationDescriptor),
Interface(InterfaceDescriptor),
Endpoint(EndpointDescriptor),
Hid(HidDescriptor),
Other(DescriptorType, Vec<u8>),
Truncated(DescriptorType, Vec<u8>),
}
Expand All @@ -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),
Expand All @@ -892,20 +948,23 @@ impl Descriptor {
pub struct DescriptorIterator<'bytes> {
bytes: &'bytes [u8],
offset: usize,
class: Option<ClassId>,
}

impl<'bytes> DescriptorIterator<'bytes> {
fn from(bytes: &'bytes [u8]) -> Self {
DescriptorIterator {
bytes,
offset: 0
offset: 0,
class: None,
}
}

fn decode_descriptor(
&mut self,
desc_type: DescriptorType,
desc_bytes: &[u8],
class: Option<ClassId>,
) -> Descriptor {
// Decide how many bytes to decode.
let bytes = match desc_type.expected_length() {
Expand Down Expand Up @@ -935,6 +994,13 @@ impl<'bytes> DescriptorIterator<'bytes> {
DescriptorType::InterfaceAssociation =>
Descriptor::InterfaceAssociation(
pod_read_unaligned::<InterfaceAssociationDescriptor>(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())
}
}
Expand Down Expand Up @@ -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)
}
}
};
Expand Down Expand Up @@ -1287,6 +1361,7 @@ pub mod prelude {
InterfaceAssociationDescriptor,
InterfaceDescriptor,
EndpointDescriptor,
HidDescriptor,
Configuration,
Function,
Interface,
Expand All @@ -1305,6 +1380,7 @@ pub mod prelude {
InterfaceField,
EndpointNum,
EndpointField,
HidField,
UTF16ByteVec,
};
}
8 changes: 7 additions & 1 deletion tests/emf2022-badge/devices-reference.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion tests/mouse/devices-reference.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 14 additions & 2 deletions tests/split-enum/devices-reference.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit b9bf1d3

Please sign in to comment.