Skip to main content

iscsi_client_rs/models/command/
response.rs

1//! This module defines the structures for iSCSI SCSI Command Response PDUs.
2//! It includes the `ScsiCommandResponse` header and related methods.
3
4// SPDX-License-Identifier: AGPL-3.0-or-later
5// Copyright (C) 2012-2025 Andrei Maltsev
6
7use anyhow::{Result, bail};
8use tracing::warn;
9use zerocopy::{
10    BigEndian, FromBytes as ZFromBytes, Immutable, IntoBytes, KnownLayout, U32,
11};
12
13use crate::{
14    client::pdu_connection::FromBytes,
15    models::{
16        command::zero_copy::{RawResponseCode, RawScsiCmdRespFlags, RawScsiStatus},
17        common::{BasicHeaderSegment, HEADER_LEN, InitiatorTaskTag, SendingData},
18        data_fromat::ZeroCopyType,
19        opcode::{BhsOpcode, Opcode, RawBhsOpcode},
20    },
21};
22
23/// Basic Header Segment for iSCSI SCSI Command Response PDU
24///
25/// Represents the 48-byte header structure for SCSI Command Response PDU as
26/// defined in RFC 7143. Contains response status, sequence numbers, residual
27/// counts, and other information returned by the target after executing a SCSI
28/// command.
29#[repr(C)]
30#[derive(Debug, PartialEq, ZFromBytes, IntoBytes, KnownLayout, Immutable)]
31pub struct ScsiCommandResponse {
32    /// PDU opcode (byte 0) - should be 0x21 for SCSI Response
33    pub opcode: RawBhsOpcode,
34    /// Response flags (byte 1) - Final bit and residual overflow/underflow
35    /// indicators
36    pub flags: RawScsiCmdRespFlags,
37    /// Response code (byte 2) - indicates if command completed successfully
38    pub response: RawResponseCode,
39    /// SCSI status (byte 3) - SCSI command execution status
40    pub status: RawScsiStatus,
41    /// Total Additional Header Segments length (byte 4)
42    pub total_ahs_length: u8,
43    /// Data Segment Length (bytes 5-7) - length of sense data or other response
44    /// data
45    pub data_segment_length: [u8; 3],
46    /// Reserved bytes (8-15)
47    reserved: [u8; 8],
48    /// Initiator Task Tag (bytes 16-19) - matches the original command ITT
49    pub initiator_task_tag: InitiatorTaskTag,
50    /// SNACK Tag (bytes 20-23) - used for data recovery
51    pub snack_tag: U32<BigEndian>,
52    /// Status Sequence Number (bytes 24-27) - sequence number for this response
53    pub stat_sn: U32<BigEndian>,
54    /// Expected Command Sequence Number (bytes 28-31) - next expected command
55    pub exp_cmd_sn: U32<BigEndian>,
56    /// Maximum Command Sequence Number (bytes 32-35) - command window limit
57    pub max_cmd_sn: U32<BigEndian>,
58    /// Expected Data Sequence Number (bytes 36-39) - for data recovery
59    pub exp_data_sn: U32<BigEndian>,
60    /// Bidirectional Read Residual Count (bytes 40-43) - unused read data
61    /// length
62    pub bidirectional_read_residual_count: U32<BigEndian>,
63    /// Residual Count (bytes 44-47) - difference between expected and actual
64    /// data transfer
65    pub residual_count: U32<BigEndian>,
66}
67
68impl ScsiCommandResponse {
69    /// Serializes the BHS into a byte buffer.
70    pub fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
71        buf.fill(0);
72        if buf.len() != HEADER_LEN {
73            bail!("buffer length must be {HEADER_LEN}, got {}", buf.len());
74        }
75        buf.copy_from_slice(self.as_bytes());
76        Ok(())
77    }
78
79    /// Deserializes the BHS from a byte buffer.
80    pub fn from_bhs_bytes(buf: &mut [u8]) -> Result<&mut Self> {
81        let hdr = <Self as zerocopy::FromBytes>::mut_from_bytes(buf).map_err(|e| {
82            anyhow::anyhow!("failed convert buffer ScsiCommandResponse: {e}")
83        })?;
84        if hdr.opcode.opcode_known() != Some(Opcode::ScsiCommandResp) {
85            anyhow::bail!(
86                "ScsiCommandResponse: invalid opcode 0x{:02x}",
87                hdr.opcode.opcode_raw()
88            );
89        }
90        Ok(hdr)
91    }
92
93    /// Checks if the residual count is valid.
94    #[inline]
95    pub fn residual_valid(&self) -> bool {
96        self.flags.u_big() || self.flags.o_big()
97    }
98
99    /// Returns the effective residual count.
100    #[inline]
101    pub fn residual_effective(&self) -> u32 {
102        if self.residual_valid() {
103            self.residual_count.get()
104        } else {
105            0
106        }
107    }
108
109    /// Checks if the bidirectional read residual count is valid.
110    #[inline]
111    pub fn bidi_read_residual_valid(&self) -> bool {
112        self.flags.u_small() || self.flags.o_small()
113    }
114
115    /// Returns the effective bidirectional read residual count.
116    #[inline]
117    pub fn bidi_read_residual_effective(&self) -> u32 {
118        if self.bidi_read_residual_valid() {
119            self.bidirectional_read_residual_count.get()
120        } else {
121            0
122        }
123    }
124}
125
126impl SendingData for ScsiCommandResponse {
127    fn get_final_bit(&self) -> bool {
128        self.flags.fin()
129    }
130
131    fn set_final_bit(&mut self) {
132        warn!("ScsiCommand Response must contain Final");
133        self.flags.set_fin(true);
134    }
135
136    fn get_continue_bit(&self) -> bool {
137        false
138    }
139
140    fn set_continue_bit(&mut self) {
141        warn!("ScsiCommand Response don`t support Continue");
142    }
143}
144
145impl FromBytes for ScsiCommandResponse {
146    fn from_bhs_bytes(bytes: &mut [u8]) -> Result<&mut Self> {
147        ScsiCommandResponse::from_bhs_bytes(bytes)
148    }
149}
150
151impl BasicHeaderSegment for ScsiCommandResponse {
152    #[inline]
153    fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()> {
154        self.to_bhs_bytes(buf)
155    }
156
157    #[inline]
158    fn get_opcode(&self) -> Result<BhsOpcode> {
159        BhsOpcode::try_from(self.opcode.raw())
160    }
161
162    #[inline]
163    fn get_initiator_task_tag(&self) -> u32 {
164        self.initiator_task_tag.get()
165    }
166
167    #[inline]
168    fn get_ahs_length_bytes(&self) -> usize {
169        (self.total_ahs_length as usize) * 4
170    }
171
172    #[inline]
173    fn set_ahs_length_bytes(&mut self, len: u8) {
174        self.total_ahs_length = len >> 2;
175    }
176
177    #[inline]
178    fn get_data_length_bytes(&self) -> usize {
179        u32::from_be_bytes([
180            0,
181            self.data_segment_length[0],
182            self.data_segment_length[1],
183            self.data_segment_length[2],
184        ]) as usize
185    }
186
187    #[inline]
188    fn set_data_length_bytes(&mut self, len: u32) {
189        let be = len.to_be_bytes();
190        self.data_segment_length = [be[1], be[2], be[3]];
191    }
192}
193
194impl ZeroCopyType for ScsiCommandResponse {}