iscsi_client_rs/state_machine/
logout_states.rs1use 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#[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 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#[derive(Debug)]
112pub struct Idle;
113
114#[derive(Debug)]
116pub struct Wait;
117
118#[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}