Skip to main content

upc/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code)]
3#![warn(missing_docs)]
4
5#[cfg(any(feature = "host", feature = "device", feature = "web"))]
6use std::io::{Error, ErrorKind};
7use std::time::Duration;
8
9#[cfg(feature = "device")]
10pub mod device;
11
12#[cfg(any(feature = "host", feature = "web"))]
13pub mod host;
14
15mod trace;
16
17/// Maximum info size.
18pub const INFO_SIZE: usize = 4096;
19
20/// Default maximum packet size.
21pub const MAX_SIZE: usize = 16_777_216;
22
23/// Maximum USB packets per transfer.
24///
25/// This limits the number of USB packets per IN and OUT transfer.
26/// Setting this too high will cause issues with UDCs like dwc2;
27/// it sometimes corrupts transfers if it has to split them up.
28#[doc(hidden)]
29pub const TRANSFER_PACKETS: usize = 128;
30
31/// Length of send and receive queues.
32#[allow(dead_code)]
33const QUEUE_LEN: usize = 32;
34
35/// Timeout for flushing stale data from endpoints during connection setup.
36#[allow(dead_code)]
37const FLUSH_TIMEOUT: Duration = Duration::from_millis(100);
38
39/// USB interface class.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub struct Class {
42    /// Class code.
43    pub class: u8,
44    /// Subclass code.
45    pub sub_class: u8,
46    /// Protocol code.
47    pub protocol: u8,
48}
49
50impl Class {
51    /// Vendor specific class code.
52    pub const VENDOR_SPECIFIC: u8 = 0xff;
53
54    /// Creates a new USB device or interface class.
55    pub const fn new(class: u8, sub_class: u8, protocol: u8) -> Self {
56        Self { class, sub_class, protocol }
57    }
58
59    /// Creates a new USB device or interface class with vendor-specific class code.
60    pub const fn vendor_specific(sub_class: u8, protocol: u8) -> Self {
61        Self::new(Self::VENDOR_SPECIFIC, sub_class, protocol)
62    }
63}
64
65#[cfg(feature = "device")]
66impl From<Class> for usb_gadget::Class {
67    fn from(Class { class, sub_class, protocol }: Class) -> Self {
68        usb_gadget::Class { class, sub_class, protocol }
69    }
70}
71
72/// Vendor-specific control requests (host → device).
73#[allow(dead_code)]
74mod ctrl_req {
75    /// Probe whether the interface speaks UPC (device responds with [`PROBE_RESPONSE`]).
76    pub const PROBE: u8 = 0;
77    /// Open a connection.
78    pub const OPEN: u8 = 1;
79    /// Close the connection (both directions).
80    pub const CLOSE: u8 = 2;
81    /// Read device-provided information.
82    pub const INFO: u8 = 3;
83    /// Host is done sending (close send direction).
84    pub const CLOSE_SEND: u8 = 4;
85    /// Host is done receiving (close receive direction).
86    pub const CLOSE_RECV: u8 = 5;
87    /// Ping / status request (device responds with status bytes).
88    pub const STATUS: u8 = 6;
89    /// Query device capabilities (device-to-host) / set host capabilities (host-to-device).
90    pub const CAPABILITIES: u8 = 7;
91    /// Echo data back via the bulk IN endpoint (only when connection is closed).
92    pub const ECHO: u8 = 8;
93
94    /// Expected response to a PROBE request.
95    pub const PROBE_RESPONSE: &[u8] = b"UPC";
96}
97
98/// Status response bytes returned by the device in reply to a STATUS control request.
99#[allow(dead_code)]
100mod status {
101    /// Device receiver has been dropped (device done receiving, OUT direction closed).
102    pub const RECV_CLOSED: u8 = 1;
103    /// Maximum size of the status response.
104    pub const MAX_SIZE: usize = 8;
105}
106
107/// TLV (tag-length-value) encoding helpers for capabilities.
108#[allow(dead_code)]
109mod tlv {
110    use std::io::{Error, ErrorKind};
111
112    /// Appends a TLV entry to the buffer.
113    pub fn encode(buf: &mut Vec<u8>, tag: u8, value: &[u8]) {
114        buf.push(tag);
115        buf.extend_from_slice(&(value.len() as u16).to_le_bytes());
116        buf.extend_from_slice(value);
117    }
118
119    /// Decodes TLV entries from a byte slice.
120    ///
121    /// Returns a list of `(tag, value)` pairs. Unknown tags are preserved
122    /// so callers can skip them for forward compatibility.
123    pub fn decode(data: &[u8]) -> std::io::Result<Vec<(u8, &[u8])>> {
124        let mut entries = Vec::new();
125        let mut pos = 0;
126        while pos < data.len() {
127            if pos + 3 > data.len() {
128                return Err(Error::new(ErrorKind::InvalidData, "capabilities data truncated"));
129            }
130            let tag = data[pos];
131            let len = u16::from_le_bytes([data[pos + 1], data[pos + 2]]) as usize;
132            pos += 3;
133            if pos + len > data.len() {
134                return Err(Error::new(ErrorKind::InvalidData, "capabilities data truncated"));
135            }
136            entries.push((tag, &data[pos..pos + len]));
137            pos += len;
138        }
139        Ok(entries)
140    }
141}
142
143/// Device capabilities.
144///
145/// Encoded using a TLV (tag-length-value) format for forward and backward
146/// compatibility: unknown tags are silently skipped during decoding and
147/// missing tags fall back to their default values.
148#[cfg(any(feature = "host", feature = "device", feature = "web"))]
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub(crate) struct DeviceCapabilities {
151    /// Ping timeout duration.
152    pub ping_timeout: Option<Duration>,
153    /// Whether the device supports the STATUS control request.
154    pub status_supported: bool,
155    /// Whether the device supports the ECHO control request.
156    pub echo_supported: bool,
157    /// Maximum receive size.
158    pub max_size: u64,
159}
160
161#[cfg(any(feature = "host", feature = "device", feature = "web"))]
162impl Default for DeviceCapabilities {
163    fn default() -> Self {
164        Self { ping_timeout: None, status_supported: false, echo_supported: false, max_size: MAX_SIZE as u64 }
165    }
166}
167
168#[cfg(any(feature = "host", feature = "device", feature = "web"))]
169impl DeviceCapabilities {
170    /// Maximum encoded capabilities size.
171    #[cfg(any(feature = "host", feature = "web"))]
172    const SIZE: usize = 256;
173
174    /// TLV tag for ping timeout.
175    const TAG_PING_TIMEOUT: u8 = 0x01;
176    /// TLV tag for status supported.
177    const TAG_STATUS_SUPPORTED: u8 = 0x02;
178    /// TLV tag for max packet size.
179    const TAG_MAX_PACKET_SIZE: u8 = 0x03;
180    /// TLV tag for echo supported.
181    const TAG_ECHO_SUPPORTED: u8 = 0x04;
182
183    /// Encodes the capabilities into a byte vector using TLV encoding.
184    #[cfg(feature = "device")]
185    pub fn encode(&self) -> Vec<u8> {
186        let mut buf = Vec::new();
187
188        // Tag 0x01: ping_timeout as u32 millis (0 = None).
189        let millis: u32 = self.ping_timeout.map_or(0, |d| d.as_millis().try_into().unwrap_or(u32::MAX));
190        tlv::encode(&mut buf, Self::TAG_PING_TIMEOUT, &millis.to_le_bytes());
191
192        // Tag 0x02: status_supported as u8 (0 = false, 1 = true).
193        tlv::encode(&mut buf, Self::TAG_STATUS_SUPPORTED, &[u8::from(self.status_supported)]);
194
195        // Tag 0x03: max_size as u64.
196        tlv::encode(&mut buf, Self::TAG_MAX_PACKET_SIZE, &self.max_size.to_le_bytes());
197
198        // Tag 0x04: echo_supported as u8 (0 = false, 1 = true).
199        tlv::encode(&mut buf, Self::TAG_ECHO_SUPPORTED, &[u8::from(self.echo_supported)]);
200
201        buf
202    }
203
204    /// Decodes capabilities from a TLV-encoded byte slice.
205    ///
206    /// Unknown tags are skipped. Missing tags keep their default values.
207    #[cfg(any(feature = "host", feature = "web"))]
208    pub fn decode(data: &[u8]) -> std::io::Result<Self> {
209        let mut caps = Self::default();
210        for (tag, value) in tlv::decode(data)? {
211            match tag {
212                Self::TAG_PING_TIMEOUT => {
213                    if value.len() >= 4 {
214                        let millis = u32::from_le_bytes([value[0], value[1], value[2], value[3]]);
215                        caps.ping_timeout =
216                            if millis == 0 { None } else { Some(Duration::from_millis(millis.into())) };
217                    }
218                }
219
220                Self::TAG_STATUS_SUPPORTED => {
221                    if !value.is_empty() {
222                        caps.status_supported = value[0] != 0;
223                    }
224                }
225
226                Self::TAG_MAX_PACKET_SIZE => {
227                    if value.len() >= 8 {
228                        let size = u64::from_le_bytes([
229                            value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7],
230                        ]);
231                        caps.max_size = size;
232                    }
233                }
234
235                Self::TAG_ECHO_SUPPORTED => {
236                    if !value.is_empty() {
237                        caps.echo_supported = value[0] != 0;
238                    }
239                }
240
241                _ => { /* unknown tag — skip for forward compatibility */ }
242            }
243        }
244        Ok(caps)
245    }
246}
247
248/// Host capabilities.
249///
250/// Encoded using a TLV (tag-length-value) format for forward and backward
251/// compatibility: unknown tags are silently skipped during decoding and
252/// missing tags fall back to their default values.
253#[cfg(any(feature = "host", feature = "device", feature = "web"))]
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub(crate) struct HostCapabilities {
256    /// Maximum receive size.
257    pub max_size: u64,
258}
259
260#[cfg(any(feature = "host", feature = "device", feature = "web"))]
261impl Default for HostCapabilities {
262    fn default() -> Self {
263        Self { max_size: MAX_SIZE as u64 }
264    }
265}
266
267#[cfg(any(feature = "host", feature = "device", feature = "web"))]
268impl HostCapabilities {
269    /// Maximum encoded capabilities size.
270    #[allow(dead_code)]
271    #[cfg(feature = "device")]
272    const SIZE: usize = 256;
273
274    /// TLV tag for max packet size.
275    const TAG_MAX_PACKET_SIZE: u8 = 0x03;
276
277    /// Encodes the capabilities into a byte vector using TLV encoding.
278    #[cfg(any(feature = "host", feature = "web"))]
279    pub fn encode(&self) -> Vec<u8> {
280        let mut buf = Vec::new();
281        tlv::encode(&mut buf, Self::TAG_MAX_PACKET_SIZE, &self.max_size.to_le_bytes());
282        buf
283    }
284
285    /// Decodes capabilities from a TLV-encoded byte slice.
286    ///
287    /// Unknown tags are skipped. Missing tags keep their default values.
288    #[cfg(feature = "device")]
289    pub fn decode(data: &[u8]) -> std::io::Result<Self> {
290        let mut caps = Self::default();
291        for (tag, value) in tlv::decode(data)? {
292            match tag {
293                Self::TAG_MAX_PACKET_SIZE => {
294                    if value.len() >= 8 {
295                        let size = u64::from_le_bytes([
296                            value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7],
297                        ]);
298                        caps.max_size = size;
299                    }
300                }
301                _ => { /* unknown tag — skip for forward compatibility */ }
302            }
303        }
304        Ok(caps)
305    }
306}
307
308/// Creates an I/O error for a closed UPC channel.
309///
310/// For [`ErrorKind::BrokenPipe`] this produces "UPC channel closed";
311/// for other kinds it uses the standard error text.
312#[cfg(any(feature = "host", feature = "device", feature = "web"))]
313pub(crate) fn channel_error(kind: ErrorKind) -> Error {
314    if kind == ErrorKind::BrokenPipe {
315        Error::new(kind, "UPC channel closed")
316    } else {
317        kind.into()
318    }
319}