Skip to main content

iscsi_client_rs/models/data/
request.rs

1//! This module defines the structures for iSCSI SCSI Data-Out PDUs.
2//! It includes the `ScsiDataOut` header and a builder for constructing it.
3
4// SPDX-License-Identifier: AGPL-3.0-or-later
5// Copyright (C) 2012-2025 Andrei Maltsev
6
7use anyhow::{Result, bail};
8use zerocopy::{
9    BigEndian, FromBytes as ZFromBytes, Immutable, IntoBytes, KnownLayout, U32,
10};
11
12use crate::{
13    client::pdu_connection::FromBytes,
14    models::{
15        common::{
16            BasicHeaderSegment, HEADER_LEN, InitiatorTaskTag, LogicalUnitNumber,
17            SendingData,
18        },
19        data::common::RawDataOutFlags,
20        data_fromat::ZeroCopyType,
21        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
22    },
23};
24
25/// BHS for SCSI Data-Out (opcode 0x26)
26#[repr(C)]
27#[derive(Debug, Default, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
28pub struct ScsiDataOut {
29    pub opcode: RawBhsOpcode,                 // 0 (0x26)
30    pub flags: RawDataOutFlags,               // 1 (F, rest 0)
31    pub reserved2: [u8; 2],                   // 2..4
32    pub total_ahs_length: u8,                 // 4
33    pub data_segment_length: [u8; 3],         // 5..8
34    pub lun: LogicalUnitNumber,               // 8..16
35    pub initiator_task_tag: InitiatorTaskTag, // 16..20
36    pub target_transfer_tag: U32<BigEndian>,  // 20..23
37    pub exp_stat_sn: U32<BigEndian>,          // 24..28
38    pub reserved3: [u8; 8],                   // 28..36
39    pub data_sn: U32<BigEndian>,              // 36..40
40    pub buffer_offset: U32<BigEndian>,        // 40..44
41    pub reserved4: u32,                       // 44..48
42}
43
44impl ScsiDataOut {
45    /// Serializes the BHS into a byte buffer.
46    pub fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
47        buf.fill(0);
48        if buf.len() != HEADER_LEN {
49            bail!("buffer length must be {HEADER_LEN}, got {}", buf.len());
50        }
51        buf.copy_from_slice(self.as_bytes());
52        Ok(())
53    }
54
55    /// Deserializes the BHS from a byte buffer.
56    pub fn from_bhs_bytes(buf: &mut [u8]) -> Result<&mut Self> {
57        let hdr = Self::mut_from_bytes(buf)
58            .map_err(|e| anyhow::anyhow!("failed convert buffer ScsiDataOut: {e}"))?;
59        if hdr.opcode.opcode_known() != Some(Opcode::ScsiDataOut) {
60            anyhow::bail!(
61                "ScsiDataOut: invalid opcode 0x{:02x}",
62                hdr.opcode.opcode_raw()
63            );
64        }
65        Ok(hdr)
66    }
67}
68
69impl SendingData for ScsiDataOut {
70    fn get_final_bit(&self) -> bool {
71        self.flags.fin()
72    }
73
74    fn set_final_bit(&mut self) {
75        self.flags.set_fin(true);
76    }
77
78    fn get_continue_bit(&self) -> bool {
79        !self.flags.fin()
80    }
81
82    fn set_continue_bit(&mut self) {
83        self.flags.set_fin(false);
84    }
85}
86
87impl FromBytes for ScsiDataOut {
88    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
89        ScsiDataOut::from_bhs_bytes(bytes)
90    }
91}
92
93impl BasicHeaderSegment for ScsiDataOut {
94    #[inline]
95    fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
96        self.to_bhs_bytes(buf)
97    }
98
99    #[inline]
100    fn get_opcode(&self) -> Result<BhsOpcode> {
101        BhsOpcode::try_from(self.opcode.raw())
102    }
103
104    fn get_initiator_task_tag(&self) -> u32 {
105        self.initiator_task_tag.get()
106    }
107
108    #[inline]
109    fn get_ahs_length_bytes(&self) -> usize {
110        (self.total_ahs_length as usize) * 4
111    }
112
113    #[inline]
114    fn set_ahs_length_bytes(&mut self, len_bytes: u8) {
115        self.total_ahs_length = len_bytes >> 2;
116    }
117
118    #[inline]
119    fn get_data_length_bytes(&self) -> usize {
120        u32::from_be_bytes([
121            0,
122            self.data_segment_length[0],
123            self.data_segment_length[1],
124            self.data_segment_length[2],
125        ]) as usize
126    }
127
128    #[inline]
129    fn set_data_length_bytes(&mut self, len: u32) {
130        let be = len.to_be_bytes();
131        self.data_segment_length = [be[1], be[2], be[3]];
132    }
133}
134
135/// Builder for **SCSI Data-Out** PDUs (opcode `0x26`).
136///
137/// This helper prepares the Basic Header Segment (BHS) for Data-Out and
138/// lets the higher layer stream an arbitrary payload split into chunks
139/// that respect the negotiated **MaxRecvDataSegmentLength (MRDSL)**.
140///
141/// When the payload is later converted into wire frames (by your
142/// `ToBytes`/`Builder` implementation), each chunk will be emitted as a
143/// separate Data-Out PDU with:
144/// - **F (Final) = 1** only on the **last** chunk,
145/// - **DataSN** increasing sequentially per PDU,
146/// - **BufferOffset** set to the cumulative number of bytes already sent,
147/// - **DataSegmentLength** equal to the current chunk size (without padding).
148///
149/// Target Transfer Tag (**TTT**) semantics:
150/// - For **unsolicited / initial burst** Data-Out PDUs, use `DEFAULT_TTT =
151///   0xFFFF_FFFF`.
152/// - For **R2T-driven** transfers, set the TTT received in the R2T PDU.
153///
154/// You can also request HeaderDigest and/or DataDigest emission; these
155/// flags only affect how the final frames are serialized (they do not
156/// modify the BHS fields directly).
157#[derive(Debug, Default)]
158pub struct ScsiDataOutBuilder {
159    pub header: ScsiDataOut,
160
161    enable_header_digest: bool,
162    enable_data_digest: bool,
163}
164
165impl ScsiDataOutBuilder {
166    /// The default Target Transfer Tag for unsolicited Data-Out PDUs.
167    pub const DEFAULT_TTT: u32 = 0xFFFF_FFFF;
168
169    /// Creates a new `ScsiDataOutBuilder` with default values.
170    pub fn new() -> Self {
171        Self {
172            header: ScsiDataOut {
173                opcode: {
174                    let mut tmp = RawBhsOpcode::default();
175                    tmp.set_opcode_known(Opcode::ScsiDataOut);
176                    tmp
177                },
178                ..Default::default()
179            },
180            enable_header_digest: false,
181            enable_data_digest: false,
182        }
183    }
184
185    /// Sets the Logical Unit Number (LUN) for the data transfer.
186    pub fn lun(mut self, lun: u64) -> Self {
187        self.header.lun.set(lun);
188        self
189    }
190
191    /// Sets the Initiator Task Tag (ITT) for the command.
192    pub fn initiator_task_tag(mut self, itt: u32) -> Self {
193        self.header.initiator_task_tag.set(itt);
194        self
195    }
196
197    /// Sets the Target Transfer Tag (TTT) for the data transfer.
198    pub fn target_transfer_tag(mut self, ttt: u32) -> Self {
199        self.header.target_transfer_tag.set(ttt);
200        self
201    }
202
203    /// Sets the expected status sequence number (ExpStatSN).
204    pub fn exp_stat_sn(mut self, sn: u32) -> Self {
205        self.header.exp_stat_sn.set(sn);
206        self
207    }
208
209    /// Sets the Data Sequence Number (DataSN) for this PDU.
210    pub fn data_sn(mut self, data_sn: u32) -> Self {
211        self.header.data_sn.set(data_sn);
212        self
213    }
214
215    /// Sets the buffer offset for this PDU.
216    pub fn buffer_offset(mut self, buffer_offset: u32) -> Self {
217        self.header.buffer_offset.set(buffer_offset);
218        self
219    }
220
221    /// Enables header digest for the PDU.
222    pub fn with_header_digest(mut self) -> Self {
223        self.enable_header_digest = true;
224        self
225    }
226
227    /// Enables data digest for the PDU.
228    pub fn with_data_digest(mut self) -> Self {
229        self.enable_data_digest = true;
230        self
231    }
232}
233
234impl ZeroCopyType for ScsiDataOut {}