Skip to main content

leodos_protocols/transport/cfdp/pdu/
mod.rs

1//! Defines the structures and serialization/deserialization logic for
2//! CCSDS File Delivery Protocol (CFDP) Protocol Data Units (PDUs).
3//!
4//! This module provides safe, zero-copy views and builders for CFDP PDUs,
5//! following the pattern used by `SpacePacket`. A single PDU enum, `Pdu<'a>`,
6//! holds references to concrete PDU types that are views over the underlying network buffer.
7//! This allows for efficient, allocation-free parsing of incoming packets.
8
9/// File Data PDU types and builders.
10pub mod file_data;
11/// File Directive PDU types (EOF, Finished, ACK, Metadata, NAK, Prompt, KeepAlive).
12pub mod file_directive;
13/// PDU header structures and field accessors.
14pub mod header;
15/// Type-Length-Value (TLV) record types and iterators.
16pub mod tlv;
17
18use core::fmt;
19use core::ops::Deref;
20use core::ops::DerefMut;
21
22use crate::transport::cfdp::pdu::file_data::with_meta::FileDataPduWithMeta;
23use crate::transport::cfdp::pdu::file_data::without_meta::FileDataPduWithoutMeta;
24use crate::transport::cfdp::pdu::file_data::FileDataPdu;
25use crate::transport::cfdp::pdu::file_directive::ack::AckPdu;
26use crate::transport::cfdp::pdu::file_directive::eof::EofPdu;
27use crate::transport::cfdp::pdu::file_directive::finished::FinishedPdu;
28use crate::transport::cfdp::pdu::file_directive::keepalive::large::KeepAlivePduLarge;
29use crate::transport::cfdp::pdu::file_directive::keepalive::small::KeepAlivePduSmall;
30use crate::transport::cfdp::pdu::file_directive::keepalive::KeepAlivePdu;
31use crate::transport::cfdp::pdu::file_directive::metadata::MetadataPdu;
32use crate::transport::cfdp::pdu::file_directive::nak::large::NakPduLarge;
33use crate::transport::cfdp::pdu::file_directive::nak::large::NakSegmentLarge;
34use crate::transport::cfdp::pdu::file_directive::nak::small::NakPduSmall;
35use crate::transport::cfdp::pdu::file_directive::nak::small::NakSegmentSmall;
36use crate::transport::cfdp::pdu::file_directive::nak::NakPdu;
37use crate::transport::cfdp::pdu::file_directive::prompt::PromptPdu;
38use crate::transport::cfdp::pdu::file_directive::DirectiveCode;
39use crate::transport::cfdp::pdu::file_directive::FileDirectivePdu;
40use crate::transport::cfdp::pdu::header::PduHeaderFixedPart;
41use crate::transport::cfdp::pdu::header::PduType;
42use crate::transport::cfdp::CfdpError;
43use crate::utils::min_len;
44use bon::bon;
45use zerocopy::FromBytes;
46use zerocopy::Immutable;
47use zerocopy::IntoBytes;
48use zerocopy::KnownLayout;
49use zerocopy::Unaligned;
50
51/// A zero-copy view of a generic CFDP PDU, containing the header and raw bytes.
52///
53/// +---------------------------------------+---------------+
54/// | Field Name                            | Size          |
55/// +---------------------------------------+---------------+
56/// | -- PDU Header (Variable Length) ----- | ------------- |
57/// |                                       |               |
58/// | -- Fixed Part (4 bytes) ------------- | ------------- |
59/// |                                       |               |
60/// | Version Number                        | 3 bits        |
61/// | PDU Type                              | 1 bit         |
62/// | Direction                             | 1 bit         |
63/// | Transmission Mode                     | 1 bit         |
64/// | CRC Flag                              | 1 bit         |
65/// | Large File Flag                       | 1 bit         |
66/// |                                       |               |
67/// | PDU Data Field Length                 | 16 bits       |
68/// |                                       |               |
69/// | Segmentation Control                  | 1 bit         |
70/// | Length of Entity IDs                  | 3 bits        |
71/// | Segment Metadata Flag                 | 1 bit         |
72/// | Length of Transaction Sequence Number | 3 bits        |
73/// |                                       |               |
74/// | -- Variable Part (3 to 24 bytes) ---- | ------------- |
75/// |                                       |               |
76/// | Source Entity ID                      | 1 to 8 octets |
77/// | Transaction Sequence Number           | 1 to 8 octets |
78/// | Destination Entity ID                 | 1 to 8 octets |
79/// |                                       |               |
80/// +---------------------------------------+---------------+
81/// | -- PDU Data Field (Variable Length) - | ------------- |
82/// |                                       |               |
83/// +---------------------------------------+---------------+
84#[repr(C)]
85#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
86pub struct Pdu {
87    /// The fixed-size portion of the PDU header.
88    header_fixed: PduHeaderFixedPart,
89    /// Variable header and data field bytes.
90    rest: [u8],
91}
92
93/// An enum representing a zero-copy view of a parsed PDU.
94#[derive(Debug)]
95pub enum PduVariant<'a> {
96    /// A view of an End of File (EOF) PDU.
97    Eof(&'a EofPdu),
98    /// A view of a Finished PDU.
99    Finished(&'a FinishedPdu),
100    /// A view of an Acknowledgment (ACK) PDU.
101    Ack(&'a AckPdu),
102    /// A view of a Metadata PDU.
103    Metadata(&'a MetadataPdu),
104    /// A view of a File Data PDU.
105    FileData(FileDataPdu<'a>),
106    /// A view of a Negative Acknowledgment (NAK) PDU.
107    Nak(NakPdu<'a>),
108    /// A view of a Prompt PDU.
109    Prompt(&'a PromptPdu),
110    /// A view of a Keep Alive PDU.
111    KeepAlive(KeepAlivePdu<'a>),
112}
113
114/// Writes a `u64` value into a variable-length byte slice in big-endian order.
115fn write_to_slice(val: u64, slice: &mut [u8]) -> Result<(), CfdpError> {
116    let len = slice.len();
117    if len == 0 || len > 8 {
118        return Err(CfdpError::Custom("Invalid slice length for Entity ID"));
119    }
120    // Ensure the value can be represented in the given length.
121    if len < min_len(val) {
122        return Err(CfdpError::Custom(
123            "Slice too small to represent Entity ID value",
124        ));
125    }
126
127    let full_bytes = val.to_be_bytes();
128    let relevant_bytes = &full_bytes[8 - len..];
129    slice.copy_from_slice(relevant_bytes);
130    Ok(())
131}
132
133/// The unique identifier for a CFDP entity, stored as an owned `u64`.
134///
135/// CFDP Entity IDs are variable-length integers up to 8 bytes. This type
136/// normalizes them to a `u64` for easy storage, comparison, and hashing.
137#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
138pub struct EntityId(pub u64);
139
140impl EntityId {
141    /// Creates an `EntityId` from a variable-length byte slice.
142    /// The slice must be 1 to 8 bytes long.
143    pub fn from_bytes(slice: &[u8]) -> Result<Self, CfdpError> {
144        if slice.is_empty() || slice.len() > 8 {
145            return Err(CfdpError::Custom("Invalid Entity ID length"));
146        }
147        let mut bytes = [0u8; 8];
148        // Right-align the slice into the 8-byte array to handle smaller IDs correctly.
149        bytes[8 - slice.len()..].copy_from_slice(slice);
150        Ok(EntityId(u64::from_be_bytes(bytes)))
151    }
152
153    /// Serializes this entity ID into a variable-length byte slice.
154    pub fn write_to_slice(&self, slice: &mut [u8]) -> Result<(), CfdpError> {
155        write_to_slice(self.0, slice)
156    }
157
158    /// Returns the minimum number of bytes needed to represent this ID.
159    pub fn len(&self) -> usize {
160        min_len(self.0)
161    }
162}
163
164// Implement Debug manually for a cleaner hex output
165impl fmt::Debug for EntityId {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        write!(f, "EntityId(0x{:X})", self.0)
168    }
169}
170
171/// A CFDP transaction sequence number, stored as an owned `u64`.
172#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default, PartialOrd, Ord)]
173pub struct TransactionSeqNum(pub u64);
174
175impl TransactionSeqNum {
176    /// Increments the sequence number by one, wrapping on overflow.
177    pub fn increment(&mut self) {
178        self.0 = self.0.wrapping_add(1);
179    }
180
181    /// Creates a `TransactionSeqNum` from a variable-length byte slice (1 to 8 bytes).
182    pub fn from_bytes(slice: &[u8]) -> Result<Self, CfdpError> {
183        if slice.is_empty() || slice.len() > 8 {
184            return Err(CfdpError::Custom(
185                "Invalid Transaction Sequence Number length",
186            ));
187        }
188        let mut bytes = [0u8; 8];
189        bytes[8 - slice.len()..].copy_from_slice(slice);
190        Ok(TransactionSeqNum(u64::from_be_bytes(bytes)))
191    }
192
193    /// Serializes this sequence number into a variable-length byte slice.
194    pub fn write_to_slice(&self, slice: &mut [u8]) -> Result<(), CfdpError> {
195        write_to_slice(self.0, slice)
196    }
197
198    /// Returns the minimum number of bytes needed to represent this value.
199    pub fn len(&self) -> usize {
200        min_len(self.0)
201    }
202}
203
204impl Pdu {
205    // --- Public API ---
206
207    /// Returns the fixed part of the header.
208    pub fn header(&self) -> &PduHeaderFixedPart {
209        &self.header_fixed
210    }
211
212    /// Returns a mutable reference to the fixed part of the header.
213    pub fn header_mut(&mut self) -> &mut PduHeaderFixedPart {
214        &mut self.header_fixed
215    }
216
217    /// Parses the PDU data field into a typed `PduVariant`.
218    pub fn variant(&self) -> Result<PduVariant<'_>, CfdpError> {
219        let header = self.header();
220        let data = self.data_field()?;
221        match header.pdu_type() {
222            PduType::FileData => {
223                let large_file_flag = header.large_file_flag();
224                let seg_meta_flag = header.segment_metadata_flag();
225
226                if seg_meta_flag {
227                    let file_data_pdu = FileDataPduWithMeta::ref_from_bytes(data)
228                        .map_err(|_| CfdpError::Custom("Failed to parse FileDataPduWithMeta"))?;
229                    let metadata_len = file_data_pdu.metadata_len();
230                    let offset_len = if large_file_flag { 8 } else { 4 };
231                    if file_data_pdu.rest().len() < metadata_len + offset_len {
232                        return Err(CfdpError::Custom(
233                            "Insufficient data for metadata and offset",
234                        ));
235                    }
236                    Ok(PduVariant::FileData(FileDataPdu::WithMeta(file_data_pdu)))
237                } else {
238                    let file_data_pdu = FileDataPduWithoutMeta::ref_from_bytes(data)
239                        .map_err(|_| CfdpError::Custom("Failed to parse FileDataPduWithoutMeta"))?;
240                    let offset_len = if large_file_flag { 8 } else { 4 };
241                    if file_data_pdu.rest().len() < offset_len {
242                        return Err(CfdpError::Custom("Insufficient data for offset"));
243                    }
244                    Ok(PduVariant::FileData(FileDataPdu::WithoutMeta(
245                        file_data_pdu,
246                    )))
247                }
248            }
249            PduType::FileDirective => {
250                let file_directive = FileDirectivePdu::ref_from_bytes(data)
251                    .map_err(|_| CfdpError::Custom("Failed to parse FileDirectivePdu"))?;
252                let rest = file_directive.rest();
253
254                match file_directive.directive_code()? {
255                    DirectiveCode::Eof => {
256                        let pdu = EofPdu::ref_from_bytes(rest)
257                            .map_err(|_| CfdpError::Custom("Failed to parse EofPdu"))?;
258                        let min_rest_len = if header.large_file_flag() { 8 } else { 4 };
259                        if pdu.rest().len() < min_rest_len {
260                            return Err(CfdpError::Custom("Insufficient data for EOF file size"));
261                        }
262                        Ok(PduVariant::Eof(pdu))
263                    }
264                    DirectiveCode::Finished => {
265                        let pdu = FinishedPdu::ref_from_bytes(rest)
266                            .map_err(|_| CfdpError::Custom("Failed to parse FinishedPdu"))?;
267                        Ok(PduVariant::Finished(pdu))
268                    }
269                    DirectiveCode::Ack => {
270                        let pdu = AckPdu::ref_from_bytes(rest)
271                            .map_err(|_| CfdpError::Custom("Failed to parse AckPdu"))?;
272                        Ok(PduVariant::Ack(pdu))
273                    }
274                    DirectiveCode::Metadata => {
275                        let pdu = MetadataPdu::ref_from_bytes(rest)
276                            .map_err(|_| CfdpError::Custom("Failed to parse MetadataPdu"))?;
277                        let min_rest_len = (if header.large_file_flag() { 8 } else { 4 }) + 1 + 1;
278                        if pdu.rest().len() < min_rest_len {
279                            return Err(CfdpError::Custom("Insufficient data for Metadata PDU"));
280                        }
281                        Ok(PduVariant::Metadata(pdu))
282                    }
283                    DirectiveCode::Nak => {
284                        if header.large_file_flag() {
285                            let pdu = NakPduLarge::ref_from_bytes(rest)
286                                .map_err(|_| CfdpError::Custom("Failed to parse NakPduLarge"))?;
287                            if pdu.rest().len() % core::mem::size_of::<NakSegmentLarge>() != 0 {
288                                return Err(CfdpError::Custom(
289                                    "Invalid NAK segment requests length",
290                                ));
291                            }
292                            Ok(PduVariant::Nak(NakPdu::Large(pdu)))
293                        } else {
294                            let pdu = NakPduSmall::ref_from_bytes(rest)
295                                .map_err(|_| CfdpError::Custom("Failed to parse NakPduSmall"))?;
296                            if pdu.rest().len() % core::mem::size_of::<NakSegmentSmall>() != 0 {
297                                return Err(CfdpError::Custom(
298                                    "Invalid NAK segment requests length",
299                                ));
300                            }
301                            Ok(PduVariant::Nak(NakPdu::Small(pdu)))
302                        }
303                    }
304                    DirectiveCode::Prompt => {
305                        let pdu = PromptPdu::ref_from_bytes(rest)
306                            .map_err(|_| CfdpError::Custom("Failed to parse PromptPdu"))?;
307                        Ok(PduVariant::Prompt(pdu))
308                    }
309                    DirectiveCode::KeepAlive => {
310                        if header.large_file_flag() {
311                            let pdu = KeepAlivePduLarge::ref_from_bytes(rest).map_err(|_| {
312                                CfdpError::Custom("Failed to parse KeepAlivePduLarge")
313                            })?;
314                            Ok(PduVariant::KeepAlive(KeepAlivePdu::Large(pdu)))
315                        } else {
316                            let pdu = KeepAlivePduSmall::ref_from_bytes(rest).map_err(|_| {
317                                CfdpError::Custom("Failed to parse KeepAlivePduSmall")
318                            })?;
319                            Ok(PduVariant::KeepAlive(KeepAlivePdu::Small(pdu)))
320                        }
321                    }
322                }
323            }
324        }
325    }
326
327    /// Returns the variable-length portion of the PDU header as a byte slice.
328    pub(crate) fn variable_header(&self) -> Result<&[u8], CfdpError> {
329        let len = self.header_fixed.variable_header_len();
330        self.rest
331            .get(0..len)
332            .ok_or_else(|| CfdpError::Custom("Invalid variable header slice"))
333    }
334
335    /// Returns a mutable reference to the variable-length portion of the PDU header.
336    pub(crate) fn variable_header_mut(&mut self) -> Result<&mut [u8], CfdpError> {
337        let len = self.header_fixed.variable_header_len();
338        self.rest
339            .get_mut(0..len)
340            .ok_or_else(|| CfdpError::Custom("Invalid variable header slice"))
341    }
342
343    /// Sets the fixed part of the header.
344    pub fn set_header(&mut self, header: PduHeaderFixedPart) {
345        self.header_fixed = header;
346    }
347
348    /// Returns the raw rest field as a byte slice.
349    pub fn rest(&self) -> &[u8] {
350        &self.rest
351    }
352
353    /// Returns the total length of the PDU header (fixed + variable parts).
354    pub fn header_len(&self) -> usize {
355        core::mem::size_of::<PduHeaderFixedPart>() + self.header_fixed.variable_header_len()
356    }
357
358    /// Parses and returns a slice for the source entity ID.
359    pub fn source_entity_id(&self) -> Result<EntityId, CfdpError> {
360        let entity_id_len = self.header_fixed.entity_id_len();
361        let bytes = self
362            .rest
363            .get(0..entity_id_len)
364            .ok_or_else(|| CfdpError::Custom("Invalid source entity ID slice"))?;
365        EntityId::from_bytes(bytes)
366    }
367
368    /// Writes the source entity ID into the variable header.
369    pub fn set_source_entity_id(&mut self, source_entity_id: EntityId) -> Result<(), CfdpError> {
370        let entity_id_len = self.header_fixed.entity_id_len();
371        let var_header_slice = self
372            .variable_header_mut()?
373            .get_mut(0..entity_id_len)
374            .ok_or(CfdpError::Custom("Invalid slice for source ID"))?;
375
376        source_entity_id.write_to_slice(var_header_slice)
377    }
378
379    /// Parses and returns a slice for the transaction sequence number.
380    pub fn transaction_seq_num(&self) -> Result<TransactionSeqNum, CfdpError> {
381        let entity_id_len = self.header_fixed.entity_id_len();
382        let txn_seq_num_len = self.header_fixed.txn_seq_num_len();
383        let offset = entity_id_len;
384        let bytes = self
385            .variable_header()?
386            .get(offset..offset + txn_seq_num_len)
387            .ok_or_else(|| CfdpError::Custom("Invalid transaction sequence number slice"))?;
388        TransactionSeqNum::from_bytes(bytes)
389    }
390
391    /// Writes the transaction sequence number into the variable header.
392    pub fn set_transaction_seq_num(
393        &mut self,
394        txn_seq_num: TransactionSeqNum,
395    ) -> Result<(), CfdpError> {
396        let entity_id_len = self.header_fixed.entity_id_len();
397        let txn_seq_num_len = self.header_fixed.txn_seq_num_len();
398        let offset = entity_id_len;
399
400        let var_header_slice = self
401            .variable_header_mut()?
402            .get_mut(offset..offset + txn_seq_num_len)
403            .ok_or(CfdpError::Custom("Invalid slice for seq num"))?;
404
405        txn_seq_num.write_to_slice(var_header_slice)
406    }
407
408    /// Parses and returns a slice for the destination entity ID.
409    pub fn set_destination_entity_id(&mut self, dest_entity_id: EntityId) -> Result<(), CfdpError> {
410        let entity_id_len = self.header_fixed.entity_id_len();
411        let txn_seq_num_len = self.header_fixed.txn_seq_num_len();
412        let offset = entity_id_len + txn_seq_num_len;
413
414        let var_header_slice = self
415            .variable_header_mut()?
416            .get_mut(offset..offset + entity_id_len)
417            .ok_or(CfdpError::Custom("Invalid slice for dest ID"))?;
418
419        dest_entity_id.write_to_slice(var_header_slice)
420    }
421
422    /// Returns a slice representing the PDU's data field.
423    pub fn data_field(&self) -> Result<&[u8], CfdpError> {
424        let start = self.header_fixed.variable_header_len();
425        self.rest
426            .get(start..)
427            .ok_or_else(|| CfdpError::Custom("Invalid data field slice"))
428    }
429
430    /// Returns a mutable slice representing the PDU's data field.
431    pub fn data_field_mut(&mut self) -> Result<&mut [u8], CfdpError> {
432        let start = self.header_fixed.variable_header_len();
433        self.rest
434            .get_mut(start..)
435            .ok_or_else(|| CfdpError::Custom("Invalid data field slice"))
436    }
437
438    /// A convenience method to parse the entire PDU from a byte buffer.
439    pub fn from_bytes(buffer: &[u8]) -> Result<&Pdu, CfdpError> {
440        let (header_fixed, _rest) = PduHeaderFixedPart::ref_from_prefix(buffer)
441            .map_err(|_| CfdpError::Custom("Failed to parse PDU header fixed part"))?;
442
443        let expected_total_len = header_fixed.total_pdu_len();
444
445        if buffer.len() < expected_total_len {
446            return Err(CfdpError::Custom("Buffer too small for complete PDU"));
447        }
448
449        Pdu::ref_from_bytes(&buffer[..expected_total_len])
450            .map_err(|_| CfdpError::Custom("Failed to parse complete PDU"))
451    }
452
453    /// A convenience method to parse the entire PDU from a byte buffer.
454    pub fn from_bytes_mut(buffer: &mut [u8]) -> Result<&mut Pdu, CfdpError> {
455        let buffer_len = buffer.len();
456
457        let (header_fixed, _rest) =
458            PduHeaderFixedPart::mut_from_prefix(buffer).map_err(|_| CfdpError::BufferTooSmall {
459                required: core::mem::size_of::<PduHeaderFixedPart>(),
460                provided: buffer_len,
461            })?;
462
463        let expected_total_len = header_fixed.total_pdu_len();
464
465        if buffer_len < expected_total_len {
466            return Err(CfdpError::BufferTooSmall {
467                required: expected_total_len,
468                provided: buffer_len,
469            });
470        }
471
472        Ok(Pdu::mut_from_bytes(&mut buffer[..expected_total_len])
473            .expect("Buffer size already validated"))
474    }
475}
476
477#[bon]
478impl Pdu {
479    /// Builds a new PDU in the given buffer with header and entity IDs.
480    #[builder]
481    pub fn new<'a>(
482        buffer: &'a mut [u8],
483        header_fixed: PduHeaderFixedPart,
484        source_entity_id: EntityId,
485        destination_entity_id: EntityId,
486        transaction_seq_num: TransactionSeqNum,
487    ) -> Result<&'a mut Pdu, CfdpError> {
488        let pdu = Pdu::from_bytes_mut(buffer)?;
489        pdu.set_header(header_fixed);
490        pdu.header_fixed.set_entity_id_len(core::cmp::max(
491            source_entity_id.len(),
492            destination_entity_id.len(),
493        ))?;
494        pdu.header_fixed
495            .set_txn_seq_num_len(transaction_seq_num.len())?;
496        pdu.set_source_entity_id(source_entity_id)?;
497        pdu.set_transaction_seq_num(transaction_seq_num)?;
498        pdu.set_destination_entity_id(destination_entity_id)?;
499        Ok(pdu)
500    }
501}
502
503impl Deref for Pdu {
504    type Target = PduHeaderFixedPart;
505
506    fn deref(&self) -> &Self::Target {
507        &self.header_fixed
508    }
509}
510
511impl DerefMut for Pdu {
512    fn deref_mut(&mut self) -> &mut Self::Target {
513        &mut self.header_fixed
514    }
515}