feat(host/steam): shippable usbip/vhci_hcd virtual Deck + client leave-shortcuts
apple / screenshots (push) Has been cancelled
android / android (push) Has been cancelled
apple / swift (push) Has been cancelled
audit / cargo-audit (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
ci / rust (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
release / apple (push) Has been cancelled
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
windows-host / package (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled

Steam Deck pass-through (design/steam-deck-passthrough-plan.md), code-complete +
all CI checks green on Linux + adversarially reviewed; on-glass validation pending:

- usbip/`vhci_hcd` virtual Deck transport (inject/linux/steam_usbip.rs) for
  non-SteamOS hosts (Bazzite/generic) — presents a real interface-2 USB Deck so
  Steam Input promotes it. In-process vhci attach (loopback OP_REQ_IMPORT handshake
  → sysfs attach) with a bounded `usbip`-CLI fallback; detach on drop.
- Backed by a vendored, libusb-free trim of the `usbip` crate
  (crates/punktfunk-host/vendor/usbip-sim, MIT + NOTICE; host/cdc/hid + rusb/nusb
  removed; interrupt-IN paced by bInterval).
- Selection ladder raw_gadget (SteamOS fast-path) → usbip (universal) → UHID,
  with PUNKTFUNK_STEAM_USBIP / PUNKTFUNK_USBIP_ATTACH knobs.
- Shared Deck descriptors + the 0x83/0xAE feature contract + a Steam-accepted
  serial consolidated into steam_proto.rs; the raw_gadget backend reuses them.
- Linux client leave-shortcuts: Ctrl+Alt+Shift+D + holding the escape chord
  (L1+R1+Start+Select) >=1.5s end the session (short press still exits
  fullscreen); the chord state resets across sessions.

Also bundles in-progress work already staged in the tree:
- host(kwin): xdg-output logical-geometry mapping so the KWin fake_input backend
  places absolute coordinates correctly under display scaling.
- docs: design/README index entries + design/controller-only-mode.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 19:17:00 +00:00
parent 831b37b4b7
commit 580b1ea7a7
26 changed files with 3292 additions and 145 deletions
+122
View File
@@ -0,0 +1,122 @@
use super::*;
/// A list of known USB speeds
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum UsbSpeed {
Unknown = 0x0,
Low,
Full,
High,
Wireless,
Super,
SuperPlus,
}
/// A list of defined USB class codes
// https://www.usb.org/defined-class-codes
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ClassCode {
SeeInterface = 0,
Audio,
CDC,
HID,
Physical = 0x05,
Image,
Printer,
MassStorage,
Hub,
CDCData,
SmartCard,
ContentSecurity = 0x0D,
Video,
PersonalHealthcare,
AudioVideo,
Billboard,
TypeCBridge,
Diagnostic = 0xDC,
WirelessController = 0xE0,
Misc = 0xEF,
ApplicationSpecific = 0xFE,
VendorSpecific = 0xFF,
}
/// A list of defined USB endpoint attributes
#[derive(Copy, Clone, Debug, FromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum EndpointAttributes {
Control = 0,
Isochronous,
Bulk,
Interrupt,
}
/// USB endpoint direction: IN or OUT.
///
/// Upstream re-exported `rusb::Direction`; vendored locally so this crate carries no libusb
/// dependency. `UsbEndpoint::direction()` returns this, and `device.rs` matches on the variants.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Direction {
/// Host → device (`bEndpointAddress` bit 7 clear).
Out,
/// Device → host (`bEndpointAddress` bit 7 set).
In,
}
/// Emulated max packet size of EP0
pub const EP0_MAX_PACKET_SIZE: u16 = 64;
/// A list of defined USB standard requests
/// from USB 2.0 standard Table 9.4. Standard Request Codes
#[derive(Copy, Clone, Debug, FromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum StandardRequest {
GetStatus = 0,
ClearFeature = 1,
SetFeature = 3,
SetAddress = 5,
GetDescriptor = 6,
SetDescriptor = 7,
GetConfiguration = 8,
SetConfiguration = 9,
GetInterface = 10,
SetInterface = 11,
SynchFrame = 12,
}
/// A list of defined USB descriptor types
/// from USB 2.0 standard Table 9.5. Descriptor Types
#[derive(Copy, Clone, Debug, FromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum DescriptorType {
/// DEVICE
Device = 1,
/// CONFIGURATION
Configuration = 2,
/// STRING
String = 3,
/// INTERFACE
Interface = 4,
/// ENDPOINT
Endpoint = 5,
/// DEVICE_QUALIFIER
DeviceQualifier = 6,
/// OTHER_SPEED_CONFIGURATION
OtherSpeedConfiguration = 7,
/// INTERFACE_POINTER
InterfacePower = 8,
/// OTG
OTG = 9,
/// DEBUG
Debug = 0xA,
/// INTERFACE_ASSOCIATION
InterfaceAssociation = 0xB,
/// BOS
BOS = 0xF,
// DEVICE CAPABILITY
DeviceCapability = 0x10,
/// SUPERSPEED_USB_ENDPOINT_COMPANION
SuperspeedUsbEndpointCompanion = 0x30,
}
+555
View File
@@ -0,0 +1,555 @@
use super::*;
#[derive(Clone, Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Version {
pub major: u8,
pub minor: u8,
pub patch: u8,
}
// (Upstream's `From<rusb::Version>` conversions removed — this crate has no libusb dependency.)
/// bcdDevice
impl From<u16> for Version {
fn from(value: u16) -> Self {
Self {
major: (value >> 8) as u8,
minor: ((value >> 4) & 0xF) as u8,
patch: (value & 0xF) as u8,
}
}
}
/// Represent a USB device
#[derive(Clone, Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct UsbDevice {
pub path: String,
pub bus_id: String,
pub bus_num: u32,
pub dev_num: u32,
pub speed: u32,
pub vendor_id: u16,
pub product_id: u16,
pub device_bcd: Version,
pub device_class: u8,
pub device_subclass: u8,
pub device_protocol: u8,
pub configuration_value: u8,
pub num_configurations: u8,
pub interfaces: Vec<UsbInterface>,
#[cfg_attr(feature = "serde", serde(skip))]
pub device_handler: Option<Arc<Mutex<Box<dyn UsbDeviceHandler + Send>>>>,
pub usb_version: Version,
pub(crate) ep0_in: UsbEndpoint,
pub(crate) ep0_out: UsbEndpoint,
// strings
pub(crate) string_pool: HashMap<u8, String>,
pub(crate) string_configuration: u8,
pub(crate) string_manufacturer: u8,
pub(crate) string_product: u8,
pub(crate) string_serial: u8,
}
impl UsbDevice {
pub fn new(index: u32) -> Self {
let mut res = Self {
path: "/sys/bus/0/0/0".to_string(),
bus_id: "0-0-0".to_string(),
dev_num: index,
speed: UsbSpeed::High as u32,
ep0_in: UsbEndpoint {
address: 0x80,
attributes: EndpointAttributes::Control as u8,
max_packet_size: EP0_MAX_PACKET_SIZE,
interval: 0,
},
ep0_out: UsbEndpoint {
address: 0x00,
attributes: EndpointAttributes::Control as u8,
max_packet_size: EP0_MAX_PACKET_SIZE,
interval: 0,
},
// configured by default
configuration_value: 1,
num_configurations: 1,
..Self::default()
};
res.string_configuration = res.new_string("Default Configuration");
res.string_manufacturer = res.new_string("Manufacturer");
res.string_product = res.new_string("Product");
res.string_serial = res.new_string("Serial");
res
}
/// Returns the old value, if present.
pub fn set_configuration_name(&mut self, name: &str) -> Option<String> {
let old = (self.string_configuration != 0)
.then(|| self.string_pool.remove(&self.string_configuration))
.flatten();
self.string_configuration = self.new_string(name);
old
}
/// Unset configuration name and returns the old value, if present.
pub fn unset_configuration_name(&mut self) -> Option<String> {
let old = (self.string_configuration != 0)
.then(|| self.string_pool.remove(&self.string_configuration))
.flatten();
self.string_configuration = 0;
old
}
/// Returns the old value, if present.
pub fn set_serial_number(&mut self, name: &str) -> Option<String> {
let old = (self.string_serial != 0)
.then(|| self.string_pool.remove(&self.string_serial))
.flatten();
self.string_serial = self.new_string(name);
old
}
/// Unset serial number and returns the old value, if present.
pub fn unset_serial_number(&mut self) -> Option<String> {
let old = (self.string_serial != 0)
.then(|| self.string_pool.remove(&self.string_serial))
.flatten();
self.string_serial = 0;
old
}
/// Returns the old value, if present.
pub fn set_product_name(&mut self, name: &str) -> Option<String> {
let old = (self.string_product != 0)
.then(|| self.string_pool.remove(&self.string_product))
.flatten();
self.string_product = self.new_string(name);
old
}
/// Unset product name and returns the old value, if present.
pub fn unset_product_name(&mut self) -> Option<String> {
let old = (self.string_product != 0)
.then(|| self.string_pool.remove(&self.string_product))
.flatten();
self.string_product = 0;
old
}
/// Returns the old value, if present.
pub fn set_manufacturer_name(&mut self, name: &str) -> Option<String> {
let old = (self.string_manufacturer != 0)
.then(|| self.string_pool.remove(&self.string_manufacturer))
.flatten();
self.string_manufacturer = self.new_string(name);
old
}
/// Unset manufacturer name and returns the old value, if present.
pub fn unset_manufacturer_name(&mut self) -> Option<String> {
let old = (self.string_manufacturer != 0)
.then(|| self.string_pool.remove(&self.string_manufacturer))
.flatten();
self.string_manufacturer = 0;
old
}
pub fn with_interface(
mut self,
interface_class: u8,
interface_subclass: u8,
interface_protocol: u8,
name: Option<&str>,
endpoints: Vec<UsbEndpoint>,
handler: Arc<Mutex<Box<dyn UsbInterfaceHandler + Send>>>,
) -> Self {
let string_interface = name.map(|name| self.new_string(name)).unwrap_or(0);
let class_specific_descriptor = handler.lock().unwrap().get_class_specific_descriptor();
self.interfaces.push(UsbInterface {
interface_class,
interface_subclass,
interface_protocol,
endpoints,
string_interface,
class_specific_descriptor,
handler,
});
self
}
pub fn with_device_handler(
mut self,
handler: Arc<Mutex<Box<dyn UsbDeviceHandler + Send>>>,
) -> Self {
self.device_handler = Some(handler);
self
}
pub(crate) fn new_string(&mut self, s: &str) -> u8 {
for i in 1.. {
if let std::collections::hash_map::Entry::Vacant(e) = self.string_pool.entry(i) {
e.insert(s.to_string());
return i;
}
}
panic!("string poll exhausted")
}
pub(crate) fn find_ep(&self, ep: u8) -> Option<(UsbEndpoint, Option<&UsbInterface>)> {
if ep == self.ep0_in.address {
Some((self.ep0_in, None))
} else if ep == self.ep0_out.address {
Some((self.ep0_out, None))
} else {
for intf in &self.interfaces {
for endpoint in &intf.endpoints {
if endpoint.address == ep {
return Some((*endpoint, Some(intf)));
}
}
}
None
}
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut result = Vec::with_capacity(312);
let mut path = self.path.as_bytes().to_vec();
debug_assert!(path.len() <= 256);
path.resize(256, 0);
result.extend_from_slice(path.as_slice());
let mut bus_id = self.bus_id.as_bytes().to_vec();
debug_assert!(bus_id.len() <= 32);
bus_id.resize(32, 0);
result.extend_from_slice(bus_id.as_slice());
result.extend_from_slice(&self.bus_num.to_be_bytes());
result.extend_from_slice(&self.dev_num.to_be_bytes());
result.extend_from_slice(&self.speed.to_be_bytes());
result.extend_from_slice(&self.vendor_id.to_be_bytes());
result.extend_from_slice(&self.product_id.to_be_bytes());
result.push(self.device_bcd.major);
result.push(self.device_bcd.minor);
result.push(self.device_class);
result.push(self.device_subclass);
result.push(self.device_protocol);
result.push(self.configuration_value);
result.push(self.num_configurations);
result.push(self.interfaces.len() as u8);
result
}
pub(crate) fn to_bytes_with_interfaces(&self) -> Vec<u8> {
let mut result = self.to_bytes();
result.reserve(4 * self.interfaces.len());
for intf in &self.interfaces {
result.push(intf.interface_class);
result.push(intf.interface_subclass);
result.push(intf.interface_protocol);
result.push(0); // padding
}
result
}
pub(crate) async fn handle_urb(
&self,
ep: UsbEndpoint,
intf: Option<&UsbInterface>,
transfer_buffer_length: u32,
setup_packet: SetupPacket,
out_data: &[u8],
) -> Result<Vec<u8>> {
use DescriptorType::*;
use Direction::*;
use EndpointAttributes::*;
use StandardRequest::*;
match (FromPrimitive::from_u8(ep.attributes), ep.direction()) {
(Some(Control), In) => {
// control in
debug!("Control IN setup={setup_packet:x?}");
match (
setup_packet.request_type,
FromPrimitive::from_u8(setup_packet.request),
) {
(0b10000000, Some(GetDescriptor)) => {
// high byte: type
match FromPrimitive::from_u16(setup_packet.value >> 8) {
Some(Device) => {
debug!("Get device descriptor");
// Standard Device Descriptor
let mut desc = vec![
0x12, // bLength
Device as u8, // bDescriptorType: Device
self.usb_version.minor,
self.usb_version.major, // bcdUSB: USB 2.0
self.device_class, // bDeviceClass
self.device_subclass, // bDeviceSubClass
self.device_protocol, // bDeviceProtocol
self.ep0_in.max_packet_size as u8, // bMaxPacketSize0
self.vendor_id as u8, // idVendor
(self.vendor_id >> 8) as u8,
self.product_id as u8, // idProduct
(self.product_id >> 8) as u8,
self.device_bcd.minor, // bcdDevice
self.device_bcd.major,
self.string_manufacturer, // iManufacturer
self.string_product, // iProduct
self.string_serial, // iSerial
self.num_configurations, // bNumConfigurations
];
// requested len too short: wLength < real length
if setup_packet.length < desc.len() as u16 {
desc.resize(setup_packet.length as usize, 0);
}
Ok(desc)
}
Some(BOS) => {
debug!("Get BOS descriptor");
let mut desc = vec![
0x05, // bLength
BOS as u8, // bDescriptorType: BOS
0x05, 0x00, // wTotalLength
0x00, // bNumCapabilities
];
// requested len too short: wLength < real length
if setup_packet.length < desc.len() as u16 {
desc.resize(setup_packet.length as usize, 0);
}
Ok(desc)
}
Some(Configuration) => {
debug!("Get configuration descriptor");
// Standard Configuration Descriptor
let mut desc = vec![
0x09, // bLength
Configuration as u8, // bDescriptorType: Configuration
0x00,
0x00, // wTotalLength: to be filled below
self.interfaces.len() as u8, // bNumInterfaces
self.configuration_value, // bConfigurationValue
self.string_configuration, // iConfiguration
0x80, // bmAttributes: Bus Powered
0x32, // bMaxPower: 100mA
];
for (i, intf) in self.interfaces.iter().enumerate() {
let mut intf_desc = vec![
0x09, // bLength
Interface as u8, // bDescriptorType: Interface
i as u8, // bInterfaceNum
0x00, // bAlternateSettings
intf.endpoints.len() as u8, // bNumEndpoints
intf.interface_class, // bInterfaceClass
intf.interface_subclass, // bInterfaceSubClass
intf.interface_protocol, // bInterfaceProtocol
intf.string_interface, //iInterface
];
// class specific endpoint
let mut specific = intf.class_specific_descriptor.clone();
intf_desc.append(&mut specific);
// endpoint descriptors
for endpoint in &intf.endpoints {
let mut ep_desc = vec![
0x07, // bLength
Endpoint as u8, // bDescriptorType: Endpoint
endpoint.address, // bEndpointAddress
endpoint.attributes, // bmAttributes
endpoint.max_packet_size as u8,
(endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize
endpoint.interval, // bInterval
];
intf_desc.append(&mut ep_desc);
}
desc.append(&mut intf_desc);
}
// length
let len = desc.len() as u16;
desc[2] = len as u8;
desc[3] = (len >> 8) as u8;
// requested len too short: wLength < real length
if setup_packet.length < desc.len() as u16 {
desc.resize(setup_packet.length as usize, 0);
}
Ok(desc)
}
Some(String) => {
debug!("Get string descriptor");
let index = setup_packet.value as u8;
if index == 0 {
// String Descriptor Zero, Specifying Languages Supported by the Device
// language ids
let mut desc = vec![
4, // bLength
DescriptorType::String as u8, // bDescriptorType
0x09,
0x04, // wLANGID[0], en-US
];
// requested len too short: wLength < real length
if setup_packet.length < desc.len() as u16 {
desc.resize(setup_packet.length as usize, 0);
}
Ok(desc)
} else if let Some(s) = &self.string_pool.get(&index) {
// UNICODE String Descriptor
let bytes: Vec<u16> = s.encode_utf16().collect();
let mut desc = vec![
2 + bytes.len() as u8 * 2, // bLength
DescriptorType::String as u8, // bDescriptorType
];
for byte in bytes {
desc.push(byte as u8);
desc.push((byte >> 8) as u8);
}
// requested len too short: wLength < real length
if setup_packet.length < desc.len() as u16 {
desc.resize(setup_packet.length as usize, 0);
}
Ok(desc)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("Invalid string index: {index}"),
))
}
}
Some(DeviceQualifier) => {
debug!("Get device qualifier descriptor");
// Device_Qualifier Descriptor
let mut desc = vec![
0x0A, // bLength
DeviceQualifier as u8, // bDescriptorType: Device Qualifier
self.usb_version.minor,
self.usb_version.major, // bcdUSB
self.device_class, // bDeviceClass
self.device_subclass, // bDeviceSUbClass
self.device_protocol, // bDeviceProtocol
self.ep0_in.max_packet_size as u8, // bMaxPacketSize0
self.num_configurations, // bNumConfigurations
0x00, // bReserved
];
// requested len too short: wLength < real length
if setup_packet.length < desc.len() as u16 {
desc.resize(setup_packet.length as usize, 0);
}
Ok(desc)
}
_ => {
warn!("unknown desc type: {setup_packet:x?}");
Ok(vec![])
}
}
}
_ if setup_packet.request_type & 0xF == 1 => {
// to interface
// see https://www.beyondlogic.org/usbnutshell/usb6.shtml
// only low 8 bits are valid
let intf = &self.interfaces[setup_packet.index as usize & 0xFF];
let mut handler = intf.handler.lock().unwrap();
handler.handle_urb(intf, ep, transfer_buffer_length, setup_packet, out_data)
}
_ if setup_packet.request_type & 0xF == 0 && self.device_handler.is_some() => {
// to device
// see https://www.beyondlogic.org/usbnutshell/usb6.shtml
let lock = self.device_handler.as_ref().unwrap();
let mut handler = lock.lock().unwrap();
handler.handle_urb(transfer_buffer_length, setup_packet, out_data)
}
_ => unimplemented!("control in"),
}
}
(Some(Control), Out) => {
// control out
debug!("Control OUT setup={setup_packet:x?}");
match (
setup_packet.request_type,
FromPrimitive::from_u8(setup_packet.request),
) {
(0b00000000, Some(SetConfiguration)) => {
let mut desc = vec![
self.configuration_value, // bConfigurationValue
];
// requested len too short: wLength < real length
if setup_packet.length < desc.len() as u16 {
desc.resize(setup_packet.length as usize, 0);
}
Ok(desc)
}
_ if setup_packet.request_type & 0xF == 1 => {
// to interface
// see https://www.beyondlogic.org/usbnutshell/usb6.shtml
// only low 8 bits are valid
let intf = &self.interfaces[setup_packet.index as usize & 0xFF];
let mut handler = intf.handler.lock().unwrap();
handler.handle_urb(intf, ep, transfer_buffer_length, setup_packet, out_data)
}
_ if setup_packet.request_type & 0xF == 0 && self.device_handler.is_some() => {
// to device
// see https://www.beyondlogic.org/usbnutshell/usb6.shtml
let lock = self.device_handler.as_ref().unwrap();
let mut handler = lock.lock().unwrap();
handler.handle_urb(transfer_buffer_length, setup_packet, out_data)
}
_ => unimplemented!("control out"),
}
}
(Some(_), _) => {
// others (interrupt / bulk / iso transfers to an endpoint)
// punktfunk modification: pace IN transfers by bInterval so a virtual interrupt-IN
// endpoint mimics a real device's NAK-until-bInterval behaviour instead of
// free-running as fast as the transport allows (vhci_hcd does not throttle the
// server side, so an unpaced sim would spin the loopback link). HS bInterval N →
// 2^(N-1) microframes × 125µs.
if let In = ep.direction() {
let n = ep.interval.clamp(1, 16) as u32;
let period_us = (1u32 << (n - 1)) * 125;
tokio::time::sleep(std::time::Duration::from_micros(period_us as u64)).await;
}
let intf = intf.unwrap();
let mut handler = intf.handler.lock().unwrap();
handler.handle_urb(intf, ep, transfer_buffer_length, setup_packet, out_data)
}
_ => unimplemented!("transfer to {:?}", ep),
}
}
}
/// A handler for URB targeting the device
pub trait UsbDeviceHandler: std::fmt::Debug {
/// Handle a URB(USB Request Block) targeting at this device
///
/// When the lower 4 bits of `bmRequestType` is zero and the URB is not handled by the library, this function is called.
/// The resulting data should not exceed `transfer_buffer_length`
fn handle_urb(
&mut self,
transfer_buffer_length: u32,
setup: SetupPacket,
req: &[u8],
) -> Result<Vec<u8>>;
/// Helper to downcast to actual struct
///
/// Please implement it as:
/// ```ignore
/// fn as_any(&mut self) -> &mut dyn Any {
/// self
/// }
/// ```
fn as_any(&mut self) -> &mut dyn Any;
}
// (In-crate test module removed in the vendored copy — see NOTICE.)
+31
View File
@@ -0,0 +1,31 @@
use super::*;
/// Represent a USB endpoint
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UsbEndpoint {
/// bEndpointAddress
pub address: u8,
/// bmAttributes
pub attributes: u8,
/// wMaxPacketSize
pub max_packet_size: u16,
/// bInterval
pub interval: u8,
}
impl UsbEndpoint {
/// Get direction from MSB of address
pub fn direction(&self) -> Direction {
if self.address & 0x80 != 0 {
Direction::In
} else {
Direction::Out
}
}
/// Whether this is endpoint zero
pub fn is_ep0(&self) -> bool {
self.address & 0x7F == 0
}
}
+45
View File
@@ -0,0 +1,45 @@
use super::*;
/// Represent a USB interface
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct UsbInterface {
pub interface_class: u8,
pub interface_subclass: u8,
pub interface_protocol: u8,
pub endpoints: Vec<UsbEndpoint>,
pub string_interface: u8,
pub class_specific_descriptor: Vec<u8>,
#[cfg_attr(feature = "serde", serde(skip))]
pub handler: Arc<Mutex<Box<dyn UsbInterfaceHandler + Send>>>,
}
/// A handler of a custom usb interface
pub trait UsbInterfaceHandler: std::fmt::Debug {
/// Return the class specific descriptor which is inserted between interface descriptor and endpoint descriptor
fn get_class_specific_descriptor(&self) -> Vec<u8>;
/// Handle a URB(USB Request Block) targeting at this interface
///
/// Can be one of: control transfer to ep0 or other types of transfer to its endpoint.
/// The resulting data should not exceed `transfer_buffer_length`.
fn handle_urb(
&mut self,
interface: &UsbInterface,
ep: UsbEndpoint,
transfer_buffer_length: u32,
setup: SetupPacket,
req: &[u8],
) -> Result<Vec<u8>>;
/// Helper to downcast to actual struct
///
/// Please implement it as:
/// ```ignore
/// fn as_any(&mut self) -> &mut dyn Any {
/// self
/// }
/// ```
fn as_any(&mut self) -> &mut dyn Any;
}
+250
View File
@@ -0,0 +1,250 @@
//! A USB/IP server (simulation path only).
//!
//! Vendored + trimmed from `usbip` v0.8.0 (jiegec/usbip, MIT); the USB *host* modules and the
//! `rusb`/`nusb` device constructors are removed so this carries no libusb dependency. See `NOTICE`.
use log::*;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::any::Any;
use std::collections::HashMap;
use std::io::{ErrorKind, Result};
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener;
use tokio::sync::RwLock;
use usbip_protocol::UsbIpCommand;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
mod consts;
mod device;
mod endpoint;
mod interface;
mod setup;
pub mod usbip_protocol;
mod util;
pub use consts::*;
pub use device::*;
pub use endpoint::*;
pub use interface::*;
pub use setup::*;
pub use util::*;
use crate::usbip_protocol::{UsbIpResponse, USBIP_RET_SUBMIT, USBIP_RET_UNLINK};
/// Main struct of a USB/IP server
#[derive(Default, Debug)]
pub struct UsbIpServer {
available_devices: RwLock<Vec<UsbDevice>>,
used_devices: RwLock<HashMap<String, UsbDevice>>,
}
impl UsbIpServer {
/// Create a [UsbIpServer] with simulated devices
pub fn new_simulated(devices: Vec<UsbDevice>) -> Self {
Self {
available_devices: RwLock::new(devices),
used_devices: RwLock::new(HashMap::new()),
}
}
pub async fn add_device(&self, device: UsbDevice) {
self.available_devices.write().await.push(device);
}
pub async fn remove_device(&self, bus_id: &str) -> Result<()> {
let mut available_devices = self.available_devices.write().await;
if let Some(device) = available_devices.iter().position(|d| d.bus_id == bus_id) {
available_devices.remove(device);
Ok(())
} else if let Some(device) = self
.used_devices
.read()
.await
.values()
.find(|d| d.bus_id == bus_id)
{
Err(std::io::Error::other(format!(
"Device {} is in use",
device.bus_id
)))
} else {
Err(std::io::Error::new(
ErrorKind::NotFound,
format!("Device {bus_id} not found"),
))
}
}
}
pub async fn handler<T: AsyncReadExt + AsyncWriteExt + Unpin>(
mut socket: &mut T,
server: Arc<UsbIpServer>,
) -> Result<()> {
let mut current_import_device_id: Option<String> = None;
loop {
let command = UsbIpCommand::read_from_socket(&mut socket).await;
if let Err(err) = command {
if let Some(dev_id) = current_import_device_id {
let mut used_devices = server.used_devices.write().await;
let mut available_devices = server.available_devices.write().await;
match used_devices.remove(&dev_id) {
Some(dev) => available_devices.push(dev),
None => unreachable!(),
}
}
if err.kind() == ErrorKind::UnexpectedEof {
info!("Remote closed the connection");
return Ok(());
} else {
return Err(err);
}
}
let used_devices = server.used_devices.read().await;
let mut current_import_device = current_import_device_id
.clone()
.and_then(|ref id| used_devices.get(id));
match command.unwrap() {
UsbIpCommand::OpReqDevlist { .. } => {
trace!("Got OP_REQ_DEVLIST");
let devices = server.available_devices.read().await;
// OP_REP_DEVLIST
UsbIpResponse::op_rep_devlist(&devices)
.write_to_socket(socket)
.await?;
trace!("Sent OP_REP_DEVLIST");
}
UsbIpCommand::OpReqImport { busid, .. } => {
trace!("Got OP_REQ_IMPORT");
current_import_device_id = None;
current_import_device = None;
std::mem::drop(used_devices);
let mut used_devices = server.used_devices.write().await;
let mut available_devices = server.available_devices.write().await;
let busid_compare =
&busid[..busid.iter().position(|&x| x == 0).unwrap_or(busid.len())];
for (i, dev) in available_devices.iter().enumerate() {
if busid_compare == dev.bus_id.as_bytes() {
let dev = available_devices.remove(i);
let dev_id = dev.bus_id.clone();
used_devices.insert(dev.bus_id.clone(), dev);
current_import_device_id = dev_id.clone().into();
current_import_device = Some(used_devices.get(&dev_id).unwrap());
break;
}
}
let res = if let Some(dev) = current_import_device {
UsbIpResponse::op_rep_import_success(dev)
} else {
UsbIpResponse::op_rep_import_fail()
};
res.write_to_socket(socket).await?;
trace!("Sent OP_REP_IMPORT");
}
UsbIpCommand::UsbIpCmdSubmit {
mut header,
transfer_buffer_length,
setup,
data,
..
} => {
trace!("Got USBIP_CMD_SUBMIT");
let device = current_import_device.unwrap();
let out = header.direction == 0;
let real_ep = if out { header.ep } else { header.ep | 0x80 };
header.command = USBIP_RET_SUBMIT.into();
let res = match device.find_ep(real_ep as u8) {
None => {
warn!("Endpoint {real_ep:02x?} not found");
UsbIpResponse::usbip_ret_submit_fail(&header)
}
Some((ep, intf)) => {
trace!("->Endpoint {ep:02x?}");
trace!("->Setup {setup:02x?}");
trace!("->Request {data:02x?}");
let resp = device
.handle_urb(
ep,
intf,
transfer_buffer_length,
SetupPacket::parse(&setup),
&data,
)
.await;
match resp {
Ok(resp) => {
if out {
trace!("<-Wrote {}", data.len());
} else {
trace!("<-Resp {resp:02x?}");
}
UsbIpResponse::usbip_ret_submit_success(&header, 0, 0, resp, vec![])
}
Err(err) => {
warn!("Error handling URB: {err}");
UsbIpResponse::usbip_ret_submit_fail(&header)
}
}
}
};
res.write_to_socket(socket).await?;
trace!("Sent USBIP_RET_SUBMIT");
}
UsbIpCommand::UsbIpCmdUnlink {
mut header,
unlink_seqnum,
} => {
trace!("Got USBIP_CMD_UNLINK for {unlink_seqnum:10x?}");
header.command = USBIP_RET_UNLINK.into();
let res = UsbIpResponse::usbip_ret_unlink_success(&header);
res.write_to_socket(socket).await?;
trace!("Sent USBIP_RET_UNLINK");
}
}
}
}
/// Spawn a USB/IP server at `addr` using [TcpListener]
pub async fn server(addr: SocketAddr, server: Arc<UsbIpServer>) {
let listener = TcpListener::bind(addr).await.expect("bind to addr");
let server = async move {
loop {
match listener.accept().await {
Ok((mut socket, _addr)) => {
info!("Got connection from {:?}", socket.peer_addr());
let new_server = server.clone();
tokio::spawn(async move {
let res = handler(&mut socket, new_server).await;
info!("Handler ended with {res:?}");
});
}
Err(err) => {
warn!("Got error {err:?}");
}
}
}
};
server.await
}
// (Host-mode constructors and in-crate tests removed in the vendored copy — see NOTICE.)
+31
View File
@@ -0,0 +1,31 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Parse the SETUP packet of control transfers
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SetupPacket {
/// bmRequestType
pub request_type: u8,
/// bRequest
pub request: u8,
/// wValue
pub value: u16,
/// wIndex
pub index: u16,
/// wLength
pub length: u16,
}
impl SetupPacket {
/// Parse a [SetupPacket] from raw setup packet
pub fn parse(setup: &[u8; 8]) -> SetupPacket {
SetupPacket {
request_type: setup[0],
request: setup[1],
value: ((setup[3] as u16) << 8) | (setup[2] as u16),
index: ((setup[5] as u16) << 8) | (setup[4] as u16),
length: ((setup[7] as u16) << 8) | (setup[6] as u16),
}
}
}
@@ -0,0 +1,498 @@
//! USB/IP protocol structs
//!
//! This module contains declarations of all structs used in the USB/IP protocol,
//! as well as functions to serialize and deserialize them to/from byte arrays,
//! and functions to send and receive them over a socket.
//!
//! They are based on the [Linux kernel documentation](https://docs.kernel.org/usb/usbip_protocol.html).
use log::trace;
use std::io::Result;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::UsbDevice;
/// USB/IP protocol version
///
/// This is currently the only supported version of USB/IP
/// for this library.
pub const USBIP_VERSION: u16 = 0x0111;
/// Command code: Retrieve the list of exported USB devices
pub const OP_REQ_DEVLIST: u16 = 0x8005;
/// Command code: import a remote USB device
pub const OP_REQ_IMPORT: u16 = 0x8003;
/// Reply code: The list of exported USB devices
pub const OP_REP_DEVLIST: u16 = 0x0005;
/// Reply code: Reply to import
pub const OP_REP_IMPORT: u16 = 0x0003;
/// Command code: Submit an URB
pub const USBIP_CMD_SUBMIT: u16 = 0x0001;
/// Command code: Unlink an URB
pub const USBIP_CMD_UNLINK: u16 = 0x0002;
/// Reply code: Reply for submitting an URB
pub const USBIP_RET_SUBMIT: u16 = 0x0003;
/// Reply code: Reply for URB unlink
pub const USBIP_RET_UNLINK: u16 = 0x0004;
/// USB/IP direction
///
/// NOTE: Must not be confused with rusb::Direction,
/// which has the opposite enum values. This is only for
/// internal use in the USB/IP protocol.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Direction {
Out = 0,
In = 1,
}
/// Common header for all context sensitive packets
///
/// All commands/responses which rely on a device being attached
/// to a client use this header.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UsbIpHeaderBasic {
pub command: u32,
pub seqnum: u32,
pub devid: u32,
pub direction: u32,
pub ep: u32,
}
impl UsbIpHeaderBasic {
/// Converts a byte array into a [UsbIpHeaderBasic].
pub fn from_bytes(bytes: &[u8; 20]) -> Self {
let result = UsbIpHeaderBasic {
command: u32::from_be_bytes(bytes[0..4].try_into().unwrap()),
seqnum: u32::from_be_bytes(bytes[4..8].try_into().unwrap()),
devid: u32::from_be_bytes(bytes[8..12].try_into().unwrap()),
direction: u32::from_be_bytes(bytes[12..16].try_into().unwrap()),
ep: u32::from_be_bytes(bytes[16..20].try_into().unwrap()),
};
// The direction should be 0 or 1
debug_assert!(result.direction & 1 == result.direction);
result
}
/// Converts the [UsbIpHeaderBasic] into a byte array.
pub fn to_bytes(&self) -> [u8; 20] {
let mut result = [0u8; 20];
result[0..4].copy_from_slice(&self.command.to_be_bytes());
result[4..8].copy_from_slice(&self.seqnum.to_be_bytes());
result[8..12].copy_from_slice(&self.devid.to_be_bytes());
result[12..16].copy_from_slice(&self.direction.to_be_bytes());
result[16..20].copy_from_slice(&self.ep.to_be_bytes());
result
}
pub(crate) async fn read_from_socket_with_command<T: AsyncReadExt + Unpin>(
socket: &mut T,
command: u16,
) -> Result<Self> {
let seqnum = socket.read_u32().await?;
let devid = socket.read_u32().await?;
let direction = socket.read_u32().await?;
// The direction should be 0 or 1
debug_assert!(direction & 1 == direction);
let ep = socket.read_u32().await?;
Ok(UsbIpHeaderBasic {
command: command.into(),
seqnum,
devid,
direction,
ep,
})
}
}
/// Client side commands from the Virtual Host Controller
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum UsbIpCommand {
OpReqDevlist {
status: u32,
},
OpReqImport {
status: u32,
busid: [u8; 32],
},
UsbIpCmdSubmit {
header: UsbIpHeaderBasic,
transfer_flags: u32,
transfer_buffer_length: u32,
start_frame: u32,
number_of_packets: u32,
interval: u32,
setup: [u8; 8],
data: Vec<u8>,
iso_packet_descriptor: Vec<u8>,
},
UsbIpCmdUnlink {
header: UsbIpHeaderBasic,
unlink_seqnum: u32,
},
}
impl UsbIpCommand {
/// Constructs a [UsbIpCommand] from a socket
///
/// This will consume a variable amount of bytes from the socket.
/// It might fail if the bytes does not follow the USB/IP protocol properly.
pub async fn read_from_socket<T: AsyncReadExt + Unpin>(socket: &mut T) -> Result<UsbIpCommand> {
let version: u16 = socket.read_u16().await?;
if version != 0 && version != USBIP_VERSION {
return Err(std::io::Error::other(format!(
"Unknown version: {version:#04X}"
)));
}
let command: u16 = socket.read_u16().await?;
trace!(
"Received command: {:#04X} ({}), parsing...",
command,
match command {
OP_REQ_DEVLIST => "OP_REQ_DEVLIST",
OP_REQ_IMPORT => "OP_REQ_IMPORT",
USBIP_CMD_SUBMIT => "USBIP_CMD_SUBMIT",
USBIP_CMD_UNLINK => "USBIP_CMD_UNLINK",
_ => "Unknown",
}
);
match command {
OP_REQ_DEVLIST => {
let status = socket.read_u32().await?;
debug_assert!(status == 0);
Ok(UsbIpCommand::OpReqDevlist { status })
}
OP_REQ_IMPORT => {
let status = socket.read_u32().await?;
debug_assert!(status == 0);
let mut busid = [0; 32];
socket.read_exact(&mut busid).await?;
Ok(UsbIpCommand::OpReqImport { status, busid })
}
USBIP_CMD_SUBMIT => {
let header =
UsbIpHeaderBasic::read_from_socket_with_command(socket, USBIP_CMD_SUBMIT)
.await?;
let transfer_flags = socket.read_u32().await?;
let transfer_buffer_length = socket.read_u32().await?;
let start_frame = socket.read_u32().await?;
let number_of_packets = socket.read_u32().await?;
let interval = socket.read_u32().await?;
let mut setup = [0; 8];
socket.read_exact(&mut setup).await?;
let data = if header.direction == Direction::In as u32 {
vec![]
} else {
let mut data = vec![0; transfer_buffer_length as usize];
socket.read_exact(&mut data).await?;
data
};
// The kernel docs specifies that this should be set to 0xFFFFFFFF for all
// non-ISO packets, however the actual implementation resorts to 0x00000000
// https://stackoverflow.com/questions/76899798/usb-ip-what-is-the-size-of-the-iso-packet-descriptor
let iso_packet_descriptor =
if number_of_packets != 0 && number_of_packets != 0xFFFFFFFF {
let mut result = vec![0; 16 * number_of_packets as usize];
socket.read_exact(&mut result).await?;
result
} else {
vec![]
};
Ok(UsbIpCommand::UsbIpCmdSubmit {
header,
transfer_flags,
transfer_buffer_length,
start_frame,
number_of_packets,
interval,
setup,
data,
iso_packet_descriptor,
})
}
USBIP_CMD_UNLINK => {
let header =
UsbIpHeaderBasic::read_from_socket_with_command(socket, USBIP_CMD_UNLINK)
.await?;
let unlink_seqnum = socket.read_u32().await?;
let mut _padding = [0; 24];
socket.read_exact(&mut _padding).await?;
Ok(UsbIpCommand::UsbIpCmdUnlink {
header,
unlink_seqnum,
})
}
_ => Err(std::io::Error::other(format!(
"Unknown command: {command:#04X}"
))),
}
}
/// Converts the [UsbIpCommand] into a byte vector
pub fn to_bytes(&self) -> Vec<u8> {
match *self {
UsbIpCommand::OpReqDevlist { status } => {
let mut result = Vec::with_capacity(8);
result.extend_from_slice(&USBIP_VERSION.to_be_bytes());
result.extend_from_slice(&OP_REQ_DEVLIST.to_be_bytes());
result.extend_from_slice(&status.to_be_bytes());
result
}
UsbIpCommand::OpReqImport { status, busid } => {
let mut result = Vec::with_capacity(40);
result.extend_from_slice(&USBIP_VERSION.to_be_bytes());
result.extend_from_slice(&OP_REQ_IMPORT.to_be_bytes());
result.extend_from_slice(&status.to_be_bytes());
result.extend_from_slice(&busid);
result
}
UsbIpCommand::UsbIpCmdSubmit {
ref header,
transfer_flags,
transfer_buffer_length,
start_frame,
number_of_packets,
interval,
setup,
ref data,
ref iso_packet_descriptor,
} => {
debug_assert!(
header.direction != Direction::Out as u32
|| transfer_buffer_length == data.len() as u32
);
let mut result = Vec::with_capacity(48 + data.len() + iso_packet_descriptor.len());
result.extend_from_slice(&header.to_bytes());
result.extend_from_slice(&transfer_flags.to_be_bytes());
result.extend_from_slice(&transfer_buffer_length.to_be_bytes());
result.extend_from_slice(&start_frame.to_be_bytes());
result.extend_from_slice(&number_of_packets.to_be_bytes());
result.extend_from_slice(&interval.to_be_bytes());
result.extend_from_slice(&setup);
result.extend_from_slice(data);
result.extend_from_slice(iso_packet_descriptor);
result
}
UsbIpCommand::UsbIpCmdUnlink {
ref header,
unlink_seqnum,
} => {
let mut result = Vec::with_capacity(48);
result.extend_from_slice(&header.to_bytes());
result.extend_from_slice(&unlink_seqnum.to_be_bytes());
result.extend_from_slice(&[0; 24]);
result
}
}
}
}
/// Server side responses from the USB Host
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum UsbIpResponse {
OpRepDevlist {
status: u32,
device_count: u32,
devices: Vec<UsbDevice>,
},
OpRepImport {
status: u32,
device: Option<UsbDevice>,
},
UsbIpRetSubmit {
header: UsbIpHeaderBasic,
status: u32,
actual_length: u32,
start_frame: u32,
number_of_packets: u32,
error_count: u32,
transfer_buffer: Vec<u8>,
iso_packet_descriptor: Vec<u8>,
},
UsbIpRetUnlink {
header: UsbIpHeaderBasic,
status: u32,
},
}
impl UsbIpResponse {
/// Converts the [UsbIpResponse] into a byte vector
pub fn to_bytes(&self) -> Vec<u8> {
match *self {
Self::OpRepDevlist {
status,
device_count,
ref devices,
} => {
let mut result = Vec::with_capacity(
12 + devices.len() * 312
+ devices
.iter()
.map(|d| d.interfaces.len() * 4)
.sum::<usize>(),
);
result.extend_from_slice(&USBIP_VERSION.to_be_bytes());
result.extend_from_slice(&OP_REP_DEVLIST.to_be_bytes());
result.extend_from_slice(&status.to_be_bytes());
result.extend_from_slice(&device_count.to_be_bytes());
for dev in devices {
result.extend_from_slice(&dev.to_bytes_with_interfaces());
}
result
}
Self::OpRepImport { status, ref device } => {
let mut result = Vec::with_capacity(320);
result.extend_from_slice(&USBIP_VERSION.to_be_bytes());
result.extend_from_slice(&OP_REP_IMPORT.to_be_bytes());
result.extend_from_slice(&status.to_be_bytes());
if let Some(device) = device {
result.extend_from_slice(&device.to_bytes());
}
result
}
Self::UsbIpRetSubmit {
ref header,
status,
actual_length,
start_frame,
number_of_packets,
error_count,
ref transfer_buffer,
ref iso_packet_descriptor,
} => {
let mut result =
Vec::with_capacity(48 + transfer_buffer.len() + iso_packet_descriptor.len());
debug_assert!(header.command == USBIP_RET_SUBMIT.into());
debug_assert!(if header.direction == Direction::In as u32 {
actual_length == transfer_buffer.len() as u32
} else {
actual_length == 0
});
result.extend_from_slice(&header.to_bytes());
result.extend_from_slice(&status.to_be_bytes());
result.extend_from_slice(&actual_length.to_be_bytes());
result.extend_from_slice(&start_frame.to_be_bytes());
result.extend_from_slice(&number_of_packets.to_be_bytes());
result.extend_from_slice(&error_count.to_be_bytes());
result.extend_from_slice(&[0; 8]);
result.extend_from_slice(transfer_buffer);
result.extend_from_slice(iso_packet_descriptor);
result
}
Self::UsbIpRetUnlink { ref header, status } => {
let mut result = Vec::with_capacity(48);
debug_assert!(header.command == USBIP_RET_UNLINK.into());
result.extend_from_slice(&header.to_bytes());
result.extend_from_slice(&status.to_be_bytes());
result.extend_from_slice(&[0; 24]);
result
}
}
}
pub async fn write_to_socket<T: AsyncWriteExt + Unpin>(&self, socket: &mut T) -> Result<()> {
socket.write_all(&self.to_bytes()).await
}
/// Constructs a OP_REP_DEVLIST response
pub fn op_rep_devlist(devices: &[UsbDevice]) -> Self {
Self::OpRepDevlist {
status: 0,
device_count: devices.len() as u32,
devices: devices.to_vec(),
}
}
/// Constructs a successful OP_REP_IMPORT response
pub fn op_rep_import_success(device: &UsbDevice) -> Self {
Self::OpRepImport {
status: 0,
device: Some(device.clone()),
}
}
/// Constructs a failed OP_REP_IMPORT response
pub fn op_rep_import_fail() -> Self {
Self::OpRepImport {
status: 1,
device: None,
}
}
/// Constructs a successful OP_REP_IMPORT response
pub fn usbip_ret_submit_success(
header: &UsbIpHeaderBasic,
start_frame: u32,
number_of_packets: u32,
transfer_buffer: Vec<u8>,
iso_packet_descriptor: Vec<u8>,
) -> Self {
Self::UsbIpRetSubmit {
header: header.clone(),
status: 0,
actual_length: transfer_buffer.len() as u32,
start_frame,
number_of_packets,
error_count: 0,
transfer_buffer,
iso_packet_descriptor,
}
}
/// Constructs a failed OP_REP_IMPORT response
pub fn usbip_ret_submit_fail(header: &UsbIpHeaderBasic) -> Self {
Self::UsbIpRetSubmit {
header: header.clone(),
status: 1,
actual_length: 0,
start_frame: 0,
number_of_packets: 0,
error_count: 0,
transfer_buffer: vec![],
iso_packet_descriptor: vec![],
}
}
/// Constructs a successful OP_REP_IMPORT response
pub fn usbip_ret_unlink_success(header: &UsbIpHeaderBasic) -> Self {
Self::UsbIpRetUnlink {
header: header.clone(),
status: 0,
}
}
/// Constructs a failed OP_REP_IMPORT response.
pub fn usbip_ret_unlink_fail(header: &UsbIpHeaderBasic) -> Self {
Self::UsbIpRetUnlink {
header: header.clone(),
status: 1,
}
}
}
// (In-crate test module removed in the vendored copy — see NOTICE.)
+10
View File
@@ -0,0 +1,10 @@
/// Check validity of a USB descriptor
pub fn verify_descriptor(desc: &[u8]) {
let mut offset = 0;
while offset < desc.len() {
offset += desc[offset] as usize; // length
}
assert_eq!(offset, desc.len());
}
// (In-crate test module removed in the vendored copy — see NOTICE.)