diff --git a/CHANGELOG.md b/CHANGELOG.md index 6104b65..fe04300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `UnsignedByteField` as a type-erased helper. - Added `SerializablePusPacket` as a generic abstraction for PUS packets which are writable. +- Added new `PusTmZeroCopyWriter` class which allows to set fields on a raw TM packet, + which might be more efficient that modification and re-writing a packet with the + `PusTm` object. ## Changed diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index a05c9c3..adaa6c4 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -598,12 +598,12 @@ mod tests { // No record boundary preservation assert_eq!((buf[3] >> 7) & 1, pdu_conf.seg_ctrl as u8); // Entity ID length raw value is actual number of octets - 1 => 0 - let entity_id_len = pdu_conf.pdu_conf.source_entity_id.len(); + let entity_id_len = pdu_conf.pdu_conf.source_entity_id.size(); assert_eq!((buf[3] >> 4) & 0b111, entity_id_len as u8 - 1); // No segment metadata assert_eq!((buf[3] >> 3) & 0b1, pdu_conf.seg_metadata_flag as u8); // Transaction Sequence ID length raw value is actual number of octets - 1 => 0 - let seq_num_len = pdu_conf.pdu_conf.transaction_seq_num.len(); + let seq_num_len = pdu_conf.pdu_conf.transaction_seq_num.size(); assert_eq!(buf[3] & 0b111, seq_num_len as u8 - 1); let mut current_idx = 4; let mut byte_field_check = |field_len: usize, ubf: &UnsignedByteField| { diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index d3697ab..add2d24 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -257,16 +257,22 @@ pub(crate) fn user_data_from_raw( } } -pub(crate) fn verify_crc16_ccitt_false_from_raw( +pub(crate) fn verify_crc16_ccitt_false_from_raw_to_pus_error( raw_data: &[u8], crc16: u16, ) -> Result<(), PusError> { + verify_crc16_ccitt_false_from_raw(raw_data) + .then(|| ()) + .ok_or(PusError::IncorrectCrc(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); if digest.finalize() == 0 { - return Ok(()); + return true; } - Err(PusError::IncorrectCrc(crc16)) + false } macro_rules! ccsds_impl { diff --git a/src/tc.rs b/src/tc.rs index f46c4c0..92c51d6 100644 --- a/src/tc.rs +++ b/src/tc.rs @@ -33,7 +33,7 @@ //! ``` use crate::ecss::{ ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls, user_data_from_raw, - verify_crc16_ccitt_false_from_raw, CrcType, PusError, PusPacket, PusVersion, + verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion, SerializablePusPacket, }; use crate::{ @@ -394,7 +394,10 @@ impl<'raw_data> PusTc<'raw_data> { calc_crc_on_serialization: false, crc16: Some(crc_from_raw_data(raw_data)?), }; - verify_crc16_ccitt_false_from_raw(raw_data, pus_tc.crc16.expect("CRC16 invalid"))?; + verify_crc16_ccitt_false_from_raw_to_pus_error( + raw_data, + pus_tc.crc16.expect("CRC16 invalid"), + )?; Ok((pus_tc, total_len)) } @@ -500,7 +503,7 @@ impl GenericPusTcSecondaryHeader for PusTc<'_> { #[cfg(all(test, feature = "std"))] mod tests { use crate::ecss::PusVersion::PusC; - use crate::ecss::{PusError, PusPacket}; + use crate::ecss::{PusError, PusPacket, SerializablePusPacket}; use crate::tc::ACK_ALL; use crate::tc::{GenericPusTcSecondaryHeader, PusTc, PusTcSecondaryHeader}; use crate::{ByteConversionError, SpHeader}; diff --git a/src/tm.rs b/src/tm.rs index d92dcc4..6c2a766 100644 --- a/src/tm.rs +++ b/src/tm.rs @@ -1,13 +1,13 @@ //! This module contains all components required to create a ECSS PUS C telemetry packets according //! to [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). use crate::ecss::{ - ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls, user_data_from_raw, - verify_crc16_ccitt_false_from_raw, CrcType, PusError, PusPacket, PusVersion, - SerializablePusPacket, + calc_pus_crc16, ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls, + user_data_from_raw, verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, + PusPacket, PusVersion, SerializablePusPacket, }; use crate::{ ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SizeMissmatch, SpHeader, - CCSDS_HEADER_LEN, CRC_CCITT_FALSE, + CCSDS_HEADER_LEN, CRC_CCITT_FALSE, MAX_SEQ_COUNT, }; use core::mem::size_of; #[cfg(feature = "serde")] @@ -388,7 +388,10 @@ impl<'raw_data> PusTm<'raw_data> { calc_crc_on_serialization: false, crc16: Some(crc_from_raw_data(raw_data)?), }; - verify_crc16_ccitt_false_from_raw(raw_data, pus_tm.crc16.expect("CRC16 invalid"))?; + verify_crc16_ccitt_false_from_raw_to_pus_error( + raw_data, + pus_tm.crc16.expect("CRC16 invalid"), + )?; Ok((pus_tm, total_len)) } @@ -399,6 +402,62 @@ impl<'raw_data> PusTm<'raw_data> { } } +/// This is a helper class to update certain fields in a raw PUS telemetry packet directly in place. +/// This can be more efficient than creating a full [PusTm], modifying the fields and then writing +/// it back to another buffer. +/// +/// Please note that the [Self::finish] method has to be called for the PUS TM CRC16 to be valid +/// after changing fields of the TM packet. Furthermore, the constructor of this class will not +/// do any checks except a length check to ensure that all relevant fields can be updated without +/// a panic. If a full validity check of the PUS TM packet is required, it is recommended +/// to construct a full [PusTm] object from the raw bytestream first. +pub struct PusTmZeroCopyWriter<'raw> { + raw_tm: &'raw mut [u8], +} + +impl<'raw> PusTmZeroCopyWriter<'raw> { + /// This function will not do any other checks on the raw data other than a length check + /// for all internal fields which can be updated. It is the responsibility of the user to ensure + /// the raw slice contains a valid telemetry packet. The slice should have the exact length + /// of the telemetry packet for this class to work properly. + pub fn new(raw_tm: &'raw mut [u8]) -> Option { + if raw_tm.len() < 13 { + return None; + } + Some(Self { raw_tm }) + } + + /// This function sets the message counter in the PUS TM secondary header. + pub fn set_msg_count(&mut self, msg_count: u16) { + self.raw_tm[9..11].copy_from_slice(&msg_count.to_be_bytes()); + } + + /// This function sets the destination ID in the PUS TM secondary header. + pub fn set_destination_id(&mut self, dest_id: u16) { + self.raw_tm[11..13].copy_from_slice(&dest_id.to_be_bytes()) + } + + /// Set the sequence count. Returns false and does not update the value if the passed value + /// exceeds [MAX_SEQ_COUNT]. + pub fn set_seq_count_in_place(&mut self, seq_count: u16) -> bool { + if seq_count > MAX_SEQ_COUNT { + return false; + } + let new_psc = + (u16::from_be_bytes(self.raw_tm[2..4].try_into().unwrap()) & 0xC000) | seq_count; + self.raw_tm[2..4].copy_from_slice(&new_psc.to_be_bytes()); + true + } + + /// This method has to be called after modifying fields to ensure the CRC16 of the telemetry + /// packet remains valid. + pub fn finish(self) { + let slice_len = self.raw_tm.len(); + let crc16 = calc_pus_crc16(&self.raw_tm[..slice_len - 2]); + self.raw_tm[slice_len - 2..].copy_from_slice(&crc16.to_be_bytes()); + } +} + impl SerializablePusPacket for PusTm<'_> { fn len_packed(&self) -> usize { let mut length = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA; @@ -714,4 +773,26 @@ mod tests { pus_tm.write_to_bytes(&mut buf).unwrap(); assert_eq!(pus_tm, PusTm::from_bytes(&buf, timestamp.len()).unwrap().0); } + + #[test] + fn test_zero_copy_writer() { + let ping_tm = base_ping_reply_full_ctor(dummy_timestamp()); + let mut buf: [u8; 64] = [0; 64]; + let tm_size = ping_tm + .write_to_bytes(&mut buf) + .expect("writing PUS ping TM failed"); + let mut writer = PusTmZeroCopyWriter::new(&mut buf[..tm_size]) + .expect("Creating zero copy writer failed"); + writer.set_destination_id(55); + writer.set_msg_count(100); + writer.set_seq_count_in_place(MAX_SEQ_COUNT - 1); + writer.finalize(); + // This performs all necessary checks, including the CRC check. + let (tm_read_back, tm_size_read_back) = + PusTm::from_bytes(&buf, 7).expect("Re-creating PUS TM failed"); + assert_eq!(tm_size_read_back, 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 - 1); + } } diff --git a/src/util.rs b/src/util.rs index d77ca66..3fd41dc 100644 --- a/src/util.rs +++ b/src/util.rs @@ -317,7 +317,7 @@ pub mod tests { #[test] fn test_simple_u8() { let u8 = UnsignedByteFieldU8::new(5); - assert_eq!(u8.len(), 1); + assert_eq!(u8.size(), 1); let mut buf: [u8; 8] = [0; 8]; let len = u8 .write_to_be_bytes(&mut buf) @@ -332,7 +332,7 @@ pub mod tests { #[test] fn test_simple_u16() { let u16 = UnsignedByteFieldU16::new(3823); - assert_eq!(u16.len(), 2); + assert_eq!(u16.size(), 2); let mut buf: [u8; 8] = [0; 8]; let len = u16 .write_to_be_bytes(&mut buf) @@ -348,7 +348,7 @@ pub mod tests { #[test] fn test_simple_u32() { let u32 = UnsignedByteFieldU32::new(80932); - assert_eq!(u32.len(), 4); + assert_eq!(u32.size(), 4); let mut buf: [u8; 8] = [0; 8]; let len = u32 .write_to_be_bytes(&mut buf) @@ -364,7 +364,7 @@ pub mod tests { #[test] fn test_simple_u64() { let u64 = UnsignedByteFieldU64::new(5999999); - assert_eq!(u64.len(), 8); + assert_eq!(u64.size(), 8); let mut buf: [u8; 8] = [0; 8]; let len = u64 .write_to_be_bytes(&mut buf) @@ -493,7 +493,7 @@ pub mod tests { #[test] fn type_erased_u8_write() { let u8 = UnsignedByteField::new(1, 5); - assert_eq!(u8.len(), 1); + assert_eq!(u8.size(), 1); let mut buf: [u8; 8] = [0; 8]; u8.write_to_be_bytes(&mut buf) .expect("writing to raw buffer failed"); @@ -506,7 +506,7 @@ pub mod tests { #[test] fn type_erased_u16_write() { let u16 = UnsignedByteField::new(2, 3823); - assert_eq!(u16.len(), 2); + assert_eq!(u16.size(), 2); let mut buf: [u8; 8] = [0; 8]; u16.write_to_be_bytes(&mut buf) .expect("writing to raw buffer failed"); @@ -520,7 +520,7 @@ pub mod tests { #[test] fn type_erased_u32_write() { let u32 = UnsignedByteField::new(4, 80932); - assert_eq!(u32.len(), 4); + assert_eq!(u32.size(), 4); let mut buf: [u8; 8] = [0; 8]; u32.write_to_be_bytes(&mut buf) .expect("writing to raw buffer failed"); @@ -534,7 +534,7 @@ pub mod tests { #[test] fn type_erased_u64_write() { let u64 = UnsignedByteField::new(8, 5999999); - assert_eq!(u64.len(), 8); + assert_eq!(u64.size(), 8); let mut buf: [u8; 8] = [0; 8]; u64.write_to_be_bytes(&mut buf) .expect("writing to raw buffer failed");