Skip to main content

netget/server/usb/
guard.rs

1//! A USB/IP message screen that sits between the network and the `usbip` crate.
2//!
3//! # Why this exists
4//!
5//! `usbip` 0.9.0 sizes two buffers from numbers the peer supplies and checks neither.
6//! `UsbIpCommand::read_from_socket` reads a 48-byte `USBIP_CMD_SUBMIT` header and then does
7//!
8//! ```notrust
9//! let mut data = vec![0; transfer_buffer_length as usize];
10//! socket.read_exact(&mut data).await?;
11//! ```
12//!
13//! — so a header declaring `transfer_buffer_length = 0xFFFFFFFF` allocates and zero-fills
14//! 4 GiB **before one payload byte arrives**. Immediately after it does the same with the
15//! isochronous descriptor array:
16//!
17//! ```notrust
18//! let mut result = vec![0; 16 * number_of_packets as usize];
19//! ```
20//!
21//! which reaches ~68 GiB on a 64-bit host and wraps on a 32-bit one.
22//!
23//! USB/IP has **no authentication of any kind** — `OP_REQ_IMPORT` carries a bus id and nothing
24//! else — and every NetGet USB server spawns its USB/IP session task *before* it makes the
25//! attach LLM call, so both allocations are reachable pre-auth and pre-model, from 48 bytes.
26//!
27//! # It also decides *when* a device has been attached
28//!
29//! Every USB server used to make its attach LLM call the moment `accept()` returned, so
30//! `nc <host> <port>` — one TCP handshake, not one byte of USB/IP — bought a model round-trip
31//! on a protocol that authenticates nothing. The screen already reads and classifies every
32//! message, so it is the one place that knows when the peer has actually asked to import a
33//! device. [`run_guarded_usbip`] takes a [`oneshot::Sender`](tokio::sync::oneshot::Sender) and
34//! fires it on the first admitted `OP_REQ_IMPORT`, and the callers hang their attach event on
35//! that instead of on the accept.
36//!
37//! `OP_REQ_DEVLIST` deliberately does **not** fire it: listing what a host exports is not
38//! attaching to it, and `usbip list -r` is exactly the enumeration a scanner would do.
39//!
40//! # The seam
41//!
42//! `usbip::handler` is generic over `T: AsyncReadExt + AsyncWriteExt + Unpin`, so unlike
43//! `nfsserve` (see `src/server/nfs/guard.rs`, the precedent this follows) no second listener
44//! is needed: NetGet keeps the real socket, hands the crate an in-memory
45//! [`tokio::io::duplex`] pipe, and copies only admitted messages into it. Bytes the screen
46//! refuses never reach the crate.
47//!
48//! # What it refuses
49//!
50//! One message at a time, by the numbers the peer *announced*, before any arithmetic and
51//! before anything is read or allocated for them:
52//!
53//! - a `USBIP_CMD_SUBMIT` declaring more than [`MAX_TRANSFER_BUFFER_BYTES`],
54//! - a `USBIP_CMD_SUBMIT` declaring more than [`MAX_ISO_PACKETS`] isochronous packets,
55//! - a command word that is none of the four USB/IP defines,
56//! - a `direction` that is not a single bit, or an operation-code message with a non-zero
57//!   `status` -- the crate only `debug_assert!`s both, so either is a panic from the wire in a
58//!   debug or test build, swallowed by `tokio::spawn` and leaving the peer hung,
59//! - a payload the peer announced and then stopped sending ([`URB_BODY_TIMEOUT`]).
60//!
61//! # It also bounds silence
62//!
63//! A peer that connects and says nothing, or that goes quiet mid-session, holds a socket, a
64//! task, an `AppState` row and one of [`MAX_USBIP_CONNECTIONS`] slots -- pre-authentication,
65//! because USB/IP has none. [`UsbIpDeadlines`] gives the wait for the *start* of a message a
66//! deadline: a short one before the peer's first message and a long one after it, each argued
67//! at its constant and each overridable per server (`first_byte_timeout_secs`,
68//! `idle_timeout_secs`). The two numbers are far apart on purpose; the argument for the gap is
69//! on [`DEFAULT_FIRST_MESSAGE_TIMEOUT`] and [`DEFAULT_IDLE_TIMEOUT`].
70//!
71//! **A refusal closes the connection rather than answering it, and that is a real limitation
72//! rather than the only option.** `USBIP_RET_SUBMIT` has a `status` field that could carry
73//! `-EINVAL`, but a well-formed reply also has to echo the `seqnum` and `devid` of a URB the
74//! crate never saw, and the crate — not the screen — owns the reply stream it is multiplexed
75//! into. Writing one from underneath would interleave with whatever the crate is sending. So
76//! the screen closes, and the operator gets the reason from the log: every refusal is an ERROR
77//! carrying a stable `decision=fail_closed_*` tag, in the shape `src/server/radius/` set.
78//!
79//! # What it does not do
80//!
81//! The screen bounds message framing, not USB semantics. An admitted URB still goes to the
82//! protocol's own `UsbInterfaceHandler`, which decides whether the endpoint, the setup packet
83//! and the payload make sense.
84
85use std::sync::Arc;
86
87use tokio::io::{AsyncReadExt, AsyncWriteExt};
88use tokio::net::TcpStream;
89use tokio::sync::mpsc::UnboundedSender;
90use tokio::time::Duration;
91
92use crate::logging::emit::Log;
93
94/// Largest `transfer_buffer_length` a `USBIP_CMD_SUBMIT` may declare.
95///
96/// **This is a NetGet policy choice, not a figure from the USB/IP specification** — the
97/// protocol's field is a bare `u32` and names no ceiling. It is chosen from what the six
98/// devices NetGet emulates actually transfer: HID keyboard and mouse reports are 4-8 bytes,
99/// CTAPHID frames are 64, CCID bulk messages are a few hundred, and the largest by far is mass
100/// storage, where a Bulk-Only Transport data phase is one SCSI transfer — Linux's `usb-storage`
101/// caps those well below a megabyte, and NetGet's own MSC handler serves 512-byte sectors.
102///
103/// 1 MiB therefore leaves every legitimate transfer these devices can make comfortable, while
104/// refusing the 4 GiB the wire format allows. It is also what a protocol declares to the model
105/// and the operator through `ProtocolMetadataV2::max_inbound_bytes`.
106pub const MAX_TRANSFER_BUFFER_BYTES: usize = 1024 * 1024;
107
108/// Largest `number_of_packets` a `USBIP_CMD_SUBMIT` may declare for an isochronous transfer.
109///
110/// Unlike the constant above this one **is** the number the other implementations use: the
111/// Linux kernel's own USB/IP stub defines `USBIP_MAX_ISO_PACKETS` as 1024 in
112/// `drivers/usb/usbip/usbip_common.h` and rejects a larger `number_of_packets` on the submit
113/// path in `stub_rx.c`, for the same reason this exists.
114///
115/// It is generous here to the point of being theoretical: no device NetGet emulates declares an
116/// isochronous endpoint at all, so the only values a working session produces are the two
117/// exempt ones, `0` and `0xFFFFFFFF`. Matching the kernel rather than refusing every non-exempt
118/// value keeps the screen from being the thing that breaks a future isochronous device.
119///
120/// 1024 packets is 16 KiB of descriptors, so `16 * number_of_packets` cannot overflow once the
121/// bound has been applied — it is still computed with `checked_mul`, because the point of the
122/// bound is that the arithmetic happens *after* it.
123pub const MAX_ISO_PACKETS: u32 = 1024;
124
125/// Concurrent USB/IP connections one USB server admits, for
126/// [`accept_bounded`](crate::server::accept_bounded::accept_bounded).
127///
128/// Smaller than the shared [`DEFAULT_MAX_CONNECTIONS`](crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS)
129/// of 256, because a USB/IP connection is not a request — it is an entire emulated device.
130/// Each one costs a `usbip::UsbIpServer`, a device's worth of descriptors, a live
131/// `UsbInterfaceHandler` with its own session state (a CCID card, a FIDO2 credential store, a
132/// memory-mapped MSC disk image), an `AppState` connection entry, and up to
133/// [`MAX_TRANSFER_BUFFER_BYTES`] in flight. It is also not a number a working deployment ever
134/// approaches: a virtual device is imported by *one* host, and the only reason this is not 2 is
135/// that a host reconnecting while the previous session winds down, and a test harness opening
136/// several at once, are both ordinary.
137///
138/// 32 therefore multiplies the 1 MiB transfer bound into 32 MiB rather than 256 MiB, and is
139/// still an order of magnitude above anything legitimate.
140pub const MAX_USBIP_CONNECTIONS: usize = 32;
141
142/// What a USB server says to a connection it is refusing: nothing.
143///
144/// USB/IP has no "busy", "try later" or "too many clients" message. Its only server-to-client
145/// messages are `OP_REP_DEVLIST`, `OP_REP_IMPORT` and `USBIP_RET_SUBMIT`/`USBIP_RET_UNLINK`,
146/// and each is a positive assertion about a device — `OP_REP_IMPORT` with a non-zero status is
147/// the closest thing to a refusal, but sending one unprompted would be answering a question the
148/// peer has not asked, and a peer that has only completed a TCP handshake has by definition
149/// asked nothing. So the refusal is a plain close and the reason lives in the log, which is the
150/// case `accept_bounded`'s empty-slice arm exists for.
151pub const USBIP_NO_REFUSAL: &[u8] = &[];
152
153/// How long a peer may stall part-way through a message it has already begun.
154///
155/// This governs everything after the first four bytes of a message: the rest of its fixed
156/// header, and the payload a `USBIP_CMD_SUBMIT` announced. A peer that has written a command
157/// word has made a claim and not honoured it, which is a different thing from a peer that is
158/// simply quiet *between* messages -- that one is governed by [`UsbIpDeadlines::idle`], which is
159/// far longer, for the reasons given there.
160///
161/// The tail read used to have no bound at all, so four bytes bought a connection held for as
162/// long as the peer liked.
163const URB_BODY_TIMEOUT: Duration = Duration::from_secs(30);
164
165/// How long a peer that has only completed a TCP handshake may send nothing at all.
166///
167/// **The 300 seconds `tcp`, `telnet`, `ldap`, `whois` and `redis` each settled on does not
168/// transfer here, and the reason is worth stating rather than copying the number.** For those,
169/// the peer is frequently NetGet's own client of the same protocol -- created from the
170/// dashboard's `[ + <proto> client ]`, routed `*` -> manual, and parked at `[ send message ]`
171/// waiting for a person to type. It has sent nothing since it connected and it is *correct*
172/// that it has, so the bound must be at least the window a `manual` rule gives a human
173/// (`src/state/intercepts.rs`, 300 seconds).
174///
175/// **That peer cannot exist in front of a USB/IP server**, and every part of this is checkable:
176///
177/// * **NetGet has no USB/IP client.** The only `usb` client is `src/client/usb`, which drives a
178///   real local device through `nusb`. It opens no socket and cannot import one of these.
179/// * **The dashboard cannot create one either.** `src/protocol/dual.rs` deliberately pairs no
180///   `USB-*` profile server with the generic "USB" client -- the generic clients "speak the base
181///   transport, not a specific profile" -- so `[ + client ]` on one of these cards is the
182///   disabled button reading "no client implementation for this protocol is compiled in".
183/// * **Nothing can park in front of the first message.** The attach event does not hang off the
184///   accept; it hangs off the first admitted `OP_REQ_IMPORT` (see the module docs above). So
185///   before a peer has spoken there is no event, no model call and no `manual` window to wait
186///   out. A peer silent here is waiting for nobody.
187/// * **USB/IP is unconditionally client-speaks-first.** The server writes nothing until it is
188///   asked: `usbip list -r` sends `OP_REQ_DEVLIST` and `usbip attach` sends `OP_REQ_IMPORT`,
189///   each assembled before the socket is opened and written as soon as it connects, and
190///   `tests/helpers/usbip_client.rs` does the same.
191///
192/// So the honest default is a short one. 30 seconds is generous by orders of magnitude for
193/// writing eight bytes -- it absorbs a slow link and a scheduler stall in a
194/// `--test-threads=100` run -- and what it buys is that a stranger who connects and says
195/// nothing holds one of [`MAX_USBIP_CONNECTIONS`] slots for half a minute rather than forever.
196/// Overridable per server with `first_byte_timeout_secs`.
197pub const DEFAULT_FIRST_MESSAGE_TIMEOUT: Duration = Duration::from_secs(30);
198
199/// How long an established session may go between messages.
200///
201/// Deliberately very long, and for the opposite reason to the bound above. USB/IP has no
202/// keepalive, no ping and no heartbeat: nothing in the protocol obliges an attached host to say
203/// anything, and a host asking nothing of a device it has imported is not misbehaving -- it is
204/// the ordinary state of an attached device. A mass-storage device a host has attached but not
205/// mounted issues no URB at all; the HID and CDC cases only look busy because their kernel
206/// drivers poll. Closing those is unplugging a device from a host that did nothing wrong, which
207/// is the live-transfer eviction the project `CLAUDE.md` records TFTP learning about.
208///
209/// So this bound exists to reap a peer that is *gone* in a way TCP has not noticed -- a dropped
210/// route, a killed VM -- rather than to police one that is present. Half an hour is longer than
211/// any pause a hand-driven or test session produces and still turns "forever" into a number;
212/// together with [`MAX_USBIP_CONNECTIONS`] it bounds what a peer that got as far as one valid
213/// import can hold. A server exporting to strangers rather than to a host the operator controls
214/// should set `idle_timeout_secs` far lower.
215///
216/// **It is armed while the device may still owe the host a reply, and that is safe here in a
217/// way it would not be in most of this tree.** Every `UsbInterfaceHandler::handle_urb` under
218/// `src/server/usb/` is synchronous and answers out of state the connection already holds, so
219/// no URB ever waits on an LLM round-trip or on a 300-second `manual` park -- the model call an
220/// event raises runs in the connection task beside this loop (the `select!` in each
221/// `usb/*/mod.rs`), never in front of a reply. A host that is quiet is quiet by its own choice.
222pub const DEFAULT_IDLE_TIMEOUT: Duration = Duration::from_secs(1800);
223
224/// The two read deadlines a USB server hands the screen.
225///
226/// Both are startup parameters (`first_byte_timeout_secs`, `idle_timeout_secs`) because the
227/// right value is a property of who is on the other end, which only the operator knows.
228#[derive(Clone, Copy, Debug)]
229pub struct UsbIpDeadlines {
230    /// Waiting for the first message of a connection. [`DEFAULT_FIRST_MESSAGE_TIMEOUT`].
231    pub first_message: Duration,
232    /// Waiting for a further message once one has been admitted. [`DEFAULT_IDLE_TIMEOUT`].
233    pub idle: Duration,
234}
235
236impl Default for UsbIpDeadlines {
237    fn default() -> Self {
238        Self {
239            first_message: DEFAULT_FIRST_MESSAGE_TIMEOUT,
240            idle: DEFAULT_IDLE_TIMEOUT,
241        }
242    }
243}
244
245impl UsbIpDeadlines {
246    /// Build from the two startup parameters, falling back to the defaults for whichever the
247    /// caller left out. One line per protocol, so the argument for the numbers lives here
248    /// rather than being restated -- and drifting -- in six `actions.rs`.
249    pub fn from_secs(first_message_secs: Option<u64>, idle_secs: Option<u64>) -> Self {
250        let default = Self::default();
251        Self {
252            first_message: first_message_secs
253                .map(Duration::from_secs)
254                .unwrap_or(default.first_message),
255            idle: idle_secs.map(Duration::from_secs).unwrap_or(default.idle),
256        }
257    }
258}
259
260/// Bytes read from the peer in one go while streaming an admitted payload.
261///
262/// The screen never allocates from the length the peer declared — it allocates this, once per
263/// connection, and streams.
264const RELAY_CHUNK_BYTES: usize = 64 * 1024;
265
266/// Size of the in-memory pipe standing in for the socket the crate thinks it has.
267///
268/// Both directions are drained concurrently, so this only has to be big enough that a small
269/// message does not cost several wakeups; a payload larger than it simply streams through.
270const DUPLEX_BUFFER_BYTES: usize = 64 * 1024;
271
272/// How long the crate-side session is given to wind up after the peer is gone.
273///
274/// Closing the write half of the pipe makes the crate's next read return EOF, which ends
275/// `usbip::handler`. A handler parked inside a long `handle_urb` will not see that, so the
276/// wait is bounded and then the task is aborted.
277const SESSION_SHUTDOWN_GRACE: Duration = Duration::from_secs(2);
278
279/// USB/IP protocol version, as carried by the operation-code messages.
280const USBIP_VERSION: u16 = 0x0111;
281
282const OP_REQ_DEVLIST: u16 = 0x8005;
283const OP_REQ_IMPORT: u16 = 0x8003;
284const USBIP_CMD_SUBMIT: u16 = 0x0001;
285const USBIP_CMD_UNLINK: u16 = 0x0002;
286
287/// Bytes after the four-byte version/command word, per message kind.
288const OP_REQ_DEVLIST_TAIL: usize = 4; // status
289const OP_REQ_IMPORT_TAIL: usize = 36; // status + busid[32]
290const CMD_TAIL: usize = 44; // both CMD_SUBMIT and CMD_UNLINK are 48 bytes total
291
292/// Offsets within the 48-byte `USBIP_CMD_SUBMIT` header, counted from the command word.
293const OFF_DIRECTION: usize = 12;
294const OFF_TRANSFER_BUFFER_LENGTH: usize = 24;
295const OFF_NUMBER_OF_PACKETS: usize = 32;
296
297/// Offset of `status`, the first field of both operation-code messages.
298const OFF_STATUS: usize = 4;
299
300/// `direction` value meaning host-to-device, i.e. the only case carrying a payload.
301const DIRECTION_OUT: u32 = 0;
302
303/// `number_of_packets` values that mean "not an isochronous transfer".
304///
305/// The kernel documentation specifies `0xFFFFFFFF` for every non-ISO URB; the implementation
306/// sends `0`. Both are in the wild, so both are exempt — the crate makes the same allowance.
307const NON_ISO_PACKET_COUNTS: [u32; 2] = [0, 0xFFFF_FFFF];
308
309/// Why the screen refused a message.
310#[derive(Clone, Copy, Debug, PartialEq, Eq)]
311pub enum Refusal {
312    /// The version word is neither 0 nor [`USBIP_VERSION`].
313    UnknownVersion { version: u16 },
314    /// The command word is none of the four USB/IP defines.
315    UnknownCommand { command: u16 },
316    /// `transfer_buffer_length` exceeded [`MAX_TRANSFER_BUFFER_BYTES`].
317    TransferBufferTooLarge { announced: u32 },
318    /// `number_of_packets` exceeded [`MAX_ISO_PACKETS`].
319    TooManyIsoPackets { announced: u32 },
320    /// `16 * number_of_packets` does not fit in a `usize` on this target.
321    IsoDescriptorOverflow { announced: u32 },
322    /// `direction` is neither 0 (OUT) nor 1 (IN).
323    ///
324    /// The crate `debug_assert!`s this, so in a debug or test build a peer that sends 2 panics
325    /// the session task from the wire, pre-auth — and `tokio::spawn` swallows the panic.
326    InvalidDirection { direction: u32 },
327    /// An operation-code message declared a non-zero `status`, which the crate also only
328    /// `debug_assert!`s. A real host sends zero.
329    NonZeroStatus { status: u32 },
330    /// The peer announced a payload and then stopped sending it.
331    BodyStalled,
332}
333
334impl Refusal {
335    /// A stable tag an operator can grep for, in the shape `src/server/radius/` established.
336    pub fn decision_tag(self) -> &'static str {
337        match self {
338            Self::UnknownVersion { .. } => "fail_closed_unknown_version",
339            Self::UnknownCommand { .. } => "fail_closed_unknown_command",
340            Self::TransferBufferTooLarge { .. } => "fail_closed_oversized_urb",
341            Self::TooManyIsoPackets { .. } | Self::IsoDescriptorOverflow { .. } => {
342                "fail_closed_oversized_iso"
343            }
344            Self::InvalidDirection { .. } => "fail_closed_invalid_direction",
345            Self::NonZeroStatus { .. } => "fail_closed_nonzero_status",
346            Self::BodyStalled => "fail_closed_urb_stalled",
347        }
348    }
349
350    /// Operator-facing detail. Never reaches the wire.
351    pub fn describe(self) -> String {
352        match self {
353            Self::UnknownVersion { version } => {
354                format!(
355                    "version {:#06X} is neither 0 nor {:#06X}",
356                    version, USBIP_VERSION
357                )
358            }
359            Self::UnknownCommand { command } => format!("command {:#06X} is not USB/IP", command),
360            Self::TransferBufferTooLarge { announced } => format!(
361                "transfer_buffer_length announced {} bytes, limit {}",
362                announced, MAX_TRANSFER_BUFFER_BYTES
363            ),
364            Self::TooManyIsoPackets { announced } => format!(
365                "number_of_packets announced {}, limit {}",
366                announced, MAX_ISO_PACKETS
367            ),
368            Self::IsoDescriptorOverflow { announced } => format!(
369                "16 * number_of_packets ({}) does not fit in usize",
370                announced
371            ),
372            Self::InvalidDirection { direction } => {
373                format!("direction {} is neither OUT (0) nor IN (1)", direction)
374            }
375            Self::NonZeroStatus { status } => format!("status {} is not zero", status),
376            Self::BodyStalled => format!(
377                "announced payload stalled for more than {}s",
378                URB_BODY_TIMEOUT.as_secs()
379            ),
380        }
381    }
382}
383
384/// Aborts the task it holds when dropped.
385///
386/// `tokio::spawn` does not cascade cancellation, and every caller of
387/// [`run_guarded_usbip`] drives it from a `select!` that aborts it when the connection ends.
388/// Without this, the crate-side session and the reply relay would outlive that abort still
389/// holding the socket — the detached-task defect the project `CLAUDE.md` describes.
390struct AbortOnDrop<T>(tokio::task::JoinHandle<T>);
391
392impl<T> Drop for AbortOnDrop<T> {
393    fn drop(&mut self) {
394        self.0.abort();
395    }
396}
397
398/// How relaying one message ended.
399enum Relayed {
400    /// The whole message reached the crate.
401    Complete,
402    /// The peer closed, reset, or the crate-side pipe went away. Ordinary, not a refusal.
403    PeerGone,
404    /// The peer began no message before its deadline. Not a refusal either: nothing was
405    /// malformed and nothing was asked for, so this is logged at INFO and the session ends the
406    /// way a close does.
407    WentQuiet,
408}
409
410/// Run a USB/IP session for `stream` with every inbound message screened first.
411///
412/// Drop-in for `usbip::handler(&mut stream, server)`: it owns the socket, returns when the
413/// session ends, and reports the crate's own error where there is one. `device` names the
414/// protocol for the log (`"USB MSC"`, `"USB keyboard"`, …) and `peer` identifies the
415/// connection.
416///
417/// `on_import` is fired once, on the first admitted `OP_REQ_IMPORT`. That is the point at which
418/// a peer has asked for the device rather than merely opened a socket, and it is where the
419/// caller's attach event belongs — see the module docs. If the peer never imports anything the
420/// sender is dropped when this returns, so the caller's receiver resolves with `Err` and it can
421/// tell "never attached" from "attached and then left".
422pub async fn run_guarded_usbip(
423    stream: TcpStream,
424    server: Arc<usbip::UsbIpServer>,
425    device: &str,
426    peer: String,
427    status_tx: UnboundedSender<String>,
428    on_import: Option<tokio::sync::oneshot::Sender<()>>,
429    deadlines: UsbIpDeadlines,
430) -> std::io::Result<()> {
431    let log = Log::new(Some(&status_tx));
432    let _ = stream.set_nodelay(true);
433
434    let (guard_side, mut crate_side) = tokio::io::duplex(DUPLEX_BUFFER_BYTES);
435    let mut session = AbortOnDrop(tokio::spawn(async move {
436        usbip::handler(&mut crate_side, server).await
437    }));
438
439    let (mut from_peer, mut to_peer) = stream.into_split();
440    let (mut from_crate, mut to_crate) = tokio::io::split(guard_side);
441
442    // The crate's replies have to drain concurrently with the screen's inbound loop: the crate
443    // reads the next message only after it has written the previous answer, so a screen that
444    // relayed in one direction at a time would deadlock on the first URB.
445    let replies = AbortOnDrop(tokio::spawn(async move {
446        let _ = tokio::io::copy(&mut from_crate, &mut to_peer).await;
447        let _ = to_peer.shutdown().await;
448    }));
449
450    let mut chunk = vec![0u8; RELAY_CHUNK_BYTES];
451    let mut on_import = on_import;
452    // The first message is bounded far more tightly than the ones after it, because a peer that
453    // has not spoken yet is waiting for nothing this server is doing. See the two constants.
454    let mut admitted_one = false;
455    let refusal = loop {
456        let deadline = if admitted_one {
457            deadlines.idle
458        } else {
459            deadlines.first_message
460        };
461        match relay_one_message(
462            &mut from_peer,
463            &mut to_crate,
464            &mut chunk,
465            &mut on_import,
466            deadline,
467        )
468        .await
469        {
470            Ok(Relayed::Complete) => admitted_one = true,
471            Ok(Relayed::PeerGone) => break None,
472            Ok(Relayed::WentQuiet) => {
473                log.info(format!(
474                    "{} closed a USB/IP connection from {} that sent {} for {}s",
475                    device,
476                    peer,
477                    if admitted_one {
478                        "no further message"
479                    } else {
480                        "nothing at all"
481                    },
482                    deadline.as_secs()
483                ));
484                break None;
485            }
486            Err(refusal) => break Some(refusal),
487        }
488    };
489
490    if let Some(refusal) = refusal {
491        log.error(format!(
492            "{} refused a USB/IP message from {} decision={} ({}); connection closed",
493            device,
494            peer,
495            refusal.decision_tag(),
496            refusal.describe()
497        ));
498    }
499
500    // Closing the write half is what tells the crate its peer is gone.
501    drop(to_crate);
502
503    let outcome = match tokio::time::timeout(SESSION_SHUTDOWN_GRACE, &mut session.0).await {
504        Ok(Ok(result)) => result,
505        // A panic or an abort inside the session task is not something the caller can act on
506        // differently from a closed connection, and `src/panic_log.rs` has already recorded it.
507        Ok(Err(_)) => Ok(()),
508        Err(_) => Ok(()),
509    };
510
511    // Once the crate's end of the pipe is gone the reply relay sees EOF and finishes, having
512    // flushed whatever the crate managed to answer. Bounded, because an aborted session task
513    // drops its end at a moment tokio chooses rather than immediately.
514    let mut replies = replies;
515    let _ = tokio::time::timeout(SESSION_SHUTDOWN_GRACE, &mut replies.0).await;
516
517    // `session` and `replies` abort on drop, so a handler still parked in `handle_urb` and a
518    // relay still holding the socket both end here rather than outliving the connection.
519    drop(session);
520    drop(replies);
521
522    if refusal.is_some() {
523        return Err(std::io::Error::other(
524            "USB/IP message refused by the screen",
525        ));
526    }
527    outcome
528}
529
530/// Read one whole USB/IP message from the peer and relay it verbatim to the crate.
531///
532/// Every bound is applied to the value the peer *declared*, before the bytes it describes are
533/// read and before any arithmetic is done on it.
534async fn relay_one_message<R, W>(
535    from_peer: &mut R,
536    to_crate: &mut W,
537    chunk: &mut [u8],
538    on_import: &mut Option<tokio::sync::oneshot::Sender<()>>,
539    start_deadline: Duration,
540) -> Result<Relayed, Refusal>
541where
542    R: AsyncReadExt + Unpin,
543    W: AsyncWriteExt + Unpin,
544{
545    // `start_deadline` covers this read and nothing else: the caller arms it only once the
546    // previous message has been relayed in full, and every later await in this function is
547    // bounded by `URB_BODY_TIMEOUT` instead, because by then the peer has begun a message.
548    let mut head = [0u8; 4];
549    match tokio::time::timeout(start_deadline, from_peer.read_exact(&mut head)).await {
550        Ok(Ok(_)) => {}
551        Ok(Err(_)) => return Ok(Relayed::PeerGone),
552        Err(_) => return Ok(Relayed::WentQuiet),
553    }
554    let version = u16::from_be_bytes([head[0], head[1]]);
555    let command = u16::from_be_bytes([head[2], head[3]]);
556
557    // Operation-code messages carry the version; the command messages leave it zero, because
558    // those four bytes are the top half of a `u32` command field.
559    if version != 0 && version != USBIP_VERSION {
560        return Err(Refusal::UnknownVersion { version });
561    }
562
563    let tail_len = match command {
564        OP_REQ_DEVLIST => OP_REQ_DEVLIST_TAIL,
565        OP_REQ_IMPORT => OP_REQ_IMPORT_TAIL,
566        USBIP_CMD_SUBMIT | USBIP_CMD_UNLINK => CMD_TAIL,
567        // The crate would error on this too, but then it is the crate deciding. The screen
568        // decides, so nothing unrecognised is forwarded in the first place.
569        _ => return Err(Refusal::UnknownCommand { command }),
570    };
571
572    let mut message = Vec::with_capacity(4 + tail_len);
573    message.extend_from_slice(&head);
574    message.resize(4 + tail_len, 0);
575    // A peer that has written a command word has begun a message, so the body bound governs from
576    // here -- not the much longer idle one, which is about a peer that has begun nothing.
577    match tokio::time::timeout(URB_BODY_TIMEOUT, from_peer.read_exact(&mut message[4..])).await {
578        Ok(Ok(_)) => {}
579        Ok(Err(_)) => return Ok(Relayed::PeerGone),
580        Err(_) => return Err(Refusal::BodyStalled),
581    }
582
583    // Both command messages carry `direction`, and the crate only `debug_assert!`s that it is
584    // a single bit -- a panic from the wire in any debug or test build.
585    if command == USBIP_CMD_SUBMIT || command == USBIP_CMD_UNLINK {
586        let direction = be_u32(&message, OFF_DIRECTION);
587        if direction > 1 {
588            return Err(Refusal::InvalidDirection { direction });
589        }
590    } else {
591        // Same reasoning for the operation-code messages, whose `status` the crate reads and
592        // then only `debug_assert!`s to be zero.
593        let status = be_u32(&message, OFF_STATUS);
594        if status != 0 {
595            return Err(Refusal::NonZeroStatus { status });
596        }
597    }
598
599    // How many further bytes this message carries: none, unless it is a SUBMIT.
600    let (payload_len, iso_len) = if command == USBIP_CMD_SUBMIT {
601        submit_trailer_lengths(&message)?
602    } else {
603        (0, 0)
604    };
605
606    if to_crate.write_all(&message).await.is_err() {
607        return Ok(Relayed::PeerGone);
608    }
609
610    // The peer has now asked, in USB/IP's own words, for the device. Everything before this
611    // point is a peer that has said nothing the protocol recognises as a request, and must
612    // therefore cost nothing — no event, no model call. Fired after the message is admitted
613    // and relayed, so a refused or malformed one never reaches here.
614    if command == OP_REQ_IMPORT {
615        if let Some(tx) = on_import.take() {
616            let _ = tx.send(());
617        }
618    }
619
620    stream_trailer(from_peer, to_crate, chunk, payload_len + iso_len).await
621}
622
623/// Read a big-endian `u32` field out of a message the caller has already read in full.
624fn be_u32(message: &[u8], off: usize) -> u32 {
625    u32::from_be_bytes([
626        message[off],
627        message[off + 1],
628        message[off + 2],
629        message[off + 3],
630    ])
631}
632
633/// Decide a `USBIP_CMD_SUBMIT` from its 48-byte header: `(payload bytes, ISO descriptor bytes)`.
634fn submit_trailer_lengths(header: &[u8]) -> Result<(usize, usize), Refusal> {
635    let direction = be_u32(header, OFF_DIRECTION);
636    let transfer_buffer_length = be_u32(header, OFF_TRANSFER_BUFFER_LENGTH);
637    let number_of_packets = be_u32(header, OFF_NUMBER_OF_PACKETS);
638
639    // Bound the declared length, whichever direction it is for: the crate allocates from it
640    // only for OUT transfers, but an IN transfer's declaration is what sizes the *reply*
641    // buffer, and neither number should reach the crate unchecked.
642    if u64::from(transfer_buffer_length) > MAX_TRANSFER_BUFFER_BYTES as u64 {
643        return Err(Refusal::TransferBufferTooLarge {
644            announced: transfer_buffer_length,
645        });
646    }
647    // An IN URB asks the device for data and carries none, so nothing follows the header.
648    let payload_len = if direction == DIRECTION_OUT {
649        transfer_buffer_length as usize
650    } else {
651        0
652    };
653
654    let iso_len = if NON_ISO_PACKET_COUNTS.contains(&number_of_packets) {
655        0
656    } else {
657        if number_of_packets > MAX_ISO_PACKETS {
658            return Err(Refusal::TooManyIsoPackets {
659                announced: number_of_packets,
660            });
661        }
662        // Only now, with the count bounded, is it multiplied out — and still checked, because
663        // the bound is what makes the arithmetic safe rather than the arithmetic itself.
664        usize::try_from(number_of_packets)
665            .ok()
666            .and_then(|n| n.checked_mul(16))
667            .ok_or(Refusal::IsoDescriptorOverflow {
668                announced: number_of_packets,
669            })?
670    };
671
672    Ok((payload_len, iso_len))
673}
674
675/// Stream `remaining` admitted bytes from the peer to the crate, `chunk` at a time.
676async fn stream_trailer<R, W>(
677    from_peer: &mut R,
678    to_crate: &mut W,
679    chunk: &mut [u8],
680    mut remaining: usize,
681) -> Result<Relayed, Refusal>
682where
683    R: AsyncReadExt + Unpin,
684    W: AsyncWriteExt + Unpin,
685{
686    while remaining > 0 {
687        let want = remaining.min(chunk.len());
688        let read = match tokio::time::timeout(URB_BODY_TIMEOUT, from_peer.read(&mut chunk[..want]))
689            .await
690        {
691            Ok(Ok(0)) | Ok(Err(_)) => return Ok(Relayed::PeerGone),
692            Ok(Ok(n)) => n,
693            Err(_) => return Err(Refusal::BodyStalled),
694        };
695        if to_crate.write_all(&chunk[..read]).await.is_err() {
696            return Ok(Relayed::PeerGone);
697        }
698        remaining -= read;
699    }
700    Ok(Relayed::Complete)
701}