Compare commits

..

1 Commits

Author SHA1 Message Date
30d17ea805 API variants which use table-less CRC 2025-05-03 14:41:02 +02:00
8 changed files with 284 additions and 79 deletions

View File

@ -8,6 +8,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
## Changed
- Moved CRC constants/implementations to dedicated `crc` module.
- `crc::CRC_CCITT_FALSE_NO_TABLE` and `crc::CRC_CCITT_FALSE_BIG_TABLE` variants.
- Renamed `PusPacket::crc16` to `PusPacket::opt_crc16`.
## Added
- `WritablePusPacket::write_to_bytes_crc_no_table` and `WritablePusPacket::write_to_bytes_no_crc`
variants.
- `PusTmReader::new_crc_no_table` and `PusTcReader::new_crc_no_table` variants.
- `crc16` methods for PUS TM and PUS TC reader.
- PUS TM and PUS TC reader now return the reader instance directly instead of a tuple of the reader
and the read size. The instance `total_len` method can be used to retrieve the read lenght.
# [v0.13.1] 2025-03-21 # [v0.13.1] 2025-03-21
- Bugfix due to operator precendence for `PusTcSecondaryHeader::pus_version`, - Bugfix due to operator precendence for `PusTcSecondaryHeader::pus_version`,

View File

@ -13,7 +13,7 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
crc = "3" crc = { version = "3" }
delegate = ">=0.8, <=0.13" delegate = ">=0.8, <=0.13"
paste = "1" paste = "1"

View File

@ -1,8 +1,8 @@
//! CFDP Packet Data Unit (PDU) support. //! CFDP Packet Data Unit (PDU) support.
use crate::cfdp::*; use crate::cfdp::*;
use crate::crc::CRC_CCITT_FALSE;
use crate::util::{UnsignedByteField, UnsignedByteFieldU8, UnsignedEnum}; use crate::util::{UnsignedByteField, UnsignedByteFieldU8, UnsignedEnum};
use crate::ByteConversionError; use crate::ByteConversionError;
use crate::CRC_CCITT_FALSE;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;

11
src/crc.rs Normal file
View File

@ -0,0 +1,11 @@
/// 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> =
crc::Crc::<u16, crc::NoTable>::new(&crc::CRC_16_IBM_3740);
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard, using
/// [crc::Table<1>] as the CRC implementation.
pub const CRC_CCITT_FALSE: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_3740);
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard, using
/// a [crc::Table<16>] large table as the CRC implementation.
pub const CRC_CCITT_FALSE_BIG_TABLE: crc::Crc<u16, crc::Table<16>> =
crc::Crc::<u16, crc::Table<16>>::new(&crc::CRC_16_IBM_3740);

View File

