Skip to main content

leodos_protocols/transport/cfdp/pdu/
header.rs

1use bon::bon;
2use zerocopy::FromBytes;
3use zerocopy::Immutable;
4use zerocopy::IntoBytes;
5use zerocopy::KnownLayout;
6use zerocopy::Unaligned;
7use zerocopy::network_endian::U16;
8
9use crate::transport::cfdp::CfdpError;
10use crate::utils::get_bits_u8;
11use crate::utils::set_bits_u8;
12
13/// The fixed-size (4-byte) portion at the start of every CFDP PDU header.
14/// This struct can be safely read from any PDU to determine the lengths
15/// of the variable-sized fields that follow.
16#[repr(C)]
17#[derive(Copy, Clone, Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
18pub struct PduHeaderFixedPart {
19    /// An 8-bit field containing Version (3), PDU Type (1), Direction (1),
20    /// Tx Mode (1), CRC Flag (1), and Large File Flag (1).
21    version_and_flags: u8,
22    /// The length of the PDU Data Field in octets.
23    data_field_len: U16,
24    /// An 8-bit field containing Segmentation Control (1), Entity ID Length (3),
25    /// Segment Metadata Flag (1), and Txn Sequence Number Length (3).
26    lengths_and_metadata_flag: u8,
27}
28
29/// Identifies whether the PDU is a File Data PDU or a File Directive PDU.
30#[repr(u8)]
31#[derive(Debug, PartialEq, Eq)]
32pub enum PduType {
33    /// The PDU contains file data.
34    FileData = 0,
35    /// The PDU contains a directive (e.g., Metadata, EOF, Finished).
36    FileDirective = 1,
37}
38
39/// Indicates the direction of the PDU relative to the file transfer direction.
40#[repr(u8)]
41#[derive(Debug, PartialEq, Eq, Copy, Clone)]
42pub enum Direction {
43    /// Toward the entity that receives the file.
44    TowardReceiver = 0,
45    /// Toward the entity that sends the file.
46    TowardSender = 1,
47}
48
49/// The reliability mode for the transaction.
50#[repr(u8)]
51#[derive(Debug, PartialEq, Eq, Copy, Clone)]
52pub enum TransmissionMode {
53    /// Reliable mode with acknowledgments.
54    Acknowledged = 0,
55    /// Unreliable, best-effort mode.
56    Unacknowledged = 1,
57}
58
59// In PduType's file or a common types file
60impl From<bool> for PduType {
61    fn from(val: bool) -> Self {
62        if !val {
63            PduType::FileData
64        } else {
65            PduType::FileDirective
66        }
67    }
68}
69
70// In Direction's file or a common types file
71impl From<bool> for Direction {
72    fn from(val: bool) -> Self {
73        if !val {
74            Direction::TowardReceiver
75        } else {
76            Direction::TowardSender
77        }
78    }
79}
80
81// In TransmissionMode's file or a common types file
82impl From<bool> for TransmissionMode {
83    fn from(val: bool) -> Self {
84        if !val {
85            TransmissionMode::Acknowledged
86        } else {
87            TransmissionMode::Unacknowledged
88        }
89    }
90}
91
92#[rustfmt::skip]
93/// Bit masks for extracting fields from the PDU header's packed bytes.
94mod bitmasks {
95    /// Mask for the 3-bit CFDP version number.
96    pub const VERSION_MASK: u8 =         0b_11100000;
97    /// Mask for the PDU type bit.
98    pub const PDU_TYPE_MASK: u8 =        0b_00010000;
99    /// Mask for the direction bit.
100    pub const DIRECTION_MASK: u8 =       0b_00001000;
101    /// Mask for the transmission mode bit.
102    pub const TX_MODE_MASK: u8 =         0b_00000100;
103    /// Mask for the CRC flag bit.
104    pub const CRC_FLAG_MASK: u8 =        0b_00000010;
105    /// Mask for the large file flag bit.
106    pub const LARGE_FILE_FLAG_MASK: u8 = 0b_00000001;
107
108    /// Mask for the segmentation control bit.
109    pub const SEG_CTRL_MASK: u8 =                  0b_10000000;
110    /// Mask for the 3-bit entity ID length (minus one).
111    pub const ENTITY_ID_LEN_MINUS_ONE_MASK: u8 =   0b_01110000;
112    /// Mask for the segment metadata flag bit.
113    pub const SEG_META_FLAG_MASK: u8 =             0b_00001000;
114    /// Mask for the 3-bit transaction sequence number length (minus one).
115    pub const TXN_SEQ_NUM_LEN_MINUS_ONE_MASK: u8 = 0b_00000111;
116}
117
118use bitmasks::*;
119
120#[bon]
121impl PduHeaderFixedPart {
122    /// Builds a new `PduHeaderFixedPart` with the specified field values.
123    #[builder]
124    pub fn new(
125        version: u8,
126        pdu_type: PduType,
127        direction: Direction,
128        tx_mode: TransmissionMode,
129        crc_flag: bool,
130        large_file_flag: bool,
131        data_field_len: u16,
132        seg_ctrl: bool,
133        seg_meta_flag: bool,
134    ) -> Result<Self, CfdpError> {
135        let mut header = PduHeaderFixedPart {
136            version_and_flags: 0,
137            data_field_len: U16::new(0),
138            lengths_and_metadata_flag: 0,
139        };
140
141        header.set_version(version);
142        header.set_pdu_type(pdu_type);
143        header.set_direction(direction);
144        header.set_tx_mode(tx_mode);
145        header.set_crc_flag(crc_flag);
146        header.set_large_file_flag(large_file_flag);
147        header.set_data_field_len(data_field_len);
148        header.set_segmentation_control(seg_ctrl);
149        header.set_segment_metadata_flag(seg_meta_flag);
150        // These fields are automatically set by the PDU builder once the lengths
151        // of the entity IDs and seq num are known
152        header.set_entity_id_len(1)?;
153        header.set_txn_seq_num_len(1)?;
154
155        Ok(header)
156    }
157}
158
159impl PduHeaderFixedPart {
160    // --- Accessors for fields within `version_and_flags` ---
161    /// Returns the 3-bit CFDP version number.
162    pub fn version(&self) -> u8 {
163        get_bits_u8(self.version_and_flags, VERSION_MASK)
164    }
165    /// Sets the 3-bit CFDP version number.
166    pub fn set_version(&mut self, version: u8) {
167        set_bits_u8(&mut self.version_and_flags, VERSION_MASK, version);
168    }
169
170    /// Returns the PDU type (File Data or File Directive).
171    pub fn pdu_type(&self) -> PduType {
172        PduType::from(get_bits_u8(self.version_and_flags, PDU_TYPE_MASK) == 1)
173    }
174    /// Sets the PDU type field.
175    pub fn set_pdu_type(&mut self, pdu_type: PduType) {
176        let val = match pdu_type {
177            PduType::FileData => 0,
178            PduType::FileDirective => 1,
179        };
180        set_bits_u8(&mut self.version_and_flags, PDU_TYPE_MASK, val);
181    }
182
183    /// Returns the direction of the PDU.
184    pub fn direction(&self) -> Direction {
185        Direction::from(get_bits_u8(self.version_and_flags, DIRECTION_MASK) == 1)
186    }
187    /// Sets the direction field.
188    pub fn set_direction(&mut self, direction: Direction) {
189        set_bits_u8(&mut self.version_and_flags, DIRECTION_MASK, direction as u8);
190    }
191
192    /// Returns the transmission mode (Acknowledged or Unacknowledged).
193    pub fn tx_mode(&self) -> TransmissionMode {
194        TransmissionMode::from(get_bits_u8(self.version_and_flags, TX_MODE_MASK) == 1)
195    }
196    /// Sets the transmission mode field.
197    pub fn set_tx_mode(&mut self, tx_mode: TransmissionMode) {
198        set_bits_u8(&mut self.version_and_flags, TX_MODE_MASK, tx_mode as u8);
199    }
200
201    /// Returns `true` if the PDU includes a CRC.
202    pub fn crc_flag(&self) -> bool {
203        get_bits_u8(self.version_and_flags, CRC_FLAG_MASK) == 1
204    }
205    /// Sets the CRC flag.
206    pub fn set_crc_flag(&mut self, crc_flag: bool) {
207        let val = if crc_flag { 1 } else { 0 };
208        set_bits_u8(&mut self.version_and_flags, CRC_FLAG_MASK, val);
209    }
210
211    /// Returns `true` if this is a large-file transaction (64-bit offsets).
212    pub fn large_file_flag(&self) -> bool {
213        get_bits_u8(self.version_and_flags, LARGE_FILE_FLAG_MASK) == 1
214    }
215    /// Sets the large file flag.
216    pub fn set_large_file_flag(&mut self, large_file_flag: bool) {
217        let val = if large_file_flag { 1 } else { 0 };
218        set_bits_u8(&mut self.version_and_flags, LARGE_FILE_FLAG_MASK, val);
219    }
220
221    // --- Accessor for `data_field_len` ---
222    /// Returns the length of the PDU data field in bytes.
223    pub fn data_field_len(&self) -> usize {
224        self.data_field_len.get() as usize
225    }
226    /// Sets the PDU data field length.
227    pub fn set_data_field_len(&mut self, len: u16) {
228        self.data_field_len.set(len);
229    }
230
231    // --- Accessors for fields within `lengths_and_metadata_flag` ---
232    /// Returns the segmentation control flag.
233    pub fn segmentation_control(&self) -> bool {
234        get_bits_u8(self.lengths_and_metadata_flag, SEG_CTRL_MASK) == 1
235    }
236    /// Sets the segmentation control flag.
237    pub fn set_segmentation_control(&mut self, seg_ctrl: bool) {
238        let val = if seg_ctrl { 1 } else { 0 };
239        set_bits_u8(&mut self.lengths_and_metadata_flag, SEG_CTRL_MASK, val);
240    }
241
242    /// Returns the length of entity IDs in bytes (1 to 8).
243    pub fn entity_id_len(&self) -> usize {
244        let val = get_bits_u8(self.lengths_and_metadata_flag, ENTITY_ID_LEN_MINUS_ONE_MASK);
245        val as usize + 1
246    }
247    /// Sets the entity ID length (must be 1 to 8).
248    pub fn set_entity_id_len(&mut self, len: usize) -> Result<(), CfdpError> {
249        if len == 0 || len > 8 {
250            return Err(CfdpError::Custom(
251                "Entity ID length must be between 1 and 8",
252            ));
253        }
254        set_bits_u8(
255            &mut self.lengths_and_metadata_flag,
256            ENTITY_ID_LEN_MINUS_ONE_MASK,
257            len as u8 - 1,
258        );
259        Ok(())
260    }
261
262    /// Returns the segment metadata flag.
263    pub fn segment_metadata_flag(&self) -> bool {
264        get_bits_u8(self.lengths_and_metadata_flag, SEG_META_FLAG_MASK) == 1
265    }
266    /// Sets the segment metadata flag.
267    pub fn set_segment_metadata_flag(&mut self, seg_meta_flag: bool) {
268        let val = if seg_meta_flag { 1 } else { 0 };
269        set_bits_u8(&mut self.lengths_and_metadata_flag, SEG_META_FLAG_MASK, val);
270    }
271
272    /// Returns the length of the transaction sequence number in bytes.
273    pub fn txn_seq_num_len(&self) -> usize {
274        let val = get_bits_u8(
275            self.lengths_and_metadata_flag,
276            TXN_SEQ_NUM_LEN_MINUS_ONE_MASK,
277        );
278        val as usize + 1
279    }
280    /// Sets the transaction sequence number length (must be 1 to 8).
281    pub fn set_txn_seq_num_len(&mut self, len: usize) -> Result<(), CfdpError> {
282        if len == 0 || len > 8 {
283            return Err(CfdpError::Custom(
284                "Transaction Sequence Number length must be between 1 and 8",
285            ));
286        }
287        set_bits_u8(
288            &mut self.lengths_and_metadata_flag,
289            TXN_SEQ_NUM_LEN_MINUS_ONE_MASK,
290            len as u8 - 1,
291        );
292        Ok(())
293    }
294
295    /// Returns the size of the fixed header portion in bytes.
296    pub fn fixed_header_len(&self) -> usize {
297        core::mem::size_of::<PduHeaderFixedPart>()
298    }
299
300    /// Calculates the length of the variable-sized portion of the PDU header
301    pub fn variable_header_len(&self) -> usize {
302        let entity_id_len = self.entity_id_len();
303        let txn_seq_num_len = self.txn_seq_num_len();
304        entity_id_len * 2 + txn_seq_num_len
305    }
306
307    /// Returns the total header length (fixed + variable parts).
308    pub fn total_header_len(&self) -> usize {
309        self.fixed_header_len() + self.variable_header_len()
310    }
311
312    /// Returns the total PDU length (header + data field).
313    pub fn total_pdu_len(&self) -> usize {
314        self.total_header_len() + self.data_field_len()
315    }
316}