Skip to main content

netget/server/usb/msc/
handler.rs

1//! USB Mass Storage Class handler with BOT protocol and SCSI commands
2//!
3//! This module implements the UsbInterfaceHandler trait for Mass Storage Class devices.
4//! It handles Bulk-Only Transport (BOT) protocol and SCSI transparent command set.
5//!
6//! **Everything here is synchronous.** `usbip` calls `UsbInterfaceHandler::handle_urb` from
7//! inside an async fn on a tokio worker thread, so `Handle::current().block_on(...)` — which is
8//! what this file used to do for every SCSI command — panics with "Cannot block the current
9//! thread from within a runtime". The disk sits behind a `std::sync::Mutex` and the SCSI path
10//! never awaits.
11
12#[cfg(all(unix, feature = "libusb"))]
13use super::disk::DiskImage;
14#[cfg(all(unix, feature = "libusb"))]
15use crate::server::usb::descriptors::{
16    scsi_opcode, scsi_sense_key, CommandBlockWrapper, CommandStatusWrapper,
17};
18#[cfg(all(unix, feature = "libusb"))]
19use anyhow::Result;
20#[cfg(all(unix, feature = "libusb"))]
21use std::sync::{Arc, Mutex};
22#[cfg(all(unix, feature = "libusb"))]
23use tokio::sync::mpsc;
24#[cfg(all(unix, feature = "libusb"))]
25use tracing::{debug, error, info, trace, warn};
26
27/// Shared handle on the mounted disk image.
28#[cfg(all(unix, feature = "libusb"))]
29pub type SharedDisk = Arc<Mutex<DiskImage>>;
30
31/// A completed sector transfer, reported to the connection task so it can raise an LLM event.
32///
33/// `handle_urb` is synchronous and cannot call the LLM, so it pushes these onto a channel
34/// instead. This is the only reason `usb_msc_read` / `usb_msc_write` can fire at all.
35#[cfg(all(unix, feature = "libusb"))]
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum MscIoEvent {
38    Read {
39        lba: u32,
40        sectors: u32,
41        bytes: usize,
42    },
43    Write {
44        lba: u32,
45        sectors: u32,
46        bytes: usize,
47    },
48}
49
50/// USB Mass Storage Class handler implementing BOT protocol
51///
52/// `Debug` is required by `usbip::UsbInterfaceHandler` as of 0.9. Derived rather than
53/// hand-written: none of the fields is a secret, and the disk image behind the `Mutex`
54/// prints as a handle, not as its contents.
55#[cfg(all(unix, feature = "libusb"))]
56#[derive(Debug)]
57pub struct UsbMscHandler {
58    /// Virtual disk image backend
59    disk_image: SharedDisk,
60
61    /// Whether a medium is present. `eject_disk` clears it; `mount_disk` sets it.
62    medium_present: bool,
63
64    /// BOT protocol state
65    current_cbw: Option<CommandBlockWrapper>,
66    pending_data: Vec<u8>,
67    last_tag: u32,
68    /// Bytes the host asked for in the current CBW, for the CSW residue.
69    expected_transfer: u32,
70    /// Bytes actually moved for the current CBW.
71    transferred: u32,
72    csw_pending: bool,
73    /// Status the SCSI layer decided for the current command.
74    command_status: u8,
75
76    /// SCSI sense data (error reporting)
77    sense_key: u8,
78    sense_asc: u8,
79    sense_ascq: u8,
80
81    /// Pending write operation (LBA, transfer length in sectors)
82    pending_write: Option<(u32, u32)>,
83
84    /// Write-protect flag
85    write_protect: bool,
86
87    /// Where completed transfers are reported for LLM eventing.
88    io_tx: Option<mpsc::UnboundedSender<MscIoEvent>>,
89}
90
91#[cfg(all(unix, feature = "libusb"))]
92impl UsbMscHandler {
93    /// Create new MSC handler with disk image
94    pub fn new(disk_image: SharedDisk, write_protect: bool) -> Self {
95        Self {
96            disk_image,
97            medium_present: true,
98            current_cbw: None,
99            pending_data: Vec::new(),
100            last_tag: 0,
101            expected_transfer: 0,
102            transferred: 0,
103            csw_pending: false,
104            command_status: CommandStatusWrapper::STATUS_PASSED,
105            sense_key: scsi_sense_key::NO_SENSE,
106            sense_asc: 0,
107            sense_ascq: 0,
108            pending_write: None,
109            write_protect,
110            io_tx: None,
111        }
112    }
113
114    /// Report completed transfers on `io_tx` so the connection task can raise events.
115    pub fn with_io_events(mut self, io_tx: mpsc::UnboundedSender<MscIoEvent>) -> Self {
116        self.io_tx = Some(io_tx);
117        self
118    }
119
120    fn report_io(&self, event: MscIoEvent) {
121        if let Some(tx) = &self.io_tx {
122            // A closed channel means the connection task is gone; the transfer itself already
123            // succeeded, so this is not an error for the host.
124            let _ = tx.send(event);
125        }
126    }
127
128    /// Set sense data for error reporting
129    fn set_sense(&mut self, key: u8, asc: u8, ascq: u8) {
130        self.sense_key = key;
131        self.sense_asc = asc;
132        self.sense_ascq = ascq;
133        debug!(
134            "SCSI sense set: key={:#04x}, asc={:#04x}, ascq={:#04x}",
135            key, asc, ascq
136        );
137    }
138
139    /// Clear sense data
140    fn clear_sense(&mut self) {
141        self.sense_key = scsi_sense_key::NO_SENSE;
142        self.sense_asc = 0;
143        self.sense_ascq = 0;
144    }
145
146    /// Set write-protect flag
147    ///
148    /// When enabled, WRITE(10) commands will fail with DATA_PROTECT sense.
149    pub fn set_write_protect(&mut self, enabled: bool) {
150        info!(
151            "USB MSC: Write protection {}",
152            if enabled { "enabled" } else { "disabled" }
153        );
154        self.write_protect = enabled;
155    }
156
157    /// Get current write-protect status
158    pub fn is_write_protected(&self) -> bool {
159        self.write_protect
160    }
161
162    /// Replace the disk image with a new one
163    ///
164    /// This allows mounting a different disk image at runtime.
165    pub fn mount_disk(&mut self, new_disk: SharedDisk) {
166        info!("USB MSC: Mounting new disk image");
167        self.disk_image = new_disk;
168        self.medium_present = true;
169        self.reset_bot_state();
170    }
171
172    /// Eject the current disk (simulates media removal)
173    ///
174    /// After ejection every command that needs the medium fails with NOT_READY until a new
175    /// disk is mounted. Setting sense alone was not enough: the very next command cleared it,
176    /// so an ejected device kept happily serving sectors.
177    pub fn eject_disk(&mut self) {
178        info!("USB MSC: Ejecting disk");
179        self.medium_present = false;
180        self.set_sense(scsi_sense_key::NOT_READY, 0x3A, 0x00); // Medium not present
181    }
182
183    /// Whether a medium is currently present.
184    pub fn is_medium_present(&self) -> bool {
185        self.medium_present
186    }
187
188    /// Get disk capacity info as (total_sectors, bytes_per_sector).
189    pub fn get_disk_info(&self) -> Option<(u32, u32)> {
190        let disk = self.disk_image.lock().ok()?;
191        Some((disk.total_sectors(), disk.bytes_per_sector()))
192    }
193
194    /// Reset BOT state
195    fn reset_bot_state(&mut self) {
196        debug!("Resetting BOT state");
197        self.current_cbw = None;
198        self.pending_data.clear();
199        self.csw_pending = false;
200        self.pending_write = None;
201        self.expected_transfer = 0;
202        self.transferred = 0;
203        self.command_status = CommandStatusWrapper::STATUS_PASSED;
204        self.clear_sense();
205    }
206
207    /// Fail the current command because there is no medium.
208    fn no_medium(&mut self) -> u8 {
209        self.set_sense(scsi_sense_key::NOT_READY, 0x3A, 0x00);
210        CommandStatusWrapper::STATUS_FAILED
211    }
212
213    /// Fail the current command because the CDB is too short to be the command it claims to be.
214    fn malformed_cdb(&mut self, opcode: u8, need: usize, got: usize) -> u8 {
215        warn!(
216            "SCSI command {:#04x} needs {} CDB bytes, got {}",
217            opcode, need, got
218        );
219        self.set_sense(scsi_sense_key::ILLEGAL_REQUEST, 0x24, 0x00); // Invalid field in CDB
220        CommandStatusWrapper::STATUS_FAILED
221    }
222
223    /// Handle SCSI command from CBW
224    fn handle_scsi_command(&mut self, cmd: &[u8]) -> u8 {
225        let Some(&opcode) = cmd.first() else {
226            warn!("Empty SCSI command block");
227            self.set_sense(scsi_sense_key::ILLEGAL_REQUEST, 0x20, 0x00);
228            return CommandStatusWrapper::STATUS_FAILED;
229        };
230        trace!("SCSI command: opcode={:#04x} ({} bytes)", opcode, cmd.len());
231
232        match opcode {
233            scsi_opcode::INQUIRY => self.scsi_inquiry(cmd),
234            scsi_opcode::TEST_UNIT_READY => self.scsi_test_unit_ready(),
235            scsi_opcode::READ_CAPACITY_10 => self.scsi_read_capacity(),
236            scsi_opcode::READ_10 => self.scsi_read10(cmd),
237            scsi_opcode::WRITE_10 => self.scsi_write10(cmd),
238            scsi_opcode::REQUEST_SENSE => self.scsi_request_sense(),
239            scsi_opcode::MODE_SENSE_6 => self.scsi_mode_sense(cmd),
240            scsi_opcode::PREVENT_ALLOW_MEDIUM_REMOVAL => self.scsi_prevent_allow_removal(),
241            scsi_opcode::READ_FORMAT_CAPACITIES => self.scsi_read_format_capacities(),
242            _ => {
243                warn!("Unsupported SCSI command: {:#04x}", opcode);
244                self.set_sense(scsi_sense_key::ILLEGAL_REQUEST, 0x20, 0x00);
245                CommandStatusWrapper::STATUS_FAILED
246            }
247        }
248    }
249
250    /// SCSI INQUIRY command (0x12) - Return device information
251    ///
252    /// INQUIRY must answer even with no medium loaded; that is how a host learns the device
253    /// exists at all.
254    fn scsi_inquiry(&mut self, cmd: &[u8]) -> u8 {
255        let Some(&alloc_len) = cmd.get(4) else {
256            return self.malformed_cdb(scsi_opcode::INQUIRY, 5, cmd.len());
257        };
258        let alloc_len = alloc_len as usize;
259        debug!("SCSI INQUIRY (alloc_len={})", alloc_len);
260
261        #[rustfmt::skip]
262        let response = vec![
263            0x00, // Direct access block device
264            0x80, // Removable media
265            0x05, // SPC-3 compliant
266            0x02, // Response format (v2)
267            0x1F, // Additional length (31 bytes)
268            0x00, 0x00, 0x00,
269            // Vendor ID (8 bytes, padded with spaces)
270            b'N', b'e', b't', b'G', b'e', b't', b' ', b' ',
271            // Product ID (16 bytes, padded with spaces)
272            b'V', b'i', b'r', b't', b'u', b'a', b'l', b' ',
273            b'D', b'i', b's', b'k', b' ', b' ', b' ', b' ',
274            // Product revision (4 bytes)
275            b'1', b'.', b'0', b' ',
276        ];
277
278        self.pending_data = response[..alloc_len.min(response.len())].to_vec();
279        self.clear_sense();
280        CommandStatusWrapper::STATUS_PASSED
281    }
282
283    /// SCSI TEST_UNIT_READY command (0x00) - Check device readiness
284    fn scsi_test_unit_ready(&mut self) -> u8 {
285        debug!("SCSI TEST_UNIT_READY");
286        if !self.medium_present {
287            return self.no_medium();
288        }
289        self.clear_sense();
290        CommandStatusWrapper::STATUS_PASSED
291    }
292
293    /// SCSI READ_CAPACITY(10) command (0x25) - Return disk capacity
294    fn scsi_read_capacity(&mut self) -> u8 {
295        if !self.medium_present {
296            return self.no_medium();
297        }
298
299        let Some((total_sectors, block_size)) = self.get_disk_info() else {
300            error!("READ_CAPACITY: disk image mutex poisoned");
301            self.set_sense(scsi_sense_key::HARDWARE_ERROR, 0x44, 0x00);
302            return CommandStatusWrapper::STATUS_FAILED;
303        };
304        let last_lba = total_sectors.saturating_sub(1);
305
306        debug!(
307            "SCSI READ_CAPACITY: last_lba={}, block_size={}",
308            last_lba, block_size
309        );
310
311        let mut response = Vec::with_capacity(8);
312        response.extend_from_slice(&last_lba.to_be_bytes());
313        response.extend_from_slice(&block_size.to_be_bytes());
314
315        self.pending_data = response;
316        self.clear_sense();
317        CommandStatusWrapper::STATUS_PASSED
318    }
319
320    /// SCSI READ(10) command (0x28) - Read sectors from disk
321    fn scsi_read10(&mut self, cmd: &[u8]) -> u8 {
322        if cmd.len() < 9 {
323            return self.malformed_cdb(scsi_opcode::READ_10, 9, cmd.len());
324        }
325        if !self.medium_present {
326            return self.no_medium();
327        }
328
329        let lba = u32::from_be_bytes([cmd[2], cmd[3], cmd[4], cmd[5]]);
330        let transfer_len = u16::from_be_bytes([cmd[7], cmd[8]]) as u32;
331
332        debug!(
333            "SCSI READ(10): lba={}, transfer_len={} sectors",
334            lba, transfer_len
335        );
336
337        let result = match self.disk_image.lock() {
338            Ok(disk) => disk.read_sectors(lba, transfer_len),
339            Err(_) => Err(anyhow::anyhow!("disk image mutex poisoned")),
340        };
341
342        match result {
343            Ok(data) => {
344                let bytes = data.len();
345                self.pending_data = data;
346                self.clear_sense();
347                self.report_io(MscIoEvent::Read {
348                    lba,
349                    sectors: transfer_len,
350                    bytes,
351                });
352                CommandStatusWrapper::STATUS_PASSED
353            }
354            Err(e) => {
355                error!("READ(10) failed: {}", e);
356                // Logical block address out of range.
357                self.set_sense(scsi_sense_key::ILLEGAL_REQUEST, 0x21, 0x00);
358                CommandStatusWrapper::STATUS_FAILED
359            }
360        }
361    }
362
363    /// SCSI WRITE(10) command (0x2A) - Write sectors to disk
364    fn scsi_write10(&mut self, cmd: &[u8]) -> u8 {
365        if cmd.len() < 9 {
366            return self.malformed_cdb(scsi_opcode::WRITE_10, 9, cmd.len());
367        }
368        if !self.medium_present {
369            return self.no_medium();
370        }
371
372        let lba = u32::from_be_bytes([cmd[2], cmd[3], cmd[4], cmd[5]]);
373        let transfer_len = u16::from_be_bytes([cmd[7], cmd[8]]) as u32;
374
375        debug!(
376            "SCSI WRITE(10): lba={}, transfer_len={} sectors (write_protect={})",
377            lba, transfer_len, self.write_protect
378        );
379
380        if self.write_protect {
381            warn!("WRITE(10) blocked: disk is write-protected");
382            self.set_sense(scsi_sense_key::DATA_PROTECT, 0x27, 0x00);
383            return CommandStatusWrapper::STATUS_FAILED;
384        }
385
386        // Store pending write info - data will arrive in separate bulk OUT transfer
387        self.pending_write = Some((lba, transfer_len));
388        self.clear_sense();
389        CommandStatusWrapper::STATUS_PASSED
390    }
391
392    /// SCSI REQUEST_SENSE command (0x03) - Return sense data
393    fn scsi_request_sense(&mut self) -> u8 {
394        debug!(
395            "SCSI REQUEST_SENSE: key={:#04x}, asc={:#04x}, ascq={:#04x}",
396            self.sense_key, self.sense_asc, self.sense_ascq
397        );
398
399        #[rustfmt::skip]
400        let response = vec![
401            0x70,             // Response code (current error)
402            0x00,
403            self.sense_key,   // Sense key
404            0x00, 0x00, 0x00, 0x00,
405            0x0A,             // Additional sense length
406            0x00, 0x00, 0x00, 0x00,
407            self.sense_asc,   // Additional sense code
408            self.sense_ascq,  // Additional sense code qualifier
409            0x00, 0x00, 0x00, 0x00,
410        ];
411
412        self.pending_data = response;
413        // Reporting the sense consumes it, which is what a host expects. An ejected medium is
414        // a standing condition, not a one-shot error, so re-arm it.
415        self.clear_sense();
416        if !self.medium_present {
417            self.set_sense(scsi_sense_key::NOT_READY, 0x3A, 0x00);
418        }
419        CommandStatusWrapper::STATUS_PASSED
420    }
421
422    /// SCSI MODE_SENSE(6) command (0x1A) - Return device parameters
423    fn scsi_mode_sense(&mut self, cmd: &[u8]) -> u8 {
424        let page_code = cmd.get(2).map(|b| b & 0x3F).unwrap_or(0x3F);
425        debug!("SCSI MODE_SENSE(6): page_code={:#04x}", page_code);
426
427        #[rustfmt::skip]
428        let response = vec![
429            0x03,             // Mode data length
430            0x00,             // Medium type
431            if self.write_protect { 0x80 } else { 0x00 }, // Device-specific parameter
432            0x00,             // Block descriptor length
433        ];
434
435        self.pending_data = response;
436        self.clear_sense();
437        CommandStatusWrapper::STATUS_PASSED
438    }
439
440    /// SCSI PREVENT_ALLOW_MEDIUM_REMOVAL command (0x1E)
441    fn scsi_prevent_allow_removal(&mut self) -> u8 {
442        debug!("SCSI PREVENT_ALLOW_MEDIUM_REMOVAL");
443        // We don't enforce this, just acknowledge
444        self.clear_sense();
445        CommandStatusWrapper::STATUS_PASSED
446    }
447
448    /// SCSI READ_FORMAT_CAPACITIES command (0x23)
449    fn scsi_read_format_capacities(&mut self) -> u8 {
450        if !self.medium_present {
451            return self.no_medium();
452        }
453
454        let Some((total_sectors, block_size)) = self.get_disk_info() else {
455            error!("READ_FORMAT_CAPACITIES: disk image mutex poisoned");
456            self.set_sense(scsi_sense_key::HARDWARE_ERROR, 0x44, 0x00);
457            return CommandStatusWrapper::STATUS_FAILED;
458        };
459
460        debug!("SCSI READ_FORMAT_CAPACITIES: {} sectors", total_sectors);
461
462        #[rustfmt::skip]
463        let mut response = vec![
464            0x00, 0x00, 0x00, 0x08,  // Capacity list length (8 bytes)
465        ];
466        response.extend_from_slice(&total_sectors.to_be_bytes());
467        response.push(0x02); // Descriptor type: formatted media
468        response.extend_from_slice(&block_size.to_be_bytes()[1..]); // Block size (24-bit)
469
470        self.pending_data = response;
471        self.clear_sense();
472        CommandStatusWrapper::STATUS_PASSED
473    }
474
475    /// Handle write data received from bulk OUT endpoint
476    fn handle_write_data(&mut self, data: &[u8]) -> Result<()> {
477        let Some((lba, transfer_len)) = self.pending_write.take() else {
478            warn!("Received write data with no pending WRITE(10) command");
479            return Ok(());
480        };
481
482        debug!(
483            "Writing {} bytes to LBA {} ({} sectors expected)",
484            data.len(),
485            lba,
486            transfer_len
487        );
488
489        let result = match self.disk_image.lock() {
490            Ok(mut disk) => disk.write_sectors(lba, data),
491            Err(_) => Err(anyhow::anyhow!("disk image mutex poisoned")),
492        };
493
494        match result {
495            Ok(sectors_written) => {
496                info!(
497                    "WRITE(10) completed: {} sectors written to LBA {}",
498                    sectors_written, lba
499                );
500                self.report_io(MscIoEvent::Write {
501                    lba,
502                    sectors: sectors_written,
503                    bytes: data.len(),
504                });
505                Ok(())
506            }
507            Err(e) => {
508                error!("WRITE(10) failed: {}", e);
509                self.set_sense(scsi_sense_key::MEDIUM_ERROR, 0x03, 0x00);
510                Err(e)
511            }
512        }
513    }
514
515    /// Start a new BOT command from a freshly parsed CBW.
516    fn begin_command(&mut self, cbw: CommandBlockWrapper) {
517        // Copy packed fields out before touching them: taking a reference to a field of a
518        // `#[repr(packed)]` struct is undefined behaviour.
519        let tag = cbw.tag;
520        let lun = cbw.lun;
521        let flags = cbw.flags;
522        let data_transfer_length = cbw.data_transfer_length;
523
524        debug!(
525            "BOT: Received CBW (tag={:#010x}, lun={}, flags={:#04x}, length={})",
526            tag, lun, flags, data_transfer_length
527        );
528
529        self.last_tag = tag;
530        self.expected_transfer = data_transfer_length;
531        self.transferred = 0;
532        self.pending_data.clear();
533
534        let scsi_cmd = cbw.scsi_command().to_vec();
535        self.command_status = self.handle_scsi_command(&scsi_cmd);
536        self.current_cbw = Some(cbw);
537
538        // With neither data to send nor data to receive, the CSW is the whole rest of the
539        // command.
540        if self.pending_data.is_empty() && self.pending_write.is_none() {
541            self.csw_pending = true;
542        }
543    }
544
545    /// Build the CSW that ends the current command.
546    fn build_csw(&mut self) -> [u8; CommandStatusWrapper::SIZE] {
547        let residue = self.expected_transfer.saturating_sub(self.transferred);
548        let csw = CommandStatusWrapper::new(self.last_tag, residue, self.command_status);
549
550        let tag = csw.tag;
551        let status = csw.status;
552        debug!(
553            "BOT: Sending CSW (tag={:#010x}, status={}, residue={})",
554            tag, status, residue
555        );
556
557        self.csw_pending = false;
558        self.current_cbw = None;
559        self.expected_transfer = 0;
560        self.transferred = 0;
561        self.command_status = CommandStatusWrapper::STATUS_PASSED;
562
563        csw.to_bytes()
564    }
565}
566
567// Implement usbip::UsbInterfaceHandler trait
568#[cfg(all(unix, feature = "libusb"))]
569impl usbip::UsbInterfaceHandler for UsbMscHandler {
570    fn handle_urb(
571        &mut self,
572        _interface: &usbip::UsbInterface,
573        endpoint: usbip::UsbEndpoint,
574        // usbip 0.9 passes the host's declared transfer length. The BOT state machine
575        // derives its own lengths from the CBW, so this only caps how much of a queued
576        // response goes out in one transfer.
577        transfer_buffer_length: u32,
578        setup: usbip::SetupPacket,
579        data: &[u8],
580    ) -> std::result::Result<Vec<u8>, std::io::Error> {
581        // `is_ep0()`, not `address == 0`: the crate's control IN endpoint is 0x80, so an
582        // address comparison sends every class request down the bulk IN path — Get Max LUN
583        // would have been answered with whatever the BOT state machine had queued.
584        if endpoint.is_ep0() {
585            // Control transfer
586            trace!(
587                "MSC control request: type={:#04x}, request={:#04x}",
588                setup.request_type,
589                setup.request
590            );
591
592            return match (setup.request_type, setup.request) {
593                // Bulk-Only Mass Storage Reset (0x21, 0xFF)
594                (0x21, 0xFF) => {
595                    debug!("BOT: Mass Storage Reset");
596                    self.reset_bot_state();
597                    Ok(vec![])
598                }
599
600                // Get Max LUN (0xA1, 0xFE)
601                (0xA1, 0xFE) => {
602                    debug!("BOT: Get Max LUN");
603                    Ok(vec![0x00]) // Single LUN device
604                }
605
606                _ => {
607                    warn!(
608                        "Unsupported MSC control request: type={:#04x}, request={:#04x}",
609                        setup.request_type, setup.request
610                    );
611                    Err(std::io::Error::new(
612                        std::io::ErrorKind::InvalidInput,
613                        "Unsupported control request",
614                    ))
615                }
616            };
617        }
618
619        if matches!(endpoint.direction(), usbip::Direction::Out) {
620            // Bulk OUT endpoint (host to device) - receives CBW and write data.
621            //
622            // Which of the two this is depends on the BOT phase, not on the length. Deciding
623            // by `len() == 31` alone misreads a 31-byte data-out payload as a command.
624            if self.pending_write.is_some() {
625                if let Err(e) = self.handle_write_data(data) {
626                    error!("Failed to handle write data: {}", e);
627                    self.command_status = CommandStatusWrapper::STATUS_FAILED;
628                } else {
629                    self.transferred = self
630                        .transferred
631                        .saturating_add(u32::try_from(data.len()).unwrap_or(u32::MAX));
632                }
633                self.csw_pending = true;
634                return Ok(vec![]);
635            }
636
637            if self.csw_pending {
638                // The command already failed (write-protected media, say) but the host is
639                // still pushing the data phase it promised in the CBW. A real device stalls
640                // the endpoint here; there is no stall over USB/IP, so discard the bytes and
641                // let the CSW report them as residue.
642                debug!(
643                    "BOT: discarding {} byte(s) of data for a command that already completed",
644                    data.len()
645                );
646                return Ok(vec![]);
647            }
648
649            return match CommandBlockWrapper::parse(data) {
650                Some(cbw) => {
651                    self.begin_command(cbw);
652                    Ok(vec![])
653                }
654                None => {
655                    error!(
656                        "Failed to parse CBW: invalid format ({} bytes on the bulk OUT endpoint)",
657                        data.len()
658                    );
659                    Err(std::io::Error::new(
660                        std::io::ErrorKind::InvalidData,
661                        "Invalid CBW format",
662                    ))
663                }
664            };
665        }
666
667        // Bulk IN endpoint (device to host) - sends data then CSW.
668        if !self.pending_data.is_empty() {
669            let take = if transfer_buffer_length == 0 {
670                self.pending_data.len()
671            } else {
672                (transfer_buffer_length as usize).min(self.pending_data.len())
673            };
674            let chunk: Vec<u8> = self.pending_data.drain(..take).collect();
675            debug!(
676                "BOT: Sending {} bytes of data ({} still queued)",
677                chunk.len(),
678                self.pending_data.len()
679            );
680            self.transferred = self
681                .transferred
682                .saturating_add(u32::try_from(chunk.len()).unwrap_or(u32::MAX));
683            if self.pending_data.is_empty() {
684                self.csw_pending = true;
685            }
686            return Ok(chunk);
687        }
688
689        if self.csw_pending {
690            return Ok(self.build_csw().to_vec());
691        }
692
693        // Nothing queued: an idle bulk IN poll. A zero-length reply is the honest answer.
694        Ok(vec![])
695    }
696
697    fn get_class_specific_descriptor(&self) -> Vec<u8> {
698        // MSC doesn't have class-specific descriptors beyond standard interface descriptor
699        vec![]
700    }
701
702    fn as_any(&mut self) -> &mut dyn std::any::Any {
703        self
704    }
705}