Skip to main content

iscsi_client_rs/models/logout/
response.rs

1//! This module defines the structures for iSCSI Logout Response PDUs.
2//! It includes the `LogoutResponse` header and related methods for handling the
3//! logout process.
4
5// SPDX-License-Identifier: AGPL-3.0-or-later
6// Copyright (C) 2012-2025 Andrei Maltsev
7
8use anyhow::{Result, bail};
9use tracing::{error, warn};
10use zerocopy::{
11    BigEndian, FromBytes as ZFromBytes, Immutable, IntoBytes, KnownLayout, U16, U32,
12};
13
14use crate::{
15    client::pdu_connection::FromBytes,
16    models::{
17        common::{BasicHeaderSegment, HEADER_LEN, InitiatorTaskTag, SendingData},
18        data_fromat::ZeroCopyType,
19        logout::common::RawLogoutResponseCode,
20        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
21    },
22};
23
24/// Represents the Basic Header Segment (BHS) for a Logout Response PDU.
25#[repr(C)]
26#[derive(Debug, Default, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
27pub struct LogoutResponse {
28    pub opcode: RawBhsOpcode,                 // byte 0: 0x26
29    pub flags: u8,                            // byte 1: F-bit at bit7, others reserved
30    pub response: RawLogoutResponseCode,      // byte 2: response code
31    reserved0: u8,                            // byte 3: reserved
32    pub total_ahs_length: u8,                 // byte 4: must be 0
33    pub data_segment_length: [u8; 3],         // bytes 5..8: must be [0,0,0]
34    reserved1: [u8; 8],                       // bytes 8..16: reserved
35    pub initiator_task_tag: InitiatorTaskTag, // bytes 16..20: ITT
36    reserved2: [u8; 4],                       // bytes 20..24: reserved
37    pub stat_sn: U32<BigEndian>,              // bytes 24..28
38    pub exp_cmd_sn: U32<BigEndian>,           // bytes 28..32
39    pub max_cmd_sn: U32<BigEndian>,           // bytes 32..36
40    reserved3: [u8; 4],                       // bytes 36..40: reserved
41    pub time2wait: U16<BigEndian>,            // bytes 40..42
42    pub time2retain: U16<BigEndian>,          // bytes 42..44
43    reserved4: [u8; 4],                       // bytes 44..48: reserved
44}
45
46impl LogoutResponse {
47    /// Serializes the BHS into a byte buffer.
48    pub fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
49        if buf.len() != HEADER_LEN {
50            bail!("buffer length must be {HEADER_LEN}, got {}", buf.len());
51        }
52        buf.copy_from_slice(self.as_bytes());
53        Ok(())
54    }
55
56    /// Deserializes the BHS from a byte buffer.
57    pub fn from_bhs_bytes(buf: &mut [u8]) -> Result<&mut Self> {
58        let hdr = <Self as zerocopy::FromBytes>::mut_from_bytes(buf)
59            .map_err(|e| anyhow::anyhow!("failed convert buffer LogoutResponse: {e}"))?;
60        if hdr.opcode.opcode_known() != Some(Opcode::LogoutResp) {
61            anyhow::bail!(
62                "LogoutResponse: invalid opcode 0x{:02x}",
63                hdr.opcode.opcode_raw()
64            );
65        }
66        Ok(hdr)
67    }
68
69    /// Checks if the Final (F) bit is set.
70    #[inline]
71    pub fn is_final(&self) -> bool {
72        (self.flags & 0b1000_0000) != 0
73    }
74
75    /// Sets the Final (F) bit.
76    #[inline]
77    pub fn set_final(&mut self) {
78        self.flags |= 0b1000_0000;
79    }
80}
81
82impl SendingData for LogoutResponse {
83    fn get_final_bit(&self) -> bool {
84        self.is_final()
85    }
86
87    fn set_final_bit(&mut self) {
88        self.set_final();
89    }
90
91    fn get_continue_bit(&self) -> bool {
92        false
93    }
94
95    fn set_continue_bit(&mut self) {
96        warn!("Logout Response cannot be marked as Contine");
97    }
98}
99
100impl FromBytes for LogoutResponse {
101    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
102        LogoutResponse::from_bhs_bytes(bytes)
103    }
104}
105
106impl BasicHeaderSegment for LogoutResponse {
107    #[inline]
108    fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
109        self.to_bhs_bytes(buf)
110    }
111
112    #[inline]
113    fn get_opcode(&self) -> Result<BhsOpcode> {
114        BhsOpcode::try_from(self.opcode.raw())
115    }
116
117    #[inline]
118    fn get_initiator_task_tag(&self) -> u32 {
119        self.initiator_task_tag.get()
120    }
121
122    #[inline]
123    fn get_ahs_length_bytes(&self) -> usize {
124        (self.total_ahs_length as usize) * 4
125    }
126
127    #[inline]
128    fn set_ahs_length_bytes(&mut self, len: u8) {
129        self.total_ahs_length = len >> 2;
130    }
131
132    #[inline]
133    fn get_data_length_bytes(&self) -> usize {
134        u32::from_be_bytes([
135            0,
136            self.data_segment_length[0],
137            self.data_segment_length[1],
138            self.data_segment_length[2],
139        ]) as usize
140    }
141
142    #[inline]
143    fn set_data_length_bytes(&mut self, len: u32) {
144        error!("LogoutResp must have zero DataSegmentLength");
145        let be = len.to_be_bytes();
146        self.data_segment_length = [be[1], be[2], be[3]];
147    }
148
149    #[inline]
150    fn get_header_diggest(&self, _: bool) -> usize {
151        0
152    }
153
154    #[inline]
155    fn get_data_diggest(&self, _: bool) -> usize {
156        0
157    }
158}
159
160impl ZeroCopyType for LogoutResponse {}