iscsi-client-rs 0.0.9

A pure-Rust iSCSI initiator library
Documentation
//! This module defines common structures and enums for iSCSI Logout PDUs.
//! It includes reason and response codes for the logout process.

// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2012-2025 Andrei Maltsev

use std::fmt;

use anyhow::{Result, bail};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

/// iSCSI Logout Reason Code.
#[derive(Debug, Default, PartialEq, Clone)]
#[repr(u8)]
pub enum LogoutReason {
    /// Close the entire session.
    #[default]
    CloseSession = 0x00,
    /// Close a specific connection.
    CloseConnection = 0x01,
    /// Remove a connection for recovery.
    RemoveConnectionForRecovery = 0x02,
}

impl LogoutReason {
    /// Returns the reason code as a `u8`.
    #[inline]
    pub fn as_u8(&self) -> u8 {
        match self {
            LogoutReason::CloseSession => 0x00,
            LogoutReason::CloseConnection => 0x01,
            LogoutReason::RemoveConnectionForRecovery => 0x02,
        }
    }
}

impl TryFrom<u8> for LogoutReason {
    type Error = anyhow::Error;

    fn try_from(value: u8) -> Result<Self> {
        Ok(match value {
            0x00 => LogoutReason::CloseSession,
            0x01 => LogoutReason::CloseConnection,
            0x02 => LogoutReason::RemoveConnectionForRecovery,
            other => bail!("unexpected logout code {other}"),
        })
    }
}

impl fmt::Display for LogoutReason {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use LogoutReason::*;
        let s = match self {
            CloseSession => "CloseSession",
            CloseConnection => "CloseConnection",
            RemoveConnectionForRecovery => "RemoveConnectionForRecovery",
        };
        f.write_str(s)
    }
}

/// Wire-safe, zero-copy wrapper for Logout Reason (1 byte on the wire).
///
/// Use this in BHS structs instead of `LogoutReason`:
/// `pub reason: RawLogoutReason`
#[repr(transparent)]
#[derive(
    Copy, Clone, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable,
)]
pub struct RawLogoutReason(u8);

impl Default for RawLogoutReason {
    #[inline]
    fn default() -> Self {
        Self(LogoutReason::CloseSession.as_u8())
    }
}

impl RawLogoutReason {
    /// Returns the raw 8-bit value of the reason code.
    #[inline]
    pub const fn raw(self) -> u8 {
        self.0
    }

    /// Creates a new `RawLogoutReason` from a raw 8-bit value.
    #[inline]
    pub const fn from_raw(v: u8) -> Self {
        Self(v)
    }

    /// Decodes the raw value into a `LogoutReason` enum.
    #[inline]
    pub fn decode(self) -> Result<LogoutReason> {
        LogoutReason::try_from(self.0)
    }

    /// Encodes a `LogoutReason` enum into the raw value.
    #[inline]
    pub fn encode(&mut self, r: LogoutReason) {
        self.0 = r.as_u8();
    }
}

/* Convenience conversions */

impl TryFrom<RawLogoutReason> for LogoutReason {
    type Error = anyhow::Error;

    #[inline]
    fn try_from(w: RawLogoutReason) -> Result<Self> {
        w.decode()
    }
}

impl From<LogoutReason> for RawLogoutReason {
    #[inline]
    fn from(r: LogoutReason) -> Self {
        Self(r.as_u8())
    }
}

impl From<&LogoutReason> for RawLogoutReason {
    #[inline]
    fn from(r: &LogoutReason) -> Self {
        Self(r.as_u8())
    }
}

/// iSCSI Logout Response Code.
#[derive(Debug, Default, PartialEq, Eq)]
#[repr(u8)]
pub enum LogoutResponseCode {
    /// Connection or session closed successfully.
    #[default]
    Success = 0x00,
    /// The CID was not found.
    CidNotFound = 0x01,
    /// Connection recovery is not supported.
    RecoveryNotSupported = 0x02,
    /// Cleanup failed for various reasons.
    CleanupFailed = 0x03,
}

impl LogoutResponseCode {
    /// Returns the response code as a `u8`.
    #[inline]
    pub fn as_u8(&self) -> u8 {
        match self {
            LogoutResponseCode::Success => 0x00,
            LogoutResponseCode::CidNotFound => 0x01,
            LogoutResponseCode::RecoveryNotSupported => 0x02,
            LogoutResponseCode::CleanupFailed => 0x03,
        }
    }
}

impl TryFrom<u8> for LogoutResponseCode {
    type Error = anyhow::Error;

    fn try_from(v: u8) -> Result<Self> {
        Ok(match v {
            0x00 => LogoutResponseCode::Success,
            0x01 => LogoutResponseCode::CidNotFound,
            0x02 => LogoutResponseCode::RecoveryNotSupported,
            0x03 => LogoutResponseCode::CleanupFailed,
            other => bail!("invalid LogoutResponseCode: {other:#04x}"),
        })
    }
}

impl fmt::Display for LogoutResponseCode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use LogoutResponseCode::*;
        let s = match self {
            Success => "Success",
            CidNotFound => "CidNotFound",
            RecoveryNotSupported => "RecoveryNotSupported",
            CleanupFailed => "CleanupFailed",
        };
        f.write_str(s)
    }
}

/// Wire-safe, zero-copy wrapper for Logout Response Code (1 byte on the wire).
///
/// Use this in your BHS structs:
/// `pub response_code: RawLogoutResponseCode`
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)]
pub struct RawLogoutResponseCode(u8);

impl Default for RawLogoutResponseCode {
    #[inline]
    fn default() -> Self {
        Self(LogoutResponseCode::Success.as_u8())
    }
}

impl RawLogoutResponseCode {
    /// Returns the raw 8-bit value of the response code.
    #[inline]
    pub const fn raw(self) -> u8 {
        self.0
    }

    /// Creates a new `RawLogoutResponseCode` from a raw 8-bit value.
    #[inline]
    pub const fn from_raw(v: u8) -> Self {
        Self(v)
    }

    /// Decodes the raw value into a `LogoutResponseCode` enum.
    #[inline]
    pub fn decode(self) -> Result<LogoutResponseCode> {
        LogoutResponseCode::try_from(self.0)
    }

    /// Encodes a `LogoutResponseCode` enum into the raw value.
    #[inline]
    pub fn encode(&mut self, r: LogoutResponseCode) {
        self.0 = r.as_u8();
    }
}

impl TryFrom<RawLogoutResponseCode> for LogoutResponseCode {
    type Error = anyhow::Error;

    #[inline]
    fn try_from(w: RawLogoutResponseCode) -> Result<Self> {
        w.decode()
    }
}

impl From<LogoutResponseCode> for RawLogoutResponseCode {
    #[inline]
    fn from(r: LogoutResponseCode) -> Self {
        Self(r.as_u8())
    }
}

impl From<&LogoutResponseCode> for RawLogoutResponseCode {
    #[inline]
    fn from(r: &LogoutResponseCode) -> Self {
        Self(r.as_u8())
    }
}

impl fmt::Debug for RawLogoutResponseCode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let decoded = match self.decode() {
            Ok(st) => format!("{st:?}"),
            Err(_e) => format!("invalid(0x{:02X})", self.raw()),
        };

        write!(f, "RawScsiStatus {{ {:?} }}", decoded)
    }
}