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