Skip to main content

iscsi_client_rs/models/logout/
request.rs

1//! This module defines the structures for iSCSI Logout Request PDUs.
2//! It includes the `LogoutRequest` 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, error, warn};
9use zerocopy::{
10    BigEndian, FromBytes as ZFromBytes, Immutable, IntoBytes, KnownLayout, U16, U32,
11};
12
13use crate::{
14    client::pdu_connection::FromBytes,
15    models::{
16        common::{BasicHeaderSegment, HEADER_LEN, InitiatorTaskTag, SendingData},
17        data_fromat::ZeroCopyType,
18        logout::common::{LogoutReason, RawLogoutReason},
19        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
20    },
21};
22
23/// BHS structure for **Logout Request** (opcode `LogoutReq`)
24///
25/// Fits into 48-byte Basic Header Segment.
26/// Data Segment length must always be zero for Logout Request.
27#[repr(C)]
28#[derive(Debug, Default, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
29pub struct LogoutRequest {
30    pub opcode: RawBhsOpcode,         // byte 0: I|0x06
31    pub reason: RawLogoutReason,      // byte 1: Reason Code
32    reserved0: [u8; 2],               // bytes 2..4: Reserved
33    pub total_ahs_length: u8,         // byte 4: normally 0
34    pub data_segment_length: [u8; 3], // bytes 5..8: must be zero
35    reserved1: [u8; 8],               /* bytes 8..16: Reserved (no ISID/Tsih in
36                                       * LogoutReq) */
37    pub initiator_task_tag: InitiatorTaskTag, // bytes 16..20: ITT
38    pub cid: U16<BigEndian>,                  /* bytes 20..22: CID (if closing a
39                                               * specific connection) */
40    reserved2: [u8; 2],              // bytes 22..24: Reserved
41    pub cmd_sn: U32<BigEndian>,      // bytes 24..28
42    pub exp_stat_sn: U32<BigEndian>, // bytes 28..32
43    reserved3: [u8; 16],             // bytes 32..48: Reserved
44}
45
46impl LogoutRequest {
47    /// Serializes the BHS into a byte buffer.
48    pub fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
49        buf.fill(0);
50        if buf.len() != HEADER_LEN {
51            bail!("buffer length must be {HEADER_LEN}, got {}", buf.len());
52        }
53        buf.copy_from_slice(self.as_bytes());
54        Ok(())
55    }
56
57    /// Deserializes the BHS from a byte buffer.
58    pub fn from_bhs_bytes(buf: &mut [u8]) -> Result<&mut Self> {
59        let hdr = <Self as zerocopy::FromBytes>::mut_from_bytes(buf)
60            .map_err(|e| anyhow::anyhow!("failed convert buffer LogoutRequest: {e}"))?;
61        if hdr.opcode.opcode_known() != Some(Opcode::LogoutReq) {
62            anyhow::bail!(
63                "LogoutRequest: invalid opcode 0x{:02x}",
64                hdr.opcode.opcode_raw()
65            );
66        }
67        Ok(hdr)
68    }
69}
70
71/// Builder for **Logout Request**
72///
73/// Defaults to an Immediate Logout (`I` bit) with empty AHS and zero Data
74/// Segment length.
75#[derive(Debug, Default)]
76pub struct LogoutRequestBuilder {
77    pub header: LogoutRequest,
78}
79
80impl LogoutRequestBuilder {
81    /// Creates a new `LogoutRequestBuilder` with the given reason, ITT, and
82    /// CID.
83    pub fn new(reason: LogoutReason, itt: u32, cid: u16) -> Self {
84        Self {
85            header: LogoutRequest {
86                opcode: {
87                    let mut tmp = RawBhsOpcode::default();
88                    tmp.set_opcode_known(Opcode::LogoutReq);
89                    tmp.set_i();
90                    tmp
91                },
92                reason: reason.into(),
93                total_ahs_length: 0,
94                data_segment_length: [0, 0, 0],
95                initiator_task_tag: InitiatorTaskTag::new(itt),
96                cid: cid.into(),
97                ..Default::default()
98            },
99        }
100    }
101
102    /// Sets the Connection ID (CID) for the logout request.
103    pub fn connection_id(mut self, cid: u16) -> Self {
104        self.header.cid.set(cid);
105        self
106    }
107
108    /// Sets the command sequence number (CmdSN) for this request.
109    pub fn cmd_sn(mut self, cmd_sn: u32) -> Self {
110        self.header.cmd_sn.set(cmd_sn);
111        self
112    }
113
114    /// Sets the expected status sequence number (ExpStatSN) from the target.
115    pub fn exp_stat_sn(mut self, exp_stat_sn: u32) -> Self {
116        self.header.exp_stat_sn.set(exp_stat_sn);
117        self
118    }
119}
120
121impl SendingData for LogoutRequest {
122    fn get_final_bit(&self) -> bool {
123        true
124    }
125
126    fn set_final_bit(&mut self) {
127        debug!("Logout Request cannot be marked as Final");
128    }
129
130    fn get_continue_bit(&self) -> bool {
131        false
132    }
133
134    fn set_continue_bit(&mut self) {
135        warn!("Logout Request cannot be marked as Contine");
136    }
137}
138
139impl FromBytes for LogoutRequest {
140    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
141        LogoutRequest::from_bhs_bytes(bytes)
142    }
143}
144
145impl BasicHeaderSegment for LogoutRequest {
146    #[inline]
147    fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
148        self.to_bhs_bytes(buf)
149    }
150
151    #[inline]
152    fn get_opcode(&self) -> Result<BhsOpcode> {
153        BhsOpcode::try_from(self.opcode.raw())
154    }
155
156    #[inline]
157    fn get_initiator_task_tag(&self) -> u32 {
158        self.initiator_task_tag.get()
159    }
160
161    #[inline]
162    fn get_ahs_length_bytes(&self) -> usize {
163        (self.total_ahs_length as usize) * 4
164    }
165
166    #[inline]
167    fn set_ahs_length_bytes(&mut self, len: u8) {
168        self.total_ahs_length = len >> 2;
169    }
170
171    #[inline]
172    fn get_data_length_bytes(&self) -> usize {
173        u32::from_be_bytes([
174            0,
175            self.data_segment_length[0],
176            self.data_segment_length[1],
177            self.data_segment_length[2],
178        ]) as usize
179    }
180
181    #[inline]
182    fn set_data_length_bytes(&mut self, len: u32) {
183        error!("LogoutReq must have zero DataSegmentLength");
184        let be = len.to_be_bytes();
185        self.data_segment_length = [be[1], be[2], be[3]];
186    }
187
188    #[inline]
189    fn get_header_diggest(&self, header_digest: bool) -> usize {
190        4 * (header_digest as usize)
191    }
192
193    #[inline]
194    fn get_data_diggest(&self, _: bool) -> usize {
195        0
196    }
197}
198
199impl ZeroCopyType for LogoutRequest {}