1mod transport;
2mod types;
3
4pub use types::{
5 Cdb, Direction, MAX_CDB_LEN, MAX_SENSE_LEN, OpenOpts, ScsiCommand, ScsiResult, ScsiStatus,
6 Sense,
7};
8
9#[derive(Debug)]
11pub enum Error {
12 Io(std::io::Error),
13 InvalidParameter(&'static str),
14 Internal(&'static str),
15}
16
17impl std::fmt::Display for Error {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 match self {
20 Error::Io(e) => write!(f, "I/O error: {e}"),
21 Error::InvalidParameter(msg) => write!(f, "invalid parameter: {msg}"),
22 Error::Internal(msg) => write!(f, "internal error: {msg}"),
23 }
24 }
25}
26
27impl std::error::Error for Error {
28 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
29 match self {
30 Error::Io(e) => Some(e),
31 _ => None,
32 }
33 }
34}
35
36impl From<std::io::Error> for Error {
37 fn from(e: std::io::Error) -> Self {
38 Error::Io(e)
39 }
40}
41
42pub struct ScsiDevice(transport::Device);
47
48impl ScsiDevice {
49 pub fn open(path: &std::path::Path, opts: &OpenOpts) -> Result<Self, Error> {
59 transport::Device::open(path, opts).map(ScsiDevice)
60 }
61
62 pub fn execute(&mut self, cmd: ScsiCommand) -> Result<ScsiResult, Error> {
68 self.0.execute(cmd)
69 }
70}
71
72#[cfg(test)]
75impl ScsiDevice {
76 #[cfg(target_os = "windows")]
77 fn new_test() -> std::io::Result<Self> {
78 transport::Device::new_test().map(ScsiDevice)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
89 fn error_display_invalid_parameter() {
90 let e = Error::InvalidParameter("something went wrong");
91 assert_eq!(e.to_string(), "invalid parameter: something went wrong");
92 }
93
94 #[test]
95 fn error_display_io() {
96 let e = Error::Io(std::io::Error::from(std::io::ErrorKind::NotFound));
97 assert!(e.to_string().starts_with("I/O error:"));
98 }
99
100 #[test]
101 fn error_source_io_is_some() {
102 use std::error::Error as _;
103 let e = Error::Io(std::io::Error::from(std::io::ErrorKind::PermissionDenied));
104 assert!(e.source().is_some());
105 }
106
107 #[test]
108 fn error_source_invalid_parameter_is_none() {
109 use std::error::Error as _;
110 let e = Error::InvalidParameter("x");
111 assert!(e.source().is_none());
112 }
113
114 #[test]
117 fn open_nonexistent_path_returns_io_error() {
118 let result = ScsiDevice::open(
119 std::path::Path::new(r"\\.\LibScsiNonexistentDevice999"),
120 &OpenOpts::default(),
121 );
122 assert!(matches!(result, Err(Error::Io(_))));
123 }
124
125 #[cfg(target_os = "windows")]
129 #[test]
130 fn execute_valid_cdb_reaches_ioctl() {
131 let mut dev = ScsiDevice::new_test().expect("temp file");
132 let cmd = ScsiCommand {
133 cdb: Cdb::new([0u8; 6]).unwrap(),
134 direction: Direction::None,
135 data: vec![],
136 timeout_secs: None,
137 };
138 assert!(matches!(dev.execute(cmd), Err(Error::Io(_))));
139 }
140}