Skip to main content

iscsi_client_rs/models/text/
request.rs

1//! This module defines the structures for iSCSI Text Request PDUs.
2//! It includes the `TextRequest` 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, TargetTaskTag,
18        },
19        data_fromat::ZeroCopyType,
20        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
21        text::common::RawStageFlags,
22    },
23};
24
25/// Represents the Basic Header Segment (BHS) for a Text Request PDU.
26#[repr(C)]
27#[derive(Default, Debug, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
28pub struct TextRequest {
29    pub opcode: RawBhsOpcode, /* Byte 0: F/I + 6-bit opcode (should be
30                               * `Opcode::TextReq`). */
31    pub flags: RawStageFlags, /* Byte 1: stage flags (F/C); interpretation is Text-PDU
32                               * specific. */
33    reserved1: [u8; 2],                       // Byte 2..5
34    pub total_ahs_length: u8,                 // Bytes 5..8
35    pub data_segment_length: [u8; 3],         // Bytes 8..16
36    pub lun: LogicalUnitNumber,               // Bytes 16..20
37    pub initiator_task_tag: InitiatorTaskTag, // Bytes 20..24
38    pub target_task_tag: TargetTaskTag,       // Bytes 24..28
39    pub cmd_sn: U32<BigEndian>,               // Bytes 28..32
40    pub exp_stat_sn: U32<BigEndian>,          // Bytes 32..36
41    reserved2: [u8; 16],
42}
43
44impl TextRequest {
45    /// The default task tag value for Text requests.
46    pub const DEFAULT_TAG: u32 = 0xFFFF_FFFF;
47
48    /// Serializes the BHS into a byte buffer.
49    pub fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
50        buf.fill(0);
51        if buf.len() != HEADER_LEN {
52            bail!("buffer length must be {HEADER_LEN}, got {}", buf.len());
53        }
54        buf.copy_from_slice(self.as_bytes());
55        Ok(())
56    }
57
58    /// Deserializes the BHS from a byte buffer.
59    pub fn from_bhs_bytes(buf: &mut [u8]) -> Result<&mut Self> {
60        let hdr = <Self as zerocopy::FromBytes>::mut_from_bytes(buf)
61            .map_err(|e| anyhow::anyhow!("failed convert buffer TextRequest: {e}"))?;
62        if hdr.opcode.opcode_known() != Some(Opcode::TextReq) {
63            anyhow::bail!(
64                "TextRequest: invalid opcode 0x{:02x}",
65                hdr.opcode.opcode_raw()
66            );
67        }
68        Ok(hdr)
69    }
70}
71
72/// Builder for an iSCSI **Text Request** PDU (`Opcode::TextReq`).
73///
74/// Text PDUs carry key–value negotiation (e.g., `HeaderDigest=None`,
75/// `MaxRecvDataSegmentLength=…`) and other textual exchanges defined by the
76/// iSCSI spec. This builder initializes a 48-byte BHS with sensible defaults
77/// (opcode set to `TextReq`, `StageFlags::FINAL` by default, `CmdSN = 1`),
78/// and lets you fill in the sequence numbers, tags, LUN, and (optionally)
79/// enable digests.
80///
81/// # What you can set
82/// - **Immediate bit**: `immediate()` sets the *I* flag in byte 0.
83/// - **Sequencing**: `cmd_sn(..)` and `exp_stat_sn(..)` as usual for the
84///   session.
85/// - **Tags**: `initiator_task_tag(..)` and `target_task_tag(..)`.
86/// - **LUN**: `lun(..)` accepts an 8-byte encoded LUN (often zero for TEXT).
87/// - **Digests**: `with_header_digest()` / `with_data_digest()` opt into
88///   including CRC32C digests when your connection logic honors negotiated
89///   `HeaderDigest` / `DataDigest`.
90///
91/// # F/C (Final/Continue)
92/// By default the builder sets `StageFlags::FINAL`, meaning the message is
93/// complete. If you intend to split a long key/value payload across multiple
94/// Text PDUs, toggle the stage flags on `header.flags` (see `StageFlags`) so
95/// that intermediate PDUs have **CONTINUE** set and the last one has **FINAL**.
96#[derive(Debug, Default)]
97pub struct TextRequestBuilder {
98    pub header: TextRequest,
99    enable_header_digest: bool,
100    enable_data_digest: bool,
101}
102
103impl TextRequestBuilder {
104    /// Creates a new `TextRequestBuilder` with default values.
105    pub fn new() -> Self {
106        TextRequestBuilder {
107            header: TextRequest {
108                opcode: {
109                    let mut tmp = RawBhsOpcode::default();
110                    tmp.set_opcode_known(Opcode::TextReq);
111                    tmp
112                },
113                ..Default::default()
114            },
115            enable_data_digest: false,
116            enable_header_digest: false,
117        }
118    }
119
120    /// Sets the Immediate bit in the PDU header.
121    pub fn immediate(mut self) -> Self {
122        self.header.opcode.set_i();
123        self
124    }
125
126    /// Enables header digest for the PDU.
127    pub fn with_header_digest(mut self) -> Self {
128        self.enable_header_digest = true;
129        self
130    }
131
132    /// Enables data digest for the PDU.
133    pub fn with_data_digest(mut self) -> Self {
134        self.enable_data_digest = true;
135        self
136    }
137
138    /// Sets the initiator task tag, a unique identifier for this command.
139    pub fn initiator_task_tag(mut self, tag: u32) -> Self {
140        self.header.initiator_task_tag.set(tag);
141        self
142    }
143
144    /// Sets the target task tag, used to identify a command to which this is a
145    /// response.
146    pub fn target_task_tag(mut self, tag: u32) -> Self {
147        self.header.target_task_tag.set(tag);
148        self
149    }
150
151    /// Sets the command sequence number (CmdSN) for this request.
152    pub fn cmd_sn(mut self, sn: u32) -> Self {
153        self.header.cmd_sn.set(sn);
154        self
155    }
156
157    /// Sets the expected status sequence number (ExpStatSN) from the target.
158    pub fn exp_stat_sn(mut self, sn: u32) -> Self {
159        self.header.exp_stat_sn.set(sn);
160        self
161    }
162
163    /// Sets the Logical Unit Number (LUN) for the command.
164    pub fn lun(mut self, lun: u64) -> Self {
165        self.header.lun.set(lun);
166        self
167    }
168}
169
170impl SendingData for TextRequest {
171    fn get_final_bit(&self) -> bool {
172        self.flags.get_final_bit()
173    }
174
175    fn set_final_bit(&mut self) {
176        // F ← 1,  C ← 0
177        self.flags.set_final_bit();
178        if self.get_continue_bit() {
179            self.flags.set_continue_bit();
180        }
181    }
182
183    fn get_continue_bit(&self) -> bool {
184        self.flags.get_continue_bit()
185    }
186
187    fn set_continue_bit(&mut self) {
188        // C ← 1,  F ← 0
189        self.flags.set_continue_bit();
190        if self.get_final_bit() {
191            self.flags.set_final_bit();
192        }
193    }
194}
195
196impl FromBytes for TextRequest {
197    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
198        TextRequest::from_bhs_bytes(bytes)
199    }
200}
201
202impl BasicHeaderSegment for TextRequest {
203    #[inline]
204    fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
205        self.to_bhs_bytes(buf)
206    }
207
208    #[inline]
209    fn get_opcode(&self) -> Result<BhsOpcode> {
210        BhsOpcode::try_from(self.opcode.raw())
211    }
212
213    #[inline]
214    fn get_initiator_task_tag(&self) -> u32 {
215        self.initiator_task_tag.get()
216    }
217
218    #[inline]
219    fn get_ahs_length_bytes(&self) -> usize {
220        (self.total_ahs_length as usize) * 4
221    }
222
223    #[inline]
224    fn set_ahs_length_bytes(&mut self, len: u8) {
225        self.total_ahs_length = len >> 2;
226    }
227
228    #[inline]
229    fn get_data_length_bytes(&self) -> usize {
230        u32::from_be_bytes([
231            0,
232            self.data_segment_length[0],
233            self.data_segment_length[1],
234            self.data_segment_length[2],
235        ]) as usize
236    }
237
238    #[inline]
239    fn set_data_length_bytes(&mut self, len: u32) {
240        let be = len.to_be_bytes();
241        self.data_segment_length = [be[1], be[2], be[3]];
242    }
243}
244
245impl ZeroCopyType for TextRequest {}