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}