Skip to main content

iscsi_client_rs/models/nop/
request.rs

1//! This module defines the structures for iSCSI NOP-Out PDUs.
2//! It includes the `NopOutRequest` 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 tracing::{debug, warn};
9use zerocopy::{
10    BigEndian, FromBytes as ZFromBytes, Immutable, IntoBytes, KnownLayout, U32,
11};
12
13use crate::{
14    client::pdu_connection::FromBytes,
15    models::{
16        common::{
17            BasicHeaderSegment, HEADER_LEN, InitiatorTaskTag, LogicalUnitNumber,
18            SendingData, TargetTaskTag,
19        },
20        data_fromat::ZeroCopyType,
21        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
22    },
23};
24
25/// Represents the Basic Header Segment (BHS) for a NOP-Out PDU.
26#[repr(C)]
27#[derive(Debug, Default, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
28pub struct NopOutRequest {
29    pub opcode: RawBhsOpcode,                 // 0
30    reserved1: [u8; 3],                       // 1..4
31    pub total_ahs_length: u8,                 // 4
32    pub data_segment_length: [u8; 3],         // 5..8
33    pub lun: LogicalUnitNumber,               // 8..16
34    pub initiator_task_tag: InitiatorTaskTag, // 16..20
35    pub target_task_tag: TargetTaskTag,       // 20..24
36    pub cmd_sn: U32<BigEndian>,               // 24..28
37    pub exp_stat_sn: U32<BigEndian>,          // 28..32
38    reserved2: [u8; 16],                      // 32..48
39}
40
41impl NopOutRequest {
42    /// The default task tag value for NOP-Out requests.
43    pub const DEFAULT_TAG: u32 = 0xffffffff_u32;
44
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 as zerocopy::FromBytes>::mut_from_bytes(buf)
58            .map_err(|e| anyhow::anyhow!("failed convert buffer NopOutRequest: {e}"))?;
59        if hdr.opcode.opcode_known() != Some(Opcode::NopOut) {
60            anyhow::bail!(
61                "NopOutRequest: invalid opcode 0x{:02x}",
62                hdr.opcode.opcode_raw()
63            );
64        }
65        Ok(hdr)
66    }
67}
68
69/// Builder for an iSCSI **NOP-Out** PDU (opcode `NopOut`).
70///
71/// NOP-Out is a lightweight “ping/keep-alive” PDU used to verify liveness,
72/// measure round-trip time, or provoke a NOP-In from the target. It carries
73/// no SCSI semantics and **does not use F/C (Final/Continue) bits**.
74///
75/// This builder prepares the 48-byte BHS; if you want to attach an optional
76/// data segment (rare for NOPs), wrap the header with `PDUWithData` and call
77/// `append_data(...)`.
78///
79/// # What you can set
80/// - **Immediate bit**: `immediate()` sets the *I* flag in byte 0.
81/// - **Initiator/Target Task Tags**:
82///   - `initiator_task_tag(..)` sets **ITT** (used to match the reply).
83///   - `target_task_tag(..)` sets **TTT**:
84///     - For a *solicited ping*, use `NopOutRequest::DEFAULT_TAG`
85///       (`0xFFFF_FFFF`) to ask the target to generate a NOP-In.
86///     - For a *response to a target’s NOP-In*, copy the TTT you received.
87/// - **Sequencing**: `cmd_sn(..)` and `exp_stat_sn(..)` as usual for the
88///   session.
89/// - **LUN**: `lun(..)` accepts an 8-byte encoded LUN (often zero for NOPs).
90/// - **Digests**: `with_header_digest()` / `with_data_digest()` opt into
91///   including CRC32C digests when your connection logic honors negotiated
92///   `HeaderDigest` / `DataDigest` settings.
93///
94/// # Typical patterns
95/// - **Initiator ping** (solicit a NOP-In):
96///   - Set `TTT = 0xFFFF_FFFF`, pick a fresh ITT, send NOP-Out, wait for NOP-In
97///     with the same ITT.
98/// - **Reply to target’s NOP-In**:
99///   - Echo back the **TTT** you received in NOP-In, send NOP-Out.
100#[derive(Debug, Default)]
101pub struct NopOutRequestBuilder {
102    pub header: NopOutRequest,
103    want_header_digest: bool,
104    want_data_digest: bool,
105}
106
107impl NopOutRequestBuilder {
108    /// Creates a new `NopOutRequestBuilder` with default values.
109    pub fn new() -> Self {
110        NopOutRequestBuilder {
111            header: NopOutRequest {
112                opcode: {
113                    let mut tmp = RawBhsOpcode::default();
114                    tmp.set_opcode_known(Opcode::NopOut);
115                    tmp
116                },
117                reserved1: {
118                    let mut tmp = [0; 3];
119                    tmp[0] = 0b1000_0000;
120                    tmp
121                },
122                ..Default::default()
123            },
124            want_data_digest: false,
125            want_header_digest: false,
126        }
127    }
128
129    /// Sets the Immediate bit in the PDU header.
130    pub fn immediate(mut self) -> Self {
131        self.header.opcode.set_i();
132        self
133    }
134
135    /// Enables header digest for the PDU.
136    pub fn with_header_digest(mut self) -> Self {
137        self.want_header_digest = true;
138        self
139    }
140
141    /// Enables data digest for the PDU.
142    pub fn with_data_digest(mut self) -> Self {
143        self.want_data_digest = true;
144        self
145    }
146
147    /// Sets the initiator task tag, a unique identifier for this command.
148    pub fn initiator_task_tag(mut self, tag: u32) -> Self {
149        self.header.initiator_task_tag.set(tag);
150        self
151    }
152
153    /// Sets the target task tag, used to match a response to a NOP-In.
154    pub fn target_task_tag(mut self, tag: u32) -> Self {
155        self.header.target_task_tag.set(tag);
156        self
157    }
158
159    /// Sets the command sequence number (CmdSN) for this request.
160    pub fn cmd_sn(mut self, sn: u32) -> Self {
161        self.header.cmd_sn.set(sn);
162        self
163    }
164
165    /// Sets the expected status sequence number (ExpStatSN) from the target.
166    pub fn exp_stat_sn(mut self, sn: u32) -> Self {
167        self.header.exp_stat_sn.set(sn);
168        self
169    }
170
171    /// Sets the Logical Unit Number (LUN) for the command.
172    pub fn lun(mut self, lun: u64) -> Self {
173        self.header.lun.set(lun);
174        self
175    }
176}
177
178impl SendingData for NopOutRequest {
179    fn get_final_bit(&self) -> bool {
180        true
181    }
182
183    fn set_final_bit(&mut self) {
184        debug!("NopOut Request cannot be marked as Final")
185    }
186
187    fn get_continue_bit(&self) -> bool {
188        false
189    }
190
191    fn set_continue_bit(&mut self) {
192        warn!("NopOut Request cannot be marked as Contine");
193    }
194}
195
196impl FromBytes for NopOutRequest {
197    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
198        NopOutRequest::from_bhs_bytes(bytes)
199    }
200}
201
202impl BasicHeaderSegment for NopOutRequest {
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 NopOutRequest {}