Skip to main content

iscsi_client_rs/models/command/
common.rs

1//! This module defines common structures and enums for iSCSI Command PDUs.
2//! It includes flags, task attributes, response codes, and SCSI status codes.
3
4// SPDX-License-Identifier: AGPL-3.0-or-later
5// Copyright (C) 2012-2025 Andrei Maltsev
6
7use std::{fmt, ptr};
8
9use anyhow::bail;
10use thiserror::Error;
11
12bitflags::bitflags! {
13    #[derive(Default, Clone, PartialEq)]
14    /// iSCSI SCSI Command PDU flags
15    pub struct ScsiCommandRequestFlags: u8 {
16        const FINAL     = 0x80;
17        const READ      = 0x40;
18        const WRITE     = 0x20;
19        /// lowest 3 bits represent the TaskAttribute
20        const ATTR_MASK = 0b0000_0111;
21    }
22}
23
24impl TryFrom<u8> for ScsiCommandRequestFlags {
25    type Error = anyhow::Error;
26
27    fn try_from(value: u8) -> Result<Self, Self::Error> {
28        ScsiCommandRequestFlags::from_bits(value)
29            .ok_or_else(|| anyhow::anyhow!("invalid ScsiCommandFlags: {:#08b}", value))
30    }
31}
32
33impl fmt::Debug for ScsiCommandRequestFlags {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        use ScsiCommandRequestFlags as F;
36
37        write!(f, "ScsiCommandRequestFlags(")?;
38
39        let mut sep = "";
40        if self.contains(F::FINAL) {
41            write!(f, "FINAL")?;
42            sep = "|";
43        }
44        if self.contains(F::READ) {
45            write!(f, "{sep}READ")?;
46            sep = "|";
47        }
48        if self.contains(F::WRITE) {
49            write!(f, "{sep}WRITE")?;
50            sep = "|";
51        }
52
53        let attr_bits = self.bits() & F::ATTR_MASK.bits();
54        let attr = TaskAttribute::from(attr_bits);
55        write!(f, "{sep}ATTR={attr:?}({attr_bits:#04x})")?;
56
57        write!(f, ")")
58    }
59}
60
61/// SCSI Task Attributes for command ordering and queuing
62///
63/// Defines how SCSI commands should be queued and executed relative to other
64/// commands. These attributes control the ordering behavior of commands in the
65/// target's command queue.
66#[derive(Clone, Copy, PartialEq)]
67pub enum TaskAttribute {
68    /// Untagged command (0) - legacy simple queuing
69    Untagged,
70    /// Simple task attribute (1) - commands may be reordered for optimization
71    Simple,
72    /// Ordered task attribute (2) - commands must execute in order
73    Ordered,
74    /// Head of queue task attribute (3) - command should execute immediately
75    HeadOfQueue,
76    /// Auto Contingent Allegiance (4) - special error recovery attribute
77    ACA,
78    /// Reserved attribute values (5-7) - future use
79    Reserved(u8),
80}
81
82impl From<u8> for TaskAttribute {
83    fn from(value: u8) -> Self {
84        match value {
85            0 => TaskAttribute::Untagged,
86            1 => TaskAttribute::Simple,
87            2 => TaskAttribute::Ordered,
88            3 => TaskAttribute::HeadOfQueue,
89            4 => TaskAttribute::ACA,
90            r @ 5..=7 => TaskAttribute::Reserved(r),
91            other => TaskAttribute::Reserved(other),
92        }
93    }
94}
95
96impl From<TaskAttribute> for u8 {
97    fn from(value: TaskAttribute) -> Self {
98        match value {
99            TaskAttribute::Untagged => 0,
100            TaskAttribute::Simple => 1,
101            TaskAttribute::Ordered => 2,
102            TaskAttribute::HeadOfQueue => 3,
103            TaskAttribute::ACA => 4,
104            TaskAttribute::Reserved(v) => v & ScsiCommandRequestFlags::ATTR_MASK.bits(),
105        }
106    }
107}
108
109impl fmt::Debug for TaskAttribute {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        match *self {
112            TaskAttribute::Untagged => write!(f, "Untagged"),
113            TaskAttribute::Simple => write!(f, "Simple"),
114            TaskAttribute::Ordered => write!(f, "Ordered"),
115            TaskAttribute::HeadOfQueue => write!(f, "HeadOfQueue"),
116            TaskAttribute::ACA => write!(f, "ACA"),
117            TaskAttribute::Reserved(val) => write!(f, "Reserved({val})"),
118        }
119    }
120}
121
122bitflags::bitflags! {
123    #[derive(Debug, Default, Clone, PartialEq)]
124    /// iSCSI SCSI Command PDU flags
125    pub struct ScsiCommandResponseFlags: u8 {
126        const FINAL     = 0b1000_0000;
127        /// Bidir Read Residual Overflow (o)
128        const O_SMALL = 0b0001_0000;
129        /// Bidir Read Residual Underflow (u)
130        const U_SMALL = 0b0000_1000;
131        /// Residual Overflow (O)
132        const O_BIG = 0b0000_0100;
133        /// Residual Underflow (U)
134        const U_BIG = 0b0000_0010;
135    }
136}
137
138impl TryFrom<u8> for ScsiCommandResponseFlags {
139    type Error = anyhow::Error;
140
141    fn try_from(value: u8) -> Result<Self, Self::Error> {
142        let tmp = ScsiCommandResponseFlags::from_bits(value)
143            .ok_or_else(|| anyhow::anyhow!("invalid ScsiCommandFlags: {:#08b}", value))?;
144
145        if (tmp.contains(ScsiCommandResponseFlags::U_BIG)
146            && tmp.contains(ScsiCommandResponseFlags::O_BIG))
147            || (tmp.contains(ScsiCommandResponseFlags::U_SMALL)
148                && tmp.contains(ScsiCommandResponseFlags::O_SMALL))
149        {
150            bail!("Protocol error cause U && O both presented")
151        }
152
153        Ok(tmp)
154    }
155}
156
157/// The 1-byte “Response” field in a SCSI Response PDU (RFC 7143 § 11.4.3)
158#[repr(u8)]
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub enum ResponseCode {
161    /// 0x00 – Command Completed at Target
162    CommandCompleted = 0x00,
163    /// 0x01 – Target Failure
164    TargetFailure = 0x01,
165    /// 0x80–0xFF – Vendor‐specific
166    VendorSpecific(u8),
167    /// 0x02–0x7F (excluding 0x01) and 0x02–0x7F – reserved by the spec
168    Reserved(u8),
169}
170
171/// Error type for invalid iSCSI response codes
172///
173/// Returned when attempting to parse an invalid or unrecognized response code
174/// from an iSCSI SCSI Command Response PDU.
175#[derive(Debug, Error)]
176#[error("invalid response code: 0x{0:02x}")]
177pub struct UnknownResponseCode(pub u8);
178
179impl From<&ResponseCode> for u8 {
180    fn from(value: &ResponseCode) -> Self {
181        unsafe { ptr::read_unaligned(value as *const ResponseCode as *const u8) }
182    }
183}
184
185impl TryFrom<u8> for ResponseCode {
186    type Error = UnknownResponseCode;
187
188    fn try_from(b: u8) -> Result<Self, Self::Error> {
189        match b {
190            0x00 => Ok(ResponseCode::CommandCompleted),
191            0x01 => Ok(ResponseCode::TargetFailure),
192            0x80..=0xFF => Ok(ResponseCode::VendorSpecific(b)),
193            r @ 0x02..=0x7F => Ok(ResponseCode::Reserved(r)),
194        }
195    }
196}
197
198/// SCSI status codes returned in iSCSI SCSI Command Response PDU
199///
200/// The 1-byte "Status" field as defined in RFC 7143 § 11.4.2.
201/// Only valid when ResponseCode == CommandCompleted.
202/// These status codes indicate the result of SCSI command execution.
203#[repr(u8)]
204#[derive(Debug, Clone, Default, PartialEq, Eq)]
205pub enum ScsiStatus {
206    /// Command completed successfully (0x00)
207    #[default]
208    Good = 0x00,
209    /// Check condition - sense data available (0x02)
210    CheckCondition = 0x02,
211    /// Target busy - retry later (0x08)
212    Busy = 0x08,
213    /// Reservation conflict (0x18)
214    ReservationConflict = 0x18,
215    /// Task set full - target queue full (0x28)
216    TaskSetFull = 0x28,
217    /// Auto Contingent Allegiance active (0x30)
218    AcaActive = 0x30,
219    /// Task aborted (0x40)
220    TaskAborted = 0x40,
221    /// Any other status codes defined in SAM-x or reserved
222    Other(u8),
223}
224
225/// Error type for invalid SCSI status codes
226///
227/// Returned when attempting to parse an invalid or unrecognized SCSI status
228/// code from an iSCSI SCSI Command Response PDU.
229#[derive(Debug, Error)]
230#[error("invalid SCSI status: 0x{0:02x}")]
231pub struct UnknownScsiStatus(pub u8);
232
233impl From<&ScsiStatus> for u8 {
234    fn from(value: &ScsiStatus) -> Self {
235        unsafe { ptr::read_unaligned(value as *const ScsiStatus as *const u8) }
236    }
237}
238
239impl TryFrom<u8> for ScsiStatus {
240    type Error = UnknownScsiStatus;
241
242    fn try_from(b: u8) -> Result<Self, Self::Error> {
243        let s = match b {
244            0x00 => ScsiStatus::Good,
245            0x02 => ScsiStatus::CheckCondition,
246            0x08 => ScsiStatus::Busy,
247            0x18 => ScsiStatus::ReservationConflict,
248            0x28 => ScsiStatus::TaskSetFull,
249            0x30 => ScsiStatus::AcaActive,
250            0x40 => ScsiStatus::TaskAborted,
251            other => ScsiStatus::Other(other),
252        };
253        Ok(s)
254    }
255}