Skip to main content

leodos_protocols/datalink/spp/
mod.rs

1//! CCSDS SPP (Space Packet Protocol) definitions and utilities.
2//! * Specification: https://ccsds.org/Pubs/133x0b2e2.pdf
3
4use bon::bon;
5use core::fmt::Debug;
6use core::fmt::LowerHex;
7use core::mem::size_of;
8use core::ops::Deref;
9use core::ops::DerefMut;
10use zerocopy::ByteEq;
11use zerocopy::FromBytes;
12use zerocopy::Immutable;
13use zerocopy::IntoBytes;
14use zerocopy::KnownLayout;
15use zerocopy::Unaligned;
16use zerocopy::byteorder::network_endian;
17
18/// CCSDS Encapsulation Packet Protocol (CCSDS 133.1-B-3).
19pub mod encapsulation;
20/// Segmentation and reassembly of large data across multiple Space Packets.
21pub mod segmentation;
22
23/// Re-export of the `zerocopy` crate used for zero-copy packet views.
24pub use zerocopy;
25
26/// A zero-copy view over a CCSDS Space Packet in a raw byte buffer.
27///
28/// This struct provides a low-level, safe view over a byte slice that is
29/// known to contain a valid Space Packet. It can be created via `SpacePacket::parse()`
30/// or through the ergonomic `SpacePacket::builder()`.
31///
32/// The `data_field` is an unsized `[u8]` slice, allowing this struct to represent
33/// packets of any valid length without needing different types.
34///
35/// ```text
36/// +------------------------------------+---------+
37/// | Field Name                         | Size    |
38/// +------------------------------------+---------+
39/// + -- Primary Header (6 bytes) ------ | ------- |
40/// |                                    |         |
41/// | Packet Version Number              | 3 bits  |
42/// | Packet Identification Field        | 13 bits |
43/// |   - Packet Type                    | 1 bit   |
44/// |   - Secondary Header Flag          | 1 bit   |
45/// |   - APID (Application Process ID)  | 11 bits |
46/// | Packet Sequence Control            | 16 bits |
47/// |   - Sequence Flags                 | 2 bits  |
48/// |   - Sequence Count                 | 14 bits |
49/// | Packet Data Length                 | 16 bits |
50/// |                                    |         |
51/// | -- Packet Data Field (Variable) -- | ------- |
52/// |                                    |         |
53/// | Secondary Header (if present)      |         |
54/// | User Data Field                    | 1-65536 |
55/// |                                    | bytes   |
56/// +------------------------------------+---------+
57/// ```
58#[repr(C)]
59#[derive(ByteEq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
60pub struct SpacePacket {
61    /// The 6-byte fixed primary header.
62    pub primary_header: PrimaryHeader,
63    /// The variable-length data field (secondary header + user data).
64    pub data_field: [u8],
65}
66
67/// A trait alias for the required bounds on a zero-copy packet data payload.
68///
69/// This is a convenience trait that bundles the necessary traits from the `zerocopy`
70/// crate. Any struct that will be used as a typed data field should be able to
71/// be soundly cast to and from a byte slice.
72pub trait SpacePacketData: FromBytes + IntoBytes + KnownLayout + Unaligned + Immutable {}
73impl<T> SpacePacketData for T where T: FromBytes + IntoBytes + KnownLayout + Unaligned + Immutable {}
74
75impl Debug for SpacePacket {
76    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77        f.debug_struct("SpacePacket")
78            .field("header", &self.primary_header)
79            .field("data_field", &&self.data_field)
80            .finish()
81    }
82}
83
84/// The 6-byte primary header of a CCSDS Space Packet.
85///
86/// This struct is a zero-copy view over the first 6 bytes of a packet and provides
87/// methods to safely access the bit-packed fields.
88#[repr(C)]
89#[derive(
90    Copy, Clone, Debug, Hash, ByteEq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
91)]
92pub struct PrimaryHeader {
93    /// Contains the 3-bit Version and 13-bit Packet Identification field (Type, SecHdr, APID)
94    packet_version_and_id: network_endian::U16,
95    /// Contains the 2-bit Sequence Flags and 14-bit Sequence Count
96    packet_sequence_control: network_endian::U16,
97    /// Contains the length of the Packet Data Field minus 1
98    packet_data_length: network_endian::U16,
99}
100
101#[rustfmt::skip]
102mod bitmasks {
103    // Version and Identification field masks
104    pub const PACKET_VERSION_MASK: u16 = 0b_11100000_00000000;
105    pub const PACKET_TYPE_MASK: u16 =    0b_00010000_00000000;
106    pub const SEC_HDR_MASK: u16 =        0b_00001000_00000000;
107    pub const APID_MASK: u16 =           0b_00000111_11111111;
108
109    // Sequence_control field masks
110    pub const SEQ_FLAG_MASK: u16 =       0b_11000000_00000000;
111    pub const SEQ_COUNT_MASK: u16 =      0b_00111111_11111111;
112}
113use bitmasks::*;
114
115use crate::utils::get_bits_u16;
116use crate::utils::set_bits_u16;
117
118/// The 3-bit packet version number.
119///
120/// As per the CCSDS standard, this library currently only supports Version 1 (binary `000`).
121#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
122#[repr(transparent)]
123pub struct PacketVersion(u8);
124
125impl PacketVersion {
126    /// The version number for CCSDS Space Packets defined in 133.0-B-2 (value is 0).
127    pub const VERSION_1: Self = Self(0);
128    /// Checks if this library supports the packet version.
129    pub fn is_supported(&self) -> bool {
130        *self == Self::VERSION_1
131    }
132}
133
134/// The 1-bit packet type identifier.
135///
136/// Distinguishes between telemetry (data sent from space to ground) and
137/// telecommand (commands sent from ground to space) packets.
138#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
139#[repr(u8)]
140pub enum PacketType {
141    /// Identifies a telemetry packet (value `0`).
142    Telemetry = 0,
143    /// Identifies a telecommand packet (value `1`).
144    Telecommand = 1,
145}
146
147/// The 1-bit secondary header flag.
148///
149/// Indicates whether an optional, mission-defined Secondary Header is present
150/// at the beginning of the packet's data field.
151#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
152#[repr(u8)]
153pub enum SecondaryHeaderFlag {
154    /// Indicates that no secondary header is present.
155    #[default]
156    Absent = 0,
157    /// Indicates that a secondary header is present.
158    Present = 1,
159}
160
161/// The 11-bit Application Process Identifier (APID).
162///
163/// The APID is used to route the packet to a specific application process or
164/// component within the satellite's flight software. It acts as a "port number"
165/// or "topic" for the packet's data.
166#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
167#[repr(transparent)]
168pub struct Apid(u16);
169
170impl LowerHex for Apid {
171    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
172        write!(f, "{:#05x}", self.0)
173    }
174}
175
176impl Apid {
177    /// The maximum valid APID value.
178    pub const MAX: u16 = 0b_00000111_11111111;
179    /// The reserved APID value for idle packets.
180    ///
181    /// Idle packets are sent to maintain link synchronization when no real data is available.
182    pub const IDLE: Self = Self(Self::MAX);
183
184    /// Creates a new `Apid`, returning an error if the value is out of range.
185    pub const fn new(id: u16) -> Result<Self, BuildError> {
186        if id > Self::MAX {
187            Err(BuildError::InvalidApid { value: id })
188        } else {
189            Ok(Self(id))
190        }
191    }
192
193    /// Checks if this is the reserved idle APID.
194    pub fn is_idle(&self) -> bool {
195        *self == Self::IDLE
196    }
197
198    /// Returns the raw APID value.
199    pub fn value(&self) -> u16 {
200        self.0
201    }
202}
203
204/// The 2-bit sequence flag.
205///
206/// Indicates if a large block of user data has been split (segmented) across
207/// multiple Space Packets.
208#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
209#[repr(u8)]
210pub enum SequenceFlag {
211    /// The packet's data field is a continuation of a segmented message.
212    Continuation = 0b00,
213    /// The packet's data field is the first segment of a message.
214    First = 0b01,
215    /// The packet's data field is the last segment of a message.
216    Last = 0b10,
217    /// The packet contains a complete, unsegmented block of data.
218    #[default]
219    Unsegmented = 0b11,
220}
221
222/// The 14-bit packet sequence count.
223///
224/// A rolling counter for packets with a specific APID. This allows the receiver
225/// to detect dropped or out-of-order packets. The count wraps around from 16383 to 0.
226#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, Default)]
227pub struct SequenceCount(u16);
228
229impl SequenceCount {
230    /// The maximum valid sequence count value (16383).
231    pub const MAX: u16 = 0b_00111111_11111111;
232
233    /// Creates a new `SequenceCount` initialized to zero.
234    pub fn new() -> Self {
235        Self(0)
236    }
237
238    /// Increments the sequence count, wrapping around on overflow.
239    pub fn increment(&mut self) {
240        self.0 = (self.0 + 1) & Self::MAX;
241    }
242
243    /// Returns the raw sequence count value.
244    pub fn value(&self) -> u16 {
245        self.0
246    }
247}
248
249/// An error that can occur when parsing a byte slice into a `SpacePacket`.
250#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, thiserror::Error)]
251pub enum ParseError {
252    /// The provided slice is shorter than the 6-byte primary header.
253    #[error(
254        "slice is too short for a primary header (expected at least {} bytes, got {actual})",
255        size_of::<PrimaryHeader>()
256    )]
257    TooShortForHeader {
258        /// Actual number of bytes provided.
259        actual: usize,
260    },
261    /// The header's length field implies a packet larger than the provided buffer.
262    #[error(
263        "incomplete packet (header specifies {header_len} bytes, but buffer has only {buffer_len} bytes)"
264    )]
265    IncompletePacket {
266        /// Total packet length declared in the header.
267        header_len: usize,
268        /// Actual buffer length provided.
269        buffer_len: usize,
270    },
271    /// The packet's header fields contain semantically invalid values.
272    #[error("packet validation failed: {0}")]
273    Invalid(#[from] ValidationError),
274}
275
276/// An error representing a violation of the CCSDS semantic rules.
277#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, thiserror::Error)]
278pub enum ValidationError {
279    /// The packet version number is not supported by this library.
280    #[error("unsupported packet version: {0:?}")]
281    UnsupportedVersion(PacketVersion),
282    /// An idle packet (APID 2047) must not have a secondary header.
283    #[error("idle packets (APID 2047) must not have a secondary header")]
284    IdlePacketWithSecondaryHeader,
285}
286
287/// An error that occurs during the construction of a `SpacePacket` header.
288#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, thiserror::Error)]
289pub enum BuildError {
290    /// This variant is not used by the builder but is kept for the `SpacePacket::new` constructor.
291    #[error("buffer is too small to hold the packet")]
292    BufferTooSmall {
293        /// Minimum number of bytes needed.
294        required: usize,
295        /// Actual buffer size provided.
296        provided: usize,
297    },
298    /// The provided data field length exceeds the maximum allowed size.
299    #[error(
300        "payload too large: maximum allowed is {max} bytes, but {provided} bytes were provided"
301    )]
302    PayloadTooLarge {
303        /// Maximum allowed data field size.
304        max: usize,
305        /// Actual data field size provided.
306        provided: usize,
307    },
308    /// The CCSDS standard forbids packets with an empty data field.
309    #[error("a packet data field length of zero is forbidden")]
310    EmptyDataField,
311    /// The provided APID value is outside the valid 11-bit range (0-2047).
312    #[error("invalid APID value {value}")]
313    InvalidApid {
314        /// The invalid APID value.
315        value: u16,
316    },
317}
318
319/// An error that occurs when setting or getting the typed data field.
320#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, thiserror::Error)]
321pub enum DataFieldError {
322    /// The size of the provided data does not match the size of the packet's data field.
323    #[error("size of provided data does not match the packet's data field size")]
324    SizeMismatch,
325    /// The packet's data field could not be safely cast to the target data type,
326    /// often due to alignment issues or an invalid discriminant.
327    #[error("packet data field could not be cast to the target data type")]
328    InvalidLayout,
329    /// A secondary header was requested but the secondary header flag is absent.
330    #[error("secondary header is not present in this packet")]
331    SecondaryHeaderAbsent,
332}
333
334impl Deref for SpacePacket {
335    /// Dereferences to the `PrimaryHeader` to allow direct access to header fields.
336    type Target = PrimaryHeader;
337    fn deref(&self) -> &Self::Target {
338        &self.primary_header
339    }
340}
341
342impl DerefMut for SpacePacket {
343    fn deref_mut(&mut self) -> &mut Self::Target {
344        &mut self.primary_header
345    }
346}
347
348#[bon]
349impl SpacePacket {
350    /// Constructs a new `SpacePacket` in the provided buffer.
351    #[builder]
352    pub fn new<'a, 'b>(
353        buffer: &'a mut [u8],
354        apid: Apid,
355        packet_type: PacketType,
356        sequence_count: SequenceCount,
357        secondary_header: SecondaryHeaderFlag,
358        sequence_flag: SequenceFlag,
359        data_len: usize,
360    ) -> Result<&'a mut Self, BuildError> {
361        if data_len > u16::MAX as usize {
362            return Err(BuildError::InvalidApid { value: apid.0 });
363        }
364        if data_len == 0 {
365            return Err(BuildError::EmptyDataField);
366        }
367        let required_len = data_len + size_of::<PrimaryHeader>();
368        let provided_len = buffer.len();
369        let (packet, _) = Self::mut_from_prefix_with_elems(buffer, data_len).map_err(|_| {
370            BuildError::BufferTooSmall {
371                required: required_len,
372                provided: provided_len,
373            }
374        })?;
375
376        packet.set_version(PacketVersion::VERSION_1);
377        packet.set_packet_type(packet_type);
378        packet.set_apid(apid);
379        packet.set_sequence_count(sequence_count);
380        packet.set_data_field_len(data_len as u16);
381        packet.set_secondary_header_flag(secondary_header);
382        packet.set_sequence_flag(sequence_flag);
383
384        Ok(packet)
385    }
386
387    /// Parses (zero-copy) a raw byte slice into a `SpacePacket`.
388    ///
389    /// This function reads the packet header to determine the packet's total length
390    /// and returns a view over that exact portion of the provided slice.
391    pub fn parse(bytes: &[u8]) -> Result<&Self, ParseError> {
392        if bytes.len() < size_of::<PrimaryHeader>() {
393            return Err(ParseError::TooShortForHeader {
394                actual: bytes.len(),
395            });
396        }
397        let packet = Self::ref_from_bytes(bytes)
398            .expect("Should not fail due to prior length check and Unaligned trait");
399
400        packet.primary_header.validate()?;
401        let specified_len = packet.primary_header.packet_len();
402        if specified_len > bytes.len() {
403            return Err(ParseError::IncompletePacket {
404                header_len: specified_len,
405                buffer_len: bytes.len(),
406            });
407        }
408
409        let packet = Self::ref_from_bytes(&bytes[..specified_len])
410            .expect("Should not fail due to prior length checks");
411        Ok(packet)
412    }
413
414    /// Copies a user-defined data structure into the packet's data field.
415    ///
416    /// The size of `T` must exactly match the length of the data field.
417    pub fn set_data_field<T: SpacePacketData>(&mut self, data: &T) -> Result<(), DataFieldError> {
418        if self.data_field.len() != size_of::<T>() {
419            return Err(DataFieldError::SizeMismatch);
420        }
421        self.data_field_mut().copy_from_slice(data.as_bytes());
422        Ok(())
423    }
424
425    /// Returns a zero-copy, typed view of the packet's data field.
426    ///
427    /// This is the primary method for interpreting the packet's payload as a
428    /// specific data structure. It will fail if the size of `T` does not match
429    /// the data field's length.
430    pub fn data_as<T: SpacePacketData>(&self) -> Result<&T, DataFieldError> {
431        if self.data_field.len() != size_of::<T>() {
432            return Err(DataFieldError::SizeMismatch);
433        }
434        T::ref_from_bytes(self.data_field()).map_err(|_| DataFieldError::InvalidLayout)
435    }
436
437    /// Returns an immutable slice of the packet's data field.
438    pub fn data_field(&self) -> &[u8] {
439        &self.data_field
440    }
441
442    /// Returns a mutable slice of the packet's data field.
443    ///
444    /// **Warning:** Modifying the data field directly will invalidate any
445    /// CRC checksum. If using a `CrcSpacePacket`, prefer the safe `set_data()`
446    /// method instead.
447    pub fn data_field_mut(&mut self) -> &mut [u8] {
448        &mut self.data_field
449    }
450}
451
452impl PrimaryHeader {
453    fn validate(&self) -> Result<(), ValidationError> {
454        let version = self.version();
455        if !version.is_supported() {
456            return Err(ValidationError::UnsupportedVersion(version));
457        }
458        if self.apid().is_idle() && self.secondary_header_flag() == SecondaryHeaderFlag::Present {
459            return Err(ValidationError::IdlePacketWithSecondaryHeader);
460        }
461        Ok(())
462    }
463
464    /// Returns the 3-bit packet version number.
465    pub fn version(&self) -> PacketVersion {
466        PacketVersion(get_bits_u16(self.packet_version_and_id, PACKET_VERSION_MASK) as u8)
467    }
468
469    /// Sets the 3-bit packet version number.
470    pub fn set_version(&mut self, version: PacketVersion) {
471        set_bits_u16(
472            &mut self.packet_version_and_id,
473            PACKET_VERSION_MASK,
474            version.0 as u16,
475        );
476    }
477
478    /// Returns the `PacketType` (Telemetry or Telecommand).
479    pub fn packet_type(&self) -> PacketType {
480        if get_bits_u16(self.packet_version_and_id, PACKET_TYPE_MASK) == 0 {
481            PacketType::Telemetry
482        } else {
483            PacketType::Telecommand
484        }
485    }
486
487    /// Sets the `PacketType` (Telemetry or Telecommand).
488    pub fn set_packet_type(&mut self, packet_type: PacketType) {
489        set_bits_u16(
490            &mut self.packet_version_and_id,
491            PACKET_TYPE_MASK,
492            packet_type as u16,
493        );
494    }
495
496    /// Returns the `SecondaryHeader` flag (Present or Absent).
497    pub fn secondary_header_flag(&self) -> SecondaryHeaderFlag {
498        if get_bits_u16(self.packet_version_and_id, SEC_HDR_MASK) == 0 {
499            SecondaryHeaderFlag::Absent
500        } else {
501            SecondaryHeaderFlag::Present
502        }
503    }
504
505    /// Sets the `SecondaryHeader` flag (Present or Absent).
506    pub fn set_secondary_header_flag(&mut self, flag: SecondaryHeaderFlag) {
507        set_bits_u16(&mut self.packet_version_and_id, SEC_HDR_MASK, flag as u16);
508    }
509
510    /// Returns the 11-bit Application Process Identifier (`Apid`).
511    pub fn apid(&self) -> Apid {
512        Apid(self.packet_version_and_id.get() & APID_MASK)
513    }
514
515    /// Sets the 11-bit Application Process Identifier (`Apid`).
516    pub fn set_apid(&mut self, apid: Apid) {
517        let ident = self.packet_version_and_id.get();
518        self.packet_version_and_id
519            .set((ident & !APID_MASK) | apid.0);
520    }
521
522    /// Returns the 2-bit `SequenceFlag`.
523    pub fn sequence_flag(&self) -> SequenceFlag {
524        match get_bits_u16(self.packet_sequence_control, SEQ_FLAG_MASK) {
525            0b00 => SequenceFlag::Continuation,
526            0b01 => SequenceFlag::First,
527            0b10 => SequenceFlag::Last,
528            _ => SequenceFlag::Unsegmented,
529        }
530    }
531
532    /// Sets the 2-bit `SequenceFlag`.
533    pub fn set_sequence_flag(&mut self, flag: SequenceFlag) {
534        set_bits_u16(
535            &mut self.packet_sequence_control,
536            SEQ_FLAG_MASK,
537            flag as u16,
538        );
539    }
540
541    /// Returns the 14-bit packet sequence count.
542    pub fn sequence_count(&self) -> SequenceCount {
543        SequenceCount(get_bits_u16(self.packet_sequence_control, SEQ_COUNT_MASK))
544    }
545
546    /// Sets the 14-bit `SequenceCount`.
547    pub fn set_sequence_count(&mut self, count: SequenceCount) {
548        set_bits_u16(&mut self.packet_sequence_control, SEQ_COUNT_MASK, count.0);
549    }
550
551    /// Returns the length of the data field in bytes as specified by the header.
552    pub fn data_field_len(&self) -> usize {
553        self.packet_data_length.get() as usize + 1
554    }
555
556    /// Sets the length of the data field in bytes. The value written to the header
557    /// will be `len - 1` as per the CCSDS standard.
558    pub fn set_data_field_len(&mut self, len: u16) {
559        self.packet_data_length.set(len - 1);
560    }
561
562    /// Returns the total length of the packet (header + data field) in bytes.
563    pub fn packet_len(&self) -> usize {
564        self.data_field_len() + size_of::<PrimaryHeader>()
565    }
566
567    /// Reconstructs the CFE-style Message ID (`MsgId`) from the primary header fields.
568    ///
569    /// This is a crucial convenience function for systems that interact with the cFE
570    /// Software Bus (SB). The SB uses a single integer `MsgId` for routing, which is
571    /// a composite value created from several fields in the CCSDS header.
572    ///
573    /// A cFE `MsgId` is 16-bit integer with the following structure:
574    ///
575    /// ```text
576    /// +-----------------+------+-----------------------------------------+
577    /// | Field           | Size | Description                             |
578    /// +-----------------+------+-----------------------------------------+
579    /// | APID            | 11   | The 11-bit Application Process ID.      |
580    /// | SB Flag         | 1    | 1 indicates a Software Bus message.     |
581    /// | Type            | 1    | 0 for Telemetry, 1 for Telecommand.     |
582    /// | Reserved        | 3    | Unused, should be zero.                 |
583    /// +-----------------+------+-----------------------------------------+
584    /// ```
585    pub fn cfe_msg_id(&self) -> u16 {
586        const CFE_MSG_ID_BASE: u16 = 0b_00001000_00000000;
587        let type_bit = (self.packet_type() as u16) << 12;
588        self.apid().0 | type_bit | CFE_MSG_ID_BASE
589    }
590}
591
592impl From<u16> for SequenceCount {
593    fn from(value: u16) -> Self {
594        Self(value & Self::MAX)
595    }
596}
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601    use zerocopy::byteorder::network_endian::{F32, U64};
602
603    #[repr(C)]
604    #[derive(FromBytes, IntoBytes, KnownLayout, Unaligned, Immutable, Debug, Default)]
605    struct TelemetryData {
606        timestamp: U64,
607        reading: F32,
608        status: u8,
609    }
610
611    #[test]
612    fn build_and_set_data() {
613        let telemetry_payload = TelemetryData {
614            timestamp: U64::new(1234567890),
615            reading: F32::new(3.14159),
616            status: 0xAB,
617        };
618
619        let apid = Apid::new(42).unwrap();
620        let packet_type = PacketType::Telemetry;
621        let mut buffer = [0u8; 100];
622        let data_len = size_of::<TelemetryData>();
623
624        let packet = SpacePacket::builder()
625            .buffer(&mut buffer)
626            .apid(apid)
627            .packet_type(packet_type)
628            .sequence_count(SequenceCount::new())
629            .secondary_header(SecondaryHeaderFlag::Absent)
630            .sequence_flag(SequenceFlag::Unsegmented)
631            .data_len(data_len)
632            .build()
633            .unwrap();
634
635        packet.set_data_field(&telemetry_payload).unwrap();
636
637        let parsed_packet = SpacePacket::parse(packet.as_bytes()).unwrap();
638        let extracted_data = parsed_packet.data_as::<TelemetryData>().unwrap();
639
640        assert_eq!(extracted_data.timestamp.get(), 1234567890);
641        assert!((extracted_data.reading.get() - 3.14159).abs() < f32::EPSILON);
642        assert_eq!(extracted_data.status, 0xAB);
643    }
644
645    #[test]
646    fn deserialize_trivial_packet() {
647        let bytes = &[
648            0b0000_1000u8,
649            0b0000_0000u8, // Version, Type, SecHdr, APID
650            0b1100_0000u8,
651            0b0000_0000u8, // Seq. Flags, Seq. Count
652            0b0000_0000u8,
653            0b0000_0000u8, // Data Length = 0 (means 1 byte)
654            0xDEu8,        // Data Field (1 byte)
655        ];
656        let packet = SpacePacket::parse(bytes).unwrap();
657
658        assert_eq!(packet.packet_len(), 7);
659        assert_eq!(packet.version(), PacketVersion::VERSION_1);
660        assert_eq!(packet.packet_type(), PacketType::Telemetry);
661        assert_eq!(packet.secondary_header_flag(), SecondaryHeaderFlag::Present);
662        assert_eq!(packet.apid(), Apid::new(0).unwrap());
663        assert_eq!(packet.sequence_flag(), SequenceFlag::Unsegmented);
664        assert_eq!(packet.sequence_count(), SequenceCount::from(0));
665        assert_eq!(packet.data_field_len(), 1);
666        assert_eq!(packet.data_field(), &[0xDE]);
667    }
668
669    #[test]
670    fn roundtrip_header_fields() {
671        use rand::{RngCore, SeedableRng};
672        let mut rng = rand::rngs::SmallRng::seed_from_u64(42);
673        let mut buffer = [0u8; 1024];
674
675        for _ in 0..10_000 {
676            let packet_type = if rng.next_u32() % 2 == 0 {
677                PacketType::Telemetry
678            } else {
679                PacketType::Telecommand
680            };
681            let apid = Apid::new((rng.next_u32() & Apid::MAX as u32) as u16).unwrap();
682            let sequence_count = SequenceCount::from(rng.next_u32() as u16);
683            let data_field_len = 1;
684
685            let packet = SpacePacket::builder()
686                .buffer(&mut buffer)
687                .apid(apid)
688                .packet_type(packet_type)
689                .sequence_count(sequence_count)
690                .secondary_header(SecondaryHeaderFlag::Absent)
691                .sequence_flag(SequenceFlag::Unsegmented)
692                .data_len(data_field_len)
693                .build()
694                .unwrap();
695
696            let parsed = SpacePacket::parse(packet.as_bytes()).unwrap();
697
698            assert_eq!(parsed.packet_type(), packet_type);
699            assert_eq!(parsed.apid(), apid);
700            assert_eq!(parsed.sequence_count(), sequence_count);
701            assert_eq!(parsed.data_field_len(), data_field_len as usize);
702        }
703    }
704
705    #[test]
706    fn error_on_empty_data_field() {
707        let mut buffer = [0u8; 7];
708        let result = SpacePacket::builder()
709            .buffer(&mut buffer)
710            .apid(Apid::new(0).unwrap())
711            .packet_type(PacketType::Telemetry)
712            .sequence_count(SequenceCount::new())
713            .secondary_header(SecondaryHeaderFlag::Absent)
714            .sequence_flag(SequenceFlag::Unsegmented)
715            .data_len(0)
716            .build();
717        assert_eq!(result, Err(BuildError::EmptyDataField));
718    }
719
720    #[test]
721    fn error_on_buffer_too_small() {
722        let mut buffer = [0u8; 128];
723        let data_field_len = 200; // Requires more than 128 bytes
724        let result = SpacePacket::builder()
725            .buffer(&mut buffer)
726            .apid(Apid::new(0).unwrap())
727            .packet_type(PacketType::Telemetry)
728            .sequence_count(SequenceCount::new())
729            .secondary_header(SecondaryHeaderFlag::Absent)
730            .sequence_flag(SequenceFlag::Unsegmented)
731            .data_len(data_field_len)
732            .build();
733        assert_eq!(
734            result,
735            Err(BuildError::BufferTooSmall {
736                required: data_field_len as usize + size_of::<PrimaryHeader>(),
737                provided: 128
738            })
739        );
740    }
741
742    #[test]
743    fn error_on_incomplete_packet_parse() {
744        let mut buffer = [0u8; 256];
745        let data_field_len = 200;
746
747        // Build a valid packet that is 206 bytes long
748        let packet = SpacePacket::builder()
749            .buffer(&mut buffer)
750            .apid(Apid::new(0).expect("Valid APID"))
751            .packet_type(PacketType::Telemetry)
752            .sequence_count(SequenceCount::new())
753            .secondary_header(SecondaryHeaderFlag::Absent)
754            .sequence_flag(SequenceFlag::Unsegmented)
755            .data_len(data_field_len)
756            .build()
757            .expect("Should build successfully");
758
759        // Now try to parse a truncated slice of it
760        let truncated_bytes = &packet.as_bytes()[..127];
761        let result = SpacePacket::parse(truncated_bytes);
762
763        assert_eq!(
764            result,
765            Err(ParseError::IncompletePacket {
766                header_len: data_field_len as usize + size_of::<PrimaryHeader>(),
767                buffer_len: truncated_bytes.len(),
768            })
769        );
770    }
771
772    #[test]
773    fn error_on_data_field_size_mismatch() {
774        let mut buffer = [0u8; 100];
775        // Build a packet expecting a 10-byte data field
776        let packet = SpacePacket::builder()
777            .buffer(&mut buffer)
778            .apid(Apid::new(0).expect("Valid APID"))
779            .packet_type(PacketType::Telemetry)
780            .sequence_count(SequenceCount::new())
781            .secondary_header(SecondaryHeaderFlag::Absent)
782            .sequence_flag(SequenceFlag::Unsegmented)
783            .data_len(10)
784            .build()
785            .expect("Should build successfully");
786
787        // Try to set it with a struct that is not 10 bytes
788        let wrong_sized_data = TelemetryData::default(); // size_of is not 10
789        assert_ne!(size_of::<TelemetryData>(), 10);
790
791        let result = packet.set_data_field(&wrong_sized_data);
792        assert_eq!(result, Err(DataFieldError::SizeMismatch));
793    }
794
795    #[test]
796    fn error_on_invalid_apid() {
797        let result = Apid::new(Apid::MAX + 1);
798        assert_eq!(
799            result,
800            Err(BuildError::InvalidApid {
801                value: Apid::MAX + 1,
802            })
803        );
804    }
805}