Add usb_request crate
It has structures for representing requests & descriptors.
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"usb_report_descriptor",
|
"usb_report_descriptor",
|
||||||
|
"usb_request",
|
||||||
]
|
]
|
||||||
|
@ -3,3 +3,4 @@
|
|||||||
## Crates
|
## Crates
|
||||||
|
|
||||||
* [`usb_report_descriptor`](usb_report_descriptor) item parser for HID Report descriptors.
|
* [`usb_report_descriptor`](usb_report_descriptor) item parser for HID Report descriptors.
|
||||||
|
* [`usb_request`](usb_request) request formatter & descriptor parser.
|
||||||
|
4
usb_request/Cargo.toml
Normal file
4
usb_request/Cargo.toml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[package]
|
||||||
|
name = "usb_request"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
61
usb_request/src/descriptor/configuration.rs
Normal file
61
usb_request/src/descriptor/configuration.rs
Normal 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,
|
||||||
|
}
|
44
usb_request/src/descriptor/device.rs
Normal file
44
usb_request/src/descriptor/device.rs
Normal 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,
|
||||||
|
}
|
198
usb_request/src/descriptor/endpoint.rs
Normal file
198
usb_request/src/descriptor/endpoint.rs
Normal 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,
|
||||||
|
}
|
43
usb_request/src/descriptor/hid.rs
Normal file
43
usb_request/src/descriptor/hid.rs
Normal 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,
|
||||||
|
}
|
33
usb_request/src/descriptor/interface.rs
Normal file
33
usb_request/src/descriptor/interface.rs
Normal 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,
|
||||||
|
}
|
168
usb_request/src/descriptor/mod.rs
Normal file
168
usb_request/src/descriptor/mod.rs
Normal 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),
|
||||||
|
}
|
22
usb_request/src/descriptor/report.rs
Normal file
22
usb_request/src/descriptor/report.rs
Normal 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 {}
|
1
usb_request/src/descriptor/string.rs
Normal file
1
usb_request/src/descriptor/string.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
103
usb_request/src/lib.rs
Normal file
103
usb_request/src/lib.rs
Normal 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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user