iscsi_client_rs/state_machine/
nop_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 nop::{
27 request::{NopOutRequest, NopOutRequestBuilder},
28 response::NopInResponse,
29 },
30 },
31 state_machine::common::{StateMachine, StateMachineCtx, Transition},
32};
33
34#[derive(Debug)]
40pub struct NopCtx<'a> {
41 _lt: PhantomData<&'a ()>,
42
43 pub conn: Arc<ClientConnection>,
44 pub lun: u64,
45 pub itt: u32,
46 pub cmd_sn: u32,
47 pub exp_stat_sn: Arc<AtomicU32>,
48 pub ttt: u32,
49 pub buf: [u8; HEADER_LEN],
50
51 last_response: Option<PduResponse<NopInResponse>>,
52 state: Option<NopStates>,
53}
54
55impl<'a> NopCtx<'a> {
56 pub fn new(
58 conn: Arc<ClientConnection>,
59 lun: u64,
60 itt: Arc<AtomicU32>,
61 cmd_sn: Arc<AtomicU32>,
62 exp_stat_sn: Arc<AtomicU32>,
63 ttt: u32,
64 ) -> Self {
65 Self {
66 conn,
67 lun,
68 itt: itt.fetch_add(1, Ordering::SeqCst),
69 cmd_sn: cmd_sn.load(Ordering::SeqCst),
70 exp_stat_sn,
71 ttt,
72 buf: [0u8; HEADER_LEN],
73 state: Some(NopStates::Start(Start)),
74 last_response: None,
75 _lt: PhantomData,
76 }
77 }
78
79 pub fn set_default_state(&mut self) {
81 self.state = Some(NopStates::Start(Start));
82 }
83
84 pub fn validate_last_response_header(&mut self) -> Result<&NopInResponse> {
86 match &self.last_response {
87 Some(l) => match l.header_view() {
88 Ok(last) => {
89 self.exp_stat_sn
90 .store(last.stat_sn.get().wrapping_add(1), Ordering::SeqCst);
91 Ok(last)
92 },
93 Err(e) => Err(e),
94 },
95 None => Err(anyhow!("no last response in ctx")),
96 }
97 }
98
99 pub fn for_reply(
101 conn: Arc<ClientConnection>,
102 _itt: Arc<AtomicU32>,
103 cmd_sn: Arc<AtomicU32>,
104 exp_stat_sn: Arc<AtomicU32>,
105 response: PduResponse<NopInResponse>,
106 ) -> Result<Self> {
107 let header = response.header_view()?;
108 Ok(Self {
109 conn,
110 lun: 0,
111 itt: 0,
112 cmd_sn: cmd_sn.load(Ordering::SeqCst),
113 exp_stat_sn,
114 ttt: header.target_task_tag.get(),
115 buf: [0u8; HEADER_LEN],
116 last_response: Some(response),
117 state: Some(NopStates::Reply(Reply)),
118 _lt: PhantomData,
119 })
120 }
121
122 async fn send_nop_out(&mut self) -> Result<()> {
123 let exp_stat_sn = self.exp_stat_sn.load(Ordering::SeqCst);
124
125 let header = NopOutRequestBuilder::new()
126 .cmd_sn(self.cmd_sn)
127 .lun(self.lun)
128 .initiator_task_tag(self.itt)
129 .target_task_tag(self.ttt)
130 .exp_stat_sn(exp_stat_sn)
131 .immediate();
132
133 header.header.to_bhs_bytes(self.buf.as_mut_slice())?;
134
135 let builder = PduRequest::<NopOutRequest>::new_request(self.buf, &self.conn.cfg);
136 self.conn.send_request(self.itt, builder).await?;
137 Ok(())
138 }
139
140 async fn recieve_nop_in(&mut self) -> Result<()> {
141 match self.conn.read_response::<NopInResponse>(self.itt).await {
142 Ok(rsp) => {
143 self.last_response = Some(rsp);
144 Ok(())
145 },
146 Err(other) => bail!("got unexpected PDU: {:?}", other.to_string()),
147 }
148 }
149}
150
151#[derive(Debug)]
153pub struct Start;
154
155#[derive(Debug)]
157pub struct Wait;
158
159#[derive(Debug)]
161pub struct Reply;
162
163#[derive(Debug)]
165pub enum NopStates {
166 Start(Start),
167 Wait(Wait),
168 Reply(Reply),
169}
170
171type NopStepOut = Transition<NopStates, Result<()>>;
172
173impl<'ctx> StateMachine<NopCtx<'ctx>, NopStepOut> for Start {
174 type StepResult<'a>
175 = Pin<Box<dyn Future<Output = NopStepOut> + Send + 'a>>
176 where
177 Self: 'a,
178 NopCtx<'ctx>: 'a;
179
180 fn step<'a>(&'a self, ctx: &'a mut NopCtx<'ctx>) -> Self::StepResult<'a> {
181 Box::pin(async move {
182 match ctx.send_nop_out().await {
183 Ok(st) => Transition::Next(NopStates::Wait(Wait), Ok(st)),
184 Err(e) => Transition::Done(Err(e)),
185 }
186 })
187 }
188}
189
190impl<'ctx> StateMachine<NopCtx<'ctx>, NopStepOut> for Wait {
191 type StepResult<'a>
192 = Pin<Box<dyn Future<Output = NopStepOut> + Send + 'a>>
193 where
194 Self: 'a,
195 NopCtx<'ctx>: 'a;
196
197 fn step<'a>(&'a self, ctx: &'a mut NopCtx<'ctx>) -> Self::StepResult<'a> {
198 Box::pin(async move {
199 match ctx.recieve_nop_in().await {
200 Ok(()) => Transition::Done(Ok(())),
201 Err(e) => Transition::Done(Err(e)),
202 }
203 })
204 }
205}
206
207impl<'ctx> StateMachine<NopCtx<'ctx>, NopStepOut> for Reply {
208 type StepResult<'a>
209 = Pin<Box<dyn Future<Output = NopStepOut> + Send + 'a>>
210 where
211 Self: 'a,
212 NopCtx<'ctx>: 'a;
213
214 fn step<'a>(&'a self, ctx: &'a mut NopCtx<'ctx>) -> Self::StepResult<'a> {
215 Box::pin(async move {
216 if let Err(e) = ctx.validate_last_response_header() {
217 return Transition::Done(Err(e));
218 }
219
220 let hdr = NopOutRequestBuilder::new()
223 .immediate()
224 .lun(0)
225 .initiator_task_tag(u32::MAX)
226 .target_task_tag(ctx.ttt)
227 .cmd_sn(ctx.cmd_sn)
228 .exp_stat_sn(ctx.exp_stat_sn.load(Ordering::SeqCst))
229 .header;
230
231 if let Err(e) = hdr.to_bhs_bytes(ctx.buf.as_mut_slice()) {
232 return Transition::Done(Err(e));
233 }
234 let pdu = PduRequest::<NopOutRequest>::new_request(ctx.buf, &ctx.conn.cfg);
235
236 if let Err(e) = ctx.conn.send_request(u32::MAX, pdu).await {
238 return Transition::Done(Err(e));
239 }
240
241 Transition::Done(Ok(()))
242 })
243 }
244}
245
246impl<'s> StateMachineCtx<NopCtx<'s>, PduResponse<NopInResponse>> for NopCtx<'s> {
247 async fn execute(
248 &mut self,
249 _cancel: &CancellationToken,
250 ) -> Result<PduResponse<NopInResponse>> {
251 debug!("Loop Nop");
252 loop {
253 let state = self.state.take().context("state must be set NopCtx")?;
254 let trans = match state {
255 NopStates::Start(s) => s.step(self).await,
256 NopStates::Wait(s) => s.step(self).await,
257 NopStates::Reply(s) => s.step(self).await,
258 };
259
260 match trans {
261 Transition::Next(next, r) => {
262 r?;
263 self.state = Some(next);
264 },
265 Transition::Stay(Ok(_)) => {},
266 Transition::Stay(Err(e)) => return Err(e),
267 Transition::Done(r) => {
268 r?;
269 return self
270 .last_response
271 .take()
272 .ok_or_else(|| anyhow!("no last response in ctx"));
273 },
274 }
275 }
276 }
277}