diff --git a/src/capture.rs b/src/capture.rs index f45eb5a1..a3bf48af 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -21,7 +21,6 @@ use arc_swap::{ArcSwap, ArcSwapOption}; use bytemuck_derive::{Pod, Zeroable}; use itertools::Itertools; use num_enum::{IntoPrimitive, FromPrimitive}; -use usb_ids::FromId; // Use 2MB block size for packet data, which is a large page size on x86_64. const PACKET_DATA_BLOCK_SIZE: usize = 0x200000; @@ -277,7 +276,7 @@ pub enum DeviceItemContent { Device(Option), DeviceDescriptor(Option), DeviceDescriptorField(DeviceDescriptor, DeviceField), - Configuration(ConfigNum, ConfigDescriptor), + Configuration(ConfigNum, ConfigDescriptor, Option), ConfigurationDescriptor(ConfigDescriptor), ConfigurationDescriptorField(ConfigDescriptor, ConfigField), Function(ConfigNum, InterfaceAssociationDescriptor), @@ -289,7 +288,11 @@ pub enum DeviceItemContent { Endpoint(ConfigNum, InterfaceKey, IfaceEpNum), EndpointDescriptor(EndpointDescriptor), EndpointDescriptorField(EndpointDescriptor, EndpointField), - OtherDescriptor(Descriptor), + HidDescriptor(HidDescriptor), + HidDescriptorField(HidDescriptor, HidField), + HidDescriptorList(HidDescriptor), + HidDescriptorEntry(HidDescriptor, HidField), + OtherDescriptor(Descriptor, Option), } type IfaceEpNum = u8; @@ -1224,7 +1227,9 @@ impl CaptureReader { EndpointType::Framing => GroupContent::Framing, EndpointType::Normal(usb::EndpointType::Control) => { let addr = endpoint.device_address(); - match self.control_transfer(addr, endpoint_id, &range) { + match self.control_transfer( + device_id, addr, endpoint_id, &range) + { Ok(transfer) => GroupContent::Request(transfer), Err(_) => GroupContent::IncompleteRequest, } @@ -1270,6 +1275,7 @@ impl CaptureReader { } fn control_transfer(&mut self, + device_id: DeviceId, address: DeviceAddr, endpoint_id: EndpointId, range: &Range) @@ -1288,11 +1294,38 @@ impl CaptureReader { let last = transaction_ids.len() - 1; let last_transaction = self.transaction(transaction_ids[last])?; let result = last_transaction.control_result(direction); + let recipient = fields.type_fields.recipient(); + let dev_data = self.device_data(device_id)?; + let recipient_class = match recipient { + Recipient::Device => dev_data.device_descriptor + .load() + .as_ref() + .map(|desc| desc.device_class), + Recipient::Interface => { + let iface_num = InterfaceNum(fields.index as u8); + if let (Some(config_num), Some(iface_alt)) = ( + dev_data.config_number.load().as_ref(), + dev_data.interface_settings.load().get(iface_num)) + { + let iface_key = (iface_num, *iface_alt); + dev_data + .configurations + .load() + .get(**config_num) + .and_then(|config| config.interfaces.get(&iface_key)) + .map(|interface| interface.descriptor.interface_class) + } else { + None + } + } + _ => None, + }; Ok(ControlTransfer { address, fields, data, result, + recipient_class, }) } @@ -1958,36 +1991,42 @@ impl ItemSource for CaptureReader { { use DeviceItemContent::*; let data = self.device_data(parent.device_id)?; - let content = match parent.content { + let content = match &parent.content { Device(desc_opt) => match index { - 0 => DeviceDescriptor(desc_opt), + 0 => DeviceDescriptor(*desc_opt), n => { let conf = ConfigNum(n.try_into()?); let config = data.configuration(conf)?; - Configuration(conf, config.descriptor) + Configuration( + conf, + config.descriptor, + desc_opt.map(|desc| desc.device_class) + ) } }, DeviceDescriptor(desc_opt) => match desc_opt { Some(desc) => - DeviceDescriptorField(desc, + DeviceDescriptorField(*desc, DeviceField(index.try_into()?)), None => bail!("Device descriptor fields not available") }, - Configuration(conf, desc) => { - let config = data.configuration(conf)?; + Configuration(conf, desc, class) => { + let config = data.configuration(*conf)?; let other_count = config.other_descriptors.len(); let func_count = config.functions.len(); match index.try_into()? { - 0 => ConfigurationDescriptor(desc), + 0 => ConfigurationDescriptor(*desc), n if n < 1 + other_count => - OtherDescriptor(config - .other_descriptor(n - 1)? - .clone()), + OtherDescriptor( + config + .other_descriptor(n - 1)? + .clone(), + *class), n if n < 1 + other_count + func_count => - Function(conf, config + Function(*conf, config .function(n - 1 - other_count)? .descriptor), - n => Interface(conf, config + n => Interface(*conf, config .unassociated_interfaces() .nth(n - 1 - other_count - func_count) .context("Failed to find unassociated interface")? @@ -1995,59 +2034,79 @@ impl ItemSource for CaptureReader { } }, ConfigurationDescriptor(desc) => - ConfigurationDescriptorField(desc, + ConfigurationDescriptorField(*desc, ConfigField(index.try_into()?)), Function(conf, desc) => { - let config = data.configuration(conf)?; + let config = data.configuration(*conf)?; match index.try_into()? { - 0 => FunctionDescriptor(desc), - n => match config.associated_interfaces(&desc).nth(n - 1) { + 0 => FunctionDescriptor(*desc), + n => match config.associated_interfaces(desc).nth(n - 1) { Some(interface) => - Interface(conf, interface.descriptor), + Interface(*conf, interface.descriptor), None => bail!( "Function has no interface with index {n}") } } }, FunctionDescriptor(desc) => - FunctionDescriptorField(desc, + FunctionDescriptorField(*desc, IfaceAssocField(index.try_into()?)), Interface(conf, if_desc) => { - let config = data.configuration(conf)?; + let config = data.configuration(*conf)?; let interface = config.interface(if_desc.key())?; let desc_count = interface.other_descriptors.len(); match index.try_into()? { - 0 => InterfaceDescriptor(if_desc), + 0 => InterfaceDescriptor(*if_desc), n if n < 1 + desc_count => { let desc = interface.other_descriptor(n - 1)?.clone(); - OtherDescriptor(desc) + 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()?; - Endpoint(conf, if_desc.key(), ep_num) + Endpoint(*conf, if_desc.key(), ep_num) } } }, Endpoint(conf, if_key, ep_num) => { - let config = data.configuration(conf)?; - let interface = config.interface(if_key)?; - let endpoint = interface.endpoint(ep_num)?; + let config = data.configuration(*conf)?; + let interface = config.interface(*if_key)?; + let endpoint = interface.endpoint(*ep_num)?; match index.try_into()? { 0 => EndpointDescriptor(endpoint.descriptor), n => OtherDescriptor( endpoint.other_descriptors .get(n - 1) .context("Other endpoint descriptor not found")? - .clone() + .clone(), + Some(interface.descriptor.interface_class) ) } }, InterfaceDescriptor(desc) => - InterfaceDescriptorField(desc, + InterfaceDescriptorField(*desc, InterfaceField(index.try_into()?)), EndpointDescriptor(desc) => - EndpointDescriptorField(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 { @@ -2071,7 +2130,7 @@ impl ItemSource for CaptureReader { self.devices.len().saturating_sub(1) as usize), Some(item) => { let data = self.device_data(item.device_id)?; - match item.content { + match &item.content { Device(_) => { let count = data.configurations.load().len(); (Ongoing, if count == 0 { 1 } else { count }) @@ -2082,8 +2141,8 @@ impl ItemSource for CaptureReader { (Ongoing, usb::DeviceDescriptor::NUM_FIELDS), None => (Ongoing, 0), }, - Configuration(conf, _) => { - let config = data.configuration(conf)?; + Configuration(conf, ..) => { + let config = data.configuration(*conf)?; (Ongoing, 1 + config.other_descriptors.len() + config.functions.len() @@ -2092,31 +2151,34 @@ impl ItemSource for CaptureReader { ConfigurationDescriptor(_) => (Ongoing, usb::ConfigDescriptor::NUM_FIELDS), Function(conf, desc) => { - let config = data.configuration(conf)?; - let interfaces = config.associated_interfaces(&desc); + let config = data.configuration(*conf)?; + let interfaces = config.associated_interfaces(desc); (Complete, 1 + interfaces.count()) } FunctionDescriptor(_) => (Complete, usb::InterfaceAssociationDescriptor::NUM_FIELDS), Interface(conf, desc) => { - let config = data.configuration(conf)?; + let config = data.configuration(*conf)?; let interface = config.interface(desc.key())?; (Ongoing, 1 + interface.endpoints.len() + interface.other_descriptors.len()) }, Endpoint(conf, key, ep_num) => { - let config = data.configuration(conf)?; - let interface = config.interface(key)?; - let endpoint = interface.endpoint(ep_num)?; + let config = data.configuration(*conf)?; + let interface = config.interface(*key)?; + let endpoint = interface.endpoint(*ep_num)?; (Complete, 1 + endpoint.other_descriptors.len()) } InterfaceDescriptor(_) => (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), } @@ -2145,7 +2207,7 @@ impl ItemSource for CaptureReader { let strings = data.strings.load(); desc.field_text(*field, strings.as_ref()) }, - Configuration(conf, _) => format!( + Configuration(conf, ..) => format!( "Configuration {conf}"), ConfigurationDescriptor(_) => "Configuration descriptor".to_string(), @@ -2156,8 +2218,7 @@ impl ItemSource for CaptureReader { Function(_conf, desc) => { format!("Function {}: {}", desc.function, - usb_ids::Class::from_id(desc.function_class) - .map_or("Unknown", |c| c.name()) + desc.function_class.name() ) }, FunctionDescriptor(_) => @@ -2165,8 +2226,7 @@ impl ItemSource for CaptureReader { FunctionDescriptorField(desc, field) => desc.field_text(*field), Interface(_conf, desc) => { let num = desc.interface_number; - let class = usb_ids::Class::from_id(desc.interface_class) - .map_or("Unknown", |c| c.name()); + let class = desc.interface_class.name(); match desc.alternate_setting { InterfaceAlt(0) => format!( "Interface {num}: {class}"), @@ -2193,7 +2253,18 @@ impl ItemSource for CaptureReader { EndpointDescriptor(_) => "Endpoint descriptor".to_string(), EndpointDescriptorField(desc, field) => desc.field_text(*field), - OtherDescriptor(desc) => desc.description(), + 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/ui.rs b/src/ui.rs index 0f391257..2f5c11b3 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1328,8 +1328,8 @@ fn device_context_menu( FunctionDescriptor(desc) => bytes_of(desc), InterfaceDescriptor(desc) => bytes_of(desc), EndpointDescriptor(desc) => bytes_of(desc), - OtherDescriptor(Other(_, bytes)) => bytes, - OtherDescriptor(Truncated(_, bytes)) => bytes, + OtherDescriptor(Other(_, bytes), _) => bytes, + OtherDescriptor(Truncated(_, bytes), _) => bytes, _ => return Ok(None) }.to_vec(); Ok(Some(context_popover( diff --git a/src/usb.rs b/src/usb.rs index 7575ab21..406b53b7 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -146,6 +146,10 @@ byte_type!(EndpointField); byte_type!(EndpointAddr); byte_type!(EndpointAttr); byte_type!(IfaceAssocField); +byte_type!(HidField); +byte_type!(ClassId); +byte_type!(SubclassId); +byte_type!(ProtocolId); pub type InterfaceKey = (InterfaceNum, InterfaceAlt); @@ -173,6 +177,40 @@ impl EndpointAttr { } } +impl ClassId { + pub const HID: ClassId = ClassId(0x03); + + pub fn name(self) -> &'static str { + usb_ids::Class::from_id(self.0) + .map_or("Unknown", usb_ids::Class::name) + } + + fn description(self) -> String { + match usb_ids::Class::from_id(self.0) { + Some(c) => format!("0x{:02X}: {}", self.0, c.name()), + None => format!("0x{:02X}", self.0) + } + } +} + +impl SubclassId { + fn description(self, class: ClassId) -> String { + match usb_ids::SubClass::from_cid_scid(class.0, self.0) { + Some(s) => format!("0x{:02X}: {}", self.0, s.name()), + None => format!("0x{:02X}", self.0) + } + } +} + +impl ProtocolId { + fn description(self, class: ClassId, subclass: SubclassId) -> String { + match usb_ids::Protocol::from_cid_scid_pid(class.0, subclass.0, self.0) { + Some(p) => format!("0x{:02X}: {}", self.0, p.name()), + None => format!("0x{:02X}", self.0) + } + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] #[repr(u8)] pub enum EndpointType { @@ -390,7 +428,11 @@ pub enum StandardRequest { #[allow(clippy::useless_format)] impl StandardRequest { - pub fn description(&self, fields: &SetupFields) -> String { + pub fn description( + &self, + fields: &SetupFields, + recipient_class: Option, + ) -> String { use StandardRequest::*; match self { GetStatus => format!("Getting status"), @@ -416,7 +458,12 @@ impl StandardRequest { SetDescriptor => "Setting", _ => "" }, - descriptor_type.description(None), + match recipient_class { + Some(class) => + descriptor_type.description_with_class(class), + None => + descriptor_type.description(), + }, fields.value & 0xFF, match (descriptor_type, fields.index) { (DescriptorType::String, language) if language > 0 => @@ -453,28 +500,55 @@ fn language_name(code: u16) -> Option { } } -#[derive(Copy, Clone, Debug, FromPrimitive, PartialEq, Eq)] -#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum DescriptorType { - Invalid = 0x00, - Device = 0x01, - Configuration = 0x02, - String = 0x03, - Interface = 0x04, - Endpoint = 0x05, - DeviceQualifier = 0x06, - OtherSpeedConfiguration = 0x07, - InterfacePower = 0x08, - OnTheGo = 0x09, - Debug = 0x0A, - InterfaceAssociation = 0x0B, - BinaryObjectStore = 0x0F, - DeviceCapability = 0x10, - #[default] - Unknown = 0xFF, + Invalid, + Device, + Configuration, + String, + Interface, + Endpoint, + DeviceQualifier, + OtherSpeedConfiguration, + InterfacePower, + OnTheGo, + Debug, + InterfaceAssociation, + BinaryObjectStore, + DeviceCapability, + UnknownStandard(u8), + Class(u8), + Custom(u8), + Reserved(u8), + Unknown, } impl DescriptorType { + pub fn from(code: u8) -> DescriptorType { + use DescriptorType::*; + #[allow(clippy::match_overlapping_arm)] + match code { + 0x00 => Invalid, + 0x01 => Device, + 0x02 => Configuration, + 0x03 => String, + 0x04 => Interface, + 0x05 => Endpoint, + 0x06 => DeviceQualifier, + 0x07 => OtherSpeedConfiguration, + 0x08 => InterfacePower, + 0x09 => OnTheGo, + 0x0A => Debug, + 0x0B => InterfaceAssociation, + 0x0F => BinaryObjectStore, + 0x10 => DeviceCapability, + 0x00..=0x1F => UnknownStandard(code), + 0x20..=0x3F => Class(code), + 0x40..=0x5F => Custom(code), + 0x60..=0xFF => Reserved(code), + } + } + fn expected_length(&self) -> Option { use DescriptorType::*; match self { @@ -493,7 +567,7 @@ impl DescriptorType { } } - fn description(&self, bytes: Option<&[u8]>) -> String { + fn description(&self) -> String { use DescriptorType::*; format!("{} descriptor", match self { Invalid => "invalid", @@ -510,20 +584,30 @@ impl DescriptorType { InterfaceAssociation => "interface association", BinaryObjectStore => "BOS", DeviceCapability => "device capability", - Unknown => if let Some(type_code) = bytes.and_then(|b| b.get(1)) { - let type_group = match type_code { - 0x00..=0x1F => "standard", - 0x20..=0x3F => "class", - 0x40..=0x5F => "custom", - 0x60..=0xFF => "reserved", - }; - return format!("{} descriptor 0x{:02X}", - type_group, type_code) - } else { - "unknown" - } + UnknownStandard(code) => + return format!("standard descriptor 0x{:02X}", code), + Class(code) => + return format!("class descriptor 0x{:02X}", code), + Custom(code) => + return format!("custom descriptor 0x{:02X}", code), + Reserved(code) => + return format!("reserved descriptor 0x{:02X}", code), + Unknown => "unknown", }) } + + pub fn description_with_class(&self, class: ClassId) -> String { + if let DescriptorType::Class(code) = self { + let description = match (class, code) { + (ClassId::HID, 0x21) => "HID descriptor", + (ClassId::HID, 0x22) => "HID report descriptor", + _ => return self.description() + }; + description.to_string() + } else { + self.description() + } + } } #[derive(Copy, Clone, Debug, FromPrimitive)] @@ -554,9 +638,9 @@ pub struct DeviceDescriptor { pub length: u8, pub descriptor_type: u8, pub usb_version: BCDVersion, - pub device_class: u8, - pub device_subclass: u8, - pub device_protocol: u8, + pub device_class: ClassId, + pub device_subclass: SubclassId, + pub device_protocol: ProtocolId, pub max_packet_size_0: u8, pub vendor_id: u16, pub product_id: u16, @@ -581,18 +665,12 @@ impl DeviceDescriptor { 0 => format!("Length: {} bytes", self.length), 1 => format!("Type: 0x{:02X}", self.descriptor_type), 2 => format!("USB Version: {}", self.usb_version), - 3 => format!("Class: 0x{:02X}{}", self.device_class, - usb_ids::Class::from_id(self.device_class) - .map_or_else(String::new, |c| format!(": {}", c.name()))), - 4 => format!("Subclass: 0x{:02X}{}", self.device_subclass, - usb_ids::SubClass::from_cid_scid( - self.device_class, self.device_subclass) - .map_or_else(String::new, |s| format!(": {}", s.name()))), - 5 => format!("Protocol: 0x{:02X}{}", self.device_protocol, - usb_ids::Protocol::from_cid_scid_pid( - self.device_class, self.device_subclass, - self.device_protocol) - .map_or_else(String::new, |p| format!(": {}", p.name()))), + 3 => format!("Class: {}", self.device_class + .description()), + 4 => format!("Subclass: {}", self.device_subclass + .description(self.device_class)), + 5 => format!("Protocol: {}", self.device_protocol + .description(self.device_class, self.device_subclass)), 6 => format!("Max EP0 packet size: {} bytes", self.max_packet_size_0), 7 => format!("Vendor ID: 0x{:04X}{}", self.vendor_id, usb_ids::Vendor::from_id(self.vendor_id) @@ -658,9 +736,9 @@ pub struct InterfaceAssociationDescriptor { pub descriptor_type: u8, pub first_interface: u8, pub interface_count: u8, - pub function_class: u8, - pub function_subclass: u8, - pub function_protocol: u8, + pub function_class: ClassId, + pub function_subclass: SubclassId, + pub function_protocol: ProtocolId, pub function: u8, } @@ -673,18 +751,12 @@ impl InterfaceAssociationDescriptor { 1 => format!("Type: 0x{:02X}", self.descriptor_type), 2 => format!("First interface: {}", self.first_interface), 3 => format!("Interface count: {}", self.interface_count), - 4 => format!("Function class: 0x{:02X}{}", self.function_class, - usb_ids::Class::from_id(self.function_class) - .map_or_else(String::new, |c| format!(": {}", c.name()))), - 5 => format!("Function subclass: 0x{:02X}{}", self.function_subclass, - usb_ids::SubClass::from_cid_scid( - self.function_class, self.function_subclass) - .map_or_else(String::new, |s| format!(": {}", s.name()))), - 6 => format!("Function protocol: 0x{:02X}{}", self.function_protocol, - usb_ids::Protocol::from_cid_scid_pid( - self.function_class, self.function_subclass, - self.function_protocol) - .map_or_else(String::new, |p| format!(": {}", p.name()))), + 4 => format!("Function class: {}", self.function_class + .description()), + 5 => format!("Function subclass: {}", self.function_subclass + .description(self.function_class)), + 6 => format!("Function protocol: {}", self.function_protocol + .description(self.function_class, self.function_subclass)), 7 => format!("Function number: {}", self.function), i => format!("Error: Invalid field ID {i}") } @@ -709,9 +781,9 @@ pub struct InterfaceDescriptor { pub interface_number: InterfaceNum, pub alternate_setting: InterfaceAlt, pub num_endpoints: u8, - pub interface_class: u8, - pub interface_subclass: u8, - pub interface_protocol: u8, + pub interface_class: ClassId, + pub interface_subclass: SubclassId, + pub interface_protocol: ProtocolId, pub interface_str_id: StringId, } @@ -727,18 +799,12 @@ impl InterfaceDescriptor { 2 => format!("Interface number: {}", self.interface_number), 3 => format!("Alternate setting: {}", self.alternate_setting), 4 => format!("Number of endpoints: {}", self.num_endpoints), - 5 => format!("Class: 0x{:02X}{}", self.interface_class, - usb_ids::Class::from_id(self.interface_class) - .map_or_else(String::new, |c| format!(": {}", c.name()))), - 6 => format!("Subclass: 0x{:02X}{}", self.interface_subclass, - usb_ids::SubClass::from_cid_scid( - self.interface_class, self.interface_subclass) - .map_or_else(String::new, |s| format!(": {}", s.name()))), - 7 => format!("Protocol: 0x{:02X}{}", self.interface_protocol, - usb_ids::Protocol::from_cid_scid_pid( - self.interface_class, self.interface_subclass, - self.interface_protocol) - .map_or_else(String::new, |p| format!(": {}", p.name()))), + 5 => format!("Class: {}", self.interface_class + .description()), + 6 => format!("Subclass: {}", self.interface_subclass + .description(self.interface_class)), + 7 => format!("Protocol: {}", self.interface_protocol + .description(self.interface_class, self.interface_subclass)), 8 => format!("Interface string: {}", fmt_str_id(strings, self.interface_str_id)), i => format!("Error: Invalid field ID {i}") @@ -781,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 { @@ -789,12 +908,13 @@ pub enum Descriptor { InterfaceAssociation(InterfaceAssociationDescriptor), Interface(InterfaceDescriptor), Endpoint(EndpointDescriptor), + Hid(HidDescriptor), Other(DescriptorType, Vec), Truncated(DescriptorType, Vec), } impl Descriptor { - pub fn description(&self) -> String { + pub fn description(&self, class: Option) -> String { use Descriptor::*; match self { Device(_) => "Device descriptor".to_string(), @@ -803,10 +923,16 @@ impl Descriptor { Endpoint(_) => "Endpoint descriptor".to_string(), InterfaceAssociation(_) => "Interface association descriptor".to_string(), - Other(desc_type, bytes) => format!("{}, {} bytes", - titlecase(&desc_type.description(Some(bytes))), bytes.len()), + Hid(_) => "HID descriptor".to_string(), + Other(desc_type, bytes) => { + let description = match class { + Some(class) => desc_type.description_with_class(class), + None => desc_type.description(), + }; + format!("{}, {} bytes", titlecase(&description), bytes.len()) + }, Truncated(desc_type, bytes) => { - let description = desc_type.description(Some(bytes)); + let description = desc_type.description(); let desc_length = bytes[0] as usize; let length = bytes.len(); let expected = desc_type @@ -822,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, } } @@ -836,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() { @@ -865,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()) } } @@ -892,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) } } }; @@ -1012,6 +1156,7 @@ pub struct ControlTransfer { pub fields: SetupFields, pub data: Vec, pub result: ControlResult, + pub recipient_class: Option, } impl ControlTransfer { @@ -1030,7 +1175,8 @@ impl ControlTransfer { let mut parts = vec![format!( "{} {}", match request_type { - RequestType::Standard => std_req.description(&self.fields), + RequestType::Standard => std_req.description( + &self.fields, self.recipient_class), _ => format!( "{:?} request #{}, index {}, value {}", request_type, request, @@ -1217,9 +1363,11 @@ pub mod prelude { InterfaceAssociationDescriptor, InterfaceDescriptor, EndpointDescriptor, + HidDescriptor, Configuration, Function, Interface, + ClassId, ControlTransfer, ControlResult, DeviceAddr, @@ -1234,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 4710c08a..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' - Class descriptor 0x21, 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/emf2022-badge/reference.txt b/tests/emf2022-badge/reference.txt index 3358d80d..74a339f9 100644 --- a/tests/emf2022-badge/reference.txt +++ b/tests/emf2022-badge/reference.txt @@ -4246,7 +4246,7 @@ │ ├── IN packet on 2.0, CRC 15 │ ├── DATA1 packet with CRC 0000 and no data │ └── ACK packet - │ ○── Getting unknown descriptor #0 for interface 2.2, reading 144 bytes + │ ○── Getting HID report descriptor #0 for interface 2.2, reading 144 bytes │ ├──── SETUP transaction on 2.0 with 8 data bytes, ACK: [81, 06, 00, 22, 02, 00, 90, 00] │ │ ├── SETUP packet on 2.0, CRC 15 │ │ ├── DATA0 packet with CRC E785 and 8 data bytes: [81, 06, 00, 22, 02, 00, 90, 00] diff --git a/tests/mouse/devices-reference.txt b/tests/mouse/devices-reference.txt index 18bf0f70..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) - Class descriptor 0x21, 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/mouse/reference.txt b/tests/mouse/reference.txt index 57c639a7..c402ddef 100644 --- a/tests/mouse/reference.txt +++ b/tests/mouse/reference.txt @@ -221,7 +221,7 @@ ├── IN packet on 4.0, CRC 05 ├── DATA1 packet with CRC 0000 and no data └── ACK packet - ○── Getting unknown descriptor #0 for interface 4.0, reading 75 bytes + ○── Getting HID report descriptor #0 for interface 4.0, reading 75 bytes ├──── SETUP transaction on 4.0 with 8 data bytes, ACK: [81, 06, 00, 22, 00, 00, 4B, 00] │ ├── SETUP packet on 4.0, CRC 05 │ ├── DATA0 packet with CRC AFDE and 8 data bytes: [81, 06, 00, 22, 00, 00, 4B, 00] diff --git a/tests/split-enum/devices-reference.txt b/tests/split-enum/devices-reference.txt index 71e62eb9..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) - Class descriptor 0x21, 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) - Class descriptor 0x21, 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 diff --git a/tests/ui/emf2022-badge/reference.txt b/tests/ui/emf2022-badge/reference.txt index d60e90cf..231ca667 100644 --- a/tests/ui/emf2022-badge/reference.txt +++ b/tests/ui/emf2022-badge/reference.txt @@ -30,7 +30,7 @@ At traffic-hierarchical row 0: + Getting string descriptor #5, language 0x0409 (English/US) for device 2, reading 24 of 255 requested bytes: 'TiDAL badge' + Getting string descriptor #3, language 0x0409 (English/US) for device 2, reading 14 of 255 requested bytes: '123456' + Class request #10, index 0, value 0 for interface 2.2 -+ Getting unknown descriptor #0 for interface 2.2, reading 144 bytes ++ Getting HID report descriptor #0 for interface 2.2, reading 144 bytes + Class request #9, index 0, value 513 for interface 2.2, writing 2 bytes + Polling 194 times for interrupt transfer on endpoint 2.3 IN At traffic-transactions row 0: