Skip to main content

iscsi_client_rs/models/text/
response.rs

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