Add usb_request crate

It has structures for representing requests & descriptors.
This commit is contained in:
David Hoppenbrouwers
2022-09-08 18:46:56 +02:00
parent 86d8fc6880
commit 82626f0075
12 changed files with 679 additions and 0 deletions

4
usb_request/Cargo.toml Normal file
View File

@ -0,0 +1,4 @@
[package]
name = "usb_request"
version = "0.1.0"
edition = "2021"

View File

@ -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<Self, InvalidConfiguration> {
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,
}

View File

@ -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<Self, InvalidDevice> {
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,
}

View File

@ -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<Endpoint, InvalidEndpoint> {
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<Self> {
(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<EndpointNumber> 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<Self> {
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,
}

View File

@ -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<Hid, InvalidHid> {
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,
}

View File

@ -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<Self, InvalidInterface> {
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,
}

View File

@ -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<StringIter, InvalidString> {
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::Item> {
self.0.split_first().map(|(c, s)| {
self.0 = s;
u16::from_le_bytes(*c)
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
(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<Descriptor<'a>, InvalidDescriptor>;
fn next(&mut self) -> Option<Self::Item> {
(!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),
}

View File

@ -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<Self, InvalidReport> {
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 {}

View File

@ -0,0 +1 @@

103
usb_request/src/lib.rs Normal file
View File

@ -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!(),
}
}
}