iscsi_client_rs/state_machine/
tur_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 control_block::test_unit_ready::build_test_unit_ready,
24 models::{
25 command::{
26 common::{ScsiStatus, TaskAttribute},
27 request::{ScsiCommandRequest, ScsiCommandRequestBuilder},
28 response::ScsiCommandResponse,
29 },
30 common::HEADER_LEN,
31 data_fromat::{PduRequest, PduResponse},
32 },
33 state_machine::common::{StateMachine, StateMachineCtx, Transition},
34};
35
36#[derive(Debug)]
39pub struct TurCtx<'a> {
40 _lt: PhantomData<&'a ()>,
41
42 pub conn: Arc<ClientConnection>,
44 pub itt: u32,
46 pub cmd_sn: Arc<AtomicU32>,
48 pub exp_stat_sn: Arc<AtomicU32>,
50 pub lun: u64,
52 pub buf: [u8; HEADER_LEN],
54 pub cbd: [u8; 16],
56
57 pub last_response: Option<PduResponse<ScsiCommandResponse>>,
59 state: Option<TurStates>,
60}
61
62impl<'a> TurCtx<'a> {
63 pub fn new(
65 conn: Arc<ClientConnection>,
66 itt: Arc<AtomicU32>,
67 cmd_sn: Arc<AtomicU32>,
68 exp_stat_sn: Arc<AtomicU32>,
69 lun: u64,
70 ) -> Self {
71 Self {
72 conn,
73 itt: itt.fetch_add(1, Ordering::SeqCst),
74 cmd_sn,
75 exp_stat_sn,
76 lun,
77 buf: [0u8; HEADER_LEN],
78 cbd: [0u8; 16],
79 last_response: None,
80 state: Some(TurStates::Idle(Idle)),
81 _lt: PhantomData,
82 }
83 }
84
85 async fn send_tur(&mut self) -> Result<()> {
86 build_test_unit_ready(&mut self.cbd, 0);
87
88 let cmd_sn = self.cmd_sn.fetch_add(1, Ordering::SeqCst);
89 let esn = self.exp_stat_sn.load(Ordering::SeqCst);
90
91 let header = ScsiCommandRequestBuilder::new()
92 .initiator_task_tag(self.itt)
93 .lun(self.lun)
94 .cmd_sn(cmd_sn)
95 .exp_stat_sn(esn)
96 .task_attribute(TaskAttribute::Simple)
97 .expected_data_transfer_length(0)
98 .scsi_descriptor_block(&self.cbd);
99
100 header.header.to_bhs_bytes(&mut self.buf)?;
101 let pdu = PduRequest::<ScsiCommandRequest>::new_request(self.buf, &self.conn.cfg);
102
103 self.conn.send_request(self.itt, pdu).await?;
104 Ok(())
105 }
106
107 async fn recv_tur_resp(&mut self) -> Result<()> {
108 let rsp = self
109 .conn
110 .read_response::<ScsiCommandResponse>(self.itt)
111 .await?;
112 self.last_response = Some(rsp);
113
114 let lr = self.last_response.as_ref().expect("saved above");
115 let hv = lr.header_view()?;
116
117 self.exp_stat_sn
118 .store(hv.stat_sn.get().wrapping_add(1), Ordering::SeqCst);
119
120 let scsi_status = hv.status.decode()?;
121 if scsi_status != ScsiStatus::Good {
122 let data = lr.data()?;
123 if !data.is_empty() {
124 bail!(
125 "TEST UNIT READY failed: status={:?}, sense ({} bytes)={:02X?}",
126 scsi_status,
127 data.len(),
128 data
129 );
130 }
131 bail!("TEST UNIT READY failed: status={:?}", scsi_status);
132 }
133 Ok(())
134 }
135}
136
137#[derive(Debug)]
139pub struct Idle;
140
141#[derive(Debug)]
143pub struct Wait;
144
145#[derive(Debug)]
148pub enum TurStates {
149 Idle(Idle),
150 Wait(Wait),
151}
152
153type TurStepOut = Transition<TurStates, Result<()>>;
154
155impl<'ctx> StateMachine<TurCtx<'ctx>, TurStepOut> for Idle {
156 type StepResult<'a>
157 = Pin<Box<dyn Future<Output = TurStepOut> + Send + 'a>>
158 where
159 Self: 'a,
160 TurCtx<'ctx>: 'a;
161
162 fn step<'a>(&'a self, ctx: &'a mut TurCtx<'ctx>) -> Self::StepResult<'a> {
163 Box::pin(async move {
164 match ctx.send_tur().await {
165 Ok(()) => Transition::Next(TurStates::Wait(Wait), Ok(())),
166 Err(e) => Transition::Done(Err(e)),
167 }
168 })
169 }
170}
171
172impl<'ctx> StateMachine<TurCtx<'ctx>, TurStepOut> for Wait {
173 type StepResult<'a>
174 = Pin<Box<dyn std::future::Future<Output = TurStepOut> + Send + 'a>>
175 where
176 Self: 'a,
177 TurCtx<'ctx>: 'a;
178
179 fn step<'a>(&'a self, ctx: &'a mut TurCtx<'ctx>) -> Self::StepResult<'a> {
180 Box::pin(async move {
181 match ctx.recv_tur_resp().await {
182 Ok(()) => Transition::Done(Ok(())),
183 Err(e) => Transition::Done(Err(e)),
184 }
185 })
186 }
187}
188
189impl<'ctx> StateMachineCtx<TurCtx<'ctx>, PduResponse<ScsiCommandResponse>>
190 for TurCtx<'ctx>
191{
192 async fn execute(
193 &mut self,
194 _cancel: &CancellationToken,
195 ) -> Result<PduResponse<ScsiCommandResponse>> {
196 debug!("Loop TUR");
197
198 loop {
199 let state = self.state.take().context("state must be set TurCtx")?;
200 let tr = match state {
201 TurStates::Idle(s) => s.step(self).await,
202 TurStates::Wait(s) => s.step(self).await,
203 };
204
205 match tr {
206 Transition::Next(next, r) => {
207 r?;
208 self.state = Some(next);
209 },
210 Transition::Stay(Ok(_)) => {},
211 Transition::Stay(Err(e)) => return Err(e),
212 Transition::Done(r) => {
213 r?;
214 return self
215 .last_response
216 .take()
217 .ok_or_else(|| anyhow!("no last response in ctx"));
218 },
219 }
220 }
221 }
222}