6 Commits

5 changed files with 531 additions and 83 deletions

View File

@@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.15.0] 2025-07-18
## 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 # [v0.14.0] 2025-05-10
## Changed ## Changed
@@ -585,7 +593,8 @@ The timestamp of `PusTm` is now optional. See Added and Changed section for deta
Initial release with CCSDS Space Packet Primary Header implementation and basic PUS TC and TM Initial release with CCSDS Space Packet Primary Header implementation and basic PUS TC and TM
implementations. implementations.
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.14.0...HEAD [unreleased]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.15.0...HEAD
[v0.15.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.14.0...v0.15.0
[v0.14.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.13.1...v0.14.0 [v0.14.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.13.1...v0.14.0
[v0.13.1]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.13.0...v0.13.1 [v0.13.1]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.13.0...v0.13.1
[v0.13.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.12.0...v0.13.0 [v0.13.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.12.0...v0.13.0

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "spacepackets" name = "spacepackets"
version = "0.14.0" version = "0.15.0"
edition = "2021" edition = "2021"
rust-version = "1.70.0" rust-version = "1.70.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
@@ -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.3" crc = "3"
delegate = ">=0.8, <=0.13" delegate = ">=0.8, <=0.13"
paste = "1" paste = "1"
zerocopy = { version = "0.8", features = ["derive"] } zerocopy = { version = "0.8", features = ["derive"] }

View File

@@ -377,7 +377,7 @@ pub trait WritablePusPacket {
fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError>; 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 /// 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<usize, PusError> { fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?; let mut curr_idx = self.write_to_bytes_no_crc(slice)?;
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();

View File

@@ -372,6 +372,49 @@ impl<'app_data> PusTcCreator<'app_data> {
vec.extend_from_slice(&digest.finalize().to_be_bytes()); vec.extend_from_slice(&digest.finalize().to_be_bytes());
appended_len appended_len
} }
/// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> {
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<usize, ByteConversionError> {
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<usize, ByteConversionError> {
let writer_unfinalized = self.common_write(slice)?;
Ok(writer_unfinalized.finalize_no_crc())
}
fn common_write<'a>(
&self,
slice: &'a mut [u8],
) -> Result<PusTcCreatorWithReservedAppData<'a>, 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<'_> { impl WritablePusPacket for PusTcCreator<'_> {
@@ -380,31 +423,17 @@ impl WritablePusPacket for PusTcCreator<'_> {
PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len() PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len()
} }
/// Writes the packet to the given slice without writing the CRC. /// Write the raw PUS byte representation to a provided buffer.
///
/// The returned size is the written size WITHOUT the CRC.
fn write_to_bytes_no_crc(&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; Ok(Self::write_to_bytes_no_crc(self, slice)?)
let tc_header_len = size_of::<zc::PusTcSecondaryHeader>(); }
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)?;
curr_idx += tc_header_len; fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
slice[curr_idx..curr_idx + self.app_data.len()].copy_from_slice(self.app_data); Ok(Self::write_to_bytes(self, slice)?)
curr_idx += self.app_data.len(); }
Ok(curr_idx)
fn write_to_bytes_crc_no_table(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes_crc_no_table(self, slice)?)
} }
} }
@@ -450,6 +479,133 @@ impl GenericPusTcSecondaryHeader for PusTcCreator<'_> {
impl IsPusTelecommand 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<Self, ByteConversionError> {
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::<crate::zc::SpHeader>() 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<Self, ByteConversionError> {
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::<zc::PusTcSecondaryHeader>();
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 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 /// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the
@@ -627,8 +783,6 @@ impl PartialEq<PusTcReader<'_>> for PusTcCreator<'_> {
#[cfg(all(test, feature = "std"))] #[cfg(all(test, feature = "std"))]
mod tests { mod tests {
use std::error::Error;
use super::*; use super::*;
use crate::ecss::PusVersion::PusC; use crate::ecss::PusVersion::PusC;
use crate::ecss::{PusError, PusPacket, WritablePusPacket}; 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] #[test]
fn test_serialization_crc_no_table() { fn test_serialization_crc_no_table() {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
@@ -701,6 +881,17 @@ mod tests {
assert_eq!(test_buf[12], 0); 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] #[test]
fn test_deserialization() { fn test_deserialization() {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
@@ -718,6 +909,28 @@ mod tests {
verify_crc_no_app_data(&test_buf); 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] #[test]
fn test_deserialization_no_table() { fn test_deserialization_no_table() {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
@@ -757,6 +970,7 @@ mod tests {
tc.update_ccsds_data_len(); tc.update_ccsds_data_len();
assert_eq!(tc.data_len(), 6); assert_eq!(tc.data_len(), 6);
} }
#[test] #[test]
fn test_deserialization_with_app_data() { fn test_deserialization_with_app_data() {
let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]); 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()); let res = pus_tc.write_to_bytes(test_buf.as_mut_slice());
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();
if let PusError::ByteConversion(e) = err { assert_eq!(
assert_eq!( err,
e, ByteConversionError::ToSliceTooSmall {
ByteConversionError::ToSliceTooSmall { found: 12,
found: 12, expected: 13
expected: 13 }
} );
); assert_eq!(
assert_eq!( err.to_string(),
err.to_string(), "target slice with size 12 is too small, expected size of at least 13"
"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}");
}
} }
#[test] #[test]

View File

@@ -392,12 +392,8 @@ 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 writer_unfinalized = self.common_write(slice)?;
let mut digest = CRC_CCITT_FALSE.digest(); Ok(writer_unfinalized.finalize())
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. /// Write the raw PUS byte representation to a provided buffer.
@@ -405,39 +401,36 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> {
&self, &self,
slice: &mut [u8], slice: &mut [u8],
) -> Result<usize, ByteConversionError> { ) -> Result<usize, ByteConversionError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?; let writer_unfinalized = self.common_write(slice)?;
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest(); Ok(writer_unfinalized.finalize_crc_no_table())
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. /// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> { pub fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> {
let mut curr_idx = 0; let writer_unfinalized = self.common_write(slice)?;
let total_size = self.len_written(); Ok(writer_unfinalized.finalize_no_crc())
if total_size > slice.len() { }
fn common_write<'a>(
&self,
slice: &'a mut [u8],
) -> Result<PusTmCreatorWithReservedSourceData<'a>, ByteConversionError> {
if self.len_written() > slice.len() {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
found: slice.len(), found: slice.len(),
expected: total_size, expected: self.len_written(),
}); });
} }
self.sp_header let mut writer_unfinalized = PusTmCreatorWithReservedSourceData::write_to_bytes_partially(
.write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?; slice,
curr_idx += CCSDS_HEADER_LEN; self.sp_header,
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>(); self.sec_header,
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap(); self.source_data.len(),
sec_header )?;
.write_to(&mut slice[curr_idx..curr_idx + sec_header_len]) writer_unfinalized
.map_err(|_| ByteConversionError::ZeroCopyToError)?; .source_data_mut()
curr_idx += sec_header_len; .copy_from_slice(self.source_data);
slice[curr_idx..curr_idx + self.sec_header.timestamp.len()] Ok(writer_unfinalized)
.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)
} }
/// Append the raw PUS byte representation to a provided [alloc::vec::Vec] /// 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<usize, PusError> { fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes_no_crc(self, slice)?) Ok(Self::write_to_bytes_no_crc(self, slice)?)
} }
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes(self, slice)?)
}
fn write_to_bytes_crc_no_table(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes_crc_no_table(self, slice)?)
}
} }
impl PartialEq for PusTmCreator<'_, '_> { impl PartialEq for PusTmCreator<'_, '_> {
#[inline]
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.sp_header == other.sp_header self.sp_header == other.sp_header
&& self.sec_header == other.sec_header && self.sec_header == other.sec_header
@@ -525,6 +527,136 @@ impl GenericPusTmSecondaryHeader for PusTmCreator<'_, '_> {
impl IsPusTelemetry 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<Self, ByteConversionError> {
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::<crate::zc::SpHeader>() 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<Self, ByteConversionError> {
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::<zc::PusTmSecHeaderWithoutTimestamp>();
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 /// This class models the PUS C telemetry packet. It is the primary data structure to read
/// a telemetry packet from raw bytes. /// a telemetry packet from raw bytes.
/// ///
@@ -962,7 +1094,41 @@ 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.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] #[test]
@@ -974,7 +1140,7 @@ mod tests {
.write_to_bytes_crc_no_table(&mut buf) .write_to_bytes_crc_no_table(&mut buf)
.expect("Serialization failed"); .expect("Serialization failed");
assert_eq!(ser_len, 22); 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] #[test]
@@ -1004,6 +1170,46 @@ mod tests {
assert_eq!(buf[22], 3); 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] #[test]
fn test_setters() { fn test_setters() {
let timestamp = dummy_timestamp(); let timestamp = dummy_timestamp();
@@ -1029,6 +1235,7 @@ mod tests {
assert_eq!(tm_vec.len(), tm_deserialized.total_len()); 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]
fn test_deserialization_no_source_data() { fn test_deserialization_no_source_data() {
let timestamp = dummy_timestamp(); let timestamp = dummy_timestamp();
@@ -1046,6 +1253,22 @@ mod tests {
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_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] #[test]
fn test_deserialization_no_table() { fn test_deserialization_no_table() {
let timestamp = dummy_timestamp(); let timestamp = dummy_timestamp();
@@ -1130,7 +1353,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.opt_crc16().unwrap(), vec.as_slice()); verify_raw_ping_reply(pus_tm.opt_crc16(), vec.as_slice());
} }
#[test] #[test]
@@ -1146,7 +1369,7 @@ mod tests {
assert_eq!(vec.len(), 26); 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 // Secondary header is set -> 0b0000_1001 , APID occupies last bit of first byte
assert_eq!(buf[0], 0x09); assert_eq!(buf[0], 0x09);
// Rest of APID 0x123 // Rest of APID 0x123
@@ -1167,12 +1390,19 @@ mod tests {
assert_eq!(buf[12], 0x00); assert_eq!(buf[12], 0x00);
// Timestamp // Timestamp
assert_eq!(&buf[13..20], dummy_timestamp()); assert_eq!(&buf[13..20], dummy_timestamp());
}
fn verify_raw_ping_reply(crc16: Option<u16>, buf: &[u8]) {
verify_raw_ping_reply_no_crc(buf);
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&buf[0..20]); digest.update(&buf[0..20]);
let crc16_calced = digest.finalize(); let crc16_calced = digest.finalize();
assert_eq!(((crc16 >> 8) & 0xff) as u8, buf[20]); let crc16_read = u16::from_be_bytes([buf[20], buf[21]]);
assert_eq!((crc16 & 0xff) as u8, buf[21]); assert_eq!(crc16_read, crc16_calced);
assert_eq!(crc16, 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( fn verify_ping_reply(