diff --git a/src/lib.rs b/src/lib.rs index 48bd6f5..e369293 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,8 @@ use zerocopy::{FromBytes, IntoBytes}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use crate::crc::CRC_CCITT_FALSE; + pub mod cfdp; pub mod crc; pub mod ecss; @@ -83,14 +85,21 @@ mod private { pub trait Sealed {} } +/// Length of the CCSDS header. pub const CCSDS_HEADER_LEN: usize = core::mem::size_of::(); +/// Maximum allowed value for the 11-bit APID. pub const MAX_APID: u11 = u11::MAX; +/// Maximum allowed value for the 14-bit APID. pub const MAX_SEQ_COUNT: u14 = u14::MAX; -#[inline] -pub fn packet_type_in_raw_packet_id(packet_id: u16) -> PacketType { - PacketType::try_from((packet_id >> 12) as u8 & 0b1).unwrap() +/// Checksum types currently provided by the CCSDS packet support. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[non_exhaustive] +pub enum ChecksumType { + Crc16CcittFalse, } /// Generic error type when converting to and from raw byte slices. @@ -118,6 +127,22 @@ pub enum ZeroCopyError { ZeroCopyFromError, } +#[derive(thiserror::Error, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[error("invalid payload length: {0}")] +pub struct InvalidPayloadLengthError(usize); + +#[derive(thiserror::Error, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum CcsdsPacketCreationError { + #[error("byte conversion: {0}")] + ByteConversion(#[from] ByteConversionError), + #[error("invalid payload length: {0}")] + InvalidPayloadLength(#[from] InvalidPayloadLengthError), +} + /// CCSDS packet type enumeration. #[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -141,6 +166,29 @@ pub enum SequenceFlags { Unsegmented = 0b11, } +#[inline] +pub fn packet_type_in_raw_packet_id(packet_id: u16) -> PacketType { + PacketType::try_from((packet_id >> 12) as u8 & 0b1).unwrap() +} + +#[inline] +pub 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, + } +} + +#[inline] +pub fn packet_len_for_user_data_len_with_checksum(data_len: usize) -> usize { + ccsds_packet_len_for_user_data_len(data_len, Some(ChecksumType::Crc16CcittFalse)) +} + /// Abstraction for the CCSDS Packet ID, which forms the last thirteen bits /// of the first two bytes in the CCSDS primary header. #[derive(Debug, Eq, Copy, Clone)] @@ -372,6 +420,11 @@ pub trait CcsdsPacket { } } +pub trait CcsdsTc: CcsdsPacket { + /// Shared access to the packet data field. + fn data(&self) -> &[u8]; +} + pub trait CcsdsPrimaryHeader { fn from_composite_fields( packet_id: PacketId, @@ -708,6 +761,376 @@ pub mod zc { sph_from_other!(SpHeader, crate::SpHeader); } +/// CCSDS packet creator with optional support for a CRC16 CCITT checksum appended to the +/// end of the packet and support for copying into the user buffer directly. +/// +/// This packet creator variant reserves memory based on the required user data length specified +/// 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. +pub struct CcsdsPacketCreatorWithReservedData<'buf> { + sp_header: SpHeader, + buf: &'buf mut [u8], + checksum: Option, +} + +impl<'buf> CcsdsPacketCreatorWithReservedData<'buf> { + #[inline] + pub fn packet_len_for_user_data_with_checksum(user_data_len: usize) -> usize { + ccsds_packet_len_for_user_data_len(user_data_len, Some(ChecksumType::Crc16CcittFalse)) + } + + pub fn new( + mut sp_header: SpHeader, + packet_type: PacketType, + payload_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, + }, + None => CCSDS_HEADER_LEN + payload_len, + }; + if full_packet_len > buf.len() { + return Err(ByteConversionError::ToSliceTooSmall { + found: buf.len(), + expected: full_packet_len, + } + .into()); + } + if full_packet_len - CCSDS_HEADER_LEN - 1 > u16::MAX as usize { + return Err(InvalidPayloadLengthError(payload_len).into()); + } + sp_header.data_len = (full_packet_len - CCSDS_HEADER_LEN - 1) as u16; + sp_header.packet_id.packet_type = packet_type; + + Ok(Self { + sp_header, + buf: buf[0..full_packet_len].as_mut(), + checksum, + }) + } + + pub fn new_with_checksum( + sp_header: SpHeader, + packet_type: PacketType, + payload_len: usize, + buf: &'buf mut [u8], + ) -> Result { + Self::new( + sp_header, + packet_type, + payload_len, + buf, + Some(ChecksumType::Crc16CcittFalse), + ) + } + + pub fn new_tm_with_checksum( + sp_header: SpHeader, + payload_len: usize, + buf: &'buf mut [u8], + ) -> Result { + Self::new( + sp_header, + PacketType::Tm, + payload_len, + buf, + Some(ChecksumType::Crc16CcittFalse), + ) + } + + pub fn new_tc_with_checksum( + sp_header: SpHeader, + payload_len: usize, + buf: &'buf mut [u8], + ) -> Result { + Self::new( + sp_header, + PacketType::Tc, + payload_len, + buf, + Some(ChecksumType::Crc16CcittFalse), + ) + } +} + +impl CcsdsPacketCreatorWithReservedData<'_> { + #[inline] + pub fn packet_len(&self) -> usize { + ::packet_len(self) + } + + #[inline] + pub fn sp_header(&self) -> &SpHeader { + &self.sp_header + } + + #[inline] + pub fn packet_data_mut(&mut self) -> &mut [u8] { + let len = self.buf.len(); + &mut self.buf[CCSDS_HEADER_LEN..len - 2] + } + + #[inline] + pub fn packet_data(&mut self) -> &[u8] { + let len = self.buf.len(); + &self.buf[CCSDS_HEADER_LEN..len - 2] + } + + pub fn finish(self) -> usize { + self.sp_header + .write_to_be_bytes(&mut self.buf[0..CCSDS_HEADER_LEN]) + .unwrap(); + let len = self.buf.len(); + match self.checksum { + Some(ChecksumType::Crc16CcittFalse) => { + let crc16 = CRC_CCITT_FALSE.checksum(&self.buf[0..len - 2]); + self.buf[len - 2..len].copy_from_slice(&crc16.to_be_bytes()); + } + None => (), + }; + len + } +} + +impl CcsdsPacket for CcsdsPacketCreatorWithReservedData<'_> { + #[inline] + fn ccsds_version(&self) -> arbitrary_int::u3 { + self.sp_header.ccsds_version() + } + + #[inline] + fn packet_id(&self) -> PacketId { + self.sp_header.packet_id() + } + + #[inline] + fn psc(&self) -> PacketSequenceControl { + self.sp_header.psc() + } + + #[inline] + fn data_len(&self) -> u16 { + self.sp_header.data_len() + } +} + +/// CCSDS packet creator with optional support for a CRC16 CCITT checksum appended to the +/// end of the packet. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CcsdsPacketCreator<'app_data> { + sp_header: SpHeader, + packet_data: &'app_data [u8], + checksum: Option, +} + +impl<'app_data> CcsdsPacketCreator<'app_data> { + #[inline] + pub fn packet_len_for_user_data_with_checksum(user_data_len: usize) -> usize { + ccsds_packet_len_for_user_data_len(user_data_len, Some(ChecksumType::Crc16CcittFalse)) + } + + pub fn new( + mut sp_header: SpHeader, + packet_type: PacketType, + packet_data: &'app_data [u8], + checksum: Option, + ) -> Result { + sp_header.data_len = (packet_data.len() + + match checksum { + Some(ChecksumType::Crc16CcittFalse) => 2, + 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(), + }; + if full_packet_len - CCSDS_HEADER_LEN - 1 > u16::MAX as usize { + 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; + Ok(Self { + sp_header, + packet_data, + checksum, + }) + } + + pub fn new_with_checksum( + sp_header: SpHeader, + packet_type: PacketType, + app_data: &'app_data [u8], + ) -> Result { + Self::new( + sp_header, + packet_type, + app_data, + Some(ChecksumType::Crc16CcittFalse), + ) + } +} + +impl CcsdsPacketCreator<'_> { + pub fn len_written(&self) -> usize { + ccsds_packet_len_for_user_data_len(self.packet_data.len(), self.checksum) + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + let len_written = self.len_written(); + if len_written > buf.len() { + return Err(ByteConversionError::ToSliceTooSmall { + found: buf.len(), + expected: len_written, + }); + } + self.sp_header + .write_to_be_bytes(&mut buf[0..CCSDS_HEADER_LEN])?; + buf[CCSDS_HEADER_LEN..CCSDS_HEADER_LEN + self.packet_data.len()] + .copy_from_slice(self.packet_data); + match self.checksum { + Some(ChecksumType::Crc16CcittFalse) => { + let crc16 = CRC_CCITT_FALSE.checksum(&buf[0..len_written - 2]); + buf[len_written - 2..len_written].copy_from_slice(&crc16.to_be_bytes()); + } + None => (), + }; + Ok(len_written) + } + + #[cfg(feature = "alloc")] + pub fn to_vec(&self) -> alloc::vec::Vec { + let mut vec = alloc::vec![0u8; self.len_written()]; + // Can not fail, unless we messed up the len_written method.. + self.write_to_bytes(&mut vec).unwrap(); + vec + } +} + +#[derive(thiserror::Error, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum CcsdsPacketReadError { + #[error("byte conversion: {0}")] + ByteConversion(#[from] ByteConversionError), + #[error("CRC error")] + CrcError, +} + +/// CCSDS packet reader structure. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CcsdsPacketReader<'buf> { + sp_header: SpHeader, + packet_data: &'buf [u8], +} + +impl<'buf> CcsdsPacketReader<'buf> { + pub fn new_with_checksum( + buf: &'buf [u8], + ) -> Result, CcsdsPacketReadError> { + Self::new(buf, Some(ChecksumType::Crc16CcittFalse)) + } + + pub fn new( + buf: &'buf [u8], + checksum: Option, + ) -> Result { + let sp_header = SpHeader::from_be_bytes(&buf[0..CCSDS_HEADER_LEN]) + .unwrap() + .0; + if sp_header.packet_len() > buf.len() { + return Err(ByteConversionError::FromSliceTooSmall { + found: sp_header.packet_len(), + expected: buf.len(), + } + .into()); + } + let user_data = match checksum { + Some(ChecksumType::Crc16CcittFalse) => { + if CRC_CCITT_FALSE.checksum(&buf[0..sp_header.packet_len()]) != 0 { + return Err(CcsdsPacketReadError::CrcError); + } + &buf[CCSDS_HEADER_LEN..sp_header.packet_len() - 2] + } + None => &buf[CCSDS_HEADER_LEN..sp_header.packet_len()], + }; + Ok(Self { + sp_header, + packet_data: user_data, + }) + } +} + +impl CcsdsPacketReader<'_> { + #[inline] + pub fn sp_header(&self) -> &SpHeader { + &self.sp_header + } + + #[inline] + pub fn packet_data(&self) -> &[u8] { + self.packet_data + } + + #[inline] + pub fn apid(&self) -> u11 { + self.sp_header.apid() + } + + #[inline] + pub fn packet_id(&self) -> PacketId { + self.sp_header.packet_id() + } + + #[inline] + pub fn psc(&self) -> PacketSequenceControl { + self.sp_header.psc() + } + + #[inline] + pub fn packet_len(&self) -> usize { + ::packet_len(self) + } + + #[inline] + pub fn data_len(&self) -> u16 { + self.sp_header.data_len() + } +} + +impl CcsdsPacket for CcsdsPacketReader<'_> { + #[inline] + fn ccsds_version(&self) -> arbitrary_int::u3 { + self.sp_header.ccsds_version() + } + + #[inline] + fn packet_id(&self) -> PacketId { + self.packet_id() + } + + #[inline] + fn psc(&self) -> PacketSequenceControl { + self.psc() + } + + #[inline] + fn data_len(&self) -> u16 { + self.data_len() + } +} + #[cfg(all(test, feature = "std"))] pub(crate) mod tests { use std::collections::HashSet; @@ -1058,4 +1481,7 @@ pub(crate) mod tests { .0; assert_eq!(sp_header, sp_header_read_back); } + + #[test] + fn test_ccsds_size_function() {} }