start adding tests

This commit is contained in:
Robin Mueller
2025-10-29 13:10:37 +01:00
parent 5b0a24eea4
commit dc27edf7ac
4 changed files with 182 additions and 43 deletions

View File

@@ -1,3 +1,7 @@
//! # CRC checksum support.
//!
//! Thin wrapper around the [crc] crate.
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard, using
/// a [crc::NoTable] as the CRC implementation.
pub const CRC_CCITT_FALSE_NO_TABLE: crc::Crc<u16, crc::NoTable> =

View File

@@ -127,6 +127,7 @@ pub enum ZeroCopyError {
ZeroCopyFromError,
}
/// Invalid payload length which is bounded by [u16::MAX]
#[derive(thiserror::Error, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -171,21 +172,33 @@ pub fn packet_type_in_raw_packet_id(packet_id: u16) -> PacketType {
PacketType::try_from((packet_id >> 12) as u8 & 0b1).unwrap()
}
/// Calculate the full CCSDS packet length for a given user data length and optional checksum type.
///
/// Returns [None] if the calculated length allowed by the CCSDS data length field.
#[inline]
pub fn ccsds_packet_len_for_user_data_len(
pub const fn ccsds_packet_len_for_user_data_len(
data_len: usize,
checksum: Option<ChecksumType>,
) -> usize {
data_len
+ CCSDS_HEADER_LEN
+ match checksum {
) -> Option<usize> {
// Special case: A value of zero is not allowed for the data length field.
if data_len == 0 {
return Some(7);
}
let checksum_len = match checksum {
Some(ChecksumType::Crc16CcittFalse) => 2,
None => 0,
};
let len = data_len
.saturating_add(CCSDS_HEADER_LEN)
.saturating_add(checksum_len);
if len - CCSDS_HEADER_LEN - 1 > u16::MAX as usize {
return None;
}
Some(len)
}
#[inline]
pub fn packet_len_for_user_data_len_with_checksum(data_len: usize) -> usize {
pub fn packet_len_for_user_data_len_with_checksum(data_len: usize) -> Option<usize> {
ccsds_packet_len_for_user_data_len(data_len, Some(ChecksumType::Crc16CcittFalse))
}
@@ -267,6 +280,7 @@ impl PacketId {
self.apid = apid;
}
/// 11-bit CCSDS Application Process ID (APID) field.
#[inline]
pub const fn apid(&self) -> u11 {
self.apid
@@ -447,16 +461,16 @@ pub trait CcsdsPrimaryHeader {
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SpHeader {
pub struct SpacePacketHeader {
pub version: u3,
pub packet_id: PacketId,
pub psc: PacketSequenceControl,
pub data_len: u16,
}
pub type SpacePacketHeader = SpHeader;
pub type SpHeader = SpacePacketHeader;
impl Default for SpHeader {
impl Default for SpacePacketHeader {
/// The default function sets the sequence flag field to [SequenceFlags::Unsegmented] and the
/// data length to 0.
#[inline]
@@ -473,8 +487,8 @@ impl Default for SpHeader {
}
}
impl SpHeader {
pub const HEADER_LEN: usize = CCSDS_HEADER_LEN;
impl SpacePacketHeader {
pub const LENGTH: usize = CCSDS_HEADER_LEN;
#[inline]
pub const fn new(packet_id: PacketId, psc: PacketSequenceControl, data_len: u16) -> Self {
@@ -490,7 +504,7 @@ impl SpHeader {
/// length to 0.
#[inline]
pub const fn new_from_apid(apid: u11) -> Self {
SpHeader {
Self {
version: u3::new(0b000),
packet_id: PacketId::new(PacketType::Tm, false, apid),
psc: PacketSequenceControl {
@@ -560,7 +574,7 @@ impl SpHeader {
/// Retrieve the total packet size based on the data length field
#[inline]
pub fn packet_len(&self) -> usize {
usize::from(self.data_len()) + Self::HEADER_LEN + 1
usize::from(self.data_len()) + Self::LENGTH + 1
}
#[inline]
@@ -592,15 +606,15 @@ impl SpHeader {
/// This function also returns the remaining part of the passed slice starting past the read
/// CCSDS header.
pub fn from_be_bytes(buf: &[u8]) -> Result<(Self, &[u8]), ByteConversionError> {
if buf.len() < Self::HEADER_LEN {
if buf.len() < Self::LENGTH {
return Err(ByteConversionError::FromSliceTooSmall {
found: buf.len(),
expected: CCSDS_HEADER_LEN,
});
}
// Unwrap okay, this can not fail.
let zc_header = zc::SpHeader::read_from_bytes(&buf[0..Self::HEADER_LEN]).unwrap();
Ok((Self::from(zc_header), &buf[Self::HEADER_LEN..]))
let zc_header = zc::SpHeader::read_from_bytes(&buf[0..Self::LENGTH]).unwrap();
Ok((Self::from(zc_header), &buf[Self::LENGTH..]))
}
/// Write the header to a raw buffer using big endian format. This function returns the
@@ -609,7 +623,7 @@ impl SpHeader {
&self,
buf: &'a mut [u8],
) -> Result<&'a mut [u8], ByteConversionError> {
if buf.len() < Self::HEADER_LEN {
if buf.len() < Self::LENGTH {
return Err(ByteConversionError::FromSliceTooSmall {
found: buf.len(),
expected: CCSDS_HEADER_LEN,
@@ -617,48 +631,53 @@ impl SpHeader {
}
let zc_header: zc::SpHeader = zc::SpHeader::from(*self);
// Unwrap okay, this can not fail.
zc_header.write_to(&mut buf[0..Self::HEADER_LEN]).unwrap();
Ok(&mut buf[Self::HEADER_LEN..])
zc_header.write_to(&mut buf[0..Self::LENGTH]).unwrap();
Ok(&mut buf[Self::LENGTH..])
}
/// Create a vector containing the CCSDS header.
#[cfg(feature = "alloc")]
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut vec = alloc::vec![0; Self::HEADER_LEN];
let mut vec = alloc::vec![0; Self::LENGTH];
// This can not fail.
self.write_to_be_bytes(&mut vec[..]).unwrap();
vec
}
}
impl CcsdsPacket for SpHeader {
impl CcsdsPacket for SpacePacketHeader {
/// CCSDS version field.
#[inline]
fn ccsds_version(&self) -> u3 {
self.version
}
/// Full packet length.
#[inline]
fn packet_len(&self) -> usize {
self.packet_len()
}
/// CCSDS packet ID field.
#[inline]
fn packet_id(&self) -> PacketId {
self.packet_id
}
/// CCSDS packet sequence control.
#[inline]
fn psc(&self) -> PacketSequenceControl {
self.psc
}
/// CCSDS data length field.
#[inline]
fn data_len(&self) -> u16 {
self.data_len
}
}
impl CcsdsPrimaryHeader for SpHeader {
impl CcsdsPrimaryHeader for SpacePacketHeader {
#[inline]
fn from_composite_fields(
packet_id: PacketId,
@@ -716,21 +735,25 @@ pub mod zc {
}
impl CcsdsPacket for SpHeader {
/// CCSDS version field.
#[inline]
fn ccsds_version(&self) -> u3 {
u3::new(((self.version_packet_id.get() >> 13) as u8) & 0b111)
}
/// CCSDS packet ID field.
#[inline]
fn packet_id(&self) -> PacketId {
PacketId::from(self.packet_id_raw())
}
/// CCSDS packet sequence control field.
#[inline]
fn psc(&self) -> PacketSequenceControl {
PacketSequenceControl::from(self.psc_raw())
}
/// CCSDS data length field.
#[inline]
fn data_len(&self) -> u16 {
self.data_len.get()
@@ -768,6 +791,8 @@ pub mod zc {
/// by the user and then provides mutable or shared access to that memory. This is useful
/// to avoid an additional slice for the user data and allow copying data directly
/// into the packet.
///
/// Please note that packet creation has to be completed using the [Self::finish] call.
pub struct CcsdsPacketCreatorWithReservedData<'buf> {
sp_header: SpHeader,
buf: &'buf mut [u8],
@@ -775,23 +800,32 @@ pub struct CcsdsPacketCreatorWithReservedData<'buf> {
}
impl<'buf> CcsdsPacketCreatorWithReservedData<'buf> {
pub const HEADER_LEN: usize = CCSDS_HEADER_LEN;
#[inline]
pub fn packet_len_for_user_data_with_checksum(user_data_len: usize) -> usize {
pub fn packet_len_for_user_data_with_checksum(user_data_len: usize) -> Option<usize> {
ccsds_packet_len_for_user_data_len(user_data_len, Some(ChecksumType::Crc16CcittFalse))
}
pub fn new(
mut sp_header: SpHeader,
mut sp_header: SpacePacketHeader,
packet_type: PacketType,
payload_len: usize,
packet_data_len: usize,
buf: &'buf mut [u8],
checksum: Option<ChecksumType>,
) -> Result<Self, CcsdsPacketCreationError> {
let full_packet_len = match checksum {
Some(crc_type) => match crc_type {
ChecksumType::Crc16CcittFalse => CCSDS_HEADER_LEN + payload_len + 2,
ChecksumType::Crc16CcittFalse => CCSDS_HEADER_LEN + packet_data_len + 2,
},
None => CCSDS_HEADER_LEN + payload_len,
None => {
// Special case: At least one byte of user data is required.
if packet_data_len == 0 {
CCSDS_HEADER_LEN + 1
} else {
CCSDS_HEADER_LEN + packet_data_len
}
}
};
if full_packet_len > buf.len() {
return Err(ByteConversionError::ToSliceTooSmall {
@@ -801,7 +835,7 @@ impl<'buf> CcsdsPacketCreatorWithReservedData<'buf> {
.into());
}
if full_packet_len - CCSDS_HEADER_LEN - 1 > u16::MAX as usize {
return Err(InvalidPayloadLengthError(payload_len).into());
return Err(InvalidPayloadLengthError(packet_data_len).into());
}
sp_header.data_len = (full_packet_len - CCSDS_HEADER_LEN - 1) as u16;
sp_header.packet_id.packet_type = packet_type;
@@ -863,23 +897,36 @@ impl CcsdsPacketCreatorWithReservedData<'_> {
<Self as CcsdsPacket>::packet_len(self)
}
/// Space pacekt header.
#[inline]
pub fn sp_header(&self) -> &SpHeader {
&self.sp_header
}
/// Mutable access to the packet data field.
#[inline]
pub fn packet_data_mut(&mut self) -> &mut [u8] {
let len = self.buf.len();
&mut self.buf[CCSDS_HEADER_LEN..len - 2]
match self.checksum {
Some(ChecksumType::Crc16CcittFalse) => &mut self.buf[CCSDS_HEADER_LEN..len - 2],
None => &mut self.buf[CCSDS_HEADER_LEN..len],
}
}
/// Read-only access to the packet data field.
#[inline]
pub fn packet_data(&mut self) -> &[u8] {
let len = self.buf.len();
&self.buf[CCSDS_HEADER_LEN..len - 2]
match self.checksum {
Some(ChecksumType::Crc16CcittFalse) => &self.buf[CCSDS_HEADER_LEN..len - 2],
None => &self.buf[CCSDS_HEADER_LEN..len],
}
}
/// Finish the packet generation process.
///
/// This packet writes the space packet header. It also calculates and appends the CRC
/// checksum when configured to do so.
pub fn finish(self) -> usize {
self.sp_header
.write_to_be_bytes(&mut self.buf[0..CCSDS_HEADER_LEN])
@@ -897,21 +944,25 @@ impl CcsdsPacketCreatorWithReservedData<'_> {
}
impl CcsdsPacket for CcsdsPacketCreatorWithReservedData<'_> {
/// CCSDS version field.
#[inline]
fn ccsds_version(&self) -> arbitrary_int::u3 {
self.sp_header.ccsds_version()
}
/// CCSDS packet ID field.
#[inline]
fn packet_id(&self) -> PacketId {
self.sp_header.packet_id()
}
/// CCSDS packet sequence control field.
#[inline]
fn psc(&self) -> PacketSequenceControl {
self.sp_header.psc()
}
/// CCSDS data length field.
#[inline]
fn data_len(&self) -> u16 {
self.sp_header.data_len()
@@ -930,8 +981,12 @@ pub struct CcsdsPacketCreator<'app_data> {
}
impl<'app_data> CcsdsPacketCreator<'app_data> {
pub const HEADER_LEN: usize = CCSDS_HEADER_LEN;
/// Helper function which can be used to determine the full packet length from the user
/// data length, assuming there is a CRC16 appended at the packet.
#[inline]
pub fn packet_len_for_user_data_with_checksum(user_data_len: usize) -> usize {
pub fn packet_len_for_user_data_with_checksum(user_data_len: usize) -> Option<usize> {
ccsds_packet_len_for_user_data_len(user_data_len, Some(ChecksumType::Crc16CcittFalse))
}
@@ -947,12 +1002,18 @@ impl<'app_data> CcsdsPacketCreator<'app_data> {
None => 0,
}
- 1) as u16;
let full_packet_len = match checksum {
Some(crc_type) => match crc_type {
ChecksumType::Crc16CcittFalse => CCSDS_HEADER_LEN + packet_data.len() + 2,
},
None => CCSDS_HEADER_LEN + packet_data.len(),
None => {
// Special case: At least one byte of user data is required.
if packet_data.is_empty() {
CCSDS_HEADER_LEN + 1
} else {
CCSDS_HEADER_LEN + packet_data.len()
}
}
};
if full_packet_len - CCSDS_HEADER_LEN - 1 > u16::MAX as usize {
return Err(InvalidPayloadLengthError(packet_data.len()).into());
@@ -981,10 +1042,12 @@ impl<'app_data> CcsdsPacketCreator<'app_data> {
}
impl CcsdsPacketCreator<'_> {
/// Full length when written to bytes.
pub fn len_written(&self) -> usize {
ccsds_packet_len_for_user_data_len(self.packet_data.len(), self.checksum)
ccsds_packet_len_for_user_data_len(self.packet_data.len(), self.checksum).unwrap()
}
/// Write the CCSDS packet to the provided buffer.
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
let len_written = self.len_written();
if len_written > buf.len() {
@@ -1007,6 +1070,7 @@ impl CcsdsPacketCreator<'_> {
Ok(len_written)
}
/// Create a CCSDS packet as a vector.
#[cfg(feature = "alloc")]
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut vec = alloc::vec![0u8; self.len_written()];
@@ -1036,6 +1100,8 @@ pub struct CcsdsPacketReader<'buf> {
}
impl<'buf> CcsdsPacketReader<'buf> {
pub const HEADER_LEN: usize = CCSDS_HEADER_LEN;
pub fn new_with_checksum(
buf: &'buf [u8],
) -> Result<CcsdsPacketReader<'buf>, CcsdsPacketReadError> {
@@ -1046,9 +1112,7 @@ impl<'buf> CcsdsPacketReader<'buf> {
buf: &'buf [u8],
checksum: Option<ChecksumType>,
) -> Result<Self, CcsdsPacketReadError> {
let sp_header = SpHeader::from_be_bytes(&buf[0..CCSDS_HEADER_LEN])
.unwrap()
.0;
let sp_header = SpHeader::from_be_bytes(&buf[0..CCSDS_HEADER_LEN])?.0;
if sp_header.packet_len() > buf.len() {
return Err(ByteConversionError::FromSliceTooSmall {
found: sp_header.packet_len(),
@@ -1073,36 +1137,43 @@ impl<'buf> CcsdsPacketReader<'buf> {
}
impl CcsdsPacketReader<'_> {
/// Space pacekt header.
#[inline]
pub fn sp_header(&self) -> &SpHeader {
&self.sp_header
}
/// Read-only access to the packet data field.
#[inline]
pub fn packet_data(&self) -> &[u8] {
self.packet_data
}
/// 11-bit Application Process ID field.
#[inline]
pub fn apid(&self) -> u11 {
self.sp_header.apid()
}
/// CCSDS packet ID field.
#[inline]
pub fn packet_id(&self) -> PacketId {
self.sp_header.packet_id()
}
/// Packet sequence control field.
#[inline]
pub fn psc(&self) -> PacketSequenceControl {
self.sp_header.psc()
}
/// Full packet length with the CCSDS header.
#[inline]
pub fn packet_len(&self) -> usize {
<Self as CcsdsPacket>::packet_len(self)
}
/// Packet data length field.
#[inline]
pub fn data_len(&self) -> u16 {
self.sp_header.data_len()
@@ -1110,21 +1181,25 @@ impl CcsdsPacketReader<'_> {
}
impl CcsdsPacket for CcsdsPacketReader<'_> {
/// CCSDS version field.
#[inline]
fn ccsds_version(&self) -> arbitrary_int::u3 {
self.sp_header.ccsds_version()
}
/// Packet ID field.
#[inline]
fn packet_id(&self) -> PacketId {
self.packet_id()
}
/// Packet sequence control field.
#[inline]
fn psc(&self) -> PacketSequenceControl {
self.psc()
}
/// Packet data length without the CCSDS header.
#[inline]
fn data_len(&self) -> u16 {
self.data_len()
@@ -1140,7 +1215,9 @@ pub(crate) mod tests {
#[cfg(feature = "serde")]
use crate::CcsdsPrimaryHeader;
use crate::{
packet_type_in_raw_packet_id, zc, CcsdsPacket, PacketId, PacketSequenceControl, PacketType,
ccsds_packet_len_for_user_data_len, packet_type_in_raw_packet_id, zc, CcsdsPacket,
CcsdsPacketCreatorWithReservedData, ChecksumType, PacketId, PacketSequenceControl,
PacketType, SpacePacketHeader, CCSDS_HEADER_LEN,
};
use crate::{SequenceFlags, SpHeader};
use alloc::vec;
@@ -1483,5 +1560,58 @@ pub(crate) mod tests {
}
#[test]
fn test_ccsds_size_function() {}
fn test_ccsds_size_function() {
assert_eq!(ccsds_packet_len_for_user_data_len(1, None).unwrap(), 7);
// Special case: One dummy byte is required.
assert_eq!(ccsds_packet_len_for_user_data_len(0, None).unwrap(), 7);
assert_eq!(
ccsds_packet_len_for_user_data_len(1, Some(ChecksumType::Crc16CcittFalse)).unwrap(),
9
);
}
#[test]
fn test_ccsds_size_function_invalid_size_no_checksum() {
// This works, because the data field is the user data length minus 1.
assert!(ccsds_packet_len_for_user_data_len(u16::MAX as usize + 1, None).is_some());
// This does not work, data field length exceeded.
assert!(ccsds_packet_len_for_user_data_len(u16::MAX as usize + 2, None).is_none());
}
#[test]
fn test_ccsds_size_function_invalid_size_with_checksum() {
// 2 less bytes available because of the checksum.
assert!(ccsds_packet_len_for_user_data_len(
u16::MAX as usize - 1,
Some(ChecksumType::Crc16CcittFalse)
)
.is_some());
// This is too much.
assert!(ccsds_packet_len_for_user_data_len(
u16::MAX as usize,
Some(ChecksumType::Crc16CcittFalse)
)
.is_none());
}
#[test]
fn test_ccsds_creator_api() {
let mut buf: [u8; 32] = [0; 32];
let mut packet_creator = CcsdsPacketCreatorWithReservedData::new(
SpacePacketHeader::new_from_apid(u11::new(0x1)),
PacketType::Tc,
4,
&mut buf,
Some(ChecksumType::Crc16CcittFalse),
).unwrap();
assert_eq!(packet_creator.packet_len(), 12);
assert_eq!(packet_creator.data_len(), 5);
assert_eq!(packet_creator.apid().value(), 0x1);
assert_eq!(packet_creator.packet_data_mut(), &mut [0, 0, 0, 0]);
assert_eq!(packet_creator.packet_data(), &[0, 0, 0, 0]);
}
#[test]
fn test_ccsds_creator_creation() {
}
}

View File

@@ -1,3 +1,8 @@
//! # Sequence counter module.
//!
//! CCSDS and ECSS packet standard oftentimes use sequence counters, for example to allow detecting
//! packet gaps. This module provides basic abstractions and helper components to implement
//! sequence counters.
use crate::MAX_SEQ_COUNT;
use arbitrary_int::traits::Integer;
use core::cell::Cell;

View File

@@ -1,4 +1,4 @@
/// # Support of the CCSDS Unified Space Data Link Protocol (USLP)
//! # Support of the CCSDS Unified Space Data Link Protocol (USLP)
use crate::{crc::CRC_CCITT_FALSE, ByteConversionError};
/// Only this version is supported by the library