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