Skip to main content

iscsi_client_rs/models/command/
zero_copy.rs

1//! This module provides zero-copy structures for iSCSI Command PDUs.
2//! These structures are used for efficient serialization and deserialization of
3//! PDU fields.
4
5// SPDX-License-Identifier: AGPL-3.0-or-later
6// Copyright (C) 2012-2025 Andrei Maltsev
7
8use core::fmt;
9
10use anyhow::{Result, bail};
11use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
12
13use crate::models::command::common::{
14    ResponseCode, ScsiCommandRequestFlags, ScsiCommandResponseFlags, ScsiStatus,
15    TaskAttribute, UnknownResponseCode, UnknownScsiStatus,
16};
17
18/// Represents the 3-bit SCSI Task Attribute, which is part of the flags in a
19/// SCSI Command Request.
20#[repr(transparent)]
21#[derive(Default, Clone, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)]
22pub struct RawTaskAttribute(u8);
23
24impl RawTaskAttribute {
25    const MASK: u8 = 0b0000_0111;
26
27    /// Returns the raw 3-bit value of the task attribute.
28    #[inline]
29    pub const fn raw(&self) -> u8 {
30        self.0 & Self::MASK
31    }
32
33    /// Creates a new `RawTaskAttribute` from a 3-bit value.
34    #[inline]
35    pub const fn new(bits3: u8) -> Self {
36        Self(bits3 & Self::MASK)
37    }
38
39    /// Decodes the raw value into a `TaskAttribute` enum.
40    #[inline]
41    pub fn decode(&self) -> TaskAttribute {
42        match self.raw() {
43            0 => TaskAttribute::Untagged,
44            1 => TaskAttribute::Simple,
45            2 => TaskAttribute::Ordered,
46            3 => TaskAttribute::HeadOfQueue,
47            4 => TaskAttribute::ACA,
48            r @ 5..=7 => TaskAttribute::Reserved(r),
49            // unreachable due to mask
50            _ => TaskAttribute::Reserved(self.raw()),
51        }
52    }
53
54    /// Encodes a `TaskAttribute` enum into the raw value.
55    #[inline]
56    pub fn encode(&mut self, attr: TaskAttribute) {
57        let v = match attr {
58            TaskAttribute::Untagged => 0,
59            TaskAttribute::Simple => 1,
60            TaskAttribute::Ordered => 2,
61            TaskAttribute::HeadOfQueue => 3,
62            TaskAttribute::ACA => 4,
63            TaskAttribute::Reserved(v) => v & Self::MASK,
64        };
65        self.0 = (self.0 & !Self::MASK) | v;
66    }
67}
68
69impl From<TaskAttribute> for RawTaskAttribute {
70    #[inline]
71    fn from(a: TaskAttribute) -> Self {
72        let mut r = RawTaskAttribute::default();
73        r.encode(a);
74        r
75    }
76}
77
78impl From<RawTaskAttribute> for TaskAttribute {
79    #[inline]
80    fn from(r: RawTaskAttribute) -> Self {
81        r.decode()
82    }
83}
84
85/// Represents the wire format for the flags in a SCSI Command Request PDU.
86#[repr(transparent)]
87#[derive(Default, Clone, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)]
88pub struct RawScsiCmdReqFlags(u8);
89
90impl RawScsiCmdReqFlags {
91    /// Bitmask for the task attribute field.
92    pub const ATTR: u8 = 0x07;
93    /// Bitmask for the Final flag.
94    pub const FINAL: u8 = 0x80;
95    /// Bitmask for the Read flag.
96    pub const READ: u8 = 0x40;
97    /// Bitmask for the Write flag.
98    pub const WRITE: u8 = 0x20;
99
100    /// Returns the raw 8-bit value of the flags.
101    #[inline]
102    pub const fn raw(&self) -> u8 {
103        self.0
104    }
105
106    /// Creates a new `RawScsiCmdReqFlags` from a raw 8-bit value.
107    #[inline]
108    pub const fn new_raw(v: u8) -> Self {
109        Self(v)
110    }
111
112    /// Checks if the Final flag is set.
113    #[inline]
114    pub fn fin(&self) -> bool {
115        self.0 & Self::FINAL != 0
116    }
117
118    /// Checks if the Read flag is set.
119    #[inline]
120    pub fn read(&self) -> bool {
121        self.0 & Self::READ != 0
122    }
123
124    /// Checks if the Write flag is set.
125    #[inline]
126    pub fn write(&self) -> bool {
127        self.0 & Self::WRITE != 0
128    }
129
130    /// Sets or clears the Final flag.
131    #[inline]
132    pub fn set_fin(&mut self, on: bool) {
133        self.set(Self::FINAL, on)
134    }
135
136    /// Sets or clears the Read flag.
137    #[inline]
138    pub fn set_read(&mut self, on: bool) {
139        self.set(Self::READ, on)
140    }
141
142    /// Sets or clears the Write flag.
143    #[inline]
144    pub fn set_write(&mut self, on: bool) {
145        self.set(Self::WRITE, on)
146    }
147
148    #[inline]
149    fn set(&mut self, bit: u8, on: bool) {
150        if on {
151            self.0 |= bit;
152        } else {
153            self.0 &= !bit;
154        }
155    }
156
157    /// Returns the `TaskAttribute` from the flags.
158    #[inline]
159    pub fn task_attr(&self) -> TaskAttribute {
160        RawTaskAttribute::new(self.0 & Self::ATTR).decode()
161    }
162
163    /// Sets the `TaskAttribute` in the flags.
164    #[inline]
165    pub fn set_task_attr(&mut self, attr: TaskAttribute) {
166        let ra = RawTaskAttribute::from(attr);
167        self.0 = (self.0 & !Self::ATTR) | ra.raw();
168    }
169}
170
171impl From<ScsiCommandRequestFlags> for RawScsiCmdReqFlags {
172    #[inline]
173    fn from(f: ScsiCommandRequestFlags) -> Self {
174        Self(f.bits())
175    }
176}
177impl TryFrom<RawScsiCmdReqFlags> for ScsiCommandRequestFlags {
178    type Error = anyhow::Error;
179
180    #[inline]
181    fn try_from(r: RawScsiCmdReqFlags) -> Result<Self> {
182        ScsiCommandRequestFlags::from_bits(r.raw()).ok_or_else(|| {
183            anyhow::anyhow!("invalid ScsiCommandRequestFlags: {:#010b}", r.raw())
184        })
185    }
186}
187
188/// Represents the wire format for the flags in a SCSI Command Response PDU.
189#[repr(transparent)]
190#[derive(Default, Clone, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)]
191pub struct RawScsiCmdRespFlags(u8);
192
193impl RawScsiCmdRespFlags {
194    /// Bitmask for the Final flag.
195    pub const FINAL: u8 = 0b1000_0000;
196    /// Bitmask for the bidirectional read residual overflow flag.
197    pub const O_BIG: u8 = 0b0000_0100;
198    /// Bitmask for the bidirectional read residual underflow flag.
199    pub const O_SMALL: u8 = 0b0001_0000;
200    /// Bitmask for the residual overflow flag.
201    pub const U_BIG: u8 = 0b0000_0010;
202    /// Bitmask for the residual underflow flag.
203    pub const U_SMALL: u8 = 0b0000_1000;
204
205    /// Returns the raw 8-bit value of the flags.
206    #[inline]
207    pub const fn raw(&self) -> u8 {
208        self.0
209    }
210
211    /// Creates a new `RawScsiCmdRespFlags` from a raw 8-bit value.
212    #[inline]
213    pub const fn new_raw(v: u8) -> Self {
214        Self(v)
215    }
216
217    /// Checks if the Final flag is set.
218    #[inline]
219    pub fn fin(&self) -> bool {
220        self.0 & Self::FINAL != 0
221    }
222
223    /// Checks if the bidirectional read residual overflow flag is set.
224    #[inline]
225    pub fn o_small(&self) -> bool {
226        self.0 & Self::O_SMALL != 0
227    }
228
229    /// Checks if the bidirectional read residual underflow flag is set.
230    #[inline]
231    pub fn u_small(&self) -> bool {
232        self.0 & Self::U_SMALL != 0
233    }
234
235    /// Checks if the residual overflow flag is set.
236    #[inline]
237    pub fn o_big(&self) -> bool {
238        self.0 & Self::O_BIG != 0
239    }
240
241    /// Checks if the residual underflow flag is set.
242    #[inline]
243    pub fn u_big(&self) -> bool {
244        self.0 & Self::U_BIG != 0
245    }
246
247    /// Sets or clears the Final flag.
248    #[inline]
249    pub fn set_fin(&mut self, on: bool) {
250        self.set(Self::FINAL, on)
251    }
252
253    /// Sets or clears the bidirectional read residual overflow flag.
254    #[inline]
255    pub fn set_o_small(&mut self, on: bool) {
256        self.set_pair(Self::O_SMALL, Self::U_SMALL, on)
257    }
258
259    /// Sets or clears the bidirectional read residual underflow flag.
260    #[inline]
261    pub fn set_u_small(&mut self, on: bool) {
262        self.set_pair(Self::U_SMALL, Self::O_SMALL, on)
263    }
264
265    /// Sets or clears the residual overflow flag.
266    #[inline]
267    pub fn set_o_big(&mut self, on: bool) {
268        self.set_pair(Self::O_BIG, Self::U_BIG, on)
269    }
270
271    /// Sets or clears the residual underflow flag.
272    #[inline]
273    pub fn set_u_big(&mut self, on: bool) {
274        self.set_pair(Self::U_BIG, Self::O_BIG, on)
275    }
276
277    #[inline]
278    fn set(&mut self, bit: u8, on: bool) {
279        if on {
280            self.0 |= bit;
281        } else {
282            self.0 &= !bit;
283        }
284    }
285
286    // keep mutual exclusion of U/O pairs
287    #[inline]
288    fn set_pair(&mut self, set_bit: u8, clear_bit: u8, on: bool) {
289        if on {
290            self.0 |= set_bit;
291            self.0 &= !clear_bit;
292        } else {
293            self.0 &= !set_bit;
294        }
295    }
296
297    /// Validates the flags according to the iSCSI specification.
298    #[inline]
299    pub fn validate(&self) -> Result<()> {
300        if (self.o_big() && self.u_big()) || (self.o_small() && self.u_small()) {
301            bail!("protocol error: both Underflow and Overflow bits set");
302        }
303        Ok(())
304    }
305}
306
307/* Optional interop with bitflags type you already have. */
308impl From<ScsiCommandResponseFlags> for RawScsiCmdRespFlags {
309    #[inline]
310    fn from(f: ScsiCommandResponseFlags) -> Self {
311        Self(f.bits())
312    }
313}
314impl TryFrom<RawScsiCmdRespFlags> for ScsiCommandResponseFlags {
315    type Error = anyhow::Error;
316
317    #[inline]
318    fn try_from(r: RawScsiCmdRespFlags) -> Result<Self> {
319        let f = ScsiCommandResponseFlags::from_bits(r.raw()).ok_or_else(|| {
320            anyhow::anyhow!("invalid ScsiCommandResponseFlags: {:#010b}", r.raw())
321        })?;
322        // keep the same validation semantics as before
323        if (f.contains(ScsiCommandResponseFlags::U_BIG)
324            && f.contains(ScsiCommandResponseFlags::O_BIG))
325            || (f.contains(ScsiCommandResponseFlags::U_SMALL)
326                && f.contains(ScsiCommandResponseFlags::O_SMALL))
327        {
328            bail!("protocol error: both Underflow and Overflow bits set");
329        }
330        Ok(f)
331    }
332}
333
334/// Represents the wire format for the 1-byte ResponseCode field in a SCSI
335/// Response PDU.
336#[repr(transparent)]
337#[derive(Default, Clone, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)]
338pub struct RawResponseCode(u8);
339
340impl RawResponseCode {
341    /// Returns the raw 8-bit value of the response code.
342    #[inline]
343    pub const fn raw(&self) -> u8 {
344        self.0
345    }
346
347    /// Creates a new `RawResponseCode` from a raw 8-bit value.
348    #[inline]
349    pub const fn new_raw(v: u8) -> Self {
350        Self(v)
351    }
352
353    /// Decodes the raw value into a `ResponseCode` enum.
354    #[inline]
355    pub fn decode(&self) -> Result<ResponseCode, UnknownResponseCode> {
356        match self.0 {
357            0x00 => Ok(ResponseCode::CommandCompleted),
358            0x01 => Ok(ResponseCode::TargetFailure),
359            0x80..=0xFF => Ok(ResponseCode::VendorSpecific(self.0)),
360            r @ 0x02..=0x7F => Ok(ResponseCode::Reserved(r)),
361        }
362    }
363
364    /// Encodes a `ResponseCode` enum into the raw value.
365    #[inline]
366    pub fn encode(&mut self, rc: ResponseCode) {
367        self.0 = match rc {
368            ResponseCode::CommandCompleted => 0x00,
369            ResponseCode::TargetFailure => 0x01,
370            ResponseCode::VendorSpecific(v) => v,
371            ResponseCode::Reserved(v) => v,
372        };
373    }
374}
375
376impl TryFrom<RawResponseCode> for ResponseCode {
377    type Error = UnknownResponseCode;
378
379    #[inline]
380    fn try_from(r: RawResponseCode) -> Result<Self, Self::Error> {
381        r.decode()
382    }
383}
384impl From<ResponseCode> for RawResponseCode {
385    #[inline]
386    fn from(rc: ResponseCode) -> Self {
387        let mut w = RawResponseCode::default();
388        w.encode(rc);
389        w
390    }
391}
392
393/// Represents the wire format for the 1-byte SCSI Status field in a SCSI
394/// Response PDU.
395#[repr(transparent)]
396#[derive(Default, Clone, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)]
397pub struct RawScsiStatus(u8);
398
399impl RawScsiStatus {
400    /// Returns the raw 8-bit value of the SCSI status.
401    #[inline]
402    pub const fn raw(&self) -> u8 {
403        self.0
404    }
405
406    /// Creates a new `RawScsiStatus` from a raw 8-bit value.
407    #[inline]
408    pub const fn new_raw(v: u8) -> Self {
409        Self(v)
410    }
411
412    /// Decodes the raw value into a `ScsiStatus` enum.
413    #[inline]
414    pub fn decode(&self) -> Result<ScsiStatus, UnknownScsiStatus> {
415        Ok(match self.0 {
416            0x00 => ScsiStatus::Good,
417            0x02 => ScsiStatus::CheckCondition,
418            0x08 => ScsiStatus::Busy,
419            0x18 => ScsiStatus::ReservationConflict,
420            0x28 => ScsiStatus::TaskSetFull,
421            0x30 => ScsiStatus::AcaActive,
422            0x40 => ScsiStatus::TaskAborted,
423            other => ScsiStatus::Other(other),
424        })
425    }
426
427    /// Encodes a `ScsiStatus` enum into the raw value.
428    #[inline]
429    pub fn encode(&mut self, st: ScsiStatus) {
430        self.0 = match st {
431            ScsiStatus::Good => 0x00,
432            ScsiStatus::CheckCondition => 0x02,
433            ScsiStatus::Busy => 0x08,
434            ScsiStatus::ReservationConflict => 0x18,
435            ScsiStatus::TaskSetFull => 0x28,
436            ScsiStatus::AcaActive => 0x30,
437            ScsiStatus::TaskAborted => 0x40,
438            ScsiStatus::Other(v) => v,
439        };
440    }
441}
442
443impl TryFrom<RawScsiStatus> for ScsiStatus {
444    type Error = UnknownScsiStatus;
445
446    #[inline]
447    fn try_from(r: RawScsiStatus) -> Result<Self, Self::Error> {
448        r.decode()
449    }
450}
451impl From<ScsiStatus> for RawScsiStatus {
452    #[inline]
453    fn from(s: ScsiStatus) -> Self {
454        let mut w = RawScsiStatus::default();
455        w.encode(s);
456        w
457    }
458}
459
460// ---------- RawTaskAttribute ----------
461impl fmt::Debug for RawTaskAttribute {
462    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463        write!(f, "RawTaskAttribute {{ {:?} }}", self.decode())
464    }
465}
466
467// ---------- RawScsiCmdReqFlags ----------
468impl fmt::Debug for RawScsiCmdReqFlags {
469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470        write!(f, "RawScsiCmdReqFlags {{ ")?;
471        if self.fin() {
472            write!(f, "FIN|")?;
473        }
474        if self.read() {
475            write!(f, "READ|")?;
476        }
477        if self.write() {
478            write!(f, "WRITE|")?;
479        }
480        write!(f, "ATTR={:?} }}", self.task_attr())
481    }
482}
483
484// ---------- RawScsiCmdRespFlags ----------
485impl fmt::Debug for RawScsiCmdRespFlags {
486    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487        let valid = self.validate().is_ok();
488        write!(f, "RawScsiCmdRespFlags {{ ")?;
489        if self.fin() {
490            write!(f, "FIN|")?;
491        }
492        if self.o_small() {
493            write!(f, "O_SMALL|")?;
494        }
495        if self.u_small() {
496            write!(f, "U_SMALL|")?;
497        }
498        if self.o_big() {
499            write!(f, "O_BIG|")?;
500        }
501        if self.u_big() {
502            write!(f, "U_BIG|")?;
503        }
504        write!(f, "valid:{} }}", &valid)
505    }
506}
507
508impl fmt::Debug for RawResponseCode {
509    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510        let decoded = match self.clone().decode() {
511            Ok(rc) => format!("{rc:?}"),
512            Err(_e) => format!("invalid(0x{:02X})", self.raw()),
513        };
514
515        f.debug_struct("RawResponseCode")
516            .field("decoded", &decoded)
517            .finish()
518    }
519}
520
521// ---------- RawScsiStatus ----------
522impl fmt::Debug for RawScsiStatus {
523    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
524        let decoded = match self.decode() {
525            Ok(st) => format!("{st:?}"),
526            Err(_e) => format!("invalid(0x{:02X})", self.raw()),
527        };
528
529        write!(f, "RawScsiStatus {{ {:?} }}", decoded)
530    }
531}