Skip to main content

iscsi_client_rs/models/ready_2_transfer/
response.rs

1//! This module defines the structures for iSCSI Ready To Transfer (R2T) PDUs.
2//! It includes the `ReadyToTransfer` header and related methods for handling
3//! data transfer.
4
5// SPDX-License-Identifier: AGPL-3.0-or-later
6// Copyright (C) 2012-2025 Andrei Maltsev
7
8use anyhow::{Result, bail};
9use tracing::warn;
10use zerocopy::{
11    BigEndian, FromBytes as ZFromBytes, Immutable, IntoBytes, KnownLayout, U32,
12};
13
14use crate::{
15    client::pdu_connection::FromBytes,
16    models::{
17        common::{
18            BasicHeaderSegment, HEADER_LEN, InitiatorTaskTag, LogicalUnitNumber,
19            SendingData,
20        },
21        data_fromat::ZeroCopyType,
22        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
23    },
24};
25
26/// Represents the Basic Header Segment (BHS) for a Ready To Transfer (R2T) PDU.
27#[repr(C)]
28#[derive(Debug, Default, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
29pub struct ReadyToTransfer {
30    pub opcode: RawBhsOpcode,                         // 0
31    pub reserved1: [u8; 3],                           // 1..4
32    pub total_ahs_length: u8,                         // 4
33    pub data_segment_length: [u8; 3],                 // 5..8  (должно быть 0)
34    pub lun: LogicalUnitNumber,                       // 8..16
35    pub initiator_task_tag: InitiatorTaskTag,         // 16..20
36    pub target_transfer_tag: U32<BigEndian>,          // 20..24
37    pub stat_sn: U32<BigEndian>,                      // 24..28
38    pub exp_cmd_sn: U32<BigEndian>,                   // 28..32
39    pub max_cmd_sn: U32<BigEndian>,                   // 32..36
40    pub r2t_sn: U32<BigEndian>,                       // 36..40
41    pub buffer_offset: U32<BigEndian>,                // 40..44
42    pub desired_data_transfer_length: U32<BigEndian>, // 44..48
43}
44
45impl ReadyToTransfer {
46    /// Serializes the BHS into a byte buffer.
47    pub fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
48        if buf.len() != HEADER_LEN {
49            bail!("buffer length must be {HEADER_LEN}, got {}", buf.len());
50        }
51        buf.copy_from_slice(self.as_bytes());
52        Ok(())
53    }
54
55    /// Deserializes the BHS from a byte buffer.
56    pub fn from_bhs_bytes(buf: &mut [u8]) -> Result<&mut Self> {
57        let hdr = <Self as zerocopy::FromBytes>::mut_from_bytes(buf)
58            .map_err(|e| anyhow::anyhow!("failed convert buffer ReadyToTransfer: {e}"))?;
59        if hdr.opcode.opcode_known() != Some(Opcode::ReadyToTransfer) {
60            anyhow::bail!(
61                "ReadyToTransfer: invalid opcode 0x{:02x}",
62                hdr.opcode.opcode_raw()
63            );
64        }
65        Ok(hdr)
66    }
67}
68
69impl SendingData for ReadyToTransfer {
70    fn get_final_bit(&self) -> bool {
71        true
72    }
73
74    fn set_final_bit(&mut self) {
75        warn!("R2T is header-only; Final flag in opcode byte is not used");
76    }
77
78    fn get_continue_bit(&self) -> bool {
79        false
80    }
81
82    fn set_continue_bit(&mut self) {
83        warn!("R2T cannot be marked as Continue");
84    }
85}
86
87impl FromBytes for ReadyToTransfer {
88    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
89        ReadyToTransfer::from_bhs_bytes(bytes)
90    }
91}
92
93impl BasicHeaderSegment for ReadyToTransfer {
94    #[inline]
95    fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
96        self.to_bhs_bytes(buf)
97    }
98
99    #[inline]
100    fn get_opcode(&self) -> Result<BhsOpcode> {
101        BhsOpcode::try_from(self.opcode.raw())
102    }
103
104    #[inline]
105    fn get_initiator_task_tag(&self) -> u32 {
106        self.initiator_task_tag.get()
107    }
108
109    #[inline]
110    fn get_ahs_length_bytes(&self) -> usize {
111        (self.total_ahs_length as usize) * 4
112    }
113
114    #[inline]
115    fn set_ahs_length_bytes(&mut self, len: u8) {
116        self.total_ahs_length = len >> 2;
117    }
118
119    #[inline]
120    fn get_data_length_bytes(&self) -> usize {
121        u32::from_be_bytes([
122            0,
123            self.data_segment_length[0],
124            self.data_segment_length[1],
125            self.data_segment_length[2],
126        ]) as usize
127    }
128
129    #[inline]
130    fn set_data_length_bytes(&mut self, len: u32) {
131        let be = len.to_be_bytes();
132        self.data_segment_length = [be[1], be[2], be[3]];
133    }
134}
135
136impl ZeroCopyType for ReadyToTransfer {}