iscsi_client_rs/models/data/
sense_data.rs1use core::fmt;
9
10use anyhow::{Context, Result, anyhow};
11
12use crate::models::data::Entry;
13
14pub const FIXED_MIN_LEN: usize = 18;
16
17#[repr(C)]
19#[derive(Default, PartialEq)]
20pub struct SenseData {
21 pub valid: bool,
23 pub response_code: u8,
25 pub sense_key: u8,
27 pub ili: bool,
29 pub eom: bool,
31 pub filemark: bool,
33 pub information: u32,
35 pub additional_len: u8,
37 pub cmd_specific: u32,
39 pub asc: u8,
41 pub ascq: u8,
43}
44
45impl SenseData {
46 pub fn parse(buf: &[u8]) -> Result<Self> {
48 if buf.len() < FIXED_MIN_LEN {
49 return Err(anyhow!("sense buffer too small: {}", buf.len()));
50 }
51
52 let sense = if buf.len() >= 3 {
53 let maybe_len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
54 let rc = buf[2] & 0x7F;
55 if maybe_len + 2 == buf.len() && matches!(rc, 0x70..=0x73) {
56 &buf[2..]
57 } else {
58 buf
59 }
60 } else {
61 buf
62 };
63
64 if sense.len() < FIXED_MIN_LEN {
65 return Err(anyhow!(
66 "sense payload too small after prefix stripping: {}",
67 sense.len()
68 ));
69 }
70
71 let response_code = sense[0] & 0x7F;
72
73 match response_code {
74 0x70 | 0x71 => Self::parse_fixed(sense),
75 0x72 | 0x73 => Err(anyhow!(
76 "descriptor-format sense (0x{:02x}) is not supported yet",
77 response_code
78 )),
79 other => Err(anyhow!("unknown sense response code 0x{:02x}", other)),
80 }
81 }
82
83 fn parse_fixed(sense: &[u8]) -> Result<Self> {
84 if sense.len() < FIXED_MIN_LEN {
85 return Err(anyhow!("fixed sense too small: {}", sense.len()));
86 }
87
88 let valid = sense[0] & 0x80 != 0;
89 let response_code = sense[0] & 0x7F;
90
91 let filemark = sense[2] & 0x80 != 0;
92 let eom = sense[2] & 0x40 != 0;
93 let ili = sense[2] & 0x20 != 0;
94 let sense_key = sense[2] & 0x0F;
95
96 let information = u32::from_be_bytes(
97 sense[3..7]
98 .try_into()
99 .context("failed to read Information (3..6)")?,
100 );
101
102 let additional_len = sense[7];
103
104 let needed = 8usize + (additional_len as usize);
105 if sense.len() < needed {
106 return Err(anyhow!(
107 "sense length mismatch: have {}, need at least {} (additional_len={})",
108 sense.len(),
109 needed,
110 additional_len
111 ));
112 }
113
114 let cmd_specific = u32::from_be_bytes(
115 sense[8..12]
116 .try_into()
117 .context("failed to read Cmd-specific (8..11)")?,
118 );
119
120 let asc = sense[12];
121 let ascq = sense[13];
122
123 Ok(SenseData {
124 valid,
125 response_code,
126 sense_key,
127 ili,
128 eom,
129 filemark,
130 information,
131 additional_len,
132 cmd_specific,
133 asc,
134 ascq,
135 })
136 }
137}
138
139impl fmt::Debug for SenseData {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 f.debug_struct("SenseData")
142 .field("valid", &self.valid)
143 .field(
144 "response_code",
145 &format_args!("{:#04x}", self.response_code),
146 )
147 .field("sense_key", &format_args!("{:#x}", self.sense_key))
148 .field("filemark", &self.filemark)
149 .field("eom", &self.eom)
150 .field("ili", &self.ili)
151 .field("information", &self.information)
152 .field("additional_len", &self.additional_len)
153 .field("cmd_specific", &self.cmd_specific)
154 .field("asc", &format_args!("{:#04x}", self.asc))
155 .field("ascq", &format_args!("{:#04x}", self.ascq))
156 .field("description", &asc_ascq_to_str(self.asc, self.ascq))
157 .finish()
158 }
159}
160
161#[inline]
163pub fn asc_ascq_to_str(asc: u8, ascq: u8) -> &'static str {
164 Entry::lookup(asc, ascq).unwrap_or("UNSPECIFIED / vendor specific")
165}