iscsi_client_rs/models/common.rs
1//! This module defines common traits and constants for iSCSI Protocol Data
2//! Units (PDUs). It includes traits for handling data transmission, managing
3//! the Basic Header Segment (BHS), and building PDUs with data segments.
4
5// SPDX-License-Identifier: AGPL-3.0-or-later
6// Copyright (C) 2012-2025 Andrei Maltsev
7
8use anyhow::Result;
9use enum_dispatch::enum_dispatch;
10use zerocopy::{BigEndian, U32, U64};
11
12use crate::models::opcode::BhsOpcode;
13
14/// The fixed length of the Basic Header Segment (BHS) in bytes.
15pub const HEADER_LEN: usize = 48;
16
17pub type InitiatorTaskTag = U32<BigEndian>;
18pub type TargetTaskTag = U32<BigEndian>;
19pub type LogicalUnitNumber = U64<BigEndian>;
20
21/// Common helper-trait for PDUs that may be fragmented into several
22/// wire-frames (RFC 7143 ― “F”/“C” bits).
23///
24/// *Most* iSCSI PDUs are transferred in a single frame, but a few
25/// (Text, Login, SCSI Command/Data, …) allow the sender to split the
26/// **Data-Segment** into a sequence of chunks whose order is determined
27/// by the transport; the target relies only on the *Continue* and *Final*
28/// flags found in byte 1 of every Basic-Header-Segment.
29///
30/// Trait for PDU types that support data transmission control flags
31///
32/// Implementing `SendingData` lets generic helpers (e.g. the
33/// `PDUWithData` builder or the `Connection` read-loop) toggle and query
34/// those flags **without** knowing the concrete PDU type.
35/// Provides methods to manage Final (F) and Continue (C) bits used in multi-PDU
36/// sequences.
37#[enum_dispatch]
38pub trait SendingData: Sized {
39 /// Return the current state of the **Final (F)** bit.
40 fn get_final_bit(&self) -> bool;
41
42 /// Force **F = 1** (and, if your PDU has it, clear **C**).
43 fn set_final_bit(&mut self);
44
45 /// Return the current state of the **Continue (C)** bit.
46 fn get_continue_bit(&self) -> bool;
47
48 /// Force **C = 1** (and clear **F**).
49 fn set_continue_bit(&mut self);
50}
51
52/// Common functionality for any iSCSI PDU Basic Header Segment (BHS)
53///
54/// A BHS is always 48 bytes long according to RFC 7143; higher‐level PDUs then
55/// may carry additional AHS sections, a variable-length DataSegment,
56/// and optional digests. This trait encapsulates:
57/// 1. extracting lengths out of the BHS,
58/// 2. appending to the DataSegment,
59/// 3. and finally building the full wire format.
60///
61/// All iSCSI PDU types must implement this trait to provide basic header
62/// operations.
63#[enum_dispatch]
64pub trait BasicHeaderSegment: Sized + SendingData {
65 fn to_bhs_bytes(&self, buf: &mut [u8]) -> Result<()>;
66
67 /// first u8 of BHS
68 fn get_opcode(&self) -> Result<BhsOpcode>;
69
70 /// Expose Initiator Task Tag of this PDU
71 fn get_initiator_task_tag(&self) -> u32;
72
73 /// Number of extra AHS bytes (always a multiple of 4).
74 fn get_ahs_length_bytes(&self) -> usize;
75
76 /// Number of extra AHS bytes (always a multiple of 4).
77 fn set_ahs_length_bytes(&mut self, len: u8);
78
79 /// Get number of actual payload bytes in the DataSegment.
80 fn get_data_length_bytes(&self) -> usize;
81
82 /// Set number of actual payload bytes in the DataSegment.
83 fn set_data_length_bytes(&mut self, len: u32);
84
85 /// Number of actual payload bytes in the DataSegment.
86 #[inline]
87 fn total_length_bytes(&self) -> usize {
88 let padding_ahs = (4 - (self.get_ahs_length_bytes() % 4)) % 4;
89 let padding_data_segment = (4 - (self.get_data_length_bytes() % 4)) % 4;
90
91 HEADER_LEN
92 + self.get_ahs_length_bytes()
93 + padding_ahs
94 + self.get_data_length_bytes()
95 + padding_data_segment
96 }
97
98 #[inline]
99 fn get_header_diggest(&self, enable_header_digest: bool) -> usize {
100 4 * enable_header_digest as usize
101 }
102
103 #[inline]
104 fn get_data_diggest(&self, enable_data_digest: bool) -> usize {
105 4 * (self.get_data_length_bytes() > 0) as usize * enable_data_digest as usize
106 }
107}
108
109// Forward SendingData to &mut T
110impl<T: SendingData> SendingData for &mut T {
111 #[inline]
112 fn get_final_bit(&self) -> bool {
113 (**self).get_final_bit()
114 }
115
116 #[inline]
117 fn set_final_bit(&mut self) {
118 (**self).set_final_bit()
119 }
120
121 #[inline]
122 fn get_continue_bit(&self) -> bool {
123 (**self).get_continue_bit()
124 }
125
126 #[inline]
127 fn set_continue_bit(&mut self) {
128 (**self).set_continue_bit()
129 }
130}
131
132// Forward BasicHeaderSegment to &mut T
133impl<T: BasicHeaderSegment> BasicHeaderSegment for &mut T {
134 #[inline]
135 fn to_bhs_bytes(&self, buf: &mut [u8]) -> anyhow::Result<()> {
136 (**self).to_bhs_bytes(buf)
137 }
138
139 #[inline]
140 fn get_opcode(&self) -> anyhow::Result<BhsOpcode> {
141 (**self).get_opcode()
142 }
143
144 #[inline]
145 fn get_initiator_task_tag(&self) -> u32 {
146 (**self).get_initiator_task_tag()
147 }
148
149 #[inline]
150 fn get_ahs_length_bytes(&self) -> usize {
151 (**self).get_ahs_length_bytes()
152 }
153
154 #[inline]
155 fn set_ahs_length_bytes(&mut self, len: u8) {
156 (**self).set_ahs_length_bytes(len)
157 }
158
159 #[inline]
160 fn get_data_length_bytes(&self) -> usize {
161 (**self).get_data_length_bytes()
162 }
163
164 #[inline]
165 fn set_data_length_bytes(&mut self, len: u32) {
166 (**self).set_data_length_bytes(len)
167 }
168
169 #[inline]
170 fn total_length_bytes(&self) -> usize {
171 (**self).total_length_bytes()
172 }
173
174 #[inline]
175 fn get_header_diggest(&self, en: bool) -> usize {
176 (**self).get_header_diggest(en)
177 }
178
179 #[inline]
180 fn get_data_diggest(&self, en: bool) -> usize {
181 (**self).get_data_diggest(en)
182 }
183}
184
185/// A helper-trait for **builder objects** that construct a complete
186/// iSCSI PDU: a 48-byte Basic-Header-Segment (BHS) plus the optional
187/// **Data-Segment** and digests.
188///
189/// The concrete type that implements `Builder` usually owns a
190/// *(header + payload)* pair and offers additional, PDU-specific setter
191/// methods (e.g. `.lun( … )`, `.read()`, …).
192///
193/// When your application is ready to send the packet you call
194/// [`Builder::build`]; the helper splits the payload into chunks that
195/// respect *MaxRecvDataSegmentLength* and automatically toggles the
196/// **F/C** bits on the header copies.
197/// Trait for building iSCSI PDUs with progressive data assembly.
198///
199/// Provides functionality to construct PDU frames by appending data segments
200/// and managing digest calculations. Implementations handle the wire format
201/// serialization with proper padding and digest generation.
202pub trait Builder: Sized {
203 /// The concrete buffer type used to return the encoded header.
204 type Header: AsRef<[u8]>;
205 type Body: AsRef<[u8]>;
206
207 /// Append raw bytes to the **Data-Segment** and update the
208 /// `DataSegmentLength` field inside the owned header.
209 fn append_data(&mut self, more: &[u8]);
210
211 /// Finish the builder and produce one or more ready-to-send
212 /// `(header_bytes, data_bytes)` frames.
213 ///
214 /// The `cfg` parameter is typically used to honour negotiated session
215 /// limits such as *MaxRecvDataSegmentLength*.
216 fn build(
217 &mut self,
218 max_recv_data_segment_length: usize,
219 ) -> Result<(Self::Header, Self::Body)>;
220}