Skip to main content

iscsi_client_rs/models/command/
request.rs

1//! This module defines the structures for iSCSI SCSI Command Request PDUs.
2//! It includes the `ScsiCommandRequest` header and a builder for constructing
3//! it.
4
5// SPDX-License-Identifier: AGPL-3.0-or-later
6// Copyright (C) 2012-2025 Andrei Maltsev
7
8use anyhow::{Result, anyhow, bail};
9use zerocopy::{
10    BigEndian, FromBytes as ZFromBytes, Immutable, IntoBytes, KnownLayout, U32,
11};
12
13use crate::{
14    client::pdu_connection::FromBytes,
15    models::{
16        command::{common::TaskAttribute, zero_copy::RawScsiCmdReqFlags},
17        common::{
18            BasicHeaderSegment, HEADER_LEN, InitiatorTaskTag, LogicalUnitNumber,
19            SendingData,
20        },
21        data_fromat::ZeroCopyType,
22        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
23    },
24};
25
26/// Basic Header Segment for iSCSI SCSI Command Request PDU
27///
28/// Represents the 48-byte header structure for SCSI Command PDU as defined in
29/// RFC 7143. Contains all the fields necessary to send a SCSI command over
30/// iSCSI including task tags, sequence numbers, LUN, and the embedded SCSI CDB.
31#[repr(C)]
32#[derive(Debug, Default, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
33pub struct ScsiCommandRequest {
34    /// PDU opcode (byte 0) - should be 0x01 for SCSI Command
35    pub opcode: RawBhsOpcode,
36    /// Command flags (byte 1) - Final, Read, Write bits and task attributes
37    pub flags: RawScsiCmdReqFlags,
38    /// Reserved bytes (2-3)
39    reserved1: [u8; 2],
40    /// Total Additional Header Segments length (byte 4)
41    pub total_ahs_length: u8,
42    /// Data Segment Length (bytes 5-7) - length of immediate data
43    pub data_segment_length: [u8; 3],
44    /// Logical Unit Number (bytes 8-15)
45    pub lun: LogicalUnitNumber,
46    /// Initiator Task Tag (bytes 16-19) - unique command identifier
47    pub initiator_task_tag: InitiatorTaskTag,
48    /// Expected Data Transfer Length (bytes 20-23) - total data expected
49    pub expected_data_transfer_length: U32<BigEndian>,
50    /// Command Sequence Number (bytes 24-27) - for ordering
51    pub cmd_sn: U32<BigEndian>,
52    /// Expected Status Sequence Number (bytes 28-31) - acknowledgment
53    pub exp_stat_sn: U32<BigEndian>,
54    /// SCSI Command Descriptor Block (bytes 32-47) - the actual SCSI command
55    pub scsi_descriptor_block: [u8; 16],
56}
57
58impl ScsiCommandRequest {
59    /// The default initiator task tag value.
60    pub const DEFAULT_TAG: u32 = 0xffffffff_u32;
61
62    /// Serializes the BHS into a byte buffer.
63    pub fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
64        buf.fill(0);
65        if buf.len() != HEADER_LEN {
66            bail!("buffer length must be {HEADER_LEN}, got {}", buf.len());
67        }
68        buf.copy_from_slice(self.as_bytes());
69        Ok(())
70    }
71
72    /// Deserializes the BHS from a byte buffer.
73    pub fn from_bhs_bytes(buf: &mut [u8]) -> Result<&mut Self> {
74        let hdr = <Self as zerocopy::FromBytes>::mut_from_bytes(buf)
75            .map_err(|e| anyhow!("failed convert buffer ScsiCommandRequest: {e}"))?;
76        if hdr.opcode.opcode_known() != Some(Opcode::ScsiCommandReq) {
77            anyhow::bail!(
78                "ScsiCommandRequest: invalid opcode 0x{:02x}",
79                hdr.opcode.opcode_raw()
80            );
81        }
82        Ok(hdr)
83    }
84}
85
86/// Builder for constructing iSCSI SCSI Command Request PDUs
87///
88/// Provides methods to build and serialize SCSI Command Request PDUs with
89/// proper digest handling and data segment management.
90#[derive(Debug, Default, PartialEq)]
91pub struct ScsiCommandRequestBuilder {
92    /// The SCSI command request header structure
93    pub header: ScsiCommandRequest,
94    /// Whether to calculate and include header digest
95    enable_header_digest: bool,
96    /// Whether to calculate and include data digest
97    enable_data_digest: bool,
98}
99
100impl ScsiCommandRequestBuilder {
101    /// Creates a new `ScsiCommandRequestBuilder` with default values.
102    pub fn new() -> Self {
103        ScsiCommandRequestBuilder {
104            header: ScsiCommandRequest {
105                opcode: {
106                    let mut tmp = RawBhsOpcode::default();
107                    tmp.set_opcode_known(Opcode::ScsiCommandReq);
108                    tmp
109                },
110                ..Default::default()
111            },
112            enable_data_digest: false,
113            enable_header_digest: false,
114        }
115    }
116
117    /// Sets the Immediate bit in the PDU header.
118    pub fn immediate(mut self) -> Self {
119        self.header.opcode.set_i();
120        self
121    }
122
123    /// Sets the Read bit in the PDU header.
124    pub fn read(mut self) -> Self {
125        self.header.flags.set_read(true);
126        self
127    }
128
129    /// Sets the Write bit in the PDU header.
130    pub fn write(mut self) -> Self {
131        self.header.flags.set_write(true);
132        self
133    }
134
135    /// Sets the task attribute for the SCSI command.
136    pub fn task_attribute(mut self, task: TaskAttribute) -> Self {
137        self.header.flags.set_task_attr(task);
138        self
139    }
140
141    /// Enables header digest for the PDU.
142    pub fn with_header_digest(mut self) -> Self {
143        self.enable_header_digest = true;
144        self
145    }
146
147    /// Enables data digest for the PDU.
148    pub fn with_data_digest(mut self) -> Self {
149        self.enable_data_digest = true;
150        self
151    }
152
153    /// Sets the initiator task tag, a unique identifier for this command.
154    pub fn initiator_task_tag(mut self, tag: u32) -> Self {
155        self.header.initiator_task_tag.set(tag);
156        self
157    }
158
159    /// Sets the expected data transfer length for the command.
160    pub fn expected_data_transfer_length(mut self, expected_data_length: u32) -> Self {
161        self.header
162            .expected_data_transfer_length
163            .set(expected_data_length);
164        self
165    }
166
167    /// Sets the command sequence number (CmdSN) for this request.
168    pub fn cmd_sn(mut self, sn: u32) -> Self {
169        self.header.cmd_sn.set(sn);
170        self
171    }
172
173    /// Sets the expected status sequence number (ExpStatSN) from the target.
174    pub fn exp_stat_sn(mut self, sn: u32) -> Self {
175        self.header.exp_stat_sn.set(sn);
176        self
177    }
178
179    /// Sets the Logical Unit Number (LUN) for the command.
180    pub fn lun(mut self, lun: u64) -> Self {
181        self.header.lun.set(lun);
182        self
183    }
184
185    /// Sets the SCSI Command Descriptor Block (CDB).
186    pub fn scsi_descriptor_block(mut self, scsi_descriptor_block: &[u8; 16]) -> Self {
187        self.header
188            .scsi_descriptor_block
189            .clone_from_slice(scsi_descriptor_block);
190        self
191    }
192}
193
194impl SendingData for ScsiCommandRequest {
195    fn get_final_bit(&self) -> bool {
196        self.flags.fin()
197    }
198
199    fn set_final_bit(&mut self) {
200        self.flags.set_fin(true);
201    }
202
203    fn get_continue_bit(&self) -> bool {
204        !self.flags.fin()
205    }
206
207    fn set_continue_bit(&mut self) {
208        self.flags.set_fin(false);
209    }
210}
211
212impl FromBytes for ScsiCommandRequest {
213    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
214        ScsiCommandRequest::from_bhs_bytes(bytes)
215    }
216}
217
218impl BasicHeaderSegment for ScsiCommandRequest {
219    #[inline]
220    fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
221        self.to_bhs_bytes(buf)
222    }
223
224    #[inline]
225    fn get_opcode(&self) -> Result<BhsOpcode> {
226        BhsOpcode::try_from(self.opcode.raw())
227    }
228
229    fn get_initiator_task_tag(&self) -> u32 {
230        self.initiator_task_tag.get()
231    }
232
233    #[inline]
234    fn get_ahs_length_bytes(&self) -> usize {
235        (self.total_ahs_length as usize) * 4
236    }
237
238    #[inline]
239    fn set_ahs_length_bytes(&mut self, len: u8) {
240        self.total_ahs_length = len >> 2;
241    }
242
243    #[inline]
244    fn get_data_length_bytes(&self) -> usize {
245        u32::from_be_bytes([
246            0,
247            self.data_segment_length[0],
248            self.data_segment_length[1],
249            self.data_segment_length[2],
250        ]) as usize
251    }
252
253    #[inline]
254    fn set_data_length_bytes(&mut self, len: u32) {
255        let be = len.to_be_bytes();
256        self.data_segment_length = [be[1], be[2], be[3]];
257    }
258}
259
260impl ZeroCopyType for ScsiCommandRequest {}