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