Skip to main content

iscsi_client_rs/models/data/
response.rs

1//! This module defines the structures for iSCSI SCSI Data-In PDUs.
2//! It includes the `ScsiDataIn` header and related methods for handling data
3//! transfer from target to initiator.
4
5// SPDX-License-Identifier: AGPL-3.0-or-later
6// Copyright (C) 2012-2025 Andrei Maltsev
7
8use anyhow::{Result, anyhow, bail};
9use tracing::debug;
10use zerocopy::{
11    BigEndian, FromBytes as ZFromBytes, Immutable, IntoBytes, KnownLayout, U32,
12};
13
14use crate::{
15    client::pdu_connection::FromBytes,
16    models::{
17        command::{common::ScsiStatus, zero_copy::RawScsiStatus},
18        common::{
19            BasicHeaderSegment, HEADER_LEN, InitiatorTaskTag, LogicalUnitNumber,
20            SendingData,
21        },
22        data::common::RawDataInFlags,
23        data_fromat::ZeroCopyType,
24        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
25    },
26};
27
28/// Represents the Basic Header Segment (BHS) for a SCSI Data-In PDU (opcode
29/// 0x25).
30#[repr(C)]
31#[derive(Debug, Default, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
32pub struct ScsiDataIn {
33    pub opcode: RawBhsOpcode,          // 0  (0x25)
34    pub flags: RawDataInFlags,         // 1  (F,A,0,0,0,O,U,S)
35    pub reserved2: u8,                 // 2  (reserved)
36    pub status_or_rsvd: RawScsiStatus, // 3  (SCSI Status, if S=1; else 0)
37    pub total_ahs_length: u8,          // 4
38    pub data_segment_length: [u8; 3],  // 5..7
39    pub lun: LogicalUnitNumber,        /* 8..15  (LUN or reserved; if A=1 must
40                                        * present) */
41    pub initiator_task_tag: InitiatorTaskTag, // 16..19
42    pub target_transfer_tag: U32<BigEndian>,  // 20..23 (TTT or 0xffffffff)
43    pub stat_sn_or_rsvd: U32<BigEndian>,      // 24..27 (StatSN, if S=1; else 0)
44    pub exp_cmd_sn: U32<BigEndian>,           // 28..31
45    pub max_cmd_sn: U32<BigEndian>,           // 32..35
46    pub data_sn: U32<BigEndian>,              // 36..39
47    pub buffer_offset: U32<BigEndian>,        // 40..43
48    pub residual_count: U32<BigEndian>,       // 44..47 (valid only if S=1; else 0)
49}
50
51impl ScsiDataIn {
52    /// Returns the decoded SCSI status if the Status (S) bit is set.
53    #[inline]
54    pub fn scsi_status(&self) -> Option<ScsiStatus> {
55        if self.flags.s() {
56            self.status_or_rsvd.decode().ok()
57        } else {
58            None
59        }
60    }
61
62    /// Checks if the residual count is valid.
63    #[inline]
64    pub fn residual_valid(&self) -> bool {
65        self.flags.u() || self.flags.o()
66    }
67
68    /// Returns the effective residual count.
69    #[inline]
70    pub fn residual_effective(&self) -> u32 {
71        if self.residual_valid() {
72            self.residual_count.get()
73        } else {
74            0
75        }
76    }
77
78    /// Sets the SCSI status and updates the S and F flags accordingly.
79    #[inline]
80    pub fn set_scsi_status(&mut self, st: Option<ScsiStatus>) {
81        match st {
82            Some(s) => {
83                self.flags.set_s(true); // S = 1
84                self.flags.set_fin(true); // S ⇒ F
85                self.status_or_rsvd.encode(s);
86            },
87            None => {
88                self.flags.set_s(false); // S = 0
89                self.status_or_rsvd.encode(ScsiStatus::Good);
90                self.stat_sn_or_rsvd.set(0);
91                self.residual_count.set(0);
92            },
93        }
94    }
95
96    /// Serializes the BHS into a byte buffer, zeroing out fields as required.
97    #[inline]
98    pub fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
99        if buf.len() != HEADER_LEN {
100            bail!("buffer length must be {HEADER_LEN}, got {}", buf.len());
101        }
102        buf.copy_from_slice(self.as_bytes());
103        if !self.flags.s() {
104            buf[3] = 0; // status
105            buf[24..28].fill(0); // StatSN
106            buf[44..48].fill(0); // ResidualCount
107        }
108        Ok(())
109    }
110
111    /// Deserializes the BHS from a byte buffer.
112    #[inline]
113    pub fn from_bhs_bytes(buf: &mut [u8]) -> Result<&mut Self> {
114        if buf.len() < HEADER_LEN {
115            return Err(anyhow!(
116                "buffer too small for SCSI Data-In BHS: {}",
117                buf.len()
118            ));
119        }
120        let hdr = Self::mut_from_bytes(buf)
121            .map_err(|_| anyhow!("SCSI Data-In: zerocopy prefix error"))?;
122        // opcode check
123        if hdr.opcode.opcode_known() != Some(Opcode::ScsiDataIn) {
124            bail!(
125                "ScsiDataIn invalid opcode 0x{:02x}",
126                hdr.opcode.opcode_raw()
127            );
128        }
129        // flags validation (U/O mutual exclusion, S => F, reserved bits clear)
130        hdr.flags.validate()?;
131        Ok(hdr)
132    }
133
134    /// Returns the actual value of the Final (F) bit.
135    #[inline]
136    pub fn get_real_final_bit(&self) -> bool {
137        self.flags.fin()
138    }
139
140    /// Returns the value of the Status (S) bit.
141    #[inline]
142    pub fn get_status_bit(&self) -> bool {
143        self.flags.s()
144    }
145}
146
147impl SendingData for ScsiDataIn {
148    fn get_final_bit(&self) -> bool {
149        // In practice Data-In can be followed by a separate SCSI Response.
150        // Keep your previous semantics: final only when F=1 and either S not set,
151        // or status is Good.
152        let f = self.flags.fin();
153        let s = self.flags.s();
154        let is_final = f && s;
155        debug!(
156            "DataIn get_final_bit (channel): F={} S={} status={:?} => {}",
157            f,
158            s,
159            self.scsi_status(),
160            is_final
161        );
162        is_final
163    }
164
165    fn set_final_bit(&mut self) {
166        self.flags.set_fin(true);
167    }
168
169    fn get_continue_bit(&self) -> bool {
170        !self.flags.fin()
171    }
172
173    fn set_continue_bit(&mut self) {
174        // Clear F; and to keep S ⇒ F invariant, also clear S.
175        self.flags.set_fin(false);
176        self.flags.set_s(false);
177    }
178}
179
180impl FromBytes for ScsiDataIn {
181    #[inline]
182    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
183        ScsiDataIn::from_bhs_bytes(bytes)
184    }
185}
186
187impl BasicHeaderSegment for ScsiDataIn {
188    #[inline]
189    fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
190        self.to_bhs_bytes(buf)
191    }
192
193    #[inline]
194    fn get_opcode(&self) -> Result<BhsOpcode> {
195        BhsOpcode::try_from(self.opcode.raw())
196    }
197
198    #[inline]
199    fn get_initiator_task_tag(&self) -> u32 {
200        self.initiator_task_tag.get()
201    }
202
203    #[inline]
204    fn get_ahs_length_bytes(&self) -> usize {
205        (self.total_ahs_length as usize) * 4
206    }
207
208    #[inline]
209    fn set_ahs_length_bytes(&mut self, len_bytes: u8) {
210        self.total_ahs_length = len_bytes >> 2;
211    }
212
213    #[inline]
214    fn get_data_length_bytes(&self) -> usize {
215        u32::from_be_bytes([
216            0,
217            self.data_segment_length[0],
218            self.data_segment_length[1],
219            self.data_segment_length[2],
220        ]) as usize
221    }
222
223    #[inline]
224    fn set_data_length_bytes(&mut self, len: u32) {
225        let be = len.to_be_bytes();
226        self.data_segment_length = [be[1], be[2], be[3]];
227    }
228}
229
230impl ZeroCopyType for ScsiDataIn {}