Skip to main content

iscsi_client_rs/state_machine/
logout_states.rs

1//! This module defines the state machine for the iSCSI Logout process.
2//! It includes the states, context, and transitions for logging out of a
3//! session.
4
5// SPDX-License-Identifier: AGPL-3.0-or-later
6// Copyright (C) 2012-2025 Andrei Maltsev
7
8use std::{
9    marker::PhantomData,
10    pin::Pin,
11    sync::{
12        Arc,
13        atomic::{AtomicU32, Ordering},
14    },
15};
16
17use anyhow::{Context, Result, anyhow, bail};
18use tokio_util::sync::CancellationToken;
19use tracing::debug;
20
21use crate::{
22    client::client::ClientConnection,
23    models::{
24        common::HEADER_LEN,
25        data_fromat::{PduRequest, PduResponse},
26        logout::{
27            common::{LogoutReason, LogoutResponseCode},
28            request::{LogoutRequest, LogoutRequestBuilder},
29            response::LogoutResponse,
30        },
31    },
32    state_machine::common::{StateMachine, StateMachineCtx, Transition},
33};
34
35/// This structure represents the context for a Logout command.
36///
37/// It holds all the necessary information to manage the state of a Logout
38/// operation, including connection details and command parameters.
39#[derive(Debug)]
40pub struct LogoutCtx<'a> {
41    _lt: PhantomData<&'a ()>,
42
43    pub conn: Arc<ClientConnection>,
44    pub itt: u32,
45    pub cmd_sn: Arc<AtomicU32>,
46    pub exp_stat_sn: Arc<AtomicU32>,
47    pub cid: u16,
48    pub reason: LogoutReason,
49    pub buf: [u8; HEADER_LEN],
50
51    pub last_response: Option<PduResponse<LogoutResponse>>,
52    state: Option<LogoutStates>,
53}
54
55impl<'a> LogoutCtx<'a> {
56    /// Creates a new `LogoutCtx` with the given connection and parameters.
57    pub fn new(
58        conn: Arc<ClientConnection>,
59        itt: Arc<AtomicU32>,
60        cmd_sn: Arc<AtomicU32>,
61        exp_stat_sn: Arc<AtomicU32>,
62        cid: u16,
63        reason: LogoutReason,
64    ) -> Self {
65        Self {
66            conn,
67            itt: itt.fetch_add(1, Ordering::SeqCst),
68            cmd_sn,
69            exp_stat_sn,
70            cid,
71            reason,
72            buf: [0u8; HEADER_LEN],
73            state: Some(LogoutStates::Idle(Idle)),
74            last_response: None,
75            _lt: PhantomData,
76        }
77    }
78
79    async fn send_logout(&mut self) -> Result<()> {
80        let cmd_sn = self.cmd_sn.fetch_add(1, Ordering::SeqCst);
81        let exp_stat_sn = self.exp_stat_sn.load(Ordering::SeqCst);
82        let header = LogoutRequestBuilder::new(self.reason.clone(), self.itt, self.cid)
83            .cmd_sn(cmd_sn)
84            .exp_stat_sn(exp_stat_sn);
85
86        header.header.to_bhs_bytes(self.buf.as_mut_slice())?;
87
88        let builder = PduRequest::<LogoutRequest>::new_request(self.buf, &self.conn.cfg);
89        self.conn.send_request(self.itt, builder).await?;
90
91        Ok(())
92    }
93
94    async fn receive_logout_resp(&mut self) -> Result<()> {
95        let rsp = self.conn.read_response::<LogoutResponse>(self.itt).await?;
96        let hv = rsp.header_view()?;
97
98        self.exp_stat_sn
99            .store(hv.stat_sn.get().wrapping_add(1), Ordering::SeqCst);
100
101        if hv.response.decode()? != LogoutResponseCode::Success {
102            bail!("LogoutResp: target returned {:?}", hv.response);
103        }
104
105        self.last_response = Some(rsp);
106        Ok(())
107    }
108}
109
110/// Represents the initial state of a Logout operation.
111#[derive(Debug)]
112pub struct Idle;
113
114/// Represents the state of waiting for a response to a Logout command.
115#[derive(Debug)]
116pub struct Wait;
117
118/// Defines the possible states for a Logout operation state machine.
119#[derive(Debug)]
120pub enum LogoutStates {
121    Idle(Idle),
122    Wait(Wait),
123}
124
125type LogoutStepOut = Transition<LogoutStates, Result<()>>;
126
127impl<'ctx> StateMachine<LogoutCtx<'ctx>, LogoutStepOut> for Idle {
128    type StepResult<'a>
129        = Pin<Box<dyn std::future::Future<Output = LogoutStepOut> + Send + 'a>>
130    where
131        Self: 'a,
132        LogoutCtx<'ctx>: 'a;
133
134    fn step<'a>(&'a self, ctx: &'a mut LogoutCtx<'ctx>) -> Self::StepResult<'a> {
135        Box::pin(async move {
136            match ctx.send_logout().await {
137                Ok(st) => Transition::Next(LogoutStates::Wait(Wait), Ok(st)),
138                Err(e) => Transition::Done(Err(e)),
139            }
140        })
141    }
142}
143
144impl<'ctx> StateMachine<LogoutCtx<'ctx>, LogoutStepOut> for Wait {
145    type StepResult<'a>
146        = Pin<Box<dyn std::future::Future<Output = LogoutStepOut> + Send + 'a>>
147    where
148        Self: 'a,
149        LogoutCtx<'ctx>: 'a;
150
151    fn step<'a>(&'a self, ctx: &'a mut LogoutCtx<'ctx>) -> Self::StepResult<'a> {
152        Box::pin(async move {
153            match ctx.receive_logout_resp().await {
154                Ok(()) => Transition::Done(Ok(())),
155                Err(e) => Transition::Done(Err(e)),
156            }
157        })
158    }
159}
160
161impl<'ctx> StateMachineCtx<LogoutCtx<'ctx>, PduResponse<LogoutResponse>>
162    for LogoutCtx<'ctx>
163{
164    async fn execute(
165        &mut self,
166        _cancel: &CancellationToken,
167    ) -> Result<PduResponse<LogoutResponse>> {
168        debug!("Loop logout");
169        loop {
170            let state = self.state.take().context("state must be set LogoutCtx")?;
171            let trans = match state {
172                LogoutStates::Idle(s) => s.step(self).await,
173                LogoutStates::Wait(s) => s.step(self).await,
174            };
175
176            match trans {
177                Transition::Next(next_state, _r) => {
178                    self.state = Some(next_state);
179                },
180                Transition::Stay(Ok(_)) => {},
181                Transition::Stay(Err(e)) => return Err(e),
182                Transition::Done(r) => {
183                    r?;
184                    return self
185                        .last_response
186                        .take()
187                        .ok_or_else(|| anyhow!("no last response in ctx"));
188                },
189            }
190        }
191    }
192}