@ -3,7 +3,10 @@
//! //!
//! You can find the PUS telecommand types in the [tc] module and the the PUS telemetry //! You can find the PUS telecommand types in the [tc] module and the the PUS telemetry
//! types inside the [tm] module. //! types inside the [tm] module.
use crate::{ByteConversionError, CcsdsPacket, CRC_CCITT_FALSE}; use crate::{
crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE},
ByteConversionError, CcsdsPacket,
};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt::Debug; use core::fmt::Debug;
@ -170,7 +173,7 @@ pub trait PusPacket: CcsdsPacket {
fn service(&self) -> u8; fn service(&self) -> u8;
fn subservice(&self) -> u8; fn subservice(&self) -> u8;
fn user_data(&self) -> &[u8]; fn user_data(&self) -> &[u8];
fn crc16(&self) -> Option<u16>; fn opt_crc16(&self) -> Option<u16>;
} }
pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> { pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> {
@ -207,7 +210,7 @@ pub(crate) fn user_data_from_raw(
} }
} }
pub(crate) fn verify_crc16_ccitt_false_from_raw_to_pus_error( pub fn verify_crc16_ccitt_false_from_raw_to_pus_error(
raw_data: &[u8], raw_data: &[u8],
crc16: u16, crc16: u16,
) -> Result<(), PusError> { ) -> Result<(), PusError> {
@ -216,6 +219,15 @@ pub(crate) fn verify_crc16_ccitt_false_from_raw_to_pus_error(
.ok_or(PusError::ChecksumFailure(crc16)) .ok_or(PusError::ChecksumFailure(crc16))
} }
pub fn verify_crc16_ccitt_false_from_raw_to_pus_error_no_table(
raw_data: &[u8],
crc16: u16,
) -> Result<(), PusError> {
verify_crc16_ccitt_false_from_raw_no_table(raw_data)
.then_some(())
.ok_or(PusError::ChecksumFailure(crc16))
}
pub(crate) fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool { pub(crate) fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool {
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
digest.update(raw_data); digest.update(raw_data);
@ -225,6 +237,15 @@ pub(crate) fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool {
false false
} }
pub(crate) fn verify_crc16_ccitt_false_from_raw_no_table(raw_data: &[u8]) -> bool {
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
digest.update(raw_data);
if digest.finalize() == 0 {
return true;
}
false
}
macro_rules! ccsds_impl { macro_rules! ccsds_impl {
() => { () => {
delegate!(to self.sp_header { delegate!(to self.sp_header {
@ -349,7 +370,34 @@ generic_ecss_enum_typedefs_and_from_impls! {
/// on the serialization of those packets. /// on the serialization of those packets.
pub trait WritablePusPacket { pub trait WritablePusPacket {
fn len_written(&self) -> usize; fn len_written(&self) -> usize;
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError>;
/// Writes the packet to the given slice without writing the CRC.
///
/// The returned size is the written size WITHOUT the CRC.
fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError>;
/// First uses [Self::write_to_bytes_no_crc] to write the packet to the given slice and then
/// uses the [CRC_CCITT_FALS] to calculate the CRC and write it to the slice.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?;
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx)
}
/// First uses [Self::write_to_bytes_no_crc] to write the packet to the given slice and then
/// uses the [CRC_CCITT_FALSE_NO_TABLE] to calculate the CRC and write it to the slice.
fn write_to_bytes_crc_no_table(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?;
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx)
}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> Result<Vec<u8>, PusError> { fn to_vec(&self) -> Result<Vec<u8>, PusError> {
// This is the correct way to do this. See // This is the correct way to do this. See

View File

@ -33,13 +33,14 @@
//! assert_eq!(pus_tc.subservice(), 1); //! assert_eq!(pus_tc.subservice(), 1);
//! assert_eq!(pus_tc.apid(), 0x02); //! assert_eq!(pus_tc.apid(), 0x02);
//! ``` //! ```
use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE};
use crate::ecss::{ use crate::ecss::{
ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw, ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw,
verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion, verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion,
WritablePusPacket, WritablePusPacket,
}; };
use crate::SpHeader;
use crate::{ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, CCSDS_HEADER_LEN}; use crate::{ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, CCSDS_HEADER_LEN};
use crate::{SpHeader, CRC_CCITT_FALSE};
use core::mem::size_of; use core::mem::size_of;
use delegate::delegate; use delegate::delegate;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
@ -50,6 +51,8 @@ use zerocopy::{FromBytes, IntoBytes};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
use super::verify_crc16_ccitt_false_from_raw_to_pus_error_no_table;
/// PUS C secondary header length is fixed /// PUS C secondary header length is fixed
pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::<zc::PusTcSecondaryHeader>(); pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::<zc::PusTcSecondaryHeader>();
pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize = pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize =
@ -342,6 +345,17 @@ impl<'app_data> PusTcCreator<'app_data> {
digest.finalize() digest.finalize()
} }
/// This function calculates and returns the CRC16 for the current packet.
pub fn calc_own_crc16_no_table(&self) -> u16 {
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
digest.update(sph_zc.as_bytes());
let pus_tc_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
digest.update(pus_tc_header.as_bytes());
digest.update(self.app_data);
digest.finalize()
}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> usize { pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> usize {
let sph_zc = crate::zc::SpHeader::from(self.sp_header); let sph_zc = crate::zc::SpHeader::from(self.sp_header);
@ -367,7 +381,7 @@ impl WritablePusPacket for PusTcCreator<'_> {
} }
/// Write the raw PUS byte representation to a provided buffer. /// Write the raw PUS byte representation to a provided buffer.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> { fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = 0; let mut curr_idx = 0;
let tc_header_len = size_of::<zc::PusTcSecondaryHeader>(); let tc_header_len = size_of::<zc::PusTcSecondaryHeader>();
let total_size = self.len_written(); let total_size = self.len_written();
@ -388,10 +402,6 @@ impl WritablePusPacket for PusTcCreator<'_> {
curr_idx += tc_header_len; curr_idx += tc_header_len;
slice[curr_idx..curr_idx + self.app_data.len()].copy_from_slice(self.app_data); slice[curr_idx..curr_idx + self.app_data.len()].copy_from_slice(self.app_data);
curr_idx += self.app_data.len(); curr_idx += self.app_data.len();
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx) Ok(curr_idx)
} }
} }
@ -416,7 +426,7 @@ impl PusPacket for PusTcCreator<'_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(self.calc_own_crc16()) Some(self.calc_own_crc16())
} }
} }
@ -465,7 +475,22 @@ impl<'raw_data> PusTcReader<'raw_data> {
/// Create a [PusTcReader] instance from a raw slice. On success, it returns a tuple containing /// Create a [PusTcReader] instance from a raw slice. On success, it returns a tuple containing
/// the instance and the found byte length of the packet. This function also performs a CRC /// the instance and the found byte length of the packet. This function also performs a CRC
/// check and will return an appropriate [PusError] if the check fails. /// check and will return an appropriate [PusError] if the check fails.
pub fn new(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> { pub fn new(slice: &'raw_data [u8]) -> Result<Self, PusError> {
let pus_tc = Self::new_no_crc_check(slice)?;
verify_crc16_ccitt_false_from_raw_to_pus_error(pus_tc.raw_data(), pus_tc.crc16())?;
Ok(pus_tc)
}
/// Similar to [PusTcReader::new], but uses a table-less CRC16 algorithm which can reduce
/// binary size and memory usage.
pub fn new_crc_no_table(slice: &'raw_data [u8]) -> Result<Self, PusError> {
let pus_tc = Self::new_no_crc_check(slice)?;
verify_crc16_ccitt_false_from_raw_to_pus_error_no_table(pus_tc.raw_data(), pus_tc.crc16())?;
Ok(pus_tc)
}
/// Creates a new instance without performing a CRC check.
pub fn new_no_crc_check(slice: &'raw_data [u8]) -> Result<Self, PusError> {
let raw_data_len = slice.len(); let raw_data_len = slice.len();
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA { if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
return Err(ByteConversionError::FromSliceTooSmall { return Err(ByteConversionError::FromSliceTooSmall {
@ -498,15 +523,13 @@ impl<'raw_data> PusTcReader<'raw_data> {
.map_err(|_| ByteConversionError::ZeroCopyFromError)?; .map_err(|_| ByteConversionError::ZeroCopyFromError)?;
current_idx += PUC_TC_SECONDARY_HEADER_LEN; current_idx += PUC_TC_SECONDARY_HEADER_LEN;
let raw_data = &slice[0..total_len]; let raw_data = &slice[0..total_len];
let pus_tc = Self { Ok(Self {
sp_header, sp_header,
sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(), sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(),
raw_data, raw_data,
app_data: user_data_from_raw(current_idx, total_len, slice)?, app_data: user_data_from_raw(current_idx, total_len, slice)?,
crc16: crc_from_raw_data(raw_data)?, crc16: crc_from_raw_data(raw_data)?,
}; })
verify_crc16_ccitt_false_from_raw_to_pus_error(raw_data, pus_tc.crc16)?;
Ok((pus_tc, total_len))
} }
#[inline] #[inline]
@ -528,6 +551,11 @@ impl<'raw_data> PusTcReader<'raw_data> {
pub fn sp_header(&self) -> &SpHeader { pub fn sp_header(&self) -> &SpHeader {
&self.sp_header &self.sp_header
} }
#[inline]
pub fn crc16(&self) -> u16 {
self.crc16
}
} }
impl PartialEq for PusTcReader<'_> { impl PartialEq for PusTcReader<'_> {
@ -557,7 +585,7 @@ impl PusPacket for PusTcReader<'_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(self.crc16) Some(self.crc16)
} }
} }
@ -640,7 +668,21 @@ mod tests {
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
assert_eq!(size, 13); assert_eq!(size, 13);
assert_eq!( assert_eq!(
pus_tc.crc16().unwrap(), pus_tc.opt_crc16().unwrap(),
u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap())
);
}
#[test]
fn test_serialization_crc_no_table() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc
.write_to_bytes_crc_no_table(test_buf.as_mut_slice())
.expect("Error writing TC to buffer");
assert_eq!(size, 13);
assert_eq!(
pus_tc.opt_crc16().unwrap(),
u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap()) u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap())
); );
} }
@ -653,9 +695,27 @@ mod tests {
.write_to_bytes(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
assert_eq!(size, 13); assert_eq!(size, 13);
let (tc_from_raw, size) = let tc_from_raw =
PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed"); PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(tc_from_raw.total_len(), 13);
verify_test_tc_with_reader(&tc_from_raw, false, 13);
assert!(tc_from_raw.user_data().is_empty());
verify_test_tc_raw(&test_buf);
verify_crc_no_app_data(&test_buf);
}
#[test]
fn test_deserialization_no_table() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc
.write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer");
assert_eq!(size, 13); assert_eq!(size, 13);
let tc_from_raw =
PusTcReader::new_crc_no_table(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(tc_from_raw.total_len(), 13);
verify_test_tc_with_reader(&tc_from_raw, false, 13); verify_test_tc_with_reader(&tc_from_raw, false, 13);
assert!(tc_from_raw.user_data().is_empty()); assert!(tc_from_raw.user_data().is_empty());
verify_test_tc_raw(&test_buf); verify_test_tc_raw(&test_buf);
@ -667,9 +727,9 @@ mod tests {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
let tc_vec = pus_tc.to_vec().expect("Error writing TC to buffer"); let tc_vec = pus_tc.to_vec().expect("Error writing TC to buffer");
assert_eq!(tc_vec.len(), 13); assert_eq!(tc_vec.len(), 13);
let (tc_from_raw, size) = PusTcReader::new(tc_vec.as_slice()) let tc_from_raw = PusTcReader::new(tc_vec.as_slice())
.expect("Creating PUS TC struct from raw buffer failed"); .expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(size, 13); assert_eq!(tc_from_raw.total_len(), 13);
verify_test_tc_with_reader(&tc_from_raw, false, 13); verify_test_tc_with_reader(&tc_from_raw, false, 13);
assert!(tc_from_raw.user_data().is_empty()); assert!(tc_from_raw.user_data().is_empty());
verify_test_tc_raw(&tc_vec); verify_test_tc_raw(&tc_vec);
@ -692,15 +752,15 @@ mod tests {
.write_to_bytes(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
assert_eq!(size, 16); assert_eq!(size, 16);
let (tc_from_raw, size) = let tc_from_raw =
PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed"); PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(size, 16); assert_eq!(tc_from_raw.total_len(), 16);
verify_test_tc_with_reader(&tc_from_raw, true, 16); verify_test_tc_with_reader(&tc_from_raw, true, 16);
let user_data = tc_from_raw.user_data(); let user_data = tc_from_raw.user_data();
assert_eq!(tc_from_raw.user_data(), tc_from_raw.app_data()); assert_eq!(tc_from_raw.user_data(), tc_from_raw.app_data());
assert_eq!(tc_from_raw.raw_data(), &test_buf[..size]); assert_eq!(tc_from_raw.raw_data(), &test_buf[..size]);
assert_eq!( assert_eq!(
tc_from_raw.crc16().unwrap(), tc_from_raw.opt_crc16().unwrap(),
u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap()) u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap())
); );
assert_eq!(user_data[0], 1); assert_eq!(user_data[0], 1);
@ -715,9 +775,9 @@ mod tests {
pus_tc pus_tc
.write_to_bytes(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
let (tc_from_raw_0, _) = let tc_from_raw_0 =
PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed"); PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
let (tc_from_raw_1, _) = let tc_from_raw_1 =
PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed"); PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(tc_from_raw_0, tc_from_raw_1); assert_eq!(tc_from_raw_0, tc_from_raw_1);
} }
@ -869,7 +929,7 @@ mod tests {
assert_eq!(*tc.sp_header(), comp_header); assert_eq!(*tc.sp_header(), comp_header);
} }
fn verify_test_tc_generic(tc: &(impl CcsdsPacket + PusPacket + GenericPusTcSecondaryHeader)) { fn verify_test_tc_generic(tc: &(impl PusPacket + GenericPusTcSecondaryHeader)) {
assert_eq!(PusPacket::service(tc), 17); assert_eq!(PusPacket::service(tc), 17);
assert_eq!(GenericPusTcSecondaryHeader::service(tc), 17); assert_eq!(GenericPusTcSecondaryHeader::service(tc), 17);
assert_eq!(PusPacket::subservice(tc), 1); assert_eq!(PusPacket::subservice(tc), 1);
@ -933,8 +993,8 @@ mod tests {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
let mut buf = [0; 32]; let mut buf = [0; 32];
pus_tc.write_to_bytes(&mut buf).unwrap(); pus_tc.write_to_bytes(&mut buf).unwrap();
assert_eq!(pus_tc, PusTcReader::new(&buf).unwrap().0); assert_eq!(pus_tc, PusTcReader::new(&buf).unwrap());
assert_eq!(PusTcReader::new(&buf).unwrap().0, pus_tc); assert_eq!(PusTcReader::new(&buf).unwrap(), pus_tc);
} }
#[test] #[test]

View File

@ -35,13 +35,14 @@
//! println!("{:?}", &test_buf[0..written_size]); //! println!("{:?}", &test_buf[0..written_size]);
//! //!
//! // Deserialize from the raw byte representation //! // Deserialize from the raw byte representation
//! let (ping_tm_reader, read_size) = PusTmReader::new(&test_buf, 7).expect("Deserialization failed"); //! let ping_tm_reader = PusTmReader::new(&test_buf, 7).expect("Deserialization failed");
//! assert_eq!(written_size, read_size); //! assert_eq!(written_size, ping_tm_reader.total_len());
//! assert_eq!(ping_tm_reader.service(), 17); //! assert_eq!(ping_tm_reader.service(), 17);
//! assert_eq!(ping_tm_reader.subservice(), 2); //! assert_eq!(ping_tm_reader.subservice(), 2);
//! assert_eq!(ping_tm_reader.apid(), 0x02); //! assert_eq!(ping_tm_reader.apid(), 0x02);
//! assert_eq!(ping_tm_reader.timestamp(), &time_buf); //! assert_eq!(ping_tm_reader.timestamp(), &time_buf);
//! ``` //! ```
use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE};
use crate::ecss::{ use crate::ecss::{
calc_pus_crc16, ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw, calc_pus_crc16, ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw,
verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion, verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion,
@ -49,7 +50,7 @@ use crate::ecss::{
}; };
use crate::{ use crate::{
ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SpHeader, CCSDS_HEADER_LEN, ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SpHeader, CCSDS_HEADER_LEN,
CRC_CCITT_FALSE, MAX_APID, MAX_SEQ_COUNT, MAX_APID, MAX_SEQ_COUNT,
}; };
use core::mem::size_of; use core::mem::size_of;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@ -64,6 +65,8 @@ use crate::time::{TimeWriter, TimestampError};
use self::zc::PusTmSecHeaderWithoutTimestamp; use self::zc::PusTmSecHeaderWithoutTimestamp;
use super::verify_crc16_ccitt_false_from_raw_to_pus_error_no_table;
pub trait IsPusTelemetry {} pub trait IsPusTelemetry {}
/// Length without timestamp /// Length without timestamp
@ -389,6 +392,29 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> {
/// Write the raw PUS byte representation to a provided buffer. /// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> { pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?;
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx)
}
/// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes_crc_no_table(
&self,
slice: &mut [u8],
) -> Result<usize, ByteConversionError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?;
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx)
}
/// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> {
let mut curr_idx = 0; let mut curr_idx = 0;
let total_size = self.len_written(); let total_size = self.len_written();
if total_size > slice.len() { if total_size > slice.len() {
@ -411,10 +437,6 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> {
curr_idx += self.sec_header.timestamp.len(); curr_idx += self.sec_header.timestamp.len();
slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data); slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data);
curr_idx += self.source_data.len(); curr_idx += self.source_data.len();
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx) Ok(curr_idx)
} }
@ -446,8 +468,8 @@ impl WritablePusPacket for PusTmCreator<'_, '_> {
+ self.source_data.len() + self.source_data.len()
} }
/// Write the raw PUS byte representation to a provided buffer. /// Write the raw PUS byte representation to a provided buffer.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> { fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes(self, slice)?) Ok(Self::write_to_bytes_no_crc(self, slice)?)
} }
} }
@ -479,7 +501,7 @@ impl PusPacket for PusTmCreator<'_, '_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(self.calc_own_crc16()) Some(self.calc_own_crc16())
} }
} }
@ -534,7 +556,26 @@ impl<'raw_data> PusTmReader<'raw_data> {
/// ///
/// This function will check the CRC-16 of the PUS packet and will return an appropriate /// This function will check the CRC-16 of the PUS packet and will return an appropriate
/// [PusError] if the check fails. /// [PusError] if the check fails.
pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<(Self, usize), PusError> { pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<Self, PusError> {
let tc = Self::new_no_crc_check(slice, timestamp_len)?;
verify_crc16_ccitt_false_from_raw_to_pus_error(tc.raw_data(), tc.crc16)?;
Ok(tc)
}
/// Like [PusTmReader::new] but uses a table-less CRC implementation.
pub fn new_crc_no_table(
slice: &'raw_data [u8],
timestamp_len: usize,
) -> Result<Self, PusError> {
let tc = Self::new_no_crc_check(slice, timestamp_len)?;
verify_crc16_ccitt_false_from_raw_to_pus_error_no_table(tc.raw_data(), tc.crc16)?;
Ok(tc)
}
pub fn new_no_crc_check(
slice: &'raw_data [u8],
timestamp_len: usize,
) -> Result<Self, PusError> {
let raw_data_len = slice.len(); let raw_data_len = slice.len();
if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA { if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
return Err(ByteConversionError::FromSliceTooSmall { return Err(ByteConversionError::FromSliceTooSmall {
@ -572,15 +613,13 @@ impl<'raw_data> PusTmReader<'raw_data> {
}; };
current_idx += timestamp_len; current_idx += timestamp_len;
let raw_data = &slice[0..total_len]; let raw_data = &slice[0..total_len];
let pus_tm = Self { Ok(Self {
sp_header, sp_header,
sec_header: PusTmSecondaryHeader::try_from(zc_sec_header_wrapper).unwrap(), sec_header: PusTmSecondaryHeader::try_from(zc_sec_header_wrapper).unwrap(),
raw_data: &slice[0..total_len], raw_data: &slice[0..total_len],
source_data: user_data_from_raw(current_idx, total_len, slice)?, source_data: user_data_from_raw(current_idx, total_len, slice)?,
crc16: crc_from_raw_data(raw_data)?, crc16: crc_from_raw_data(raw_data)?,
}; })
verify_crc16_ccitt_false_from_raw_to_pus_error(raw_data, pus_tm.crc16)?;
Ok((pus_tm, total_len))
} }
#[inline] #[inline]
@ -598,6 +637,11 @@ impl<'raw_data> PusTmReader<'raw_data> {
self.sec_header.timestamp self.sec_header.timestamp
} }
#[inline]
pub fn crc16(&self) -> u16 {
self.crc16
}
/// This function will return the slice [Self] was constructed from. /// This function will return the slice [Self] was constructed from.
#[inline] #[inline]
pub fn raw_data(&self) -> &[u8] { pub fn raw_data(&self) -> &[u8] {
@ -634,8 +678,8 @@ impl PusPacket for PusTmReader<'_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(self.crc16) Some(self.crc16())
} }
} }
@ -822,7 +866,7 @@ impl PusPacket for PusTmZeroCopyWriter<'_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(u16::from_be_bytes( Some(u16::from_be_bytes(
self.raw_tm[self.sp_header().total_len() - 2..self.sp_header().total_len()] self.raw_tm[self.sp_header().total_len() - 2..self.sp_header().total_len()]
.try_into() .try_into()
@ -918,7 +962,19 @@ mod tests {
.write_to_bytes(&mut buf) .write_to_bytes(&mut buf)
.expect("Serialization failed"); .expect("Serialization failed");
assert_eq!(ser_len, 22); assert_eq!(ser_len, 22);
verify_raw_ping_reply(pus_tm.crc16().unwrap(), &buf); verify_raw_ping_reply(pus_tm.opt_crc16().unwrap(), &buf);
}
#[test]
fn test_serialization_no_source_data_no_table() {
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm
.write_to_bytes_crc_no_table(&mut buf)
.expect("Serialization failed");
assert_eq!(ser_len, 22);
verify_raw_ping_reply(pus_tm.opt_crc16().unwrap(), &buf);
} }
#[test] #[test]
@ -955,9 +1011,9 @@ mod tests {
let pus_tm = base_ping_reply_full_ctor(timestamp); let pus_tm = base_ping_reply_full_ctor(timestamp);
let tm_vec = pus_tm.to_vec().expect("Serialization failed"); let tm_vec = pus_tm.to_vec().expect("Serialization failed");
assert_eq!(tm_vec.len(), 22); assert_eq!(tm_vec.len(), 22);
let (tm_deserialized, size) = let tm_deserialized =
PusTmReader::new(tm_vec.as_slice(), 7).expect("Deserialization failed"); PusTmReader::new(tm_vec.as_slice(), 7).expect("Deserialization failed");
assert_eq!(tm_vec.len(), size); assert_eq!(tm_vec.len(), tm_deserialized.total_len());
verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp()); verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp());
} }
#[test] #[test]
@ -969,13 +1025,32 @@ mod tests {
.write_to_bytes(&mut buf) .write_to_bytes(&mut buf)
.expect("Serialization failed"); .expect("Serialization failed");
assert_eq!(ser_len, 22); assert_eq!(ser_len, 22);
let (tm_deserialized, size) = PusTmReader::new(&buf, 7).expect("Deserialization failed"); let tm_deserialized = PusTmReader::new(&buf, 7).expect("Deserialization failed");
assert_eq!(ser_len, size); assert_eq!(ser_len, tm_deserialized.total_len());
assert_eq!(tm_deserialized.user_data(), tm_deserialized.source_data()); assert_eq!(tm_deserialized.user_data(), tm_deserialized.source_data());
assert_eq!(tm_deserialized.raw_data(), &buf[..ser_len]); assert_eq!(tm_deserialized.raw_data(), &buf[..ser_len]);
assert_eq!(tm_deserialized.crc16().unwrap(), pus_tm.crc16().unwrap()); assert_eq!(tm_deserialized.crc16(), pus_tm.opt_crc16().unwrap());
verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp()); verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp());
} }
#[test]
fn test_deserialization_no_table() {
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm
.write_to_bytes(&mut buf)
.expect("Serialization failed");
assert_eq!(ser_len, 22);
let tm_deserialized =
PusTmReader::new_crc_no_table(&buf, 7).expect("Deserialization failed");
assert_eq!(ser_len, tm_deserialized.total_len());
assert_eq!(tm_deserialized.user_data(), tm_deserialized.source_data());
assert_eq!(tm_deserialized.raw_data(), &buf[..ser_len]);
assert_eq!(tm_deserialized.crc16(), pus_tm.opt_crc16().unwrap());
verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp());
}
#[test] #[test]
fn test_deserialization_faulty_crc() { fn test_deserialization_faulty_crc() {
let timestamp = dummy_timestamp(); let timestamp = dummy_timestamp();
@ -1042,7 +1117,7 @@ mod tests {
let res = pus_tm.append_to_vec(&mut vec); let res = pus_tm.append_to_vec(&mut vec);
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 22); assert_eq!(res.unwrap(), 22);
verify_raw_ping_reply(pus_tm.crc16().unwrap(), vec.as_slice()); verify_raw_ping_reply(pus_tm.opt_crc16().unwrap(), vec.as_slice());
} }
#[test] #[test]
@ -1152,7 +1227,7 @@ mod tests {
let pus_tm = base_ping_reply_full_ctor(timestamp); let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf = [0; 32]; let mut buf = [0; 32];
pus_tm.write_to_bytes(&mut buf).unwrap(); pus_tm.write_to_bytes(&mut buf).unwrap();
assert_eq!(pus_tm, PusTmReader::new(&buf, timestamp.len()).unwrap().0); assert_eq!(pus_tm, PusTmReader::new(&buf, timestamp.len()).unwrap());
} }
#[test] #[test]
@ -1172,9 +1247,8 @@ mod tests {
assert!(!writer.set_apid(MAX_SEQ_COUNT + 1)); assert!(!writer.set_apid(MAX_SEQ_COUNT + 1));
writer.finish(); writer.finish();
// This performs all necessary checks, including the CRC check. // This performs all necessary checks, including the CRC check.
let (tm_read_back, tm_size_read_back) = let tm_read_back = PusTmReader::new(&buf, 7).expect("Re-creating PUS TM failed");
PusTmReader::new(&buf, 7).expect("Re-creating PUS TM failed"); assert_eq!(tm_read_back.total_len(), tm_size);
assert_eq!(tm_size_read_back, tm_size);
assert_eq!(tm_read_back.msg_counter(), 100); assert_eq!(tm_read_back.msg_counter(), 100);
assert_eq!(tm_read_back.dest_id(), 55); assert_eq!(tm_read_back.dest_id(), 55);
assert_eq!(tm_read_back.seq_count(), MAX_SEQ_COUNT); assert_eq!(tm_read_back.seq_count(), MAX_SEQ_COUNT);
@ -1222,7 +1296,7 @@ mod tests {
assert_eq!(writer.sec_header_without_timestamp().msg_counter(), 100); assert_eq!(writer.sec_header_without_timestamp().msg_counter(), 100);
assert_eq!(writer.user_data(), DUMMY_DATA); assert_eq!(writer.user_data(), DUMMY_DATA);
// Need to check crc16 before finish, because finish will update the CRC. // Need to check crc16 before finish, because finish will update the CRC.
let crc16 = writer.crc16(); let crc16 = writer.opt_crc16();
assert!(crc16.is_some()); assert!(crc16.is_some());
assert_eq!(crc16.unwrap(), crc16_raw); assert_eq!(crc16.unwrap(), crc16_raw);
writer.finish(); writer.finish();
@ -1240,8 +1314,8 @@ mod tests {
let pus_tm = base_ping_reply_full_ctor(timestamp); let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf = [0; 32]; let mut buf = [0; 32];
pus_tm.write_to_bytes(&mut buf).unwrap(); pus_tm.write_to_bytes(&mut buf).unwrap();
let (tm_0, _) = PusTmReader::new(&buf, timestamp.len()).unwrap(); let tm_0 = PusTmReader::new(&buf, timestamp.len()).unwrap();
let (tm_1, _) = PusTmReader::new(&buf, timestamp.len()).unwrap(); let tm_1 = PusTmReader::new(&buf, timestamp.len()).unwrap();
assert_eq!(tm_0, tm_1); assert_eq!(tm_0, tm_1);
} }
#[test] #[test]

View File

@ -62,7 +62,6 @@ extern crate alloc;
extern crate std; extern crate std;
use core::{fmt::Debug, hash::Hash}; use core::{fmt::Debug, hash::Hash};
use crc::{Crc, CRC_16_IBM_3740};
use delegate::delegate; use delegate::delegate;
use zerocopy::{FromBytes, IntoBytes}; use zerocopy::{FromBytes, IntoBytes};
@ -70,6 +69,7 @@ use zerocopy::{FromBytes, IntoBytes};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod cfdp; pub mod cfdp;
pub mod crc;
pub mod ecss; pub mod ecss;
pub mod seq_count; pub mod seq_count;
pub mod time; pub mod time;
@ -81,9 +81,6 @@ mod private {
pub const CCSDS_HEADER_LEN: usize = core::mem::size_of::<crate::zc::SpHeader>(); pub const CCSDS_HEADER_LEN: usize = core::mem::size_of::<crate::zc::SpHeader>();
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard.
pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
pub const MAX_APID: u16 = 2u16.pow(11) - 1; pub const MAX_APID: u16 = 2u16.pow(11) - 1;
pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1; pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1;