diff --git a/src/crc.rs b/src/crc.rs index 63ded2c..55bc736 100644 --- a/src/crc.rs +++ b/src/crc.rs @@ -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 = diff --git a/src/lib.rs b/src/lib.rs index e369293..7727087 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, -) -> usize { - data_len - + CCSDS_HEADER_LEN - + match checksum { - Some(ChecksumType::Crc16CcittFalse) => 2, - None => 0, - } +) -> Option { + // 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 { 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 { - 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 { 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, ) -> Result { 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<'_> { ::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 { 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 { 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 { 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, CcsdsPacketReadError> { @@ -1046,9 +1112,7 @@ impl<'buf> CcsdsPacketReader<'buf> { buf: &'buf [u8], checksum: Option, ) -> Result { - 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 { ::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() { + } } diff --git a/src/seq_count.rs b/src/seq_count.rs index 61b013c..c52eb2b 100644 --- a/src/seq_count.rs +++ b/src/seq_count.rs @@ -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; diff --git a/src/uslp/mod.rs b/src/uslp/mod.rs index ddae21f..22359e0 100644 --- a/src/uslp/mod.rs +++ b/src/uslp/mod.rs @@ -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