diff --git a/Cargo.toml b/Cargo.toml index 98c2985..3001333 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] members = [ "usb_report_descriptor", + "usb_request", ] diff --git a/README.md b/README.md index c88a67a..afe39d5 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,4 @@ ## Crates * [`usb_report_descriptor`](usb_report_descriptor) item parser for HID Report descriptors. +* [`usb_request`](usb_request) request formatter & descriptor parser. diff --git a/usb_request/Cargo.toml b/usb_request/Cargo.toml new file mode 100644 index 0000000..a1a68fe --- /dev/null +++ b/usb_request/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "usb_request" +version = "0.1.0" +edition = "2021" diff --git a/usb_request/src/descriptor/configuration.rs b/usb_request/src/descriptor/configuration.rs new file mode 100644 index 0000000..15c6c80 --- /dev/null +++ b/usb_request/src/descriptor/configuration.rs @@ -0,0 +1,61 @@ +use core::fmt; + +#[derive(Debug)] +pub struct Configuration { + pub total_length: u16, + pub num_interfaces: u8, + pub configuration_value: u8, + /// Value which when used as an argument in the SET_CONFIGURATION request, + /// causes the device to assume the configuration described by this descriptor. + pub index_configuration: u8, + pub attributes: ConfigurationAttributes, + pub max_power: u8, +} + +impl Configuration { + pub(crate) fn from_raw(buf: &[u8]) -> Result { + if let &[a, b, c, d, e, f, g] = buf { + Ok(Configuration { + total_length: u16::from_le_bytes([a, b]), + num_interfaces: c, + configuration_value: d, + index_configuration: e, + attributes: ConfigurationAttributes(f), + max_power: g, + }) + } else { + Err(InvalidConfiguration::UnexpectedLength) + } + } +} + +pub struct ConfigurationAttributes(u8); + +macro_rules! flag { + ($i:literal $f:ident) => { + fn $f(&self) -> bool { + self.0 & 1 << $i != 0 + } + }; +} + +impl ConfigurationAttributes { + flag!(6 self_powered); + flag!(5 remote_wakeup); +} + +impl fmt::Debug for ConfigurationAttributes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut f = f.debug_set(); + self.self_powered() + .then(|| f.entry(&format_args!("SELF_POWERED"))); + self.remote_wakeup() + .then(|| f.entry(&format_args!("REMOTE_WAKEUP"))); + f.finish() + } +} + +#[derive(Debug)] +pub enum InvalidConfiguration { + UnexpectedLength, +} diff --git a/usb_request/src/descriptor/device.rs b/usb_request/src/descriptor/device.rs new file mode 100644 index 0000000..d984a94 --- /dev/null +++ b/usb_request/src/descriptor/device.rs @@ -0,0 +1,44 @@ +#[derive(Debug)] +pub struct Device { + pub usb: u16, + pub class: u8, + pub subclass: u8, + pub protocol: u8, + pub max_packet_size_0: u8, + pub vendor: u16, + pub product: u16, + pub device: u16, + pub index_manufacturer: u8, + pub index_product: u8, + pub index_serial_number: u8, + pub num_configurations: u8, +} + +impl Device { + pub(crate) fn from_raw(buf: &[u8]) -> Result { + if buf.len() != 16 { + return Err(InvalidDevice::UnexpectedLength); + } + let f1 = |i: usize| buf[i - 2]; + let f2 = |i: usize| u16::from_le_bytes(buf[i - 2..i].try_into().unwrap()); + Ok(Device { + usb: f2(2), + class: f1(4), + subclass: f1(5), + protocol: f1(6), + max_packet_size_0: f1(7), + vendor: f2(8), + product: f2(10), + device: f2(12), + index_manufacturer: f1(14), + index_product: f1(15), + index_serial_number: f1(16), + num_configurations: f1(17), + }) + } +} + +#[derive(Debug)] +pub enum InvalidDevice { + UnexpectedLength, +} diff --git a/usb_request/src/descriptor/endpoint.rs b/usb_request/src/descriptor/endpoint.rs new file mode 100644 index 0000000..a9ba78d --- /dev/null +++ b/usb_request/src/descriptor/endpoint.rs @@ -0,0 +1,198 @@ +use core::fmt; + +#[derive(Debug)] +pub struct Endpoint { + /// The address of the endpoint on the USB device described by this descriptor. + pub address: EndpointAddress, + pub attributes: EndpointAttributes, + pub max_packet_size: u16, + pub interval: u8, +} + +impl Endpoint { + pub(crate) fn from_raw(buf: &[u8]) -> Result { + if let &[a, b, c, d, e] = buf { + Ok(Endpoint { + address: EndpointAddress::from_raw(a).ok_or(InvalidEndpoint::InvalidAddress)?, + attributes: EndpointAttributes::from_raw(b) + .ok_or(InvalidEndpoint::InvalidAttributes)?, + max_packet_size: u16::from_le_bytes([c, d]), + interval: e, + }) + } else { + Err(InvalidEndpoint::UnexpectedLength) + } + } +} + +pub struct EndpointAddress(u8); + +impl EndpointAddress { + pub fn direction(&self) -> Direction { + if self.0 & 1 << 7 == 0 { + Direction::Out + } else { + Direction::In + } + } + + pub fn number(&self) -> EndpointNumber { + use EndpointNumber::*; + match self.0 & 0xf { + 1 => N1, + 2 => N2, + 3 => N3, + 4 => N4, + 5 => N5, + 6 => N6, + 7 => N7, + 8 => N8, + 9 => N9, + 10 => N10, + 11 => N11, + 12 => N12, + 13 => N13, + 14 => N14, + 15 => N15, + _ => unreachable!(), + } + } + + fn from_raw(n: u8) -> Option { + (1..=15).contains(&(n & 0xf)).then(|| Self(n)) + } +} + +impl fmt::Debug for EndpointAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!(EndpointAddress)) + .field("direction", &self.direction()) + .field("number", &self.number()) + .finish() + } +} + +#[derive(Debug)] +pub enum EndpointNumber { + N1, + N2, + N3, + N4, + N5, + N6, + N7, + N8, + N9, + N10, + N11, + N12, + N13, + N14, + N15, +} + +impl From for usize { + fn from(n: EndpointNumber) -> usize { + use EndpointNumber::*; + match n { + N1 => 1, + N2 => 2, + N3 => 3, + N4 => 4, + N5 => 5, + N6 => 6, + N7 => 7, + N8 => 8, + N9 => 9, + N10 => 10, + N11 => 11, + N12 => 12, + N13 => 13, + N14 => 14, + N15 => 15, + } + } +} + +pub struct EndpointAttributes(u8); + +impl EndpointAttributes { + pub fn usage(&self) -> EndpointUsage { + match self.0 >> 4 & 0x3 { + 0 => EndpointUsage::Data, + 1 => EndpointUsage::Feedback, + 2 => EndpointUsage::Implicit, + _ => unreachable!(), + } + } + + pub fn sync(&self) -> EndpointSync { + match self.0 >> 2 & 0x3 { + 0 => EndpointSync::None, + 1 => EndpointSync::Async, + 2 => EndpointSync::Adapt, + 3 => EndpointSync::Sync, + _ => unreachable!(), + } + } + + pub fn transfer(&self) -> EndpointTransfer { + match self.0 & 0x3 { + 0 => EndpointTransfer::Control, + 1 => EndpointTransfer::Isoch, + 2 => EndpointTransfer::Bulk, + 3 => EndpointTransfer::Interrupt, + _ => unreachable!(), + } + } + + fn from_raw(n: u8) -> Option { + matches!(n >> 4 & 0x3, 0 | 1 | 2).then(|| Self(n)) + } +} + +impl fmt::Debug for EndpointAttributes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!(EndpointAttributes)) + .field("usage", &self.usage()) + .field("sync", &self.sync()) + .field("transfer", &self.transfer()) + .finish() + } +} + +#[derive(Debug)] +pub enum EndpointUsage { + Data, + Feedback, + Implicit, +} + +#[derive(Debug)] +pub enum EndpointSync { + None, + Async, + Adapt, + Sync, +} + +#[derive(Debug)] +pub enum EndpointTransfer { + Control, + Isoch, + Bulk, + Interrupt, +} + +#[derive(Debug)] +pub enum Direction { + In, + Out, +} + +#[derive(Debug)] +pub enum InvalidEndpoint { + UnexpectedLength, + InvalidAddress, + InvalidAttributes, +} diff --git a/usb_request/src/descriptor/hid.rs b/usb_request/src/descriptor/hid.rs new file mode 100644 index 0000000..2cf80cf --- /dev/null +++ b/usb_request/src/descriptor/hid.rs @@ -0,0 +1,43 @@ +use core::fmt; + +pub struct Hid { + pub hid_version: u16, + pub country_code: u8, + pub num_descriptors: u8, + pub ty: u8, + pub len: u16, +} + +impl Hid { + pub(crate) fn from_raw(buf: &[u8]) -> Result { + if let &[a, b, c, d, e, f, g] = buf { + Ok(Hid { + hid_version: u16::from_le_bytes([a, b]), + country_code: c, + num_descriptors: d, + ty: e, + len: u16::from_le_bytes([f, g]), + }) + } else { + Err(InvalidHid::UnexpectedLength) + } + } +} + +impl fmt::Debug for Hid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let [maj, min] = self.hid_version.to_be_bytes(); + f.debug_struct(stringify!(Hid)) + .field("hid_version", &format_args!("{:x}.{:x}", maj, min)) + .field("country_code", &self.country_code) + .field("num_descriptors", &self.num_descriptors) + .field("ty", &format_args!("{:#04x}", self.ty)) + .field("len", &self.len) + .finish() + } +} + +#[derive(Debug)] +pub enum InvalidHid { + UnexpectedLength, +} diff --git a/usb_request/src/descriptor/interface.rs b/usb_request/src/descriptor/interface.rs new file mode 100644 index 0000000..f4350a9 --- /dev/null +++ b/usb_request/src/descriptor/interface.rs @@ -0,0 +1,33 @@ +#[derive(Debug)] +pub struct Interface { + pub number: u8, + pub alternate_setting: u8, + pub num_endpoints: u8, + pub class: u8, + pub subclass: u8, + pub protocol: u8, + pub index: u8, +} + +impl Interface { + pub(crate) fn from_raw(buf: &[u8]) -> Result { + if let &[a, b, c, d, e, f, g] = buf { + Ok(Interface { + number: a, + alternate_setting: b, + num_endpoints: c, + class: d, + subclass: e, + protocol: f, + index: g, + }) + } else { + Err(InvalidInterface::UnexpectedLength) + } + } +} + +#[derive(Debug)] +pub enum InvalidInterface { + UnexpectedLength, +} diff --git a/usb_request/src/descriptor/mod.rs b/usb_request/src/descriptor/mod.rs new file mode 100644 index 0000000..56114e3 --- /dev/null +++ b/usb_request/src/descriptor/mod.rs @@ -0,0 +1,168 @@ +mod configuration; +mod device; +mod endpoint; +mod hid; +mod interface; +mod report; +mod string; + +pub use configuration::*; +pub use device::*; +pub use endpoint::*; +pub use hid::*; +pub use interface::*; +pub use report::*; +pub use string::*; + +use core::mem; + +#[derive(Debug)] +pub enum GetDescriptor { + Device, + Configuration { index: u8 }, + String { index: u8 }, + Report, +} + +pub(crate) const DEVICE: u8 = 0x1; +pub(crate) const CONFIGURATION: u8 = 0x2; +pub(crate) const STRING: u8 = 0x3; +pub(crate) const INTERFACE: u8 = 0x4; +pub(crate) const ENDPOINT: u8 = 0x5; +#[allow(dead_code)] +pub(crate) const DEVICE_QUALIFIER: u8 = 0x6; +#[allow(dead_code)] +pub(crate) const OTHER_SPEED_CONFIGURATION: u8 = 0x7; +#[allow(dead_code)] +pub(crate) const INTERFACE_POWER: u8 = 0x8; + +pub(crate) const HID: u8 = 0x21; +pub(crate) const REPORT: u8 = 0x22; +#[allow(dead_code)] +pub(crate) const PHYSICAL: u8 = 0x23; + +#[derive(Debug)] +pub enum Descriptor<'a> { + Device(Device), + Configuration(Configuration), + String(StringIter<'a>), + Interface(Interface), + Endpoint(Endpoint), + Hid(Hid), + Report(Report<'a>), + Unknown { ty: u8, data: &'a [u8] }, +} + +macro_rules! into { + ($v:ident $f:ident $t:ty) => { + pub fn $f(self) -> Option<$t> { + match self { + Self::$v(v) => Some(v), + _ => None, + } + } + }; +} + +impl<'a> Descriptor<'a> { + into!(Device into_device Device); + into!(String into_string StringIter<'a>); + into!(Configuration into_configuration Configuration); +} + +#[derive(Debug)] +pub struct StringIter<'a>(&'a [[u8; 2]]); + +impl<'a> StringIter<'a> { + pub(crate) fn from_raw(data: &'a [u8]) -> Result { + let (s, rem) = data.as_chunks(); + rem.is_empty() + .then(|| Self(s)) + .ok_or(InvalidString::UnexpectedLength) + } +} + +impl Iterator for StringIter<'_> { + type Item = u16; + + fn next(&mut self) -> Option { + self.0.split_first().map(|(c, s)| { + self.0 = s; + u16::from_le_bytes(*c) + }) + } + + fn size_hint(&self) -> (usize, Option) { + (self.len(), Some(self.len())) + } +} + +impl ExactSizeIterator for StringIter<'_> { + fn len(&self) -> usize { + self.0.len() + } +} + +#[derive(Debug)] +pub enum InvalidString { + UnexpectedLength, +} + +pub fn decode(buf: &[u8]) -> Iter<'_> { + Iter { buf } +} + +pub struct Iter<'a> { + buf: &'a [u8], +} + +impl<'a> Iterator for Iter<'a> { + type Item = Result, InvalidDescriptor>; + + fn next(&mut self) -> Option { + (!self.buf.is_empty()).then(|| { + let buf = mem::take(&mut self.buf); + let l = buf[0]; + if l < 2 || usize::from(l) > buf.len() { + return Err(InvalidDescriptor::Truncated { length: l.max(2) }); + } + let b = &buf[2..usize::from(l)]; + let r = match buf[1] { + DEVICE => { + Descriptor::Device(Device::from_raw(b).map_err(InvalidDescriptor::Device)?) + } + CONFIGURATION => Descriptor::Configuration( + Configuration::from_raw(b).map_err(InvalidDescriptor::Configuration)?, + ), + STRING => { + Descriptor::String(StringIter::from_raw(b).map_err(InvalidDescriptor::String)?) + } + INTERFACE => Descriptor::Interface( + Interface::from_raw(b).map_err(InvalidDescriptor::Interface)?, + ), + ENDPOINT => Descriptor::Endpoint( + Endpoint::from_raw(b).map_err(InvalidDescriptor::Endpoint)?, + ), + HID => Descriptor::Hid(Hid::from_raw(b).map_err(InvalidDescriptor::Hid)?), + REPORT => { + Descriptor::Report(Report::from_raw(b).map_err(InvalidDescriptor::Report)?) + } + ty => Descriptor::Unknown { ty, data: b }, + }; + self.buf = &buf[usize::from(l)..]; + Ok(r) + }) + } +} + +#[derive(Debug)] +pub enum InvalidDescriptor { + Truncated { length: u8 }, + Device(InvalidDevice), + Configuration(InvalidConfiguration), + String(InvalidString), + Interface(InvalidInterface), + Endpoint(InvalidEndpoint), + Hid(InvalidHid), + Report(InvalidReport), +} diff --git a/usb_request/src/descriptor/report.rs b/usb_request/src/descriptor/report.rs new file mode 100644 index 0000000..ef7f892 --- /dev/null +++ b/usb_request/src/descriptor/report.rs @@ -0,0 +1,22 @@ +use core::fmt; + +pub struct Report<'a> { + pub data: &'a [u8], +} + +impl<'a> Report<'a> { + pub(crate) fn from_raw(data: &'a [u8]) -> Result { + Ok(Self { data }) + } +} + +impl fmt::Debug for Report<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!(Report)) + .field("data", &format_args!("{:02x?}", self.data)) + .finish() + } +} + +#[derive(Debug)] +pub enum InvalidReport {} diff --git a/usb_request/src/descriptor/string.rs b/usb_request/src/descriptor/string.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/usb_request/src/descriptor/string.rs @@ -0,0 +1 @@ + diff --git a/usb_request/src/lib.rs b/usb_request/src/lib.rs new file mode 100644 index 0000000..ffc00e6 --- /dev/null +++ b/usb_request/src/lib.rs @@ -0,0 +1,103 @@ +#![no_std] +#![feature(slice_as_chunks)] + +pub mod descriptor; + +#[allow(dead_code)] +const GET_STATUS: u8 = 0; +#[allow(dead_code)] +const CLEAR_FEATURE: u8 = 1; +#[allow(dead_code)] +const SET_FEATURE: u8 = 3; +#[allow(dead_code)] +const SET_ADDRESS: u8 = 5; +const GET_DESCRIPTOR: u8 = 6; +#[allow(dead_code)] +const SET_DESCRIPTOR: u8 = 7; +#[allow(dead_code)] +const GET_CONFIGURATION: u8 = 8; +const SET_CONFIGURATION: u8 = 9; +#[allow(dead_code)] +const GET_INTERFACE: u8 = 10; +#[allow(dead_code)] +const SET_INTERFACE: u8 = 11; +#[allow(dead_code)] +const SYNC_FRAME: u8 = 12; + +#[derive(Debug)] +pub enum Request { + GetDescriptor { ty: descriptor::GetDescriptor }, + SetConfiguration { value: u8 }, + GetReport { id: u8 }, + SetReport, + GetIdle, + SetIdle, + SetProtocol, + GetProtocol, +} + +pub struct RawRequest { + pub request_type: u8, + pub request: u8, + pub value: u16, + pub index: u16, +} + +impl RawRequest { + pub fn direction_in(&self) -> bool { + self.request_type & request_type::DIR_IN != 0 + } +} + +mod request_type { + pub const DIR_OUT: u8 = 0 << 7; + pub const DIR_IN: u8 = 1 << 7; + + pub const TYPE_STANDARD: u8 = 0 << 5; + #[allow(dead_code)] + pub const TYPE_CLASS: u8 = 1 << 5; + #[allow(dead_code)] + pub const TYPE_VENDOR: u8 = 2 << 5; + + pub const RECIPIENT_DEVICE: u8 = 0; + pub const RECIPIENT_INTERFACE: u8 = 1; + #[allow(dead_code)] + pub const RECIPIENT_ENDPOINT: u8 = 2; + #[allow(dead_code)] + pub const RECIPIENT_OTHER: u8 = 3; +} + +impl Request { + pub fn into_raw(self) -> RawRequest { + use request_type::*; + let w_value = |ty, i| u16::from(ty) << 8 | u16::from(i); + match self { + Self::GetDescriptor { ty } => { + use descriptor::GetDescriptor::*; + RawRequest { + request_type: DIR_IN + | TYPE_STANDARD + | match ty { + Device | Configuration { .. } | String { .. } => RECIPIENT_DEVICE, + Report => RECIPIENT_INTERFACE, + }, + request: GET_DESCRIPTOR, + value: match ty { + Device => w_value(descriptor::DEVICE, 0), + Configuration { index } => w_value(descriptor::CONFIGURATION, index), + String { index } => w_value(descriptor::STRING, index), + Report => w_value(descriptor::REPORT, 0), + }, + index: 0, + } + } + Self::SetConfiguration { value } => RawRequest { + request_type: DIR_OUT | TYPE_STANDARD | RECIPIENT_DEVICE, + request: SET_CONFIGURATION, + value: value.into(), + index: 0, + }, + _ => todo!(), + } + } +}