From 5cd5c1ce6d9f7bea5ffec1ab76736979319bd36a Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 16 May 2025 17:34:37 +0200 Subject: [PATCH] reserved data variants for ECSS TM and TC --- CHANGELOG.md | 6 + src/ecss/mod.rs | 2 +- src/ecss/tc.rs | 291 ++++++++++++++++++++++++++++++++++++++------- src/ecss/tm.rs | 306 ++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 525 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05dee8d..59b0655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +## Added + +- `PusTcCreatorWithReservedAppData` and `PusTmCreatorWithReservedSourceData` constructor variants + which allow writing source/app data into the serialization buffer directly without + requiring an extra buffer. + # [v0.14.0] 2025-05-10 ## Changed diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index 5e9883a..63229d5 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -377,7 +377,7 @@ pub trait WritablePusPacket { 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. + /// uses the [CRC_CCITT_FALSE] 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(); diff --git a/src/ecss/tc.rs b/src/ecss/tc.rs index 92b0d4b..11d5b9b 100644 --- a/src/ecss/tc.rs +++ b/src/ecss/tc.rs @@ -372,6 +372,49 @@ impl<'app_data> PusTcCreator<'app_data> { vec.extend_from_slice(&digest.finalize().to_be_bytes()); appended_len } + + /// Write the raw PUS byte representation to a provided buffer. + pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result { + let writer_unfinalized = self.common_write(slice)?; + Ok(writer_unfinalized.finalize()) + } + + /// Write the raw PUS byte representation to a provided buffer. + pub fn write_to_bytes_crc_no_table( + &self, + slice: &mut [u8], + ) -> Result { + let writer_unfinalized = self.common_write(slice)?; + Ok(writer_unfinalized.finalize_crc_no_table()) + } + + /// Write the raw PUS byte representation to a provided buffer. + pub fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result { + let writer_unfinalized = self.common_write(slice)?; + Ok(writer_unfinalized.finalize_no_crc()) + } + + fn common_write<'a>( + &self, + slice: &'a mut [u8], + ) -> Result, ByteConversionError> { + if self.len_written() > slice.len() { + return Err(ByteConversionError::ToSliceTooSmall { + found: slice.len(), + expected: self.len_written(), + }); + } + let mut writer_unfinalized = PusTcCreatorWithReservedAppData::write_to_bytes_partially( + slice, + self.sp_header, + self.sec_header, + self.app_data.len(), + )?; + writer_unfinalized + .app_data_mut() + .copy_from_slice(self.app_data); + Ok(writer_unfinalized) + } } impl WritablePusPacket for PusTcCreator<'_> { @@ -380,31 +423,17 @@ impl WritablePusPacket for PusTcCreator<'_> { PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len() } - /// Writes the packet to the given slice without writing the CRC. - /// - /// The returned size is the written size WITHOUT the CRC. + /// Write the raw PUS byte representation to a provided buffer. 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(); - if total_size > slice.len() { - return Err(ByteConversionError::ToSliceTooSmall { - found: slice.len(), - expected: total_size, - } - .into()); - } - self.sp_header.write_to_be_bytes(slice)?; - curr_idx += CCSDS_HEADER_LEN; - let sec_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap(); - sec_header - .write_to(&mut slice[curr_idx..curr_idx + tc_header_len]) - .map_err(|_| ByteConversionError::ZeroCopyToError)?; + Ok(Self::write_to_bytes_no_crc(self, slice)?) + } - 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(); - Ok(curr_idx) + fn write_to_bytes(&self, slice: &mut [u8]) -> Result { + Ok(Self::write_to_bytes(self, slice)?) + } + + fn write_to_bytes_crc_no_table(&self, slice: &mut [u8]) -> Result { + Ok(Self::write_to_bytes_crc_no_table(self, slice)?) } } @@ -450,6 +479,133 @@ impl GenericPusTcSecondaryHeader for PusTcCreator<'_> { impl IsPusTelecommand for PusTcCreator<'_> {} +/// A specialized variant of [PusTcCreator] designed for efficiency when handling large source +/// data. +/// +/// Unlike [PusTcCreator], this type does not require the user to provide the application data +/// as a separate slice. Instead, it allows writing the application data directly into the provided +/// serialization buffer. This eliminates the need for an intermediate buffer and the associated +/// memory copy, improving performance, particularly when working with large payloads. +/// +/// **Important:** The total length of the source data must be known and specified in advance +/// to ensure correct serialization behavior. +/// +/// Note that this abstraction intentionally omits certain trait implementations that are available +/// on [PusTcCreator], as they are not applicable in this optimized usage pattern. +pub struct PusTcCreatorWithReservedAppData<'buf> { + buf: &'buf mut [u8], + app_data_offset: usize, + full_len: usize, +} + +impl<'buf> PusTcCreatorWithReservedAppData<'buf> { + /// Generates a new instance with reserved space for the user application data. + /// + /// # Arguments + /// + /// * `sp_header` - Space packet header information. The correct packet type and the secondary + /// header flag are set correctly by the constructor. + /// * `sec_header` - Information contained in the secondary header, including the service + /// and subservice type + /// * `app_data_len` - Custom application data length + #[inline] + pub fn new( + buf: &'buf mut [u8], + mut sp_header: SpHeader, + sec_header: PusTcSecondaryHeader, + app_data_len: usize, + ) -> Result { + sp_header.set_packet_type(PacketType::Tc); + sp_header.set_sec_header_flag(); + let len_written = PUS_TC_MIN_LEN_WITHOUT_APP_DATA + app_data_len; + if len_written > buf.len() { + return Err(ByteConversionError::ToSliceTooSmall { + found: buf.len(), + expected: len_written, + }); + } + sp_header.data_len = len_written as u16 - size_of::() as u16 - 1; + Self::write_to_bytes_partially(buf, sp_header, sec_header, app_data_len) + } + + fn write_to_bytes_partially( + buf: &'buf mut [u8], + sp_header: SpHeader, + sec_header: PusTcSecondaryHeader, + app_data_len: usize, + ) -> Result { + let mut curr_idx = 0; + sp_header.write_to_be_bytes(&mut buf[0..CCSDS_HEADER_LEN])?; + curr_idx += CCSDS_HEADER_LEN; + let sec_header_len = size_of::(); + let sec_header_zc = zc::PusTcSecondaryHeader::try_from(sec_header).unwrap(); + sec_header_zc + .write_to(&mut buf[curr_idx..curr_idx + sec_header_len]) + .map_err(|_| ByteConversionError::ZeroCopyToError)?; + curr_idx += sec_header_len; + let app_data_offset = curr_idx; + curr_idx += app_data_len; + Ok(Self { + buf, + app_data_offset, + full_len: curr_idx + 2, + }) + } + + #[inline] + pub const fn len_written(&self) -> usize { + self.full_len + } + + /// Mutable access to the application data buffer. + #[inline] + pub fn app_data_mut(&mut self) -> &mut [u8] { + &mut self.buf[self.app_data_offset..self.full_len - 2] + } + + /// Access to the source data buffer. + #[inline] + pub fn app_data(&self) -> &[u8] { + &self.buf[self.app_data_offset..self.full_len - 2] + } + + #[inline] + pub fn app_data_len(&self) -> usize { + self.full_len - 2 - self.app_data_offset + } + + /// Finalize the TC packet by calculating and writing the CRC16. + /// + /// Returns the full packet length. + pub fn finalize(self) -> usize { + let mut digest = CRC_CCITT_FALSE.digest(); + digest.update(&self.buf[0..self.full_len - 2]); + self.buf[self.full_len - 2..self.full_len] + .copy_from_slice(&digest.finalize().to_be_bytes()); + self.full_len + } + + /// Finalize the TC packet by calculating and writing the CRC16 using a table-less + /// implementation. + /// + /// Returns the full packet length. + pub fn finalize_crc_no_table(self) -> usize { + let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest(); + digest.update(&self.buf[0..self.full_len - 2]); + self.buf[self.full_len - 2..self.full_len] + .copy_from_slice(&digest.finalize().to_be_bytes()); + self.full_len + } + + /// Finalize the TC packet without writing the CRC16. + /// + /// Returns the length WITHOUT the CRC16. + #[inline] + pub fn finalize_no_crc(self) -> usize { + self.full_len - 2 + } +} + /// This class can be used to read a PUS TC telecommand from raw memory. /// /// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the @@ -627,8 +783,6 @@ impl PartialEq> for PusTcCreator<'_> { #[cfg(all(test, feature = "std"))] mod tests { - use std::error::Error; - use super::*; use crate::ecss::PusVersion::PusC; use crate::ecss::{PusError, PusPacket, WritablePusPacket}; @@ -675,6 +829,32 @@ mod tests { ); } + #[test] + fn test_serialization_with_trait_1() { + let pus_tc = base_ping_tc_simple_ctor(); + let mut test_buf: [u8; 32] = [0; 32]; + let size = WritablePusPacket::write_to_bytes(&pus_tc, 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()) + ); + } + + #[test] + fn test_serialization_with_trait_2() { + let pus_tc = base_ping_tc_simple_ctor(); + let mut test_buf: [u8; 32] = [0; 32]; + let size = WritablePusPacket::write_to_bytes_crc_no_table(&pus_tc, 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()) + ); + } + #[test] fn test_serialization_crc_no_table() { let pus_tc = base_ping_tc_simple_ctor(); @@ -701,6 +881,17 @@ mod tests { assert_eq!(test_buf[12], 0); } + #[test] + fn test_serialization_no_crc_with_trait() { + let pus_tc = base_ping_tc_simple_ctor(); + let mut test_buf: [u8; 32] = [0; 32]; + let size = WritablePusPacket::write_to_bytes_no_crc(&pus_tc, test_buf.as_mut_slice()) + .expect("error writing tc to buffer"); + assert_eq!(size, 11); + assert_eq!(test_buf[11], 0); + assert_eq!(test_buf[12], 0); + } + #[test] fn test_deserialization() { let pus_tc = base_ping_tc_simple_ctor(); @@ -718,6 +909,28 @@ mod tests { verify_crc_no_app_data(&test_buf); } + #[test] + fn test_deserialization_alt_ctor() { + let sph = SpHeader::new_for_unseg_tc_checked(0x02, 0x34, 0).unwrap(); + let tc_header = PusTcSecondaryHeader::new_simple(17, 1); + let mut test_buf: [u8; 32] = [0; 32]; + let mut pus_tc = + PusTcCreatorWithReservedAppData::new(&mut test_buf, sph, tc_header, 0).unwrap(); + assert_eq!(pus_tc.len_written(), 13); + assert_eq!(pus_tc.app_data_len(), 0); + assert_eq!(pus_tc.app_data(), &[]); + assert_eq!(pus_tc.app_data_mut(), &[]); + let size = pus_tc.finalize(); + assert_eq!(size, 13); + 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(); @@ -757,6 +970,7 @@ mod tests { tc.update_ccsds_data_len(); assert_eq!(tc.data_len(), 6); } + #[test] fn test_deserialization_with_app_data() { let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]); @@ -859,22 +1073,17 @@ mod tests { let res = pus_tc.write_to_bytes(test_buf.as_mut_slice()); assert!(res.is_err()); let err = res.unwrap_err(); - if let PusError::ByteConversion(e) = err { - assert_eq!( - e, - ByteConversionError::ToSliceTooSmall { - found: 12, - expected: 13 - } - ); - assert_eq!( - err.to_string(), - "pus error: target slice with size 12 is too small, expected size of at least 13" - ); - assert_eq!(err.source().unwrap().to_string(), e.to_string()); - } else { - panic!("unexpected error {err}"); - } + assert_eq!( + err, + ByteConversionError::ToSliceTooSmall { + found: 12, + expected: 13 + } + ); + assert_eq!( + err.to_string(), + "target slice with size 12 is too small, expected size of at least 13" + ); } #[test] diff --git a/src/ecss/tm.rs b/src/ecss/tm.rs index 7ef20dd..116a9f1 100644 --- a/src/ecss/tm.rs +++ b/src/ecss/tm.rs @@ -392,12 +392,8 @@ 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) + let writer_unfinalized = self.common_write(slice)?; + Ok(writer_unfinalized.finalize()) } /// Write the raw PUS byte representation to a provided buffer. @@ -405,39 +401,36 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { &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) + let writer_unfinalized = self.common_write(slice)?; + Ok(writer_unfinalized.finalize_crc_no_table()) } /// 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() { + let writer_unfinalized = self.common_write(slice)?; + Ok(writer_unfinalized.finalize_no_crc()) + } + + fn common_write<'a>( + &self, + slice: &'a mut [u8], + ) -> Result, ByteConversionError> { + if self.len_written() > slice.len() { return Err(ByteConversionError::ToSliceTooSmall { found: slice.len(), - expected: total_size, + expected: self.len_written(), }); } - self.sp_header - .write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?; - curr_idx += CCSDS_HEADER_LEN; - let sec_header_len = size_of::(); - let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap(); - sec_header - .write_to(&mut slice[curr_idx..curr_idx + sec_header_len]) - .map_err(|_| ByteConversionError::ZeroCopyToError)?; - curr_idx += sec_header_len; - slice[curr_idx..curr_idx + self.sec_header.timestamp.len()] - .copy_from_slice(self.sec_header.timestamp); - 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(); - Ok(curr_idx) + let mut writer_unfinalized = PusTmCreatorWithReservedSourceData::write_to_bytes_partially( + slice, + self.sp_header, + self.sec_header, + self.source_data.len(), + )?; + writer_unfinalized + .source_data_mut() + .copy_from_slice(self.source_data); + Ok(writer_unfinalized) } /// Append the raw PUS byte representation to a provided [alloc::vec::Vec] @@ -471,9 +464,18 @@ impl WritablePusPacket for PusTmCreator<'_, '_> { fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result { Ok(Self::write_to_bytes_no_crc(self, slice)?) } + + fn write_to_bytes(&self, slice: &mut [u8]) -> Result { + Ok(Self::write_to_bytes(self, slice)?) + } + + fn write_to_bytes_crc_no_table(&self, slice: &mut [u8]) -> Result { + Ok(Self::write_to_bytes_crc_no_table(self, slice)?) + } } impl PartialEq for PusTmCreator<'_, '_> { + #[inline] fn eq(&self, other: &Self) -> bool { self.sp_header == other.sp_header && self.sec_header == other.sec_header @@ -525,6 +527,136 @@ impl GenericPusTmSecondaryHeader for PusTmCreator<'_, '_> { impl IsPusTelemetry for PusTmCreator<'_, '_> {} +/// A specialized variant of [PusTmCreator] designed for efficiency when handling large source +/// data. +/// +/// Unlike [PusTmCreator], this type does not require the user to provide the source data +/// as a separate slice. Instead, it allows writing the source data directly into the provided +/// serialization buffer. This eliminates the need for an intermediate buffer and the associated +/// memory copy, improving performance, particularly when working with large payloads. +/// +/// **Important:** The total length of the source data must be known and specified in advance +/// to ensure correct serialization behavior. +/// +/// Note that this abstraction intentionally omits certain trait implementations that are available +/// on [PusTmCreator], as they are not applicable in this optimized usage pattern. +pub struct PusTmCreatorWithReservedSourceData<'buf> { + buf: &'buf mut [u8], + source_data_offset: usize, + full_len: usize, +} + +impl<'buf> PusTmCreatorWithReservedSourceData<'buf> { + /// Generates a new instance with reserved space for the user source data. + /// + /// # Arguments + /// + /// * `sp_header` - Space packet header information. The correct packet type and the secondary + /// header flag are set correctly by the constructor. + /// * `sec_header` - Information contained in the secondary header, including the service + /// and subservice type + /// * `src_data_len` - Custom source data length + #[inline] + pub fn new( + buf: &'buf mut [u8], + mut sp_header: SpHeader, + sec_header: PusTmSecondaryHeader, + src_data_len: usize, + ) -> Result { + sp_header.set_packet_type(PacketType::Tm); + sp_header.set_sec_header_flag(); + let len_written = + PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA + sec_header.timestamp.len() + src_data_len; + if len_written > buf.len() { + return Err(ByteConversionError::ToSliceTooSmall { + found: buf.len(), + expected: len_written, + }); + } + sp_header.data_len = len_written as u16 - size_of::() as u16 - 1; + Self::write_to_bytes_partially(buf, sp_header, sec_header, src_data_len) + } + + fn write_to_bytes_partially( + buf: &'buf mut [u8], + sp_header: SpHeader, + sec_header: PusTmSecondaryHeader, + src_data_len: usize, + ) -> Result { + let mut curr_idx = 0; + sp_header.write_to_be_bytes(&mut buf[0..CCSDS_HEADER_LEN])?; + curr_idx += CCSDS_HEADER_LEN; + let sec_header_len = size_of::(); + let sec_header_zc = zc::PusTmSecHeaderWithoutTimestamp::try_from(sec_header).unwrap(); + sec_header_zc + .write_to(&mut buf[curr_idx..curr_idx + sec_header_len]) + .map_err(|_| ByteConversionError::ZeroCopyToError)?; + curr_idx += sec_header_len; + buf[curr_idx..curr_idx + sec_header.timestamp.len()].copy_from_slice(sec_header.timestamp); + curr_idx += sec_header.timestamp.len(); + let source_data_offset = curr_idx; + curr_idx += src_data_len; + Ok(Self { + buf, + source_data_offset, + full_len: curr_idx + 2, + }) + } + + #[inline] + pub const fn len_written(&self) -> usize { + self.full_len + } + + /// Mutable access to the source data buffer. + #[inline] + pub fn source_data_mut(&mut self) -> &mut [u8] { + &mut self.buf[self.source_data_offset..self.full_len - 2] + } + + /// Access to the source data buffer. + #[inline] + pub fn source_data(&self) -> &[u8] { + &self.buf[self.source_data_offset..self.full_len - 2] + } + + #[inline] + pub fn source_data_len(&self) -> usize { + self.full_len - 2 - self.source_data_offset + } + + /// Finalize the TM packet by calculating and writing the CRC16. + /// + /// Returns the full packet length. + pub fn finalize(self) -> usize { + let mut digest = CRC_CCITT_FALSE.digest(); + digest.update(&self.buf[0..self.full_len - 2]); + self.buf[self.full_len - 2..self.full_len] + .copy_from_slice(&digest.finalize().to_be_bytes()); + self.full_len + } + + /// Finalize the TM packet by calculating and writing the CRC16 using a table-less + /// implementation. + /// + /// Returns the full packet length. + pub fn finalize_crc_no_table(self) -> usize { + let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest(); + digest.update(&self.buf[0..self.full_len - 2]); + self.buf[self.full_len - 2..self.full_len] + .copy_from_slice(&digest.finalize().to_be_bytes()); + self.full_len + } + + /// Finalize the TM packet without writing the CRC16. + /// + /// Returns the length WITHOUT the CRC16. + #[inline] + pub fn finalize_no_crc(self) -> usize { + self.full_len - 2 + } +} + /// This class models the PUS C telemetry packet. It is the primary data structure to read /// a telemetry packet from raw bytes. /// @@ -962,7 +1094,41 @@ mod tests { .write_to_bytes(&mut buf) .expect("Serialization failed"); assert_eq!(ser_len, 22); - verify_raw_ping_reply(pus_tm.opt_crc16().unwrap(), &buf); + verify_raw_ping_reply(pus_tm.opt_crc16(), &buf); + } + + #[test] + fn test_serialization_no_source_data_alt_ctor() { + let timestamp = dummy_timestamp(); + let sph = SpHeader::new_for_unseg_tm_checked(0x123, 0x234, 0).unwrap(); + let tm_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp); + let mut buf: [u8; 32] = [0; 32]; + let mut pus_tm = + PusTmCreatorWithReservedSourceData::new(&mut buf, sph, tm_header, 0).unwrap(); + assert_eq!(pus_tm.source_data_len(), 0); + assert_eq!(pus_tm.source_data(), &[]); + assert_eq!(pus_tm.source_data_mut(), &[]); + let ser_len = pus_tm.finalize(); + assert_eq!(ser_len, 22); + verify_raw_ping_reply(None, &buf); + } + + #[test] + fn test_serialization_no_source_data_alt_ctor_no_crc() { + let timestamp = dummy_timestamp(); + let sph = SpHeader::new_for_unseg_tm_checked(0x123, 0x234, 0).unwrap(); + let tm_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp); + let mut buf: [u8; 32] = [0; 32]; + let mut pus_tm = + PusTmCreatorWithReservedSourceData::new(&mut buf, sph, tm_header, 0).unwrap(); + assert_eq!(pus_tm.source_data_len(), 0); + assert_eq!(pus_tm.source_data(), &[]); + assert_eq!(pus_tm.source_data_mut(), &[]); + let ser_len = pus_tm.finalize_no_crc(); + assert_eq!(ser_len, 20); + verify_raw_ping_reply_no_crc(&buf); + assert_eq!(buf[20], 0); + assert_eq!(buf[21], 0); } #[test] @@ -974,7 +1140,7 @@ mod tests { .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); + verify_raw_ping_reply(pus_tm.opt_crc16(), &buf); } #[test] @@ -1004,6 +1170,46 @@ mod tests { assert_eq!(buf[22], 3); } + #[test] + fn test_serialization_with_source_data_alt_ctor() { + let src_data = &[1, 2, 3]; + let mut buf: [u8; 32] = [0; 32]; + let sph = SpHeader::new_for_unseg_tm_checked(0x123, 0x234, 0).unwrap(); + let tc_header = PusTmSecondaryHeader::new_simple(3, 5, dummy_timestamp()); + let mut hk_reply_unwritten = + PusTmCreatorWithReservedSourceData::new(&mut buf, sph, tc_header, 3).unwrap(); + assert_eq!(hk_reply_unwritten.source_data_len(), 3); + assert_eq!(hk_reply_unwritten.source_data(), &[0, 0, 0]); + assert_eq!(hk_reply_unwritten.source_data_mut(), &[0, 0, 0]); + let source_data_mut = hk_reply_unwritten.source_data_mut(); + source_data_mut.copy_from_slice(src_data); + let ser_len = hk_reply_unwritten.finalize(); + assert_eq!(ser_len, 25); + assert_eq!(buf[20], 1); + assert_eq!(buf[21], 2); + assert_eq!(buf[22], 3); + } + + #[test] + fn test_serialization_with_source_data_alt_ctor_no_table() { + let src_data = &[1, 2, 3]; + let mut buf: [u8; 32] = [0; 32]; + let sph = SpHeader::new_for_unseg_tm_checked(0x123, 0x234, 0).unwrap(); + let tc_header = PusTmSecondaryHeader::new_simple(3, 5, dummy_timestamp()); + let mut hk_reply_unwritten = + PusTmCreatorWithReservedSourceData::new(&mut buf, sph, tc_header, 3).unwrap(); + assert_eq!(hk_reply_unwritten.source_data_len(), 3); + assert_eq!(hk_reply_unwritten.source_data(), &[0, 0, 0]); + assert_eq!(hk_reply_unwritten.source_data_mut(), &[0, 0, 0]); + let source_data_mut = hk_reply_unwritten.source_data_mut(); + source_data_mut.copy_from_slice(src_data); + let ser_len = hk_reply_unwritten.finalize_crc_no_table(); + assert_eq!(ser_len, 25); + assert_eq!(buf[20], 1); + assert_eq!(buf[21], 2); + assert_eq!(buf[22], 3); + } + #[test] fn test_setters() { let timestamp = dummy_timestamp(); @@ -1029,6 +1235,7 @@ mod tests { assert_eq!(tm_vec.len(), tm_deserialized.total_len()); verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp()); } + #[test] fn test_deserialization_no_source_data() { let timestamp = dummy_timestamp(); @@ -1046,6 +1253,22 @@ mod tests { verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp()); } + #[test] + fn test_deserialization_no_source_data_with_trait() { + let timestamp = dummy_timestamp(); + let pus_tm = base_ping_reply_full_ctor(timestamp); + let mut buf: [u8; 32] = [0; 32]; + let ser_len = + WritablePusPacket::write_to_bytes(&pus_tm, &mut buf).expect("Serialization failed"); + assert_eq!(ser_len, 22); + 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(), 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(); @@ -1130,7 +1353,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.opt_crc16().unwrap(), vec.as_slice()); + verify_raw_ping_reply(pus_tm.opt_crc16(), vec.as_slice()); } #[test] @@ -1146,7 +1369,7 @@ mod tests { assert_eq!(vec.len(), 26); } - fn verify_raw_ping_reply(crc16: u16, buf: &[u8]) { + fn verify_raw_ping_reply_no_crc(buf: &[u8]) { // Secondary header is set -> 0b0000_1001 , APID occupies last bit of first byte assert_eq!(buf[0], 0x09); // Rest of APID 0x123 @@ -1167,12 +1390,19 @@ mod tests { assert_eq!(buf[12], 0x00); // Timestamp assert_eq!(&buf[13..20], dummy_timestamp()); + } + + fn verify_raw_ping_reply(crc16: Option, buf: &[u8]) { + verify_raw_ping_reply_no_crc(buf); let mut digest = CRC_CCITT_FALSE.digest(); digest.update(&buf[0..20]); let crc16_calced = digest.finalize(); - assert_eq!(((crc16 >> 8) & 0xff) as u8, buf[20]); - assert_eq!((crc16 & 0xff) as u8, buf[21]); - assert_eq!(crc16, crc16_calced); + let crc16_read = u16::from_be_bytes([buf[20], buf[21]]); + assert_eq!(crc16_read, crc16_calced); + if let Some(crc16) = crc16 { + assert_eq!(((crc16 >> 8) & 0xff) as u8, buf[20]); + assert_eq!((crc16 & 0xff) as u8, buf[21]); + } } fn verify_ping_reply(