Skip to main content

iscsi_client_rs/models/logout/
common.rs

1//! This module defines common structures and enums for iSCSI Logout PDUs.
2//! It includes reason and response codes for the logout process.
3
4// SPDX-License-Identifier: AGPL-3.0-or-later
5// Copyright (C) 2012-2025 Andrei Maltsev
6
7use std::fmt;
8
9use anyhow::{Result, bail};
10use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
11
12/// iSCSI Logout Reason Code.
13#[derive(Debug, Default, PartialEq, Clone)]
14#[repr(u8)]
15pub enum LogoutReason {
16    /// Close the entire session.
17    #[default]
18    CloseSession = 0x00,
19    /// Close a specific connection.
20    CloseConnection = 0x01,
21    /// Remove a connection for recovery.
22    RemoveConnectionForRecovery = 0x02,
23}
24
25impl LogoutReason {
26    /// Returns the reason code as a `u8`.
27    #[inline]
28    pub fn as_u8(&self) -> u8 {
29        match self {
30            LogoutReason::CloseSession => 0x00,
31            LogoutReason::CloseConnection => 0x01,
32            LogoutReason::RemoveConnectionForRecovery => 0x02,
33        }
34    }
35}
36
37impl TryFrom<u8> for LogoutReason {
38    type Error = anyhow::Error;
39
40    fn try_from(value: u8) -> Result<Self> {
41        Ok(match value {
42            0x00 => LogoutReason::CloseSession,
43            0x01 => LogoutReason::CloseConnection,
44            0x02 => LogoutReason::RemoveConnectionForRecovery,
45            other => bail!("unexpected logout code {other}"),
46        })
47    }
48}
49
50impl fmt::Display for LogoutReason {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        use LogoutReason::*;
53        let s = match self {
54            CloseSession => "CloseSession",
55            CloseConnection => "CloseConnection",
56            RemoveConnectionForRecovery => "RemoveConnectionForRecovery",
57        };
58        f.write_str(s)
59    }
60}
61
62/// Wire-safe, zero-copy wrapper for Logout Reason (1 byte on the wire).
63///
64/// Use this in BHS structs instead of `LogoutReason`:
65/// `pub reason: RawLogoutReason`
66#[repr(transparent)]
67#[derive(
68    Copy, Clone, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable,
69)]
70pub struct RawLogoutReason(u8);
71
72impl Default for RawLogoutReason {
73    #[inline]
74    fn default() -> Self {
75        Self(LogoutReason::CloseSession.as_u8())
76    }
77}
78
79impl RawLogoutReason {
80    /// Returns the raw 8-bit value of the reason code.
81    #[inline]
82    pub const fn raw(self) -> u8 {
83        self.0
84    }
85
86    /// Creates a new `RawLogoutReason` from a raw 8-bit value.
87    #[inline]
88    pub const fn from_raw(v: u8) -> Self {
89        Self(v)
90    }
91
92    /// Decodes the raw value into a `LogoutReason` enum.
93    #[inline]
94    pub fn decode(self) -> Result<LogoutReason> {
95        LogoutReason::try_from(self.0)
96    }
97
98    /// Encodes a `LogoutReason` enum into the raw value.
99    #[inline]
100    pub fn encode(&mut self, r: LogoutReason) {
101        self.0 = r.as_u8();
102    }
103}
104
105/* Convenience conversions */
106
107impl TryFrom<RawLogoutReason> for LogoutReason {
108    type Error = anyhow::Error;
109
110    #[inline]
111    fn try_from(w: RawLogoutReason) -> Result<Self> {
112        w.decode()
113    }
114}
115
116impl From<LogoutReason> for RawLogoutReason {
117    #[inline]
118    fn from(r: LogoutReason) -> Self {
119        Self(r.as_u8())
120    }
121}
122
123impl From<&LogoutReason> for RawLogoutReason {
124    #[inline]
125    fn from(r: &LogoutReason) -> Self {
126        Self(r.as_u8())
127    }
128}
129
130/// iSCSI Logout Response Code.
131#[derive(Debug, Default, PartialEq, Eq)]
132#[repr(u8)]
133pub enum LogoutResponseCode {
134    /// Connection or session closed successfully.
135    #[default]
136    Success = 0x00,
137    /// The CID was not found.
138    CidNotFound = 0x01,
139    /// Connection recovery is not supported.
140    RecoveryNotSupported = 0x02,
141    /// Cleanup failed for various reasons.
142    CleanupFailed = 0x03,
143}
144
145impl LogoutResponseCode {
146    /// Returns the response code as a `u8`.
147    #[inline]
148    pub fn as_u8(&self) -> u8 {
149        match self {
150            LogoutResponseCode::Success => 0x00,
151            LogoutResponseCode::CidNotFound => 0x01,
152            LogoutResponseCode::RecoveryNotSupported => 0x02,
153            LogoutResponseCode::CleanupFailed => 0x03,
154        }
155    }
156}
157
158impl TryFrom<u8> for LogoutResponseCode {
159    type Error = anyhow::Error;
160
161    fn try_from(v: u8) -> Result<Self> {
162        Ok(match v {
163            0x00 => LogoutResponseCode::Success,
164            0x01 => LogoutResponseCode::CidNotFound,
165            0x02 => LogoutResponseCode::RecoveryNotSupported,
166            0x03 => LogoutResponseCode::CleanupFailed,
167            other => bail!("invalid LogoutResponseCode: {other:#04x}"),
168        })
169    }
170}
171
172impl fmt::Display for LogoutResponseCode {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        use LogoutResponseCode::*;
175        let s = match self {
176            Success => "Success",
177            CidNotFound => "CidNotFound",
178            RecoveryNotSupported => "RecoveryNotSupported",
179            CleanupFailed => "CleanupFailed",
180        };
181        f.write_str(s)
182    }
183}
184
185/// Wire-safe, zero-copy wrapper for Logout Response Code (1 byte on the wire).
186///
187/// Use this in your BHS structs:
188/// `pub response_code: RawLogoutResponseCode`
189#[repr(transparent)]
190#[derive(Copy, Clone, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)]
191pub struct RawLogoutResponseCode(u8);
192
193impl Default for RawLogoutResponseCode {
194    #[inline]
195    fn default() -> Self {
196        Self(LogoutResponseCode::Success.as_u8())
197    }
198}
199
200impl RawLogoutResponseCode {
201    /// Returns the raw 8-bit value of the response code.
202    #[inline]
203    pub const fn raw(self) -> u8 {
204        self.0
205    }
206
207    /// Creates a new `RawLogoutResponseCode` from a raw 8-bit value.
208    #[inline]
209    pub const fn from_raw(v: u8) -> Self {
210        Self(v)
211    }
212
213    /// Decodes the raw value into a `LogoutResponseCode` enum.
214    #[inline]
215    pub fn decode(self) -> Result<LogoutResponseCode> {
216        LogoutResponseCode::try_from(self.0)
217    }
218
219    /// Encodes a `LogoutResponseCode` enum into the raw value.
220    #[inline]
221    pub fn encode(&mut self, r: LogoutResponseCode) {
222        self.0 = r.as_u8();
223    }
224}
225
226impl TryFrom<RawLogoutResponseCode> for LogoutResponseCode {
227    type Error = anyhow::Error;
228
229    #[inline]
230    fn try_from(w: RawLogoutResponseCode) -> Result<Self> {
231        w.decode()
232    }
233}
234
235impl From<LogoutResponseCode> for RawLogoutResponseCode {
236    #[inline]
237    fn from(r: LogoutResponseCode) -> Self {
238        Self(r.as_u8())
239    }
240}
241
242impl From<&LogoutResponseCode> for RawLogoutResponseCode {
243    #[inline]
244    fn from(r: &LogoutResponseCode) -> Self {
245        Self(r.as_u8())
246    }
247}
248
249impl fmt::Debug for RawLogoutResponseCode {
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        let decoded = match self.decode() {
252            Ok(st) => format!("{st:?}"),
253            Err(_e) => format!("invalid(0x{:02X})", self.raw()),
254        };
255
256        write!(f, "RawScsiStatus {{ {:?} }}", decoded)
257    }
258}