Skip to main content

brainwires_hardware/usb/
types.rs

1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4/// Information about a USB device attached to the system.
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct UsbDevice {
7    /// USB bus number.
8    pub bus: u8,
9    /// Device address on the bus.
10    pub device_address: u8,
11    /// USB Vendor ID (VID).
12    pub vendor_id: u16,
13    /// USB Product ID (PID).
14    pub product_id: u16,
15    /// Device class code.
16    pub class: UsbClass,
17    /// Negotiated bus speed.
18    pub speed: UsbSpeed,
19    /// Manufacturer string, if readable.
20    pub manufacturer: Option<String>,
21    /// Product string, if readable.
22    pub product: Option<String>,
23    /// Serial number string, if readable.
24    pub serial: Option<String>,
25}
26
27impl UsbDevice {
28    /// Format as `VID:PID` hex string (e.g. `"046d:c52b"`).
29    pub fn vid_pid(&self) -> String {
30        format!("{:04x}:{:04x}", self.vendor_id, self.product_id)
31    }
32}
33
34/// USB device class codes as per the USB-IF specification.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36pub enum UsbClass {
37    /// 0x01 — Audio
38    Audio,
39    /// 0x02 — Communications and CDC Control
40    Cdc,
41    /// 0x03 — Human Interface Device
42    Hid,
43    /// 0x05 — Physical
44    Physical,
45    /// 0x06 — Still Imaging / Image
46    Image,
47    /// 0x07 — Printer
48    Printer,
49    /// 0x08 — Mass Storage
50    MassStorage,
51    /// 0x09 — Hub
52    Hub,
53    /// 0x0A — CDC-Data
54    CdcData,
55    /// 0x0B — Smart Card
56    SmartCard,
57    /// 0x0D — Content Security
58    ContentSecurity,
59    /// 0x0E — Video
60    Video,
61    /// 0x0F — Personal Healthcare
62    PersonalHealthcare,
63    /// 0x10 — Audio/Video Devices
64    AudioVideo,
65    /// 0x11 — Billboard Device Class
66    Billboard,
67    /// 0x12 — USB Type-C Bridge Class
68    TypeCBridge,
69    /// 0xDC — Diagnostic Device
70    Diagnostic,
71    /// 0xE0 — Wireless Controller (Bluetooth, etc.)
72    WirelessController,
73    /// 0xEF — Miscellaneous
74    Miscellaneous,
75    /// 0xFE — Application Specific
76    ApplicationSpecific,
77    /// 0xFF — Vendor Specific
78    VendorSpecific,
79    /// Class code not in the above list.
80    Unknown(u8),
81}
82
83impl UsbClass {
84    /// Decode a USB class byte from a device descriptor into the named variant.
85    pub fn from_code(code: u8) -> Self {
86        match code {
87            0x01 => Self::Audio,
88            0x02 => Self::Cdc,
89            0x03 => Self::Hid,
90            0x05 => Self::Physical,
91            0x06 => Self::Image,
92            0x07 => Self::Printer,
93            0x08 => Self::MassStorage,
94            0x09 => Self::Hub,
95            0x0A => Self::CdcData,
96            0x0B => Self::SmartCard,
97            0x0D => Self::ContentSecurity,
98            0x0E => Self::Video,
99            0x0F => Self::PersonalHealthcare,
100            0x10 => Self::AudioVideo,
101            0x11 => Self::Billboard,
102            0x12 => Self::TypeCBridge,
103            0xDC => Self::Diagnostic,
104            0xE0 => Self::WirelessController,
105            0xEF => Self::Miscellaneous,
106            0xFE => Self::ApplicationSpecific,
107            0xFF => Self::VendorSpecific,
108            other => Self::Unknown(other),
109        }
110    }
111}
112
113impl std::fmt::Display for UsbClass {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        let name = match self {
116            Self::Audio => "Audio",
117            Self::Cdc => "CDC",
118            Self::Hid => "HID",
119            Self::Physical => "Physical",
120            Self::Image => "Image",
121            Self::Printer => "Printer",
122            Self::MassStorage => "Mass Storage",
123            Self::Hub => "Hub",
124            Self::CdcData => "CDC-Data",
125            Self::SmartCard => "Smart Card",
126            Self::ContentSecurity => "Content Security",
127            Self::Video => "Video",
128            Self::PersonalHealthcare => "Personal Healthcare",
129            Self::AudioVideo => "Audio/Video",
130            Self::Billboard => "Billboard",
131            Self::TypeCBridge => "Type-C Bridge",
132            Self::Diagnostic => "Diagnostic",
133            Self::WirelessController => "Wireless Controller",
134            Self::Miscellaneous => "Miscellaneous",
135            Self::ApplicationSpecific => "Application Specific",
136            Self::VendorSpecific => "Vendor Specific",
137            Self::Unknown(n) => return write!(f, "Unknown(0x{n:02x})"),
138        };
139        f.write_str(name)
140    }
141}
142
143/// USB bus speed.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
145pub enum UsbSpeed {
146    /// 1.5 Mbit/s
147    Low,
148    /// 12 Mbit/s
149    Full,
150    /// 480 Mbit/s
151    High,
152    /// 5 Gbit/s
153    Super,
154    /// 10 Gbit/s
155    SuperPlus,
156    /// Speed not reported by the OS.
157    Unknown,
158}
159
160impl std::fmt::Display for UsbSpeed {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        let s = match self {
163            Self::Low => "Low-Speed (1.5 Mbit/s)",
164            Self::Full => "Full-Speed (12 Mbit/s)",
165            Self::High => "High-Speed (480 Mbit/s)",
166            Self::Super => "SuperSpeed (5 Gbit/s)",
167            Self::SuperPlus => "SuperSpeed+ (10 Gbit/s)",
168            Self::Unknown => "Unknown",
169        };
170        f.write_str(s)
171    }
172}
173
174/// Errors from USB operations.
175#[derive(Debug, Error)]
176pub enum UsbError {
177    /// No device with the requested VID:PID is attached.
178    #[error("no device found with VID:PID {vendor_id:04x}:{product_id:04x}")]
179    DeviceNotFound {
180        /// Requested USB Vendor ID.
181        vendor_id: u16,
182        /// Requested USB Product ID.
183        product_id: u16,
184    },
185    /// Device was discoverable but could not be opened.
186    #[error("failed to open device: {0}")]
187    OpenFailed(String),
188    /// Interface claim (`claim_interface`) failed.
189    #[error("failed to claim interface {0}: {1}")]
190    ClaimFailed(u8, String),
191    /// Bulk/interrupt/control transfer failed.
192    #[error("transfer failed on endpoint 0x{endpoint:02x}: {reason}")]
193    TransferFailed {
194        /// Endpoint address (0x80 bit = IN, else OUT).
195        endpoint: u8,
196        /// OS-level failure reason.
197        reason: String,
198    },
199    /// Transfer did not complete within its timeout window.
200    #[error("transfer timed out on endpoint 0x{0:02x}")]
201    Timeout(u8),
202    /// Catch-all for platform-specific failures.
203    #[error("USB error: {0}")]
204    Other(String),
205}