Skip to main content

iscsi_client_rs/models/login/
request.rs

1//! This module defines the structures for iSCSI Login Request PDUs.
2//! It includes the `LoginRequest` 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, U16, U32,
10};
11
12use crate::{
13    client::pdu_connection::FromBytes,
14    models::{
15        common::{BasicHeaderSegment, HEADER_LEN, InitiatorTaskTag, SendingData},
16        data_fromat::ZeroCopyType,
17        login::common::{RawLoginFlags, Stage},
18        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
19    },
20};
21
22/// Basic Header Segment for iSCSI Login Request PDU
23///
24/// Represents the 48-byte header structure for Login Request PDU as defined in
25/// RFC 7143. Contains session establishment parameters including version
26/// negotiation, session IDs, and connection information used during the iSCSI
27/// login process.
28#[repr(C)]
29#[derive(Debug, Default, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
30pub struct LoginRequest {
31    /// PDU opcode (byte 0) - should be 0x43 for Login Request
32    pub opcode: RawBhsOpcode,
33    /// Login flags (byte 1) - Transit, Continue bits and stage information
34    pub flags: RawLoginFlags,
35    /// Maximum version supported by initiator (byte 2)
36    pub version_max: u8,
37    /// Minimum version supported by initiator (byte 3)
38    pub version_min: u8,
39    /// Total Additional Header Segments length (byte 4)
40    pub total_ahs_length: u8,
41    /// Data Segment Length (bytes 5-7) - length of login parameters
42    pub data_segment_length: [u8; 3],
43    /// Initiator Session ID (bytes 8-13) - unique session identifier
44    pub isid: [u8; 6],
45    /// Target Session Identifying Handle (bytes 14-15) - 0 for new sessions
46    pub tsih: U16<BigEndian>,
47    /// Initiator Task Tag (bytes 16-19) - unique request identifier
48    pub initiator_task_tag: InitiatorTaskTag,
49    /// Connection ID (bytes 20-21) - connection identifier within session
50    pub cid: U16<BigEndian>,
51    /// Reserved bytes (22-23)
52    reserved1: [u8; 2],
53    /// Command Sequence Number (bytes 24-27) - for login phase ordering
54    pub cmd_sn: U32<BigEndian>,
55    /// Expected Status Sequence Number (bytes 28-31) - acknowledgment
56    pub exp_stat_sn: U32<BigEndian>,
57    /// Reserved bytes (32-47)
58    reserved2: [u8; 16],
59}
60
61impl LoginRequest {
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::anyhow!("failed convert buffer LoginRequest: {e}"))?;
76        if hdr.opcode.opcode_known() != Some(Opcode::LoginReq) {
77            anyhow::bail!(
78                "LoginRequest: invalid opcode 0x{:02x}",
79                hdr.opcode.opcode_raw()
80            );
81        }
82        Ok(hdr)
83    }
84}
85
86/// Builder for an iSCSI **Login Request** PDU (opcode `LoginReq` / BHS byte0 =
87/// I|0x03).
88///
89/// This helper constructs the 48-byte Login BHS and lets you set the
90/// connection stage flags, version fields, and sequence counters. The actual
91/// login key–value pairs (e.g. `AuthMethod=…\0`, `HeaderDigest=…\0`, …) go
92/// into the **Data Segment** and should be appended separately via
93/// `PDUWithData::append_data(...)`.
94///
95/// # What it sets
96/// - **Opcode/Immediate**: `new()` creates a LoginReq with the **I**
97///   (Immediate) bit set.
98/// - **Transit/Stages**:
99///   - `transit()` sets the **T** bit (request a stage transition).
100///   - `csg(Stage)` selects the **current stage** (CSG bits).
101///   - `nsg(Stage)` selects the **next stage** (NSG bits).
102/// - **Versions**: `versions(max, min)` set *VersionMax*/*VersionMin*.
103/// - **Session/Conn IDs**: `initiator_task_tag(…)`, `connection_id(…)`,
104///   `isid(…)`.
105/// - **Sequencing**: `cmd_sn(…)`, `exp_stat_sn(…)`.
106///
107/// # Typical flow
108/// 1. **Security → Operational** with authentication keys in Data Segment
109/// 2. (Optionally continue within Security for CHAP exchange)
110/// 3. **Operational → FullFeature** to finish login
111#[derive(Debug)]
112pub struct LoginRequestBuilder {
113    pub header: LoginRequest,
114}
115
116impl LoginRequestBuilder {
117    /// Creates a new `LoginRequestBuilder` with the given ISID and TSIH.
118    pub fn new(isid: [u8; 6], tsih: u16) -> Self {
119        LoginRequestBuilder {
120            header: LoginRequest {
121                opcode: {
122                    let mut tmp = RawBhsOpcode::default();
123                    tmp.set_opcode_known(Opcode::LoginReq);
124                    tmp.set_i();
125                    tmp
126                },
127                isid,
128                tsih: tsih.into(),
129                ..Default::default()
130            },
131        }
132    }
133
134    /// Sets the Transit (T) bit, indicating a stage transition request.
135    pub fn transit(mut self) -> Self {
136        self.header.flags.set_transit(true);
137        self
138    }
139
140    /// Sets the Current Stage (CSG) of the login phase.
141    pub fn csg(mut self, stage: Stage) -> Self {
142        self.header.flags.set_csg(stage);
143        self
144    }
145
146    /// Sets the Next Stage (NSG) of the login phase.
147    pub fn nsg(mut self, stage: Stage) -> Self {
148        self.header.flags.set_nsg(stage);
149        self
150    }
151
152    /// Sets the minimum and maximum iSCSI versions supported by the initiator.
153    pub fn versions(mut self, max: u8, min: u8) -> Self {
154        self.header.version_max = max;
155        self.header.version_min = min;
156        self
157    }
158
159    /// Sets the initiator task tag, a unique identifier for this command.
160    pub fn initiator_task_tag(mut self, tag: u32) -> Self {
161        self.header.initiator_task_tag.set(tag);
162        self
163    }
164
165    /// Sets the connection ID (CID) for this login request.
166    pub fn connection_id(mut self, cid: u16) -> Self {
167        self.header.cid.set(cid);
168        self
169    }
170
171    /// Sets the command sequence number (CmdSN) for this request.
172    pub fn cmd_sn(mut self, cmd_sn: u32) -> Self {
173        self.header.cmd_sn.set(cmd_sn);
174        self
175    }
176
177    /// Sets the expected status sequence number (ExpStatSN) from the target.
178    pub fn exp_stat_sn(mut self, exp_stat_sn: u32) -> Self {
179        self.header.exp_stat_sn.set(exp_stat_sn);
180        self
181    }
182
183    /// Sets the Initiator Session ID (ISID) for the login request.
184    pub fn isid(mut self, isid: &[u8; 6]) -> Self {
185        self.header.isid.clone_from_slice(isid);
186        self
187    }
188}
189
190impl SendingData for LoginRequest {
191    fn get_final_bit(&self) -> bool {
192        !self.flags.cont()
193    }
194
195    fn set_final_bit(&mut self) {
196        self.flags.set_cont(false);
197    }
198
199    fn get_continue_bit(&self) -> bool {
200        self.flags.cont()
201    }
202
203    fn set_continue_bit(&mut self) {
204        self.flags.set_cont(true);
205    }
206}
207
208impl FromBytes for LoginRequest {
209    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
210        LoginRequest::from_bhs_bytes(bytes)
211    }
212}
213
214impl BasicHeaderSegment for LoginRequest {
215    #[inline]
216    fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
217        self.to_bhs_bytes(buf)
218    }
219
220    #[inline]
221    fn get_opcode(&self) -> Result<BhsOpcode> {
222        BhsOpcode::try_from(self.opcode.raw())
223    }
224
225    fn get_initiator_task_tag(&self) -> u32 {
226        self.initiator_task_tag.get()
227    }
228
229    fn get_ahs_length_bytes(&self) -> usize {
230        (self.total_ahs_length as usize) * 4
231    }
232
233    fn set_ahs_length_bytes(&mut self, len: u8) {
234        self.total_ahs_length = len >> 2;
235    }
236
237    fn get_data_length_bytes(&self) -> usize {
238        u32::from_be_bytes([
239            0,
240            self.data_segment_length[0],
241            self.data_segment_length[1],
242            self.data_segment_length[2],
243        ]) as usize
244    }
245
246    fn set_data_length_bytes(&mut self, len: u32) {
247        let be = len.to_be_bytes();
248        self.data_segment_length = [be[1], be[2], be[3]];
249    }
250
251    fn get_header_diggest(&self, _: bool) -> usize {
252        0
253    }
254
255    fn get_data_diggest(&self, _: bool) -> usize {
256        0
257    }
258}
259
260impl ZeroCopyType for LoginRequest {}