diff --git a/CHANGELOG.md b/CHANGELOG.md index f8718b1..203a438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [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 - Bugfix due to operator precendence for `PusTcSecondaryHeader::pus_version`, diff --git a/Cargo.toml b/Cargo.toml index f2f58f1..1982050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 [dependencies] -crc = "3" +crc = { version = "3" } delegate = ">=0.8, <=0.13" paste = "1" diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 50f2eba..e1c511e 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -1,8 +1,8 @@ //! CFDP Packet Data Unit (PDU) support. use crate::cfdp::*; +use crate::crc::CRC_CCITT_FALSE; use crate::util::{UnsignedByteField, UnsignedByteFieldU8, UnsignedEnum}; use crate::ByteConversionError; -use crate::CRC_CCITT_FALSE; #[cfg(feature = "alloc")] use alloc::vec::Vec; diff --git a/src/crc.rs b/src/crc.rs new file mode 100644 index 0000000..63ded2c --- /dev/null +++ b/src/crc.rs @@ -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 = + crc::Crc::::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 = crc::Crc::::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> = + crc::Crc::>::new(&crc::CRC_16_IBM_3740); diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index 20ce84d..5e9883a 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -3,7 +3,10 @@ //! //! You can find the PUS telecommand types in the [tc] module and the the PUS telemetry //! 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")] use alloc::vec::Vec; use core::fmt::Debug; @@ -170,7 +173,7 @@ pub trait PusPacket: CcsdsPacket { fn service(&self) -> u8; fn subservice(&self) -> u8; fn user_data(&self) -> &[u8]; - fn crc16(&self) -> Option; + fn opt_crc16(&self) -> Option; } pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result { @@ -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], crc16: u16, ) -> Result<(), PusError> { @@ -216,6 +219,15 @@ pub(crate) fn verify_crc16_ccitt_false_from_raw_to_pus_error( .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 { let mut digest = CRC_CCITT_FALSE.digest(); digest.update(raw_data); @@ -225,6 +237,15 @@ pub(crate) fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool { 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 { () => { delegate!(to self.sp_header { @@ -349,7 +370,34 @@ generic_ecss_enum_typedefs_and_from_impls! { /// on the serialization of those packets. pub trait WritablePusPacket { fn len_written(&self) -> usize; - fn write_to_bytes(&self, slice: &mut [u8]) -> Result; + + /// 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; + + /// 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 { + 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 { + 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")] fn to_vec(&self) -> Result, PusError> { // This is the correct way to do this. See diff --git a/src/ecss/tc.rs b/src/ecss/tc.rs index 437b0b9..69d65ea 100644 --- a/src/ecss/tc.rs +++ b/src/ecss/tc.rs @@ -33,13 +33,14 @@ //! assert_eq!(pus_tc.subservice(), 1); //! assert_eq!(pus_tc.apid(), 0x02); //! ``` +use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE}; use crate::ecss::{ 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, WritablePusPacket, }; +use crate::SpHeader; use crate::{ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, CCSDS_HEADER_LEN}; -use crate::{SpHeader, CRC_CCITT_FALSE}; use core::mem::size_of; use delegate::delegate; use num_enum::{IntoPrimitive, TryFromPrimitive}; @@ -50,6 +51,8 @@ use zerocopy::{FromBytes, IntoBytes}; #[cfg(feature = "alloc")] use alloc::vec::Vec; +use super::verify_crc16_ccitt_false_from_raw_to_pus_error_no_table; + /// PUS C secondary header length is fixed pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::(); pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize = @@ -239,13 +242,13 @@ impl<'app_data> PusTcCreator<'app_data> { /// # Arguments /// /// * `sp_header` - Space packet header information. The correct packet type and the secondary - /// header flag are set correctly by the constructor. + /// header flag are set correctly by the constructor. /// * `sec_header` - Information contained in the data field header, including the service - /// and subservice type + /// and subservice type /// * `app_data` - Custom application data /// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length - /// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set - /// the correct value to this field manually + /// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set + /// the correct value to this field manually #[inline] pub fn new( mut sp_header: SpHeader, @@ -342,6 +345,17 @@ impl<'app_data> PusTcCreator<'app_data> { 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")] pub fn append_to_vec(&self, vec: &mut Vec) -> usize { 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. - fn write_to_bytes(&self, slice: &mut [u8]) -> Result { + fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result { let mut curr_idx = 0; let tc_header_len = size_of::(); let total_size = self.len_written(); @@ -388,10 +402,6 @@ impl WritablePusPacket for PusTcCreator<'_> { curr_idx += tc_header_len; slice[curr_idx..curr_idx + self.app_data.len()].copy_from_slice(self.app_data); 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) } } @@ -416,7 +426,7 @@ impl PusPacket for PusTcCreator<'_> { } #[inline] - fn crc16(&self) -> Option { + fn opt_crc16(&self) -> Option { 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 /// 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. - pub fn new(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> { + pub fn new(slice: &'raw_data [u8]) -> Result { + 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 { + 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 { let raw_data_len = slice.len(); if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA { return Err(ByteConversionError::FromSliceTooSmall { @@ -498,15 +523,13 @@ impl<'raw_data> PusTcReader<'raw_data> { .map_err(|_| ByteConversionError::ZeroCopyFromError)?; current_idx += PUC_TC_SECONDARY_HEADER_LEN; let raw_data = &slice[0..total_len]; - let pus_tc = Self { + Ok(Self { sp_header, sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(), raw_data, app_data: user_data_from_raw(current_idx, total_len, slice)?, 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] @@ -528,6 +551,11 @@ impl<'raw_data> PusTcReader<'raw_data> { pub fn sp_header(&self) -> &SpHeader { &self.sp_header } + + #[inline] + pub fn crc16(&self) -> u16 { + self.crc16 + } } impl PartialEq for PusTcReader<'_> { @@ -557,7 +585,7 @@ impl PusPacket for PusTcReader<'_> { } #[inline] - fn crc16(&self) -> Option { + fn opt_crc16(&self) -> Option { Some(self.crc16) } } @@ -640,7 +668,21 @@ mod tests { .expect("Error writing TC to buffer"); assert_eq!(size, 13); 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()) ); } @@ -653,9 +695,27 @@ mod tests { .write_to_bytes(test_buf.as_mut_slice()) .expect("Error writing TC to buffer"); 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"); + 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); + 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); assert!(tc_from_raw.user_data().is_empty()); verify_test_tc_raw(&test_buf); @@ -667,9 +727,9 @@ mod tests { let pus_tc = base_ping_tc_simple_ctor(); let tc_vec = pus_tc.to_vec().expect("Error writing TC to buffer"); 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"); - assert_eq!(size, 13); + 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(&tc_vec); @@ -692,15 +752,15 @@ mod tests { .write_to_bytes(test_buf.as_mut_slice()) .expect("Error writing TC to buffer"); 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"); - assert_eq!(size, 16); + assert_eq!(tc_from_raw.total_len(), 16); verify_test_tc_with_reader(&tc_from_raw, true, 16); 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.raw_data(), &test_buf[..size]); 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()) ); assert_eq!(user_data[0], 1); @@ -715,9 +775,9 @@ mod tests { pus_tc .write_to_bytes(test_buf.as_mut_slice()) .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"); - let (tc_from_raw_1, _) = + let tc_from_raw_1 = PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed"); assert_eq!(tc_from_raw_0, tc_from_raw_1); } @@ -869,7 +929,7 @@ mod tests { 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!(GenericPusTcSecondaryHeader::service(tc), 17); assert_eq!(PusPacket::subservice(tc), 1); @@ -933,8 +993,8 @@ mod tests { let pus_tc = base_ping_tc_simple_ctor(); let mut buf = [0; 32]; pus_tc.write_to_bytes(&mut buf).unwrap(); - assert_eq!(pus_tc, PusTcReader::new(&buf).unwrap().0); - assert_eq!(PusTcReader::new(&buf).unwrap().0, pus_tc); + assert_eq!(pus_tc, PusTcReader::new(&buf).unwrap()); + assert_eq!(PusTcReader::new(&buf).unwrap(), pus_tc); } #[test] diff --git a/src/ecss/tm.rs b/src/ecss/tm.rs index d1b2792..865d681 100644 --- a/src/ecss/tm.rs +++ b/src/ecss/tm.rs @@ -35,13 +35,14 @@ //! println!("{:?}", &test_buf[0..written_size]); //! //! // Deserialize from the raw byte representation -//! let (ping_tm_reader, read_size) = PusTmReader::new(&test_buf, 7).expect("Deserialization failed"); -//! assert_eq!(written_size, read_size); +//! let ping_tm_reader = PusTmReader::new(&test_buf, 7).expect("Deserialization failed"); +//! assert_eq!(written_size, ping_tm_reader.total_len()); //! assert_eq!(ping_tm_reader.service(), 17); //! assert_eq!(ping_tm_reader.subservice(), 2); //! assert_eq!(ping_tm_reader.apid(), 0x02); //! assert_eq!(ping_tm_reader.timestamp(), &time_buf); //! ``` +use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE}; use crate::ecss::{ 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, @@ -49,7 +50,7 @@ use crate::ecss::{ }; use crate::{ 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; #[cfg(feature = "serde")] @@ -64,6 +65,8 @@ use crate::time::{TimeWriter, TimestampError}; use self::zc::PusTmSecHeaderWithoutTimestamp; +use super::verify_crc16_ccitt_false_from_raw_to_pus_error_no_table; + pub trait IsPusTelemetry {} /// Length without timestamp @@ -277,13 +280,13 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { /// # Arguments /// /// * `sp_header` - Space packet header information. The correct packet type and the secondary - /// header flag are set correctly by the constructor. + /// header flag are set correctly by the constructor. /// * `sec_header` - Information contained in the secondary header, including the service - /// and subservice type + /// and subservice type /// * `source_data` - Custom application data /// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length - /// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set - /// the correct value to this field manually + /// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set + /// the correct value to this field manually #[inline] pub fn new( mut sp_header: SpHeader, @@ -389,6 +392,29 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { /// Write the raw PUS byte representation to a provided buffer. pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result { + 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 { + 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 { let mut curr_idx = 0; let total_size = self.len_written(); if total_size > slice.len() { @@ -411,10 +437,6 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { curr_idx += self.sec_header.timestamp.len(); slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data); 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) } @@ -446,8 +468,8 @@ impl WritablePusPacket for PusTmCreator<'_, '_> { + self.source_data.len() } /// Write the raw PUS byte representation to a provided buffer. - fn write_to_bytes(&self, slice: &mut [u8]) -> Result { - Ok(Self::write_to_bytes(self, slice)?) + fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result { + Ok(Self::write_to_bytes_no_crc(self, slice)?) } } @@ -479,7 +501,7 @@ impl PusPacket for PusTmCreator<'_, '_> { } #[inline] - fn crc16(&self) -> Option { + fn opt_crc16(&self) -> Option { 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 /// [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 { + 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 { + 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 { let raw_data_len = slice.len(); if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA { return Err(ByteConversionError::FromSliceTooSmall { @@ -572,15 +613,13 @@ impl<'raw_data> PusTmReader<'raw_data> { }; current_idx += timestamp_len; let raw_data = &slice[0..total_len]; - let pus_tm = Self { + Ok(Self { sp_header, sec_header: PusTmSecondaryHeader::try_from(zc_sec_header_wrapper).unwrap(), raw_data: &slice[0..total_len], source_data: user_data_from_raw(current_idx, total_len, slice)?, 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] @@ -598,6 +637,11 @@ impl<'raw_data> PusTmReader<'raw_data> { self.sec_header.timestamp } + #[inline] + pub fn crc16(&self) -> u16 { + self.crc16 + } + /// This function will return the slice [Self] was constructed from. #[inline] pub fn raw_data(&self) -> &[u8] { @@ -634,8 +678,8 @@ impl PusPacket for PusTmReader<'_> { } #[inline] - fn crc16(&self) -> Option { - Some(self.crc16) + fn opt_crc16(&self) -> Option { + Some(self.crc16()) } } @@ -822,7 +866,7 @@ impl PusPacket for PusTmZeroCopyWriter<'_> { } #[inline] - fn crc16(&self) -> Option { + fn opt_crc16(&self) -> Option { Some(u16::from_be_bytes( self.raw_tm[self.sp_header().total_len() - 2..self.sp_header().total_len()] .try_into() @@ -918,7 +962,19 @@ mod tests { .write_to_bytes(&mut buf) .expect("Serialization failed"); 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] @@ -955,9 +1011,9 @@ mod tests { let pus_tm = base_ping_reply_full_ctor(timestamp); let tm_vec = pus_tm.to_vec().expect("Serialization failed"); assert_eq!(tm_vec.len(), 22); - let (tm_deserialized, size) = + let tm_deserialized = 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()); } #[test] @@ -969,13 +1025,32 @@ mod tests { .write_to_bytes(&mut buf) .expect("Serialization failed"); assert_eq!(ser_len, 22); - let (tm_deserialized, size) = PusTmReader::new(&buf, 7).expect("Deserialization failed"); - assert_eq!(ser_len, size); + let tm_deserialized = PusTmReader::new(&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().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()); } + + #[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] fn test_deserialization_faulty_crc() { let timestamp = dummy_timestamp(); @@ -1042,7 +1117,7 @@ mod tests { let res = pus_tm.append_to_vec(&mut vec); assert!(res.is_ok()); 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] @@ -1152,7 +1227,7 @@ mod tests { let pus_tm = base_ping_reply_full_ctor(timestamp); let mut buf = [0; 32]; 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] @@ -1172,9 +1247,8 @@ mod tests { assert!(!writer.set_apid(MAX_SEQ_COUNT + 1)); writer.finish(); // This performs all necessary checks, including the CRC check. - let (tm_read_back, tm_size_read_back) = - PusTmReader::new(&buf, 7).expect("Re-creating PUS TM failed"); - assert_eq!(tm_size_read_back, tm_size); + let tm_read_back = PusTmReader::new(&buf, 7).expect("Re-creating PUS TM failed"); + assert_eq!(tm_read_back.total_len(), tm_size); assert_eq!(tm_read_back.msg_counter(), 100); assert_eq!(tm_read_back.dest_id(), 55); 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.user_data(), DUMMY_DATA); // 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_eq!(crc16.unwrap(), crc16_raw); writer.finish(); @@ -1240,8 +1314,8 @@ mod tests { let pus_tm = base_ping_reply_full_ctor(timestamp); let mut buf = [0; 32]; pus_tm.write_to_bytes(&mut buf).unwrap(); - let (tm_0, _) = PusTmReader::new(&buf, timestamp.len()).unwrap(); - let (tm_1, _) = PusTmReader::new(&buf, timestamp.len()).unwrap(); + let tm_0 = PusTmReader::new(&buf, timestamp.len()).unwrap(); + let tm_1 = PusTmReader::new(&buf, timestamp.len()).unwrap(); assert_eq!(tm_0, tm_1); } #[test] diff --git a/src/lib.rs b/src/lib.rs index b824379..1e765a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,8 +26,8 @@ //! //! - [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library. //! - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers -//! like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html). -//! Enabled by the `std` feature. +//! like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html). +//! Enabled by the `std` feature. //! //! ### Optional features //! @@ -62,7 +62,6 @@ extern crate alloc; extern crate std; use core::{fmt::Debug, hash::Hash}; -use crc::{Crc, CRC_16_IBM_3740}; use delegate::delegate; use zerocopy::{FromBytes, IntoBytes}; @@ -70,6 +69,7 @@ use zerocopy::{FromBytes, IntoBytes}; use serde::{Deserialize, Serialize}; pub mod cfdp; +pub mod crc; pub mod ecss; pub mod seq_count; pub mod time; @@ -81,9 +81,6 @@ mod private { pub const CCSDS_HEADER_LEN: usize = core::mem::size_of::(); -/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard. -pub const CRC_CCITT_FALSE: Crc = Crc::::new(&CRC_16_IBM_3740); - pub const MAX_APID: u16 = 2u16.pow(11) - 1; pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1; @@ -452,9 +449,9 @@ pub trait CcsdsPrimaryHeader { /// # Arguments /// /// * `version` - CCSDS version field, occupies the first 3 bits of the raw header. Will generally -/// be set to 0b000 in all constructors provided by this crate. +/// be set to 0b000 in all constructors provided by this crate. /// * `packet_id` - Packet Identifier, which can also be used as a start marker. Occupies the last -/// 13 bits of the first two bytes of the raw header +/// 13 bits of the first two bytes of the raw header /// * `psc` - Packet Sequence Control, occupies the third and fourth byte of the raw header /// * `data_len` - Data length field occupies the fifth and the sixth byte of the raw header #[derive(Debug, PartialEq, Eq, Copy, Clone)]