iscsi_client_rs/state_machine/
write_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 cfg::enums::YesNo,
23 client::client::ClientConnection,
24 models::{
25 command::{
26 common::{ResponseCode, ScsiStatus, TaskAttribute},
27 request::{ScsiCommandRequest, ScsiCommandRequestBuilder},
28 response::ScsiCommandResponse,
29 },
30 common::{BasicHeaderSegment, Builder, HEADER_LEN, SendingData},
31 data::{
32 request::{ScsiDataOut, ScsiDataOutBuilder},
33 sense_data::SenseData,
34 },
35 data_fromat::{PduRequest, PduResponse},
36 ready_2_transfer::response::ReadyToTransfer,
37 },
38 state_machine::common::{StateMachine, StateMachineCtx, Transition},
39};
40
41#[derive(Debug)]
43pub struct WriteCtx<'a> {
44 _lt: PhantomData<&'a ()>,
45
46 pub conn: Arc<ClientConnection>,
48 pub lun: u64,
50 pub itt: u32,
52 pub cmd_sn: Arc<AtomicU32>,
54 pub exp_stat_sn: Arc<AtomicU32>,
56
57 pub cdb: [u8; 16],
59 pub payload: Vec<u8>,
61 pub buf: [u8; HEADER_LEN],
63
64 pub sent_bytes: usize,
66 pub total_bytes: usize,
68
69 pub last_response: Option<PduResponse<ScsiCommandResponse>>,
71 state: Option<WriteStates>,
72}
73
74#[allow(clippy::too_many_arguments)]
75impl<'a> WriteCtx<'a> {
76 pub fn new(
78 conn: Arc<ClientConnection>,
79 lun: u64,
80 itt: Arc<AtomicU32>,
81 cmd_sn: Arc<AtomicU32>,
82 exp_stat_sn: Arc<AtomicU32>,
83 cdb: [u8; 16],
84 payload: impl Into<Vec<u8>>,
85 ) -> Self {
86 Self {
87 conn,
88 lun,
89 itt: itt.fetch_add(1, Ordering::SeqCst),
90 cmd_sn,
91 exp_stat_sn,
92 cdb,
93 payload: payload.into(),
94 buf: [0u8; HEADER_LEN],
95 sent_bytes: 0,
96 total_bytes: 0,
97 last_response: None,
98 state: Some(WriteStates::Start(Start)),
99 _lt: PhantomData,
100 }
101 }
102
103 async fn send_write_command(&mut self) -> Result<()> {
105 let cmd_sn = self.cmd_sn.fetch_add(1, Ordering::SeqCst);
106 let esn = self.exp_stat_sn.load(Ordering::SeqCst);
107
108 self.total_bytes = self.payload.len();
109
110 let header = ScsiCommandRequestBuilder::new()
111 .lun(self.lun)
112 .initiator_task_tag(self.itt)
113 .cmd_sn(cmd_sn)
114 .exp_stat_sn(esn)
115 .expected_data_transfer_length(self.total_bytes as u32)
116 .scsi_descriptor_block(&self.cdb)
117 .write()
118 .task_attribute(TaskAttribute::Simple);
119
120 header.header.to_bhs_bytes(&mut self.buf)?;
121 let pdu = PduRequest::<ScsiCommandRequest>::new_request(self.buf, &self.conn.cfg);
122 self.conn.send_request(self.itt, pdu).await?;
123
124 Ok(())
125 }
126
127 async fn recv_r2t(&self, itt: u32) -> Result<PduResponse<ReadyToTransfer>> {
129 let r2t: PduResponse<ReadyToTransfer> = self.conn.read_response(itt).await?;
130 let header = r2t.header_view()?;
131 self.exp_stat_sn
132 .store(header.stat_sn.get().wrapping_add(1), Ordering::SeqCst);
133 Ok(r2t)
134 }
135
136 async fn send_data(
138 &mut self,
139 itt: u32,
140 ttt: u32,
141 offset: usize,
142 len: usize,
143 ) -> Result<usize> {
144 let mut next_data_sn = 0;
145 if len == 0 {
146 bail!("Refuse to send Data-Out with zero length");
147 }
148 let end = offset
149 .checked_add(len)
150 .ok_or_else(|| anyhow!("offset+len overflow"))?;
151 if end > self.payload.len() {
152 bail!(
153 "Data window [{offset}..{end}) exceeds payload {}",
154 self.payload.len()
155 );
156 }
157
158 let mrdsl = self.peer_mrdsl();
159 if mrdsl == 0 {
160 bail!("MRDSL is zero");
161 }
162 let to_send_total = len;
163
164 let mut sent = 0usize;
165 while sent < to_send_total {
166 let take = (to_send_total - sent).min(mrdsl);
167 let off = offset + sent;
168 let last_chunk_in_window = sent + take == to_send_total;
169
170 let header = ScsiDataOutBuilder::new()
171 .lun(self.lun)
172 .initiator_task_tag(itt)
173 .target_transfer_tag(ttt)
174 .exp_stat_sn(self.exp_stat_sn.load(Ordering::SeqCst))
175 .buffer_offset(off as u32)
176 .data_sn(next_data_sn);
177
178 header.header.to_bhs_bytes(self.buf.as_mut_slice())?;
179
180 let mut pdu =
181 PduRequest::<ScsiDataOut>::new_request(self.buf, &self.conn.cfg);
182
183 let header = pdu.header_view_mut()?;
184
185 if last_chunk_in_window {
186 header.set_final_bit();
187 } else {
188 header.set_continue_bit();
189 }
190
191 pdu.append_data(&self.payload[off..off + take]);
192
193 self.conn.send_request(itt, pdu).await?;
194
195 next_data_sn = next_data_sn.wrapping_add(1);
196 sent += take;
197 }
198
199 Ok(sent)
200 }
201
202 async fn wait_scsi_response(&mut self, itt: u32) -> Result<()> {
204 let rsp: PduResponse<ScsiCommandResponse> = self.conn.read_response(itt).await?;
205 let header = rsp.header_view()?;
206 self.exp_stat_sn
207 .store(header.stat_sn.get().wrapping_add(1), Ordering::SeqCst);
208
209 if header.response.decode()? != ResponseCode::CommandCompleted {
210 bail!("WRITE failed: response={:?}", header.response);
211 }
212 if header.status.decode()? != ScsiStatus::Good {
213 let sense = SenseData::parse(rsp.data()?)?;
214 bail!("WRITE failed: {:?}", sense);
215 }
216
217 self.last_response = Some(rsp);
218
219 Ok(())
220 }
221
222 #[inline]
224 fn peer_initial_r2t(&self) -> bool {
225 self.conn.cfg.login.write_flow.initial_r2t == YesNo::Yes
226 }
227
228 #[inline]
230 fn peer_immediate_data(&self) -> bool {
231 self.conn.cfg.login.write_flow.immediate_data == YesNo::Yes
232 }
233
234 #[inline]
236 fn peer_first_burst(&self) -> usize {
237 self.conn.cfg.login.flow.first_burst_length as usize
238 }
239
240 #[inline]
242 fn peer_max_burst(&self) -> usize {
243 self.conn.cfg.login.flow.max_burst_length as usize
244 }
245
246 #[inline]
248 fn peer_mrdsl(&self) -> usize {
249 self.conn.cfg.login.flow.max_recv_data_segment_length as usize
250 }
251
252 async fn send_write_cmd_with_immediate(&mut self, imm_len: usize) -> Result<()> {
254 let cmd_sn = self.cmd_sn.fetch_add(1, Ordering::SeqCst);
255 let esn = self.exp_stat_sn.load(Ordering::SeqCst);
256 self.total_bytes = self.payload.len();
257
258 let header = ScsiCommandRequestBuilder::new()
259 .lun(self.lun)
260 .initiator_task_tag(self.itt)
261 .cmd_sn(cmd_sn)
262 .exp_stat_sn(esn)
263 .expected_data_transfer_length(self.total_bytes as u32)
264 .scsi_descriptor_block(&self.cdb)
265 .write()
266 .task_attribute(TaskAttribute::Simple);
267
268 header.header.to_bhs_bytes(&mut self.buf)?;
269 let mut pdu =
270 PduRequest::<ScsiCommandRequest>::new_request(self.buf, &self.conn.cfg);
271
272 if imm_len > 0 {
273 pdu.append_data(&self.payload[0..imm_len]);
274 }
275
276 self.conn.send_request(self.itt, pdu).await?;
277 self.sent_bytes = imm_len;
278 Ok(())
279 }
280
281 async fn send_unsolicited_window(
283 &mut self,
284 offset: usize,
285 len: usize,
286 ) -> Result<usize> {
287 let mrdsl = self.peer_mrdsl();
288 if len == 0 {
289 return Ok(0);
290 }
291 if offset + len > self.payload.len() {
292 bail!(
293 "unsolicited window [{offset}..{}) exceeds payload {}",
294 offset + len,
295 self.payload.len()
296 );
297 }
298
299 let mut next_data_sn = 0u32;
300 let mut sent = 0usize;
301 while sent < len {
302 let take = (len - sent).min(mrdsl);
303 let off = offset + sent;
304 let last = sent + take == len;
305
306 let header = ScsiDataOutBuilder::new()
307 .lun(self.lun)
308 .initiator_task_tag(self.itt)
309 .target_transfer_tag(u32::MAX)
310 .exp_stat_sn(self.exp_stat_sn.load(Ordering::SeqCst))
311 .buffer_offset(off as u32)
312 .data_sn(next_data_sn);
313
314 header.header.to_bhs_bytes(self.buf.as_mut_slice())?;
315
316 let mut pdu =
317 PduRequest::<ScsiDataOut>::new_request(self.buf, &self.conn.cfg);
318 {
319 let h = pdu.header_view_mut()?;
320 h.set_data_length_bytes(take as u32);
321 if last {
322 h.set_final_bit();
323 } else {
324 h.set_continue_bit();
325 }
326 }
327 pdu.append_data(&self.payload[off..off + take]);
328 self.conn.send_request(self.itt, pdu).await?;
329
330 next_data_sn = next_data_sn.wrapping_add(1);
331 sent += take;
332 }
333 Ok(sent)
334 }
335}
336
337#[derive(Debug)]
339pub struct Start;
340
341#[derive(Debug)]
343pub struct WaitR2T;
344
345#[derive(Debug)]
347pub struct Finish;
348
349#[derive(Debug)]
351pub enum WriteStates {
352 Start(Start),
354 WaitR2T(WaitR2T),
356 Finish(Finish),
358}
359
360pub type WriteStep = Transition<WriteStates, Result<()>>;
361
362impl<'ctx> StateMachine<WriteCtx<'ctx>, WriteStep> for Start {
368 type StepResult<'a>
369 = Pin<Box<dyn Future<Output = WriteStep> + Send + 'a>>
370 where
371 Self: 'a,
372 WriteCtx<'ctx>: 'a;
373
374 fn step<'a>(&'a self, ctx: &'a mut WriteCtx<'ctx>) -> Self::StepResult<'a> {
375 Box::pin(async move {
376 ctx.total_bytes = ctx.payload.len();
377
378 let use_immediate = !ctx.peer_initial_r2t() && ctx.peer_immediate_data();
379 if use_immediate && ctx.total_bytes > 0 {
380 let fbl = ctx
381 .peer_first_burst()
382 .min(ctx.peer_max_burst())
383 .min(ctx.total_bytes);
384 let imm_len = fbl.min(ctx.peer_mrdsl());
385 if let Err(e) = ctx.send_write_cmd_with_immediate(imm_len).await {
386 return Transition::Done(Err(e));
387 }
388
389 if fbl > imm_len {
390 match ctx.send_unsolicited_window(imm_len, fbl - imm_len).await {
391 Ok(s) => {
392 ctx.sent_bytes += s;
393 },
394 Err(e) => return Transition::Done(Err(e)),
395 }
396 }
397
398 if ctx.sent_bytes >= ctx.total_bytes {
399 Transition::Next(WriteStates::Finish(Finish), Ok(()))
400 } else {
401 Transition::Next(WriteStates::WaitR2T(WaitR2T), Ok(()))
402 }
403 } else {
404 if let Err(e) = ctx.send_write_command().await {
405 return Transition::Done(Err(e));
406 }
407 if ctx.total_bytes == 0 {
408 Transition::Next(WriteStates::Finish(Finish), Ok(()))
409 } else {
410 Transition::Next(WriteStates::WaitR2T(WaitR2T), Ok(()))
411 }
412 }
413 })
414 }
415}
416
417impl<'ctx> StateMachine<WriteCtx<'ctx>, WriteStep> for WaitR2T {
422 type StepResult<'a>
423 = Pin<Box<dyn Future<Output = WriteStep> + Send + 'a>>
424 where
425 Self: 'a,
426 WriteCtx<'ctx>: 'a;
427
428 fn step<'a>(&'a self, ctx: &'a mut WriteCtx<'ctx>) -> Self::StepResult<'a> {
429 Box::pin(async move {
430 let itt = ctx.itt;
431 let r2t = match ctx.recv_r2t(itt).await {
432 Ok(v) => v,
433 Err(e) => return Transition::Done(Err(e)),
434 };
435 let h = match r2t.header_view() {
436 Ok(h) => h,
437 Err(e) => {
438 return Transition::Done(Err(anyhow!(
439 "failed read ReadyToTransfer: {e}"
440 )));
441 },
442 };
443
444 let ttt = h.target_transfer_tag.get();
445 let offset = h.buffer_offset.get() as usize;
446 let want = h.desired_data_transfer_length.get() as usize;
447
448 if offset >= ctx.payload.len() {
449 return Transition::Done(Err(anyhow!(
450 "R2T buffer_offset {} beyond payload {}",
451 offset,
452 ctx.payload.len()
453 )));
454 }
455 let remaining = ctx.payload.len() - offset;
456 let len = want.min(remaining);
457 if len == 0 {
458 return Transition::Done(Err(anyhow!(
459 "R2T window has zero DesiredDataTransferLength (offset={offset}, \
460 want={want})"
461 )));
462 }
463
464 let sent = match ctx.send_data(itt, ttt, offset, len).await {
465 Ok(x) => x,
466 Err(e) => return Transition::Done(Err(e)),
467 };
468 ctx.sent_bytes = ctx.sent_bytes.saturating_add(sent);
469
470 if ctx.sent_bytes >= ctx.total_bytes {
471 Transition::Next(WriteStates::Finish(Finish), Ok(()))
472 } else {
473 Transition::Stay(Ok(()))
474 }
475 })
476 }
477}
478
479impl<'ctx> StateMachine<WriteCtx<'ctx>, WriteStep> for Finish {
483 type StepResult<'a>
484 = Pin<Box<dyn Future<Output = WriteStep> + Send + 'a>>
485 where
486 Self: 'a,
487 WriteCtx<'ctx>: 'a;
488
489 fn step<'a>(&'a self, ctx: &'a mut WriteCtx<'ctx>) -> Self::StepResult<'a> {
490 Box::pin(async move {
491 let itt = ctx.itt;
492 match ctx.wait_scsi_response(itt).await {
493 Ok(()) => Transition::Done(Ok(())),
494 Err(e) => Transition::Done(Err(e)),
495 }
496 })
497 }
498}
499
500#[derive(Debug)]
502pub struct WriteOutcome {
503 pub last_response: PduResponse<ScsiCommandResponse>,
505 pub sent_bytes: usize,
507 pub total_bytes: usize,
509}
510
511impl<'ctx> StateMachineCtx<WriteCtx<'ctx>, WriteOutcome> for WriteCtx<'ctx> {
512 async fn execute(&mut self, _cancel: &CancellationToken) -> Result<WriteOutcome> {
513 debug!("Loop WRITE");
514
515 loop {
516 let state = self.state.take().context("state must be set WriteCtx")?;
517 let tr = match &state {
518 WriteStates::Start(s) => s.step(self).await,
519 WriteStates::WaitR2T(s) => s.step(self).await,
520 WriteStates::Finish(s) => s.step(self).await,
521 };
522
523 match tr {
524 Transition::Next(next, r) => {
525 r?;
526 self.state = Some(next);
527 },
528 Transition::Stay(Ok(_)) => {
529 self.state = Some(match state {
530 WriteStates::Start(_) => WriteStates::Start(Start),
531 WriteStates::WaitR2T(_) => WriteStates::WaitR2T(WaitR2T),
532 WriteStates::Finish(_) => WriteStates::Finish(Finish),
533 });
534 },
535 Transition::Stay(Err(e)) => return Err(e),
536 Transition::Done(r) => {
537 r?;
538 return Ok(WriteOutcome {
539 last_response: self
540 .last_response
541 .take()
542 .ok_or_else(|| anyhow!("no last response in ctx"))?,
543 sent_bytes: self.sent_bytes,
544 total_bytes: self.total_bytes,
545 });
546 },
547 }
548 }
549 }
550}