1#[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#[cfg(all(unix, feature = "libusb"))]
29pub type SharedDisk = Arc<Mutex<DiskImage>>;
30
31#[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#[cfg(all(unix, feature = "libusb"))]
56#[derive(Debug)]
57pub struct UsbMscHandler {
58 disk_image: SharedDisk,
60
61 medium_present: bool,
63
64 current_cbw: Option<CommandBlockWrapper>,
66 pending_data: Vec<u8>,
67 last_tag: u32,
68 expected_transfer: u32,
70 transferred: u32,
72 csw_pending: bool,
73 command_status: u8,
75
76 sense_key: u8,
78 sense_asc: u8,
79 sense_ascq: u8,
80
81 pending_write: Option<(u32, u32)>,
83
84 write_protect: bool,
86
87 io_tx: Option<mpsc::UnboundedSender<MscIoEvent>>,
89}
90
91#[cfg(all(unix, feature = "libusb"))]
92impl UsbMscHandler {
93 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 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 let _ = tx.send(event);
125 }
126 }
127
128 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 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 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 pub fn is_write_protected(&self) -> bool {
159 self.write_protect
160 }
161
162 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 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); }
182
183 pub fn is_medium_present(&self) -> bool {
185 self.medium_present
186 }
187
188 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 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 fn no_medium(&mut self) -> u8 {
209 self.set_sense(scsi_sense_key::NOT_READY, 0x3A, 0x00);
210 CommandStatusWrapper::STATUS_FAILED
211 }
212
213 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); CommandStatusWrapper::STATUS_FAILED
221 }
222
223 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 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, 0x80, 0x05, 0x02, 0x1F, 0x00, 0x00, 0x00,
269 b'N', b'e', b't', b'G', b'e', b't', b' ', b' ',
271 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 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 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 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 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 self.set_sense(scsi_sense_key::ILLEGAL_REQUEST, 0x21, 0x00);
358 CommandStatusWrapper::STATUS_FAILED
359 }
360 }
361 }
362
363 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 self.pending_write = Some((lba, transfer_len));
388 self.clear_sense();
389 CommandStatusWrapper::STATUS_PASSED
390 }
391
392 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, 0x00,
403 self.sense_key, 0x00, 0x00, 0x00, 0x00,
405 0x0A, 0x00, 0x00, 0x00, 0x00,
407 self.sense_asc, self.sense_ascq, 0x00, 0x00, 0x00, 0x00,
410 ];
411
412 self.pending_data = response;
413 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 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, 0x00, if self.write_protect { 0x80 } else { 0x00 }, 0x00, ];
434
435 self.pending_data = response;
436 self.clear_sense();
437 CommandStatusWrapper::STATUS_PASSED
438 }
439
440 fn scsi_prevent_allow_removal(&mut self) -> u8 {
442 debug!("SCSI PREVENT_ALLOW_MEDIUM_REMOVAL");
443 self.clear_sense();
445 CommandStatusWrapper::STATUS_PASSED
446 }
447
448 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, ];
466 response.extend_from_slice(&total_sectors.to_be_bytes());
467 response.push(0x02); response.extend_from_slice(&block_size.to_be_bytes()[1..]); self.pending_data = response;
471 self.clear_sense();
472 CommandStatusWrapper::STATUS_PASSED
473 }
474
475 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 fn begin_command(&mut self, cbw: CommandBlockWrapper) {
517 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 if self.pending_data.is_empty() && self.pending_write.is_none() {
541 self.csw_pending = true;
542 }
543 }
544
545 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#[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 transfer_buffer_length: u32,
578 setup: usbip::SetupPacket,
579 data: &[u8],
580 ) -> std::result::Result<Vec<u8>, std::io::Error> {
581 if endpoint.is_ep0() {
585 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 (0x21, 0xFF) => {
595 debug!("BOT: Mass Storage Reset");
596 self.reset_bot_state();
597 Ok(vec![])
598 }
599
600 (0xA1, 0xFE) => {
602 debug!("BOT: Get Max LUN");
603 Ok(vec![0x00]) }
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 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 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 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 Ok(vec![])
695 }
696
697 fn get_class_specific_descriptor(&self) -> Vec<u8> {
698 vec![]
700 }
701
702 fn as_any(&mut self) -> &mut dyn std::any::Any {
703 self
704 }
705}