Skip to main content

leodos_protocols/transport/cfdp/pdu/file_data/
with_meta.rs

1use crate::transport::cfdp::CfdpError;
2use crate::transport::cfdp::pdu::EntityId;
3use crate::transport::cfdp::pdu::Pdu;
4use crate::transport::cfdp::pdu::PduHeaderFixedPart;
5use crate::transport::cfdp::pdu::TransactionSeqNum;
6use crate::transport::cfdp::pdu::header::Direction;
7use crate::transport::cfdp::pdu::header::PduType;
8use crate::transport::cfdp::pdu::header::TransmissionMode;
9use crate::utils::get_bits_u8;
10use crate::utils::set_bits_u8;
11
12use bon::bon;
13use zerocopy::FromBytes;
14use zerocopy::Immutable;
15use zerocopy::IntoBytes;
16use zerocopy::KnownLayout;
17use zerocopy::Unaligned;
18use zerocopy::network_endian::U32;
19use zerocopy::network_endian::U64;
20
21/// A zero-copy representation of the data field of a File Data PDU that
22/// **does** have segment metadata.
23///
24/// It consists of the administrative octet and a `rest` slice containing the
25/// `segment_metadata`, the FSS `Offset`, and the `file_data`.
26/// ```text
27/// +------------------------------------+----------------+--------------------------------------+
28/// | Field Name                         | Size           | Notes                                |
29/// +------------------------------------+----------------+--------------------------------------+
30/// | -- Start of PDU Data Field ------- | -------------- | ------------------------------------ |
31/// | Admin Octet                        |                |                                      |
32/// |   - Record continuation state      | 2 bits         | Indicates alignment with records.    |
33/// |   - Segment metadata length        | 6 bits         | Length of the metadata field (0-63). |
34/// | Segment metadata                   | 0-63 octets    | Application-specific metadata.       |
35/// |                                    |                | Present only if length > 0.          |
36/// | Offset                             | 32 or 64 bits  | FSS field. Byte offset into the file.|
37/// |                                    | (FSS)          | Size depends on PDU Header's         |
38/// |                                    |                | `Large File Flag`.                   |
39/// | File data                          | Variable       | A chunk of the file's content.       |
40/// +------------------------------------+----------------+--------------------------------------+
41/// ```
42#[repr(C)]
43#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
44pub struct FileDataPduWithMeta {
45    /// The administrative octet containing `Record continuation state` and `Segment metadata length`.
46    admin_octet: u8,
47    /// Contains the metadata, FSS `Offset`, and file data.
48    rest: [u8],
49}
50
51#[rustfmt::skip]
52/// Bit masks for the File Data PDU's administrative octet.
53mod bitmasks {
54    /// Mask for the 2-bit record continuation state.
55    pub const FILE_DATA_REC_CONT_STATE_MASK: u8 = 0b_11000000;
56    /// Mask for the 6-bit segment metadata length.
57    pub const FILE_DATA_SEG_META_LEN_MASK: u8 =   0b_00111111;
58}
59
60use bitmasks::*;
61
62impl FileDataPduWithMeta {
63    /// Returns the 2-bit record continuation state.
64    pub fn record_continuation_state(&self) -> u8 {
65        get_bits_u8(self.admin_octet, FILE_DATA_REC_CONT_STATE_MASK)
66    }
67    /// Sets the 2-bit record continuation state.
68    pub fn set_record_continuation_state(&mut self, state: u8) -> Result<(), CfdpError> {
69        if state > 0b11 {
70            return Err(CfdpError::DataTooLarge {
71                field: "record_continuation_state",
72                max: 3,
73            });
74        }
75        set_bits_u8(&mut self.admin_octet, FILE_DATA_REC_CONT_STATE_MASK, state);
76        Ok(())
77    }
78
79    /// Returns the length of the segment metadata in bytes (0 to 63).
80    pub fn metadata_len(&self) -> usize {
81        get_bits_u8(self.admin_octet, FILE_DATA_SEG_META_LEN_MASK) as usize
82    }
83    /// Sets the segment metadata length field.
84    pub fn set_metadata_len(&mut self, len: u8) -> Result<(), CfdpError> {
85        if len > 63 {
86            return Err(CfdpError::DataTooLarge {
87                field: "segment_metadata_length",
88                max: 63,
89            });
90        }
91        set_bits_u8(&mut self.admin_octet, FILE_DATA_SEG_META_LEN_MASK, len);
92        Ok(())
93    }
94
95    /// Returns the segment metadata as a slice.
96    pub fn segment_metadata(&self) -> Result<&[u8], CfdpError> {
97        self.rest
98            .get(0..self.metadata_len())
99            .ok_or_else(|| CfdpError::Custom("Invalid segment metadata slice"))
100    }
101    /// Writes segment metadata bytes into the PDU.
102    pub fn set_segment_metadata(&mut self, metadata: &[u8]) -> Result<(), CfdpError> {
103        let len = metadata.len();
104        if len > 63 {
105            return Err(CfdpError::DataTooLarge {
106                field: "segment_metadata",
107                max: 63,
108            });
109        }
110        let dest_slice = self
111            .rest
112            .get_mut(0..len)
113            .ok_or_else(|| CfdpError::Custom("Invalid segment metadata slice"))?;
114        dest_slice.copy_from_slice(metadata);
115        Ok(())
116    }
117
118    /// Parses the FSS `Offset` from the `rest` slice (after the metadata).
119    pub fn offset(&self, large_file_flag: bool) -> Result<u64, CfdpError> {
120        let offset_slice = self
121            .rest
122            .get(self.metadata_len()..)
123            .ok_or_else(|| CfdpError::Custom("Invalid FSS Offset slice"))?;
124        if large_file_flag {
125            U64::ref_from_prefix(offset_slice)
126                .map(|(len, _)| len.get())
127                .map_err(|_| CfdpError::Custom("Invalid FSS Offset"))
128        } else {
129            U32::ref_from_prefix(offset_slice)
130                .map(|(len, _)| len.get() as u64)
131                .map_err(|_| CfdpError::Custom("Invalid FSS Offset"))
132        }
133    }
134    /// Sets the FSS byte offset into the file.
135    pub fn set_offset(&mut self, offset: u64, large_file_flag: bool) -> Result<(), CfdpError> {
136        let offset_slice = self
137            .rest
138            .get_mut(self.metadata_len()..)
139            .ok_or_else(|| CfdpError::Custom("Invalid FSS Offset slice"))?;
140        if large_file_flag {
141            U64::mut_from_prefix(offset_slice)
142                .map(|(len, _)| len.set(offset))
143                .map_err(|_| CfdpError::Custom("Invalid FSS Offset"))
144        } else {
145            if offset > u32::MAX as u64 {
146                return Err(CfdpError::DataTooLarge {
147                    field: "offset",
148                    max: u32::MAX as usize,
149                });
150            }
151            U32::mut_from_prefix(offset_slice)
152                .map(|(len, _)| len.set(offset as u32))
153                .map_err(|_| CfdpError::Custom("Invalid FSS Offset"))
154        }
155    }
156
157    /// Returns the slice containing the actual file data.
158    pub fn file_data<'a>(&'a self, large_file_flag: bool) -> Result<&'a [u8], CfdpError> {
159        let offset_len = if large_file_flag { 8 } else { 4 };
160        let start = self.metadata_len() + offset_len;
161        self.rest
162            .get(start..)
163            .ok_or_else(|| CfdpError::Custom("Invalid file data slice"))
164    }
165    /// Returns a mutable slice containing the actual file data.
166    pub fn file_data_mut(&mut self, large_file_flag: bool) -> Result<&mut [u8], CfdpError> {
167        let offset_len = if large_file_flag { 8 } else { 4 };
168        let start = self.metadata_len() + offset_len;
169        self.rest
170            .get_mut(start..)
171            .ok_or_else(|| CfdpError::Custom("Invalid file data slice"))
172    }
173
174    /// Returns the raw administrative octet.
175    pub fn admin_octet(&self) -> u8 {
176        self.admin_octet
177    }
178
179    /// Returns the raw rest slice.
180    pub fn rest(&self) -> &[u8] {
181        &self.rest
182    }
183}
184
185#[bon]
186impl FileDataPduWithMeta {
187    /// Builds a complete File Data PDU with segment metadata.
188    /// The caller is responsible for writing the file data into the buffer after this function returns.
189    #[builder]
190    pub fn new<'a>(
191        buffer: &'a mut [u8],
192        // Transaction Context
193        source_entity_id: EntityId,
194        destination_entity_id: EntityId,
195        transaction_seq_num: TransactionSeqNum,
196        transmission_mode: TransmissionMode,
197        large_file_flag: bool,
198        crc_flag: bool,
199        // FileData Specific
200        segmentation_control: bool,
201        record_continuation_state: u8,
202        segment_metadata: &'a [u8],
203        offset: u64,
204        file_data_len: usize,
205    ) -> Result<&'a mut Pdu, CfdpError> {
206        if record_continuation_state > 0b11 {
207            return Err(CfdpError::DataTooLarge {
208                field: "record_continuation_state",
209                max: 3,
210            });
211        }
212        if segment_metadata.len() > 63 {
213            return Err(CfdpError::DataTooLarge {
214                field: "segment_metadata",
215                max: 63,
216            });
217        }
218
219        let fixed_part_len = size_of::<u8>();
220        let metadata_len = segment_metadata.len();
221        let offset_len = if large_file_flag { 8 } else { 4 };
222        let data_field_len = (fixed_part_len + metadata_len + offset_len + file_data_len) as u16;
223
224        let header = PduHeaderFixedPart::builder()
225            .version(1)
226            .pdu_type(PduType::FileData)
227            .direction(Direction::TowardReceiver)
228            .tx_mode(transmission_mode)
229            .crc_flag(crc_flag)
230            .large_file_flag(large_file_flag)
231            .data_field_len(data_field_len)
232            .seg_ctrl(segmentation_control)
233            .seg_meta_flag(true) // Crucial: this flag MUST be set
234            .build()?;
235
236        let pdu = Pdu::builder()
237            .buffer(buffer)
238            .header_fixed(header)
239            .source_entity_id(source_entity_id)
240            .destination_entity_id(destination_entity_id)
241            .transaction_seq_num(transaction_seq_num)
242            .build()?;
243
244        let data_field = pdu.data_field_mut().unwrap();
245        let fd_pdu = FileDataPduWithMeta::mut_from_bytes(data_field).unwrap();
246
247        fd_pdu.set_record_continuation_state(record_continuation_state)?;
248        fd_pdu.set_metadata_len(segment_metadata.len() as u8)?;
249        fd_pdu.set_segment_metadata(segment_metadata)?;
250        fd_pdu.set_offset(offset, large_file_flag)?;
251        Ok(pdu)
252    }
253}