Skip to main content

space_packet/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![forbid(unsafe_code)]
4//! Generic implementation of the CCSDS 133.0-B-2 Space Packet Protocol (SPP). That is, this crate
5//! concerns itself only with parsing and construction of CCSDS Space Packets, as that is
6//! independent of the precise implementation. Endpoint functionality, i.e., actually consuming and
7//! responding to the packet contents is implementation specific, and hence out of scope.
8//!
9//! Tested and formally-verified implementations of the underlying parsing and semantic checking
10//! functionality needed to handle Space Packets is found in the actual `SpacePacket`
11//! implementation. This functionality is included in the hope that it helps write simple and
12//! robust SPP implementations.
13
14use thiserror::Error;
15use zerocopy::byteorder::network_endian;
16use zerocopy::{
17    ByteEq, ByteHash, CastError, FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned,
18};
19
20/// Space packet
21///
22/// Space packets are implemented as dynamically-sized structs that contain the primary header as
23/// their first field, followed by the packet data as pure byte array. In this manner,
24/// deserialization can be reduced to a simple byte cast followed by interpretation of the primary
25/// header - without any data copies needed. This is useful for high-throughput applications, and
26/// ensures that no allocation or significant additional memory is needed to consume Space Packets.
27///
28/// This does also mean that Space Packets may only be handled by reference. In the context of this
29/// crate that helps enforce that no spurious copies can be made of the user data (which may be
30/// rather large and would incur additional allocations), albeit at the cost of some convenience.
31///
32/// Any means of constructing a `SpacePacket` in this crate shall perform a consistency check on
33/// any received bytes. Hence, any `SpacePacket` object may be assumed to be a valid Space Packet.
34#[repr(C, packed)]
35#[derive(ByteEq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
36pub struct SpacePacket {
37    primary_header: SpacePacketPrimaryHeader,
38    data_field: [u8],
39}
40
41impl SpacePacket {
42    /// Attempts to parse a Space Packet from a given byte slice. If this fails, a reason is
43    /// given for this failure. Shall never panic: rather, an error enum is returned explaining why
44    /// the given octet string is not a valid Space Packet.
45    ///
46    /// This deserialization is fully zero-copy. The `&SpacePacket` returned on success directly
47    /// references the input slice `bytes`, but is merely validated to be a valid Space Packet.
48    ///
49    /// # Errors
50    /// Shall return an error if the provided bytes do not make up a valid space packet.
51    pub fn parse(bytes: &[u8]) -> Result<&Self, InvalidSpacePacket> {
52        // First, we simply cast the packet into a header and check that the byte buffer permits
53        // this: i.e., if it is large enough to contain a header.
54        let primary_header = match Self::ref_from_bytes(bytes) {
55            Ok(primary_header) => primary_header,
56            Err(CastError::Size(_)) => {
57                return Err(InvalidSpacePacket::SliceTooSmallForSpacePacketHeader {
58                    length: bytes.len(),
59                });
60            }
61            Err(CastError::Alignment(_)) => unreachable!(),
62        };
63
64        // Then, we verify that the resulting packet contents semantically form a valid space
65        // packet.
66        primary_header.validate()?;
67
68        // Finally, we truncate the passed byte slice to exactly accommodate the specified space
69        // packet and construct a Space Packet that consists of only this memory region.
70        let packet_size = primary_header.packet_data_length() + Self::primary_header_size();
71        let packet_bytes = &bytes[..packet_size];
72        let Ok(packet) = Self::ref_from_bytes(packet_bytes) else {
73            unreachable!()
74        };
75
76        Ok(packet)
77    }
78
79    /// Assembles a Space Packet in-place on a given buffer. Computes the required packet data
80    /// length from the passed buffer size. It is assumed that the caller has reserved the first
81    /// six bytes of the buffer for the packet header. All other bytes are assumed to form the
82    /// packet data field. Will compute the packet data field length based on the length of the
83    /// provided buffer.
84    ///
85    /// # Errors
86    /// Shall error if the provided buffer is not large enough to store the described space packet,
87    /// if it is exactly 6 bytes (which would result in a zero-length data field, which is not
88    /// permitted), or if the resulting length is larger than the maximum supported by the space
89    /// packet data field length representation (`u16::MAX + 7` bytes).
90    pub fn construct(
91        buffer: &mut [u8],
92        packet_type: PacketType,
93        secondary_header_flag: SecondaryHeaderFlag,
94        apid: Apid,
95        sequence_flag: SequenceFlag,
96        sequence_count: PacketSequenceCount,
97    ) -> Result<&mut Self, InvalidPacketDataLength> {
98        if buffer.len() < 7 {
99            return Err(InvalidPacketDataLength::LargerThanBuffer {
100                buffer_length: buffer.len(),
101                packet_data_length: 1,
102            });
103        }
104
105        let packet_data_length = buffer.len() - 6;
106        // As per the CCSDS Space Packet Protocol standard, we must reject requests for data field
107        // lengths of zero.
108        if packet_data_length == 0 {
109            return Err(InvalidPacketDataLength::EmptyDataField);
110        }
111
112        // Verify that the packet length as requested may actually fit on the supplied buffer.
113        let Some(packet_length) = Self::primary_header_size().checked_add(packet_data_length)
114        else {
115            return Err(InvalidPacketDataLength::TooLarge { packet_data_length });
116        };
117        let buffer_length = buffer.len();
118        if packet_length > buffer_length {
119            return Err(InvalidPacketDataLength::LargerThanBuffer {
120                buffer_length,
121                packet_data_length,
122            });
123        }
124
125        // Afterwards, we truncate the buffer to use only the bytes that actually belong to the
126        // packet. With the length check done, the `SpacePacket::mut_from_bytes()` call is known
127        // to be infallible, so we simply unwrap.
128        let packet_bytes = &mut buffer[..packet_length];
129        #[allow(clippy::missing_panics_doc)]
130        let packet = Self::mut_from_bytes(packet_bytes).unwrap();
131
132        // Initialize header bytes to valid values.
133        packet.primary_header.set_apid(apid);
134        packet.primary_header.initialize_packet_version();
135        packet.primary_header.set_packet_type(packet_type);
136        packet
137            .primary_header
138            .set_secondary_header_flag(secondary_header_flag);
139        packet.primary_header.set_sequence_flag(sequence_flag);
140        packet
141            .primary_header
142            .set_packet_sequence_count(sequence_count);
143        packet
144            .primary_header
145            .set_packet_data_length(packet_data_length)?;
146
147        Ok(packet)
148    }
149
150    /// Validates that the Space Packet is valid, in that its fields are coherent. In particular,
151    /// it is verified that the version number is that of a supported Space Packet, and that the
152    /// packet size as stored in the header is not larger than the packet size as permitted by the
153    /// actual memory span of which the packet consists.
154    ///
155    /// Note that this concerns semantic validity. The implementation shall not depend on this for
156    /// memory safety.
157    fn validate(&self) -> Result<(), InvalidSpacePacket> {
158        // First, we check that the primary header is valid and consistent.
159        self.primary_header.validate()?;
160
161        // The packet header contains an indication of the actual amount of bytes stored in the packet.
162        // If this is larger than the size of the actual memory contents, only a partial packet was
163        // received.
164        let packet_size = self.packet_data_length() + Self::primary_header_size();
165        let buffer_size = self.packet_length();
166        if packet_size > buffer_size {
167            return Err(InvalidSpacePacket::PartialPacket {
168                packet_size,
169                buffer_size,
170            });
171        }
172
173        Ok(())
174    }
175
176    /// Returns the size of a Space Packet primary header, in bytes. In the version that is
177    /// presently implemented, that is always 6 bytes.
178    #[must_use]
179    pub const fn primary_header_size() -> usize {
180        core::mem::size_of::<SpacePacketPrimaryHeader>()
181    }
182
183    /// Sets the packet data length field to the provided value. Note that the given value is not
184    /// stored directly, but rather decremented by one first. Accordingly, and as per the CCSDS
185    /// Space Packet Protocol standard, packet data lengths of 0 are not allowed.
186    ///
187    /// # Errors
188    /// Shall raise an error if the provided packet data length is zero, or if it is larger than
189    /// the provided packet data buffer.
190    pub fn set_packet_data_length(
191        &mut self,
192        packet_data_length: usize,
193    ) -> Result<(), InvalidPacketDataLength> {
194        if packet_data_length == 0 {
195            return Err(InvalidPacketDataLength::EmptyDataField);
196        }
197
198        let buffer_length = self.data_field.len();
199        if packet_data_length > buffer_length {
200            return Err(InvalidPacketDataLength::LargerThanBuffer {
201                packet_data_length,
202                buffer_length,
203            });
204        }
205
206        let Ok(stored_data_field_length) = u16::try_from(packet_data_length - 1) else {
207            return Err(InvalidPacketDataLength::TooLarge { packet_data_length });
208        };
209        self.primary_header
210            .data_length
211            .set(stored_data_field_length);
212        Ok(())
213    }
214
215    /// Returns the total length of the packet in bytes. Note the distinction from the packet data
216    /// length, which refers only to the length of the data field of the packet.
217    #[must_use]
218    pub const fn packet_length(&self) -> usize {
219        self.data_field.len() + core::mem::size_of::<SpacePacketPrimaryHeader>()
220    }
221
222    /// Returns a reference to the packet data field contained in this Space Packet.
223    #[must_use]
224    pub const fn packet_data_field(&self) -> &[u8] {
225        &self.data_field
226    }
227
228    /// Returns a mutable reference to the packet data field contained in this Space Packet.
229    #[must_use]
230    pub const fn packet_data_field_mut(&mut self) -> &mut [u8] {
231        &mut self.data_field
232    }
233}
234
235impl core::hash::Hash for SpacePacket {
236    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
237        self.primary_header.hash(state);
238        self.data_field.hash(state);
239    }
240}
241
242impl core::fmt::Debug for SpacePacket {
243    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
244        f.debug_struct("SpacePacket")
245            .field("primary_header", &self.primary_header)
246            .field("data_field", &&self.data_field)
247            .finish()
248    }
249}
250
251impl core::ops::Deref for SpacePacket {
252    type Target = SpacePacketPrimaryHeader;
253
254    fn deref(&self) -> &Self::Target {
255        &self.primary_header
256    }
257}
258
259impl core::ops::DerefMut for SpacePacket {
260    fn deref_mut(&mut self) -> &mut Self::Target {
261        &mut self.primary_header
262    }
263}
264
265/// Space packet primary header
266///
267/// Representation of only the fixed-size primary header part of a space packet. Used to construct
268/// generic space packets, but mostly useful in permitting composition of derived packet types,
269/// like PUS packets; otherwise, the dynamically-sized data field member would get in the way of
270/// including the primary header directly in derived packets.
271#[repr(C)]
272#[derive(
273    Copy, Clone, Debug, ByteEq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, ByteHash,
274)]
275pub struct SpacePacketPrimaryHeader {
276    packet_identification: network_endian::U16,
277    packet_sequence_control: network_endian::U16,
278    data_length: network_endian::U16,
279}
280
281impl SpacePacketPrimaryHeader {
282    /// Validates that the Space Packet primary header is valid, in that its fields are coherent.
283    /// In particular, it is verified that the version number is that of a supported Space Packet.
284    ///
285    /// Note that this concerns semantic validity. The implementation shall not depend on this for
286    /// memory safety.
287    fn validate(self) -> Result<(), InvalidSpacePacket> {
288        // We verify that the packet version found in the packet header is a version that is
289        // supported by this library.
290        let version = self.packet_version();
291        if !version.is_supported() {
292            return Err(InvalidSpacePacket::UnsupportedPacketVersion { version });
293        }
294
295        // Idle packets may not contain a secondary header field. If we do find that the secondary
296        // header flag is set, we must reject the packet.
297        if self.apid().is_idle() && self.secondary_header_flag() == SecondaryHeaderFlag::Present {
298            return Err(InvalidSpacePacket::IdlePacketWithSecondaryHeader);
299        }
300
301        Ok(())
302    }
303
304    /// Returns the size of a Space Packet primary header, in bytes. In the version that is
305    /// presently implemented, that is always 6 bytes.
306    #[must_use]
307    pub const fn primary_header_size() -> usize {
308        6
309    }
310
311    /// Since the Space Packet protocol may technically support alternative packet structures in
312    /// future versions, the 3-bit packet version field may not actually contain a "correct" value.
313    #[must_use]
314    pub const fn packet_version(&self) -> PacketVersionNumber {
315        PacketVersionNumber(self.packet_identification.to_bytes()[0] >> 5)
316    }
317
318    /// Initializes the packet version to the proper value. Must be a fixed value, so this function
319    /// takes no arguments.
320    pub fn initialize_packet_version(&mut self) {
321        self.packet_identification.as_mut_bytes()[0] &= 0b0001_1111;
322        self.packet_identification.as_mut_bytes()[0] |=
323            PacketVersionNumber::version1_ccsds_packet().0 << 5;
324    }
325
326    /// The packet type denotes whether a packet is a telecommand (request) or telemetry (report)
327    /// packet. Note that the exact definition of telecommand and telemetry may differ per system,
328    /// and indeed the "correct" value here may differ per project.
329    #[must_use]
330    pub const fn packet_type(&self) -> PacketType {
331        if (self.packet_identification.to_bytes()[0] & 0x10) == 0x10 {
332            PacketType::Telecommand
333        } else {
334            PacketType::Telemetry
335        }
336    }
337
338    /// Sets the packet type to the given value.
339    pub fn set_packet_type(&mut self, packet_type: PacketType) {
340        self.packet_identification.as_mut_bytes()[0] &= 0b1110_1111;
341        self.packet_identification.as_mut_bytes()[0] |= (packet_type as u8) << 4;
342    }
343
344    /// Denotes whether the packet contains a secondary header. If no user field is present, the
345    /// secondary header is mandatory (presumably, to ensure that some data is always transferred,
346    /// considering the Space Packet header itself contains no meaningful data).
347    #[must_use]
348    pub const fn secondary_header_flag(&self) -> SecondaryHeaderFlag {
349        if (self.packet_identification.to_bytes()[0] & 0x08) == 0x08 {
350            SecondaryHeaderFlag::Present
351        } else {
352            SecondaryHeaderFlag::Absent
353        }
354    }
355
356    /// Updates the value of the secondary header flag with the provided value.
357    pub fn set_secondary_header_flag(&mut self, secondary_header_flag: SecondaryHeaderFlag) {
358        self.packet_identification.as_mut_bytes()[0] &= 0b1111_0111;
359        self.packet_identification.as_mut_bytes()[0] |= (secondary_header_flag as u8) << 3;
360    }
361
362    /// Returns the application process ID stored in the packet. The actual meaning of this APID
363    /// field may differ per implementation: technically, it only represents "some" data path.
364    /// In practice, it will often be a identifier for a data channel, the packet source, or the
365    /// packet destination.
366    #[must_use]
367    pub const fn apid(&self) -> Apid {
368        Apid(self.packet_identification.get() & 0b0000_0111_1111_1111)
369    }
370
371    /// Sets the APID used to route the packet to the given value.
372    pub fn set_apid(&mut self, apid: Apid) {
373        let apid = apid.0.to_be_bytes();
374        self.packet_identification.as_mut_bytes()[0] &= 0b1111_1000;
375        self.packet_identification.as_mut_bytes()[0] |= apid[0] & 0b0000_0111;
376        self.packet_identification.as_mut_bytes()[1] = apid[1];
377    }
378
379    /// Sequence flags may be used to indicate that the data contained in a packet is only part of
380    /// a larger set of application data.
381    #[must_use]
382    pub const fn sequence_flag(&self) -> SequenceFlag {
383        match self.packet_sequence_control.to_bytes()[0] >> 6i32 {
384            0b00 => SequenceFlag::Continuation,
385            0b01 => SequenceFlag::First,
386            0b10 => SequenceFlag::Last,
387            0b11 => SequenceFlag::Unsegmented,
388            _ => unreachable!(), // Internal error: Reached unreachable code segment
389        }
390    }
391
392    /// Sets the sequence flag to the provided value.
393    pub fn set_sequence_flag(&mut self, sequence_flag: SequenceFlag) {
394        self.packet_sequence_control.as_mut_bytes()[0] &= 0b0011_1111;
395        self.packet_sequence_control.as_mut_bytes()[0] |= (sequence_flag as u8) << 6;
396    }
397
398    /// The packet sequence count is unique per APID and denotes the sequential binary count of
399    /// each Space Packet (generated per APID). For telecommands (i.e., with packet type 1) this
400    /// may also be a "packet name" that identifies the telecommand packet within its
401    /// communications session.
402    #[must_use]
403    pub const fn packet_sequence_count(&self) -> PacketSequenceCount {
404        PacketSequenceCount(self.packet_sequence_control.get() & 0b0011_1111_1111_1111)
405    }
406
407    /// Sets the packet sequence count to the provided value. This value must be provided by an
408    /// external counter and is not provided at a Space Packet type level because it might differ
409    /// between packet streams.
410    pub fn set_packet_sequence_count(&mut self, sequence_count: PacketSequenceCount) {
411        self.packet_sequence_control.as_mut_bytes()[0] &= 0b1100_0000;
412        self.packet_sequence_control.as_mut_bytes()[0] |=
413            sequence_count.0.to_be_bytes()[0] & 0b0011_1111;
414        self.packet_sequence_control.as_mut_bytes()[1] = sequence_count.0.to_be_bytes()[1];
415    }
416
417    /// The packet data length field represents the length of the associated packet data field.
418    /// However, it is not stored directly: rather, the "length count" is stored, which is the
419    /// packet data length minus one.
420    #[must_use]
421    pub const fn packet_data_length(&self) -> usize {
422        self.data_length.get() as usize + 1
423    }
424
425    /// Sets the packet data length field to the provided value. Note that the given value is not
426    /// stored directly, but rather decremented by one first. Accordingly, and as per the CCSDS
427    /// Space Packet Protocol standard, packet data lengths of 0 are not allowed.
428    ///
429    /// # Errors
430    /// Shall return an error if an empty packet data length is requested.
431    pub fn set_packet_data_length(
432        &mut self,
433        packet_data_length: usize,
434    ) -> Result<(), InvalidPacketDataLength> {
435        if packet_data_length == 0 {
436            return Err(InvalidPacketDataLength::EmptyDataField);
437        }
438
439        let Ok(stored_data_field_length) = u16::try_from(packet_data_length - 1) else {
440            return Err(InvalidPacketDataLength::TooLarge { packet_data_length });
441        };
442        self.data_length.set(stored_data_field_length);
443        Ok(())
444    }
445}
446
447/// Invalid space packet
448///
449/// Representation of the set of errors that may be encountered while deserializing a Space Packet.
450/// Marked as non-exhaustive to permit extension with additional semantic errors in the future
451/// without breaking API.
452#[non_exhaustive]
453#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Error)]
454pub enum InvalidSpacePacket {
455    /// Returned when a byte slice is too small to contain any Space Packet (i.e., is smaller than
456    /// a header with a single-byte user data field).
457    #[error(
458        "buffer too small for space packet header (has {length} bytes, at least 6 are required)"
459    )]
460    SliceTooSmallForSpacePacketHeader { length: usize },
461    /// Returned when a slice does not have a known and supported packet version. For convenience,
462    /// the packet version that is stored at the "conventional" (CCSDS packet version 0) is also
463    /// returned, though it does not need to be meaningful in other packet versions.
464    #[error("unsupported CCSDS Space Packet version: {version:?}")]
465    UnsupportedPacketVersion { version: PacketVersionNumber },
466    /// Returned when the decoded packet is not fully contained in the passed buffer.
467    #[error("detected partial packet (buffer is {buffer_size} bytes, packet {packet_size})")]
468    PartialPacket {
469        packet_size: usize,
470        buffer_size: usize,
471    },
472    /// Returned when the Space Packet is idle (has an 'all ones' APID) but also contains a
473    /// secondary header. This is forbidden by CCSDS 133.0-B-2.
474    #[error("idle packet contains a secondary header, this is forbidden")]
475    IdlePacketWithSecondaryHeader,
476}
477
478/// Invalid space packet data field length
479///
480/// This error may be returned when setting the data field of some newly-constructed Space Packet
481/// if the requested packet data length is 0 (which is generally illegal), if the requested packet
482/// data length does not fit in the buffer on which the packet must be stored, or if the requested
483/// packet data length is larger than supported by its representation.
484#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Error)]
485pub enum InvalidPacketDataLength {
486    #[error("empty data field requested, this is forbidden")]
487    EmptyDataField,
488    #[error(
489        "requested packet data length ({packet_data_length} bytes) is too large for buffer ({buffer_length} bytes)"
490    )]
491    LargerThanBuffer {
492        packet_data_length: usize,
493        buffer_length: usize,
494    },
495    #[error(
496        "requested packet data length too large ({packet_data_length} bytes, may be at most `u16::MAX + 1` bytes)"
497    )]
498    TooLarge { packet_data_length: usize },
499}
500
501/// The packet version number represents the version of the Space Packet protocol that is used. In
502/// the version presently implemented, this is defined to be zeroes.
503#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
504#[repr(transparent)]
505pub struct PacketVersionNumber(u8);
506
507impl PacketVersionNumber {
508    /// The Space Packet protocol version presently implemented in this crate is based on issue 2
509    /// of the CCSDS SPP blue book, which encompasses only the Version 1 CCSDS Packet, indicated by
510    /// a version number of 0. Other packet structures may be added in the future.
511    #[must_use]
512    pub const fn is_supported(&self) -> bool {
513        matches!(self.0, 0b0000_0000u8)
514    }
515
516    /// Returns the packet version number corresponding with the Version 1 CCSDS Packet.
517    #[must_use]
518    pub const fn version1_ccsds_packet() -> Self {
519        Self(0)
520    }
521}
522
523/// Space packet type
524///
525/// The packet type denotes whether a packet is a telecommand (request) or telemetry (report)
526/// packet. Note that the exact definition of telecommand and telemetry may differ per system,
527/// and indeed the "correct" value here may differ per project.
528#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
529#[cfg_attr(kani, derive(kani::Arbitrary))]
530#[repr(u8)]
531pub enum PacketType {
532    Telemetry = 0,
533    Telecommand = 1,
534}
535
536/// Secondary header flag
537///
538/// Denotes whether the packet contains a secondary header. If no user field is present, the
539/// secondary header is mandatory (presumably, to ensure that some data is always transferred,
540/// considering the Space Packet header itself contains no meaningful data).
541#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
542#[cfg_attr(kani, derive(kani::Arbitrary))]
543#[repr(u8)]
544pub enum SecondaryHeaderFlag {
545    Absent = 0,
546    Present = 1,
547}
548
549/// Application process ID
550///
551/// Returns the application process ID stored in the packet. The actual meaning of this APID
552/// field may differ per implementation: technically, it only represents "some" data path.
553/// In practice, it will often be a identifier for: a data channel, the packet source, or the
554/// packet destination.
555#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
556#[cfg_attr(kani, derive(kani::Arbitrary))]
557#[repr(transparent)]
558pub struct Apid(u16);
559
560impl Apid {
561    const MAX: u16 = 0b0000_0111_1111_1111u16;
562
563    /// Constructs a new APID.
564    ///
565    /// # Panics
566    /// Shall panic if the provided APID exceeds `2047`.
567    #[must_use]
568    pub const fn new(id: u16) -> Self {
569        assert!(
570            id <= Self::MAX,
571            "APIDs may not exceed 2047 (due to maximum of 13 bits in representation)"
572        );
573        Self(id)
574    }
575
576    /// Helper functions used during formal verification to create an APID that is actually within
577    /// the stated bounds, since we cannot use the type system to express this range.
578    #[cfg(kani)]
579    fn any_apid() -> Self {
580        match kani::any() {
581            any @ 0..=Self::MAX => Self(any),
582            _ => Self(42),
583        }
584    }
585
586    /// A special APID value (0x7ff) is reserved for idle Space Packets, i.e., packets that do not
587    /// carry any actual data.
588    #[must_use]
589    pub const fn is_idle(&self) -> bool {
590        self.0 == 0x7ff
591    }
592
593    /// Returns the APID as a regular 16-bit unsigned integer.
594    #[must_use]
595    pub const fn as_u16(&self) -> u16 {
596        self.0
597    }
598}
599
600impl From<Apid> for u16 {
601    fn from(value: Apid) -> Self {
602        value.0
603    }
604}
605
606/// Sequence flags may be used to indicate that the data contained in a packet is only part of
607/// a larger set of application data.
608#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
609#[cfg_attr(kani, derive(kani::Arbitrary))]
610#[repr(u8)]
611pub enum SequenceFlag {
612    Continuation = 0b00,
613    First = 0b01,
614    Last = 0b10,
615    #[default]
616    Unsegmented = 0b11,
617}
618
619/// Packet sequence count
620///
621/// The packet sequence count is unique per APID and denotes the sequential binary count of
622/// each Space Packet (generated per APID). For telecommands (i.e., with packet type 1) this
623/// may also be a "packet name" that identifies the telecommand packet within its
624/// communications session.
625#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, Default)]
626#[cfg_attr(kani, derive(kani::Arbitrary))]
627pub struct PacketSequenceCount(u16);
628
629impl PacketSequenceCount {
630    const MAX: u16 = 0b0011_1111_1111_1111u16;
631
632    /// The packet sequence count is initialized to zero by default.
633    #[must_use]
634    pub const fn new() -> Self {
635        Self(0)
636    }
637
638    /// Helper functions used during formal verification to create a packet sequence count that is
639    /// actually within the stated bounds, since we cannot use the type system to express this
640    /// range.
641    #[cfg(kani)]
642    fn any_packet_sequence_count() -> Self {
643        match kani::any() {
644            any @ 0..=Self::MAX => Self(any),
645            _ => Self(42),
646        }
647    }
648
649    /// A good default behaviour is for the packet sequence count to increment by one every time
650    /// a new packet is sent. This method permits a simple wrapping increment to be performed, to
651    /// make this easier.
652    pub const fn increment(&mut self) {
653        self.0 += 1;
654        if self.0 > Self::MAX {
655            self.0 = 0;
656        }
657    }
658}
659
660/// Test harness for formal verification.
661#[cfg(kani)]
662mod kani_harness {
663    use super::*;
664    use ::kani;
665
666    /// This test verifies that all possible primary headers may be parsed for all packets up to
667    /// u16::MAX in size, without panics. Note that the packet data field is assumed to always be
668    /// zero here. This is needed to restrict the search space for kani, and is a valid assumption
669    /// because the parsing implementation never touches the packet data field contents.
670    #[kani::proof]
671    fn header_parsing() {
672        let mut bytes = [0u8; u16::MAX as usize];
673        bytes[0] = kani::any();
674        bytes[1] = kani::any();
675        bytes[2] = kani::any();
676        bytes[3] = kani::any();
677        bytes[4] = kani::any();
678        bytes[5] = kani::any();
679        bytes[6] = kani::any();
680
681        let packet = SpacePacket::parse(&bytes);
682        if let Ok(packet) = packet {
683            assert!(packet.packet_length() <= bytes.len());
684            assert_eq!(
685                packet.packet_data_field().len(),
686                packet.packet_data_length()
687            );
688            assert!(packet.apid().0 <= 0b0000_0111_1111_1111);
689        }
690    }
691
692    /// This test verifies that all (!) possible packet construction requests can be handled
693    /// without panics when working with a fixed-size buffer that does not permit all possible
694    /// packet size requests. Here, we do not touch the data field, to prevent exponential blow-up
695    /// of the proof pipeline. Since the packet constructor performs no actions on the packet data
696    /// field beyond returning a reference to it, this makes for a strong proof about the safety of
697    /// this function.
698    ///
699    /// The buffer size is rather arbitrarily chosen to be 1024. This covers a significant amount
700    /// of valid packet sizes, but also ensures that the "error path" is covered, where the
701    /// requested packet is larger than the available buffer.
702    #[kani::proof]
703    fn packet_construction() {
704        let mut bytes = [kani::any(); 1024];
705        let maximum_packet_length = bytes.len();
706        let packet_type = kani::any();
707        let secondary_header_flag = kani::any();
708        let apid = Apid::any_apid();
709        let sequence_flag = kani::any();
710        let sequence_count = PacketSequenceCount::any_packet_sequence_count();
711        let packet_data_length: u16 = kani::any();
712        let packet_length = packet_data_length as usize + 6;
713
714        if packet_length <= maximum_packet_length {
715            let packet = SpacePacket::construct(
716                &mut bytes[..packet_length],
717                packet_type,
718                secondary_header_flag,
719                apid,
720                sequence_flag,
721                sequence_count,
722            );
723
724            // First, we verify that all valid requests result in a returned packet.
725            let valid_request = packet_data_length != 0;
726            if valid_request {
727                assert!(packet.is_ok());
728            }
729
730            // Vice versa, any invalid requests must be rejected.
731            if !valid_request {
732                assert!(!packet.is_ok());
733            }
734
735            // These checks ensure that any returned packet is indeed consistent with the requested
736            // packet header information.
737            if let Ok(packet) = packet {
738                assert!(packet.packet_length() <= maximum_packet_length);
739                assert_eq!(
740                    packet.packet_data_field().len(),
741                    packet.packet_data_length()
742                );
743
744                assert_eq!(packet.packet_type(), packet_type);
745                assert_eq!(packet.secondary_header_flag(), secondary_header_flag);
746                assert_eq!(packet.apid(), apid);
747                assert_eq!(packet.sequence_flag(), sequence_flag);
748                assert_eq!(packet.packet_sequence_count(), sequence_count);
749                assert_eq!(packet.packet_data_length(), packet_data_length as usize);
750            }
751        }
752    }
753}
754
755/// Deserialization of a relatively trivial packet. Used to verify that all basic deserialization
756/// logic is correct.
757#[test]
758fn deserialize_trivial_packet() {
759    let bytes = &[
760        0b0000_1000u8,
761        0b0000_0000u8,
762        0b1100_0000u8,
763        0b0000_0000u8,
764        0b0000_0000u8,
765        0b0000_0000u8,
766        0b0000_0000u8,
767    ];
768    let packet = SpacePacket::parse(bytes).unwrap();
769
770    assert_eq!(packet.packet_length(), 7);
771    assert_eq!(
772        packet.packet_version(),
773        PacketVersionNumber::version1_ccsds_packet()
774    );
775    assert_eq!(packet.packet_type(), PacketType::Telemetry);
776    assert_eq!(packet.secondary_header_flag(), SecondaryHeaderFlag::Present);
777    assert_eq!(packet.apid(), Apid::new(0));
778    assert_eq!(packet.sequence_flag(), SequenceFlag::Unsegmented);
779    assert_eq!(packet.packet_sequence_count(), PacketSequenceCount(0));
780    assert_eq!(packet.packet_data_length(), 1);
781    assert_eq!(packet.packet_data_field(), &bytes[6..]);
782}
783
784/// Serialization of a relatively trivial packet. Used to verify that all serialization logic is
785/// correct.
786#[test]
787fn serialize_trivial_packet() {
788    let mut bytes = [0u8; 7];
789    let packet = SpacePacket::construct(
790        &mut bytes,
791        PacketType::Telemetry,
792        SecondaryHeaderFlag::Present,
793        Apid::new(0),
794        SequenceFlag::Unsegmented,
795        PacketSequenceCount(0),
796    )
797    .unwrap();
798
799    assert_eq!(packet.packet_length(), 7);
800    assert_eq!(
801        packet.packet_version(),
802        PacketVersionNumber::version1_ccsds_packet()
803    );
804    assert_eq!(packet.packet_type(), PacketType::Telemetry);
805    assert_eq!(packet.secondary_header_flag(), SecondaryHeaderFlag::Present);
806    assert_eq!(packet.apid(), Apid::new(0));
807    assert_eq!(packet.sequence_flag(), SequenceFlag::Unsegmented);
808    assert_eq!(packet.packet_sequence_count(), PacketSequenceCount(0));
809    assert_eq!(packet.packet_data_length(), 1);
810    assert_eq!(
811        packet.packet_data_field(),
812        &[
813            0b0000_1000u8,
814            0b0000_0000u8,
815            0b1100_0000u8,
816            0b0000_0000u8,
817            0b0000_0000u8,
818            0b0000_0000u8,
819            0b0000_0000u8,
820        ][6..]
821    );
822}
823
824/// Roundtrip serialization and subsequent deserialization of Space Packets shall result in exactly
825/// identical byte slices for any valid (!) input. We test this by generating 10,000 random space
826/// packets and seeing whether they remain identical through this transformation.
827///
828/// Since this test only considers valid inputs, other unit tests are needed to cover off-nominal
829/// cases, such as when the buffer is too small or when the requested data field size is 0.
830#[test]
831fn roundtrip() {
832    use rand::{RngCore, SeedableRng};
833    // Note that we always use the same seed for reproducibility.
834    let mut rng = rand::rngs::SmallRng::seed_from_u64(42);
835    let mut buffer = [0u8; 16000];
836    for _ in 0..10_000 {
837        let packet_type = match rng.next_u32() & 1 {
838            0 => PacketType::Telemetry,
839            1 => PacketType::Telecommand,
840            _ => unreachable!(),
841        };
842        let secondary_header_flag = match rng.next_u32() & 1 {
843            0 => SecondaryHeaderFlag::Absent,
844            1 => SecondaryHeaderFlag::Present,
845            _ => unreachable!(),
846        };
847        #[allow(clippy::cast_possible_truncation, reason = "Truncation intended")]
848        let apid = Apid::new((rng.next_u32() & u32::from(Apid::MAX)) as u16);
849        let sequence_flag = match rng.next_u32() & 3 {
850            0b00 => SequenceFlag::Continuation,
851            0b01 => SequenceFlag::First,
852            0b10 => SequenceFlag::Last,
853            0b11 => SequenceFlag::Unsegmented,
854            _ => unreachable!(),
855        };
856        #[allow(clippy::cast_possible_truncation, reason = "Truncation intended")]
857        let sequence_count =
858            PacketSequenceCount((rng.next_u32() & u32::from(PacketSequenceCount::MAX)) as u16);
859
860        #[allow(clippy::cast_possible_truncation, reason = "Truncation intended")]
861        let packet_data_length = (rng.next_u32() % (buffer.len() as u32 - 7)) as u16 + 1;
862        let packet_length = packet_data_length as usize + 6;
863
864        let space_packet = SpacePacket::construct(
865            &mut buffer[..packet_length],
866            packet_type,
867            secondary_header_flag,
868            apid,
869            sequence_flag,
870            sequence_count,
871        )
872        .unwrap();
873
874        assert_eq!(
875            packet_type,
876            space_packet.packet_type(),
877            "Serialized packet type ({:?}) does not match with final deserialized packet type ({:?}) for packet ({:?})",
878            packet_type,
879            space_packet.packet_type(),
880            space_packet
881        );
882
883        assert_eq!(
884            secondary_header_flag,
885            space_packet.secondary_header_flag(),
886            "Serialized secondary header flag ({:?}) does not match with final deserialized secondary header flag ({:?}) for packet ({:?})",
887            secondary_header_flag,
888            space_packet.secondary_header_flag(),
889            space_packet
890        );
891
892        assert_eq!(
893            apid,
894            space_packet.apid(),
895            "Serialized APID ({:?}) does not match with final deserialized APID ({:?}) for packet ({:?})",
896            apid,
897            space_packet.apid(),
898            space_packet
899        );
900
901        assert_eq!(
902            sequence_flag,
903            space_packet.sequence_flag(),
904            "Serialized sequence flag ({:?}) does not match with final deserialized sequence flag ({:?}) for packet ({:?})",
905            sequence_flag,
906            space_packet.sequence_flag(),
907            space_packet
908        );
909
910        assert_eq!(
911            sequence_count,
912            space_packet.packet_sequence_count(),
913            "Serialized sequence count ({:?}) does not match with final deserialized sequence count ({:?}) for packet ({:?})",
914            sequence_count,
915            space_packet.packet_sequence_count(),
916            space_packet
917        );
918
919        assert_eq!(
920            packet_data_length as usize,
921            space_packet.packet_data_length(),
922            "Serialized packet type ({:?}) does not match with final deserialized packet type ({:?}) for packet ({:?})",
923            packet_data_length,
924            space_packet.packet_data_length(),
925            space_packet
926        );
927    }
928}
929
930/// Empty packet data fields are not permitted by CCSDS 133.0-B-2, so such requests must be
931/// rejected.
932#[test]
933fn empty_packet_data_field() {
934    let mut bytes = [0u8; 6];
935    let result = SpacePacket::construct(
936        &mut bytes,
937        PacketType::Telemetry,
938        SecondaryHeaderFlag::Present,
939        Apid::new(0),
940        SequenceFlag::Unsegmented,
941        PacketSequenceCount(0),
942    );
943    assert_eq!(
944        result,
945        Err(InvalidPacketDataLength::LargerThanBuffer {
946            buffer_length: 6,
947            packet_data_length: 1
948        })
949    );
950}
951
952/// When the buffer to construct a Space Packet in is too small to contain a packet primary header,
953/// this shall be caught and an error shall be returned, independent of the actual packet request.
954#[test]
955fn buffer_too_small_for_header_construction() {
956    let mut buffer = [0u8; 5];
957    let buffer_length = buffer.len();
958    let result = SpacePacket::construct(
959        &mut buffer,
960        PacketType::Telemetry,
961        SecondaryHeaderFlag::Present,
962        Apid::new(0),
963        SequenceFlag::Unsegmented,
964        PacketSequenceCount(0),
965    );
966    assert_eq!(
967        result,
968        Err(InvalidPacketDataLength::LargerThanBuffer {
969            buffer_length,
970            packet_data_length: 1
971        })
972    );
973}
974
975/// When the buffer to parse a packet from is too small, an error shall be returned to indicate
976/// this.
977#[test]
978fn buffer_too_small_for_parsed_packet() {
979    use rand::{RngCore, SeedableRng};
980    // Note that we always use the same seed for reproducibility.
981    let mut rng = rand::rngs::SmallRng::seed_from_u64(42);
982    let mut buffer = [0u8; 256];
983
984    for _ in 0..1000 {
985        // Generate a pseudo-random packet data length between 128 and 250, so that the resulting
986        // packet will fit on a 256-byte buffer.
987        let packet_data_length = (rng.next_u32() % 128) as u16 + 122;
988        let packet_length = packet_data_length as usize + 6;
989
990        // Construct a valid Space Packet.
991        let packet = SpacePacket::construct(
992            &mut buffer[..packet_length],
993            PacketType::Telemetry,
994            SecondaryHeaderFlag::Present,
995            Apid::new(0),
996            SequenceFlag::Unsegmented,
997            PacketSequenceCount(0),
998        )
999        .unwrap();
1000
1001        // Subsequently, truncate the resulting byte sequence to 127 bytes, so that it will always
1002        // be invalid (the stored packet data length will always correspond with a packet larger
1003        // than 127 bytes).
1004        let bytes = &packet.as_bytes()[..127];
1005        let result = SpacePacket::parse(bytes);
1006        assert_eq!(
1007            result,
1008            Err(InvalidSpacePacket::PartialPacket {
1009                packet_size: packet_data_length as usize + SpacePacket::primary_header_size(),
1010                buffer_size: bytes.len()
1011            })
1012        );
1013    }
1014}