#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
use thiserror::Error;
use zerocopy::byteorder::network_endian;
use zerocopy::{
ByteEq, ByteHash, CastError, FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned,
};
#[repr(C, packed)]
#[derive(ByteEq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
pub struct SpacePacket {
primary_header: SpacePacketPrimaryHeader,
data_field: [u8],
}
impl SpacePacket {
pub fn parse(bytes: &[u8]) -> Result<&Self, InvalidSpacePacket> {
let primary_header = match Self::ref_from_bytes(bytes) {
Ok(primary_header) => primary_header,
Err(CastError::Size(_)) => {
return Err(InvalidSpacePacket::SliceTooSmallForSpacePacketHeader {
length: bytes.len(),
});
}
Err(CastError::Alignment(_)) => unreachable!(),
};
primary_header.validate()?;
let packet_size = primary_header.packet_data_length() + Self::primary_header_size();
let packet_bytes = &bytes[..packet_size];
let Ok(packet) = Self::ref_from_bytes(packet_bytes) else {
unreachable!()
};
Ok(packet)
}
pub fn construct(
buffer: &mut [u8],
packet_type: PacketType,
secondary_header_flag: SecondaryHeaderFlag,
apid: Apid,
sequence_flag: SequenceFlag,
sequence_count: PacketSequenceCount,
) -> Result<&mut Self, InvalidPacketDataLength> {
if buffer.len() < 7 {
return Err(InvalidPacketDataLength::LargerThanBuffer {
buffer_length: buffer.len(),
packet_data_length: 1,
});
}
let packet_data_length = buffer.len() - 6;
if packet_data_length == 0 {
return Err(InvalidPacketDataLength::EmptyDataField);
}
let Some(packet_length) = Self::primary_header_size().checked_add(packet_data_length)
else {
return Err(InvalidPacketDataLength::TooLarge { packet_data_length });
};
let buffer_length = buffer.len();
if packet_length > buffer_length {
return Err(InvalidPacketDataLength::LargerThanBuffer {
buffer_length,
packet_data_length,
});
}
let packet_bytes = &mut buffer[..packet_length];
#[allow(clippy::missing_panics_doc)]
let packet = Self::mut_from_bytes(packet_bytes).unwrap();
packet.primary_header.set_apid(apid);
packet.primary_header.initialize_packet_version();
packet.primary_header.set_packet_type(packet_type);
packet
.primary_header
.set_secondary_header_flag(secondary_header_flag);
packet.primary_header.set_sequence_flag(sequence_flag);
packet
.primary_header
.set_packet_sequence_count(sequence_count);
packet
.primary_header
.set_packet_data_length(packet_data_length)?;
Ok(packet)
}
fn validate(&self) -> Result<(), InvalidSpacePacket> {
self.primary_header.validate()?;
let packet_size = self.packet_data_length() + Self::primary_header_size();
let buffer_size = self.packet_length();
if packet_size > buffer_size {
return Err(InvalidSpacePacket::PartialPacket {
packet_size,
buffer_size,
});
}
Ok(())
}
#[must_use]
pub const fn primary_header_size() -> usize {
6
}
#[must_use]
pub const fn packet_version(&self) -> PacketVersionNumber {
self.primary_header.packet_version()
}
#[must_use]
pub const fn packet_type(&self) -> PacketType {
self.primary_header.packet_type()
}
pub fn set_packet_type(&mut self, packet_type: PacketType) {
self.primary_header.set_packet_type(packet_type);
}
#[must_use]
pub const fn secondary_header_flag(&self) -> SecondaryHeaderFlag {
self.primary_header.secondary_header_flag()
}
pub fn set_secondary_header_flag(&mut self, secondary_header_flag: SecondaryHeaderFlag) {
self.primary_header
.set_secondary_header_flag(secondary_header_flag);
}
#[must_use]
pub const fn apid(&self) -> Apid {
self.primary_header.apid()
}
pub fn set_apid(&mut self, apid: Apid) {
self.primary_header.set_apid(apid);
}
#[must_use]
pub const fn sequence_flag(&self) -> SequenceFlag {
self.primary_header.sequence_flag()
}
pub fn set_sequence_flag(&mut self, sequence_flag: SequenceFlag) {
self.primary_header.set_sequence_flag(sequence_flag);
}
#[must_use]
pub const fn packet_sequence_count(&self) -> PacketSequenceCount {
self.primary_header.packet_sequence_count()
}
pub fn set_packet_sequence_count(&mut self, sequence_count: PacketSequenceCount) {
self.primary_header
.set_packet_sequence_count(sequence_count);
}
#[must_use]
pub const fn packet_data_length(&self) -> usize {
self.primary_header.packet_data_length()
}
pub fn set_packet_data_length(
&mut self,
packet_data_length: usize,
) -> Result<(), InvalidPacketDataLength> {
if packet_data_length == 0 {
return Err(InvalidPacketDataLength::EmptyDataField);
}
let buffer_length = self.data_field.len();
if packet_data_length > buffer_length {
return Err(InvalidPacketDataLength::LargerThanBuffer {
packet_data_length,
buffer_length,
});
}
let Ok(stored_data_field_length) = u16::try_from(packet_data_length - 1) else {
return Err(InvalidPacketDataLength::TooLarge { packet_data_length });
};
self.primary_header
.data_length
.set(stored_data_field_length);
Ok(())
}
#[must_use]
pub const fn packet_length(&self) -> usize {
self.data_field.len() + core::mem::size_of::<SpacePacketPrimaryHeader>()
}
#[must_use]
pub const fn packet_data_field(&self) -> &[u8] {
&self.data_field
}
#[must_use]
pub const fn packet_data_field_mut(&mut self) -> &mut [u8] {
&mut self.data_field
}
}
impl core::hash::Hash for SpacePacket {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.primary_header.hash(state);
self.data_field.hash(state);
}
}
impl core::fmt::Debug for SpacePacket {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SpacePacket")
.field("primary_header", &self.primary_header)
.field("data_field", &&self.data_field)
.finish()
}
}
#[repr(C)]
#[derive(
Copy, Clone, Debug, ByteEq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, ByteHash,
)]
pub struct SpacePacketPrimaryHeader {
packet_identification: network_endian::U16,
packet_sequence_control: network_endian::U16,
data_length: network_endian::U16,
}
impl SpacePacketPrimaryHeader {
fn validate(self) -> Result<(), InvalidSpacePacket> {
let version = self.packet_version();
if !version.is_supported() {
return Err(InvalidSpacePacket::UnsupportedPacketVersion { version });
}
if self.apid().is_idle() && self.secondary_header_flag() == SecondaryHeaderFlag::Present {
return Err(InvalidSpacePacket::IdlePacketWithSecondaryHeader);
}
Ok(())
}
#[must_use]
pub const fn primary_header_size() -> usize {
6
}
#[must_use]
pub const fn packet_version(&self) -> PacketVersionNumber {
PacketVersionNumber(self.packet_identification.to_bytes()[0] >> 5)
}
pub fn initialize_packet_version(&mut self) {
self.packet_identification.as_mut_bytes()[0] &= 0b0001_1111;
self.packet_identification.as_mut_bytes()[0] |=
PacketVersionNumber::version1_ccsds_packet().0 << 5;
}
#[must_use]
pub const fn packet_type(&self) -> PacketType {
if (self.packet_identification.to_bytes()[0] & 0x10) == 0x10 {
PacketType::Telecommand
} else {
PacketType::Telemetry
}
}
pub fn set_packet_type(&mut self, packet_type: PacketType) {
self.packet_identification.as_mut_bytes()[0] &= 0b1110_1111;
self.packet_identification.as_mut_bytes()[0] |= (packet_type as u8) << 4;
}
#[must_use]
pub const fn secondary_header_flag(&self) -> SecondaryHeaderFlag {
if (self.packet_identification.to_bytes()[0] & 0x08) == 0x08 {
SecondaryHeaderFlag::Present
} else {
SecondaryHeaderFlag::Absent
}
}
pub fn set_secondary_header_flag(&mut self, secondary_header_flag: SecondaryHeaderFlag) {
self.packet_identification.as_mut_bytes()[0] &= 0b1111_0111;
self.packet_identification.as_mut_bytes()[0] |= (secondary_header_flag as u8) << 3;
}
#[must_use]
pub const fn apid(&self) -> Apid {
Apid(self.packet_identification.get() & 0b0000_0111_1111_1111)
}
pub fn set_apid(&mut self, apid: Apid) {
let apid = apid.0.to_be_bytes();
self.packet_identification.as_mut_bytes()[0] &= 0b1111_1000;
self.packet_identification.as_mut_bytes()[0] |= apid[0] & 0b0000_0111;
self.packet_identification.as_mut_bytes()[1] = apid[1];
}
#[must_use]
pub const fn sequence_flag(&self) -> SequenceFlag {
match self.packet_sequence_control.to_bytes()[0] >> 6i32 {
0b00 => SequenceFlag::Continuation,
0b01 => SequenceFlag::First,
0b10 => SequenceFlag::Last,
0b11 => SequenceFlag::Unsegmented,
_ => unreachable!(), }
}
pub fn set_sequence_flag(&mut self, sequence_flag: SequenceFlag) {
self.packet_sequence_control.as_mut_bytes()[0] &= 0b0011_1111;
self.packet_sequence_control.as_mut_bytes()[0] |= (sequence_flag as u8) << 6;
}
#[must_use]
pub const fn packet_sequence_count(&self) -> PacketSequenceCount {
PacketSequenceCount(self.packet_sequence_control.get() & 0b0011_1111_1111_1111)
}
pub fn set_packet_sequence_count(&mut self, sequence_count: PacketSequenceCount) {
self.packet_sequence_control.as_mut_bytes()[0] &= 0b1100_0000;
self.packet_sequence_control.as_mut_bytes()[0] |=
sequence_count.0.to_be_bytes()[0] & 0b0011_1111;
self.packet_sequence_control.as_mut_bytes()[1] = sequence_count.0.to_be_bytes()[1];
}
#[must_use]
pub const fn packet_data_length(&self) -> usize {
self.data_length.get() as usize + 1
}
pub fn set_packet_data_length(
&mut self,
packet_data_length: usize,
) -> Result<(), InvalidPacketDataLength> {
if packet_data_length == 0 {
return Err(InvalidPacketDataLength::EmptyDataField);
}
let Ok(stored_data_field_length) = u16::try_from(packet_data_length - 1) else {
return Err(InvalidPacketDataLength::TooLarge { packet_data_length });
};
self.data_length.set(stored_data_field_length);
Ok(())
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Error)]
pub enum InvalidSpacePacket {
#[error(
"buffer too small for space packet header (has {length} bytes, at least 6 are required)"
)]
SliceTooSmallForSpacePacketHeader { length: usize },
#[error("unsupported CCSDS Space Packet version: {version:?}")]
UnsupportedPacketVersion { version: PacketVersionNumber },
#[error("detected partial packet (buffer is {buffer_size} bytes, packet {packet_size})")]
PartialPacket {
packet_size: usize,
buffer_size: usize,
},
#[error("idle packet contains a secondary header, this is forbidden")]
IdlePacketWithSecondaryHeader,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Error)]
pub enum InvalidPacketDataLength {
#[error("empty data field requested, this is forbidden")]
EmptyDataField,
#[error(
"requested packet data length ({packet_data_length} bytes) is too large for buffer ({buffer_length} bytes)"
)]
LargerThanBuffer {
packet_data_length: usize,
buffer_length: usize,
},
#[error(
"requested packet data length too large ({packet_data_length} bytes, may be at most `u16::MAX + 1` bytes)"
)]
TooLarge { packet_data_length: usize },
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[repr(transparent)]
pub struct PacketVersionNumber(u8);
impl PacketVersionNumber {
#[must_use]
pub const fn is_supported(&self) -> bool {
matches!(self.0, 0b0000_0000u8)
}
#[must_use]
pub const fn version1_ccsds_packet() -> Self {
Self(0)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(kani, derive(kani::Arbitrary))]
#[repr(u8)]
pub enum PacketType {
Telemetry = 0,
Telecommand = 1,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(kani, derive(kani::Arbitrary))]
#[repr(u8)]
pub enum SecondaryHeaderFlag {
Absent = 0,
Present = 1,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(kani, derive(kani::Arbitrary))]
#[repr(transparent)]
pub struct Apid(u16);
impl Apid {
const MAX: u16 = 0b0000_0111_1111_1111u16;
#[must_use]
pub const fn new(id: u16) -> Self {
assert!(
id <= Self::MAX,
"APIDs may not exceed 2047 (due to maximum of 13 bits in representation)"
);
Self(id)
}
#[cfg(kani)]
fn any_apid() -> Self {
match kani::any() {
any @ 0..=Self::MAX => Self(any),
_ => Self(42),
}
}
#[must_use]
pub const fn is_idle(&self) -> bool {
self.0 == 0x7ff
}
#[must_use]
pub const fn as_u16(&self) -> u16 {
self.0
}
}
impl From<Apid> for u16 {
fn from(value: Apid) -> Self {
value.0
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[cfg_attr(kani, derive(kani::Arbitrary))]
#[repr(u8)]
pub enum SequenceFlag {
Continuation = 0b00,
First = 0b01,
Last = 0b10,
#[default]
Unsegmented = 0b11,
}
#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(kani, derive(kani::Arbitrary))]
pub struct PacketSequenceCount(u16);
impl PacketSequenceCount {
const MAX: u16 = 0b0011_1111_1111_1111u16;
#[must_use]
pub const fn new() -> Self {
Self(0)
}
#[cfg(kani)]
fn any_packet_sequence_count() -> Self {
match kani::any() {
any @ 0..=Self::MAX => Self(any),
_ => Self(42),
}
}
pub const fn increment(&mut self) {
self.0 += 1;
if self.0 > Self::MAX {
self.0 = 0;
}
}
}
#[cfg(kani)]
mod kani_harness {
use super::*;
use ::kani;
#[kani::proof]
fn header_parsing() {
let mut bytes = [0u8; u16::MAX as usize];
bytes[0] = kani::any();
bytes[1] = kani::any();
bytes[2] = kani::any();
bytes[3] = kani::any();
bytes[4] = kani::any();
bytes[5] = kani::any();
bytes[6] = kani::any();
let packet = SpacePacket::parse(&bytes);
if let Ok(packet) = packet {
assert!(packet.packet_length() <= bytes.len());
assert_eq!(
packet.packet_data_field().len(),
packet.packet_data_length()
);
assert!(packet.apid().0 <= 0b0000_0111_1111_1111);
}
}
#[kani::proof]
fn packet_construction() {
let mut bytes = [kani::any(); 1024];
let maximum_packet_length = bytes.len();
let packet_type = kani::any();
let secondary_header_flag = kani::any();
let apid = Apid::any_apid();
let sequence_flag = kani::any();
let sequence_count = PacketSequenceCount::any_packet_sequence_count();
let packet_data_length: u16 = kani::any();
let packet_length = packet_data_length as usize + 6;
if packet_length <= maximum_packet_length {
let packet = SpacePacket::construct(
&mut bytes[..packet_length],
packet_type,
secondary_header_flag,
apid,
sequence_flag,
sequence_count,
);
let valid_request = packet_data_length != 0;
if valid_request {
assert!(packet.is_ok());
}
if !valid_request {
assert!(!packet.is_ok());
}
if let Ok(packet) = packet {
assert!(packet.packet_length() <= maximum_packet_length);
assert_eq!(
packet.packet_data_field().len(),
packet.packet_data_length()
);
assert_eq!(packet.packet_type(), packet_type);
assert_eq!(packet.secondary_header_flag(), secondary_header_flag);
assert_eq!(packet.apid(), apid);
assert_eq!(packet.sequence_flag(), sequence_flag);
assert_eq!(packet.packet_sequence_count(), sequence_count);
assert_eq!(packet.packet_data_length(), packet_data_length as usize);
}
}
}
}
#[test]
fn deserialize_trivial_packet() {
let bytes = &[
0b0000_1000u8,
0b0000_0000u8,
0b1100_0000u8,
0b0000_0000u8,
0b0000_0000u8,
0b0000_0000u8,
0b0000_0000u8,
];
let packet = SpacePacket::parse(bytes).unwrap();
assert_eq!(packet.packet_length(), 7);
assert_eq!(
packet.packet_version(),
PacketVersionNumber::version1_ccsds_packet()
);
assert_eq!(packet.packet_type(), PacketType::Telemetry);
assert_eq!(packet.secondary_header_flag(), SecondaryHeaderFlag::Present);
assert_eq!(packet.apid(), Apid::new(0));
assert_eq!(packet.sequence_flag(), SequenceFlag::Unsegmented);
assert_eq!(packet.packet_sequence_count(), PacketSequenceCount(0));
assert_eq!(packet.packet_data_length(), 1);
assert_eq!(packet.packet_data_field(), &bytes[6..]);
}
#[test]
fn serialize_trivial_packet() {
let mut bytes = [0u8; 7];
let packet = SpacePacket::construct(
&mut bytes,
PacketType::Telemetry,
SecondaryHeaderFlag::Present,
Apid::new(0),
SequenceFlag::Unsegmented,
PacketSequenceCount(0),
)
.unwrap();
assert_eq!(packet.packet_length(), 7);
assert_eq!(
packet.packet_version(),
PacketVersionNumber::version1_ccsds_packet()
);
assert_eq!(packet.packet_type(), PacketType::Telemetry);
assert_eq!(packet.secondary_header_flag(), SecondaryHeaderFlag::Present);
assert_eq!(packet.apid(), Apid::new(0));
assert_eq!(packet.sequence_flag(), SequenceFlag::Unsegmented);
assert_eq!(packet.packet_sequence_count(), PacketSequenceCount(0));
assert_eq!(packet.packet_data_length(), 1);
assert_eq!(
packet.packet_data_field(),
&[
0b0000_1000u8,
0b0000_0000u8,
0b1100_0000u8,
0b0000_0000u8,
0b0000_0000u8,
0b0000_0000u8,
0b0000_0000u8,
][6..]
);
}
#[test]
fn roundtrip() {
use rand::{RngCore, SeedableRng};
let mut rng = rand::rngs::SmallRng::seed_from_u64(42);
let mut buffer = [0u8; 16000];
for _ in 0..10_000 {
let packet_type = match rng.next_u32() & 1 {
0 => PacketType::Telemetry,
1 => PacketType::Telecommand,
_ => unreachable!(),
};
let secondary_header_flag = match rng.next_u32() & 1 {
0 => SecondaryHeaderFlag::Absent,
1 => SecondaryHeaderFlag::Present,
_ => unreachable!(),
};
#[allow(clippy::cast_possible_truncation, reason = "Truncation intended")]
let apid = Apid::new((rng.next_u32() & u32::from(Apid::MAX)) as u16);
let sequence_flag = match rng.next_u32() & 3 {
0b00 => SequenceFlag::Continuation,
0b01 => SequenceFlag::First,
0b10 => SequenceFlag::Last,
0b11 => SequenceFlag::Unsegmented,
_ => unreachable!(),
};
#[allow(clippy::cast_possible_truncation, reason = "Truncation intended")]
let sequence_count =
PacketSequenceCount((rng.next_u32() & u32::from(PacketSequenceCount::MAX)) as u16);
#[allow(clippy::cast_possible_truncation, reason = "Truncation intended")]
let packet_data_length = (rng.next_u32() % (buffer.len() as u32 - 7)) as u16 + 1;
let packet_length = packet_data_length as usize + 6;
let space_packet = SpacePacket::construct(
&mut buffer[..packet_length],
packet_type,
secondary_header_flag,
apid,
sequence_flag,
sequence_count,
)
.unwrap();
assert_eq!(
packet_type,
space_packet.packet_type(),
"Serialized packet type ({:?}) does not match with final deserialized packet type ({:?}) for packet ({:?})",
packet_type,
space_packet.packet_type(),
space_packet
);
assert_eq!(
secondary_header_flag,
space_packet.secondary_header_flag(),
"Serialized secondary header flag ({:?}) does not match with final deserialized secondary header flag ({:?}) for packet ({:?})",
secondary_header_flag,
space_packet.secondary_header_flag(),
space_packet
);
assert_eq!(
apid,
space_packet.apid(),
"Serialized APID ({:?}) does not match with final deserialized APID ({:?}) for packet ({:?})",
apid,
space_packet.apid(),
space_packet
);
assert_eq!(
sequence_flag,
space_packet.sequence_flag(),
"Serialized sequence flag ({:?}) does not match with final deserialized sequence flag ({:?}) for packet ({:?})",
sequence_flag,
space_packet.sequence_flag(),
space_packet
);
assert_eq!(
sequence_count,
space_packet.packet_sequence_count(),
"Serialized sequence count ({:?}) does not match with final deserialized sequence count ({:?}) for packet ({:?})",
sequence_count,
space_packet.packet_sequence_count(),
space_packet
);
assert_eq!(
packet_data_length as usize,
space_packet.packet_data_length(),
"Serialized packet type ({:?}) does not match with final deserialized packet type ({:?}) for packet ({:?})",
packet_data_length,
space_packet.packet_data_length(),
space_packet
);
}
}
#[test]
fn empty_packet_data_field() {
let mut bytes = [0u8; 6];
let result = SpacePacket::construct(
&mut bytes,
PacketType::Telemetry,
SecondaryHeaderFlag::Present,
Apid::new(0),
SequenceFlag::Unsegmented,
PacketSequenceCount(0),
);
assert_eq!(
result,
Err(InvalidPacketDataLength::LargerThanBuffer {
buffer_length: 6,
packet_data_length: 1
})
);
}
#[test]
fn buffer_too_small_for_header_construction() {
let mut buffer = [0u8; 5];
let buffer_length = buffer.len();
let result = SpacePacket::construct(
&mut buffer,
PacketType::Telemetry,
SecondaryHeaderFlag::Present,
Apid::new(0),
SequenceFlag::Unsegmented,
PacketSequenceCount(0),
);
assert_eq!(
result,
Err(InvalidPacketDataLength::LargerThanBuffer {
buffer_length,
packet_data_length: 1
})
);
}
#[test]
fn buffer_too_small_for_parsed_packet() {
use rand::{RngCore, SeedableRng};
let mut rng = rand::rngs::SmallRng::seed_from_u64(42);
let mut buffer = [0u8; 256];
for _ in 0..1000 {
let packet_data_length = (rng.next_u32() % 128) as u16 + 122;
let packet_length = packet_data_length as usize + 6;
let packet = SpacePacket::construct(
&mut buffer[..packet_length],
PacketType::Telemetry,
SecondaryHeaderFlag::Present,
Apid::new(0),
SequenceFlag::Unsegmented,
PacketSequenceCount(0),
)
.unwrap();
let bytes = &packet.as_bytes()[..127];
let result = SpacePacket::parse(bytes);
assert_eq!(
result,
Err(InvalidSpacePacket::PartialPacket {
packet_size: packet_data_length as usize + SpacePacket::primary_header_size(),
buffer_size: bytes.len()
})
);
}
}