From 472594fa3c856b81e7bb7224a879e9dedd293dda Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 10 Sep 2025 17:25:03 +0200 Subject: [PATCH] add TM builder API --- justfile | 6 +++- src/ecss/mod.rs | 16 ---------- src/ecss/tc.rs | 25 +++++++++++++--- src/ecss/tc_pus_a.rs | 24 +++++++++++++-- src/ecss/tm.rs | 67 +++++++++++++++++++++++++++++++++++++----- src/ecss/tm_pus_a.rs | 24 +++++++++++++-- src/time/cds.rs | 69 ++++++++++++++++++++++++-------------------- src/time/mod.rs | 67 +++++++----------------------------------- 8 files changed, 176 insertions(+), 122 deletions(-) diff --git a/justfile b/justfile index 704b921..77f1b55 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -all: check clippy fmt build docs +all: check build test clippy fmt docs clippy: cargo clippy -- -D warnings @@ -9,6 +9,10 @@ fmt: check: cargo check --all-features +test: + cargo nextest r --all-features + cargo test --doc + build: cargo build --all-features diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index b0dae80..ef54f45 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -260,21 +260,6 @@ pub fn verify_crc16_ccitt_false_from_raw_no_table(raw_data: &[u8]) -> bool { false } -macro_rules! ccsds_impl { - () => { - delegate!(to self.sp_header { - #[inline] - fn ccsds_version(&self) -> u3; - #[inline] - fn packet_id(&self) -> crate::PacketId; - #[inline] - fn psc(&self) -> crate::PacketSequenceControl; - #[inline] - fn data_len(&self) -> u16; - }); - } -} - macro_rules! sp_header_impls { () => { delegate!(to self.sp_header { @@ -289,7 +274,6 @@ macro_rules! sp_header_impls { } use crate::util::{GenericUnsignedByteField, ToBeBytes, UnsignedEnum}; -pub(crate) use ccsds_impl; pub(crate) use sp_header_impls; /// Generic trait for ECSS enumeration which consist of a PFC field denoting their bit length diff --git a/src/ecss/tc.rs b/src/ecss/tc.rs index f5bab83..ba3c7bf 100644 --- a/src/ecss/tc.rs +++ b/src/ecss/tc.rs @@ -5,7 +5,6 @@ //! //! ```rust //! use spacepackets::SpHeader; -//! use spacepackets::ecss::WritablePusPacket; //! use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader, CreatorConfig}; //! use arbitrary_int::u11; //! @@ -45,7 +44,7 @@ use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE}; pub use crate::ecss::CreatorConfig; use crate::ecss::{ - ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw, + crc_from_raw_data, sp_header_impls, user_data_from_raw, verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, PusVersion, WritablePusPacket, }; @@ -497,7 +496,16 @@ impl WritablePusPacket for PusTcCreator<'_> { } impl CcsdsPacket for PusTcCreator<'_> { - ccsds_impl!(); + delegate!(to self.sp_header { + #[inline] + fn ccsds_version(&self) -> u3; + #[inline] + fn packet_id(&self) -> crate::PacketId; + #[inline] + fn psc(&self) -> crate::PacketSequenceControl; + #[inline] + fn data_len(&self) -> u16; + }); } impl PusPacket for PusTcCreator<'_> { @@ -949,7 +957,16 @@ impl PartialEq for PusTcReader<'_> { } impl CcsdsPacket for PusTcReader<'_> { - ccsds_impl!(); + delegate!(to self.sp_header { + #[inline] + fn ccsds_version(&self) -> u3; + #[inline] + fn packet_id(&self) -> crate::PacketId; + #[inline] + fn psc(&self) -> crate::PacketSequenceControl; + #[inline] + fn data_len(&self) -> u16; + }); } impl PusPacket for PusTcReader<'_> { diff --git a/src/ecss/tc_pus_a.rs b/src/ecss/tc_pus_a.rs index 24b4c64..bf6f6fc 100644 --- a/src/ecss/tc_pus_a.rs +++ b/src/ecss/tc_pus_a.rs @@ -36,7 +36,7 @@ use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE}; use crate::ecss::tc::{AckFlags, ACK_ALL}; use crate::ecss::{ - ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw, + crc_from_raw_data, sp_header_impls, user_data_from_raw, verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, PusVersion, WritablePusPacket, }; @@ -483,7 +483,16 @@ impl WritablePusPacket for PusTcCreator<'_> { } impl CcsdsPacket for PusTcCreator<'_> { - ccsds_impl!(); + delegate!(to self.sp_header { + #[inline] + fn ccsds_version(&self) -> u3; + #[inline] + fn packet_id(&self) -> crate::PacketId; + #[inline] + fn psc(&self) -> crate::PacketSequenceControl; + #[inline] + fn data_len(&self) -> u16; + }); } impl PusPacket for PusTcCreator<'_> { @@ -770,7 +779,16 @@ impl PartialEq for PusTcReader<'_> { } impl CcsdsPacket for PusTcReader<'_> { - ccsds_impl!(); + delegate!(to self.sp_header { + #[inline] + fn ccsds_version(&self) -> u3; + #[inline] + fn packet_id(&self) -> crate::PacketId; + #[inline] + fn psc(&self) -> crate::PacketSequenceControl; + #[inline] + fn data_len(&self) -> u16; + }); } impl PusPacket for PusTcReader<'_> { diff --git a/src/ecss/tm.rs b/src/ecss/tm.rs index 7703d66..ee995e7 100644 --- a/src/ecss/tm.rs +++ b/src/ecss/tm.rs @@ -4,10 +4,8 @@ //! # Examples //! //! ```rust -//! use spacepackets::time::TimeWriter; //! use spacepackets::time::cds::CdsTime; -//! use spacepackets::{CcsdsPacket, SpHeader}; -//! use spacepackets::ecss::{PusPacket, WritablePusPacket}; +//! use spacepackets::SpHeader; //! use spacepackets::ecss::tm::{PusTmCreator, PusTmReader, PusTmSecondaryHeader, CreatorConfig}; //! use arbitrary_int::u11; //! @@ -42,11 +40,20 @@ //! assert_eq!(ping_tm_reader.subservice(), 2); //! assert_eq!(ping_tm_reader.apid().value(), 0x02); //! assert_eq!(ping_tm_reader.timestamp(), &time_buf); +//! +//! // Alternative builder API +//! let pus_tm_by_builder = PusTmCreator::builder() +//! .with_service(17) +//! .with_subservice(2) +//! .with_apid(u11::new(0x02)) +//! .with_timestamp(&time_buf) +//! .build(); +//! assert_eq!(pus_tm_by_builder, ping_tm); //! ``` use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE}; pub use crate::ecss::CreatorConfig; use crate::ecss::{ - calc_pus_crc16, ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw, + calc_pus_crc16, crc_from_raw_data, sp_header_impls, user_data_from_raw, verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, PusVersion, WritablePusPacket, }; @@ -535,7 +542,16 @@ impl PartialEq for PusTmCreator<'_, '_> { } impl CcsdsPacket for PusTmCreator<'_, '_> { - ccsds_impl!(); + delegate!(to self.sp_header { + #[inline] + fn ccsds_version(&self) -> u3; + #[inline] + fn packet_id(&self) -> crate::PacketId; + #[inline] + fn psc(&self) -> crate::PacketSequenceControl; + #[inline] + fn data_len(&self) -> u16; + }); } impl PusPacket for PusTmCreator<'_, '_> { @@ -1021,6 +1037,26 @@ impl<'raw_data> PusTmReader<'raw_data> { self.user_data() } + #[inline] + pub fn service(&self) -> u8 { + self.sec_header.service + } + + #[inline] + pub fn subservice(&self) -> u8 { + self.sec_header.subservice + } + + #[inline] + pub fn packet_len(&self) -> usize { + self.sp_header.packet_len() + } + + #[inline] + pub fn apid(&self) -> u11 { + self.sp_header.packet_id.apid + } + #[inline] pub fn timestamp(&self) -> &[u8] { self.sec_header.timestamp @@ -1048,7 +1084,16 @@ impl PartialEq for PusTmReader<'_> { } impl CcsdsPacket for PusTmReader<'_> { - ccsds_impl!(); + delegate!(to self.sp_header { + #[inline] + fn ccsds_version(&self) -> u3; + #[inline] + fn packet_id(&self) -> crate::PacketId; + #[inline] + fn psc(&self) -> crate::PacketSequenceControl; + #[inline] + fn data_len(&self) -> u16; + }); } impl PusPacket for PusTmReader<'_> { @@ -1332,7 +1377,15 @@ mod tests { timestamp: &'a [u8], alt_api: bool, ) -> PusTmCreator<'a, 'b> { - if alt_api {} + if alt_api { + return PusTmCreator::builder() + .with_apid(u11::new(0x123)) + .with_sequence_count(u14::new(0x234)) + .with_service(17) + .with_subservice(2) + .with_timestamp(timestamp) + .build(); + } PusTmBuilder::new() .with_apid(u11::new(0x123)) .with_sequence_count(u14::new(0x234)) diff --git a/src/ecss/tm_pus_a.rs b/src/ecss/tm_pus_a.rs index aeffc1b..7f1367e 100644 --- a/src/ecss/tm_pus_a.rs +++ b/src/ecss/tm_pus_a.rs @@ -49,7 +49,7 @@ //! ``` 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, + calc_pus_crc16, 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, }; @@ -541,7 +541,16 @@ impl PartialEq for PusTmCreator<'_, '_> { } impl CcsdsPacket for PusTmCreator<'_, '_> { - ccsds_impl!(); + delegate!(to self.sp_header { + #[inline] + fn ccsds_version(&self) -> u3; + #[inline] + fn packet_id(&self) -> crate::PacketId; + #[inline] + fn psc(&self) -> crate::PacketSequenceControl; + #[inline] + fn data_len(&self) -> u16; + }); } impl PusPacket for PusTmCreator<'_, '_> { @@ -840,7 +849,16 @@ impl PartialEq for PusTmReader<'_> { } impl CcsdsPacket for PusTmReader<'_> { - ccsds_impl!(); + delegate!(to self.sp_header { + #[inline] + fn ccsds_version(&self) -> u3; + #[inline] + fn packet_id(&self) -> crate::PacketId; + #[inline] + fn psc(&self) -> crate::PacketSequenceControl; + #[inline] + fn data_len(&self) -> u16; + }); } impl PusPacket for PusTmReader<'_> { diff --git a/src/time/cds.rs b/src/time/cds.rs index 81f10ab..0cfc676 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -642,14 +642,12 @@ impl CdsTime { self.unix_time = UnixTime::new(unix_days_seconds, subsec_nanos); } - fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> { + fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), ByteConversionError> { if buf.len() < len_as_bytes { - return Err(TimestampError::ByteConversion( - ByteConversionError::ToSliceTooSmall { - expected: len_as_bytes, - found: buf.len(), - }, - )); + return Err(ByteConversionError::ToSliceTooSmall { + expected: len_as_bytes, + found: buf.len(), + }); } Ok(()) } @@ -957,6 +955,23 @@ impl CdsTime { Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits) } + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + self.length_check(buf, self.len_as_bytes())?; + buf[0] = self.pfield; + buf[1..3].copy_from_slice(self.ccsds_days.to_be_bytes().as_slice()); + buf[3..7].copy_from_slice(self.ms_of_day.to_be_bytes().as_slice()); + match self.submillis_precision() { + SubmillisPrecision::Microseconds => { + buf[7..9].copy_from_slice((self.submillis() as u16).to_be_bytes().as_slice()); + } + SubmillisPrecision::Picoseconds => { + buf[7..11].copy_from_slice(self.submillis().to_be_bytes().as_slice()); + } + _ => (), + } + Ok(self.len_as_bytes()) + } + pub fn from_bytes_with_u16_days(buf: &[u8]) -> Result { let submillis_precision = Self::generic_raw_read_checks(buf, LengthOfDaySegment::Short16Bits)?; @@ -1212,20 +1227,7 @@ impl TimeReader for CdsTime { impl TimeWriter for CdsTime { fn write_to_bytes(&self, buf: &mut [u8]) -> Result { - self.length_check(buf, self.len_as_bytes())?; - buf[0] = self.pfield; - buf[1..3].copy_from_slice(self.ccsds_days.to_be_bytes().as_slice()); - buf[3..7].copy_from_slice(self.ms_of_day.to_be_bytes().as_slice()); - match self.submillis_precision() { - SubmillisPrecision::Microseconds => { - buf[7..9].copy_from_slice((self.submillis() as u16).to_be_bytes().as_slice()); - } - SubmillisPrecision::Picoseconds => { - buf[7..11].copy_from_slice(self.submillis().to_be_bytes().as_slice()); - } - _ => (), - } - Ok(self.len_as_bytes()) + self.write_to_bytes(buf).map_err(TimestampError::from) } fn len_written(&self) -> usize { @@ -1233,8 +1235,8 @@ impl TimeWriter for CdsTime { } } -impl TimeWriter for CdsTime { - fn write_to_bytes(&self, buf: &mut [u8]) -> Result { +impl CdsTime { + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { self.length_check(buf, self.len_as_bytes())?; buf[0] = self.pfield; let be_days = self.ccsds_days.to_be_bytes(); @@ -1251,6 +1253,12 @@ impl TimeWriter for CdsTime { } Ok(self.len_as_bytes()) } +} + +impl TimeWriter for CdsTime { + fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + self.write_to_bytes(buf).map_err(TimestampError::from) + } fn len_written(&self) -> usize { self.len_as_bytes() @@ -1331,7 +1339,7 @@ mod tests { use super::*; use crate::time::TimestampError::{ByteConversion, InvalidTimeCode}; use crate::time::{UnixTime, DAYS_CCSDS_TO_UNIX, MS_PER_DAY}; - use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall}; + use crate::ByteConversionError::FromSliceTooSmall; use alloc::string::ToString; use chrono::{Datelike, NaiveDate, Timelike}; #[cfg(feature = "serde")] @@ -1486,12 +1494,14 @@ mod tests { assert!(res.is_err()); let error = res.unwrap_err(); match error { - ByteConversion(ToSliceTooSmall { found, expected }) => { + ByteConversionError::ToSliceTooSmall { found, expected } => { assert_eq!(found, i); assert_eq!(expected, 7); assert_eq!( error.to_string(), - format!("time stamp: target slice with size {i} is too small, expected size of at least 7") + format!( + "target slice with size {i} is too small, expected size of at least 7" + ) ); } _ => panic!( @@ -1537,10 +1547,7 @@ mod tests { if let InvalidTimeCode { expected, found } = err { assert_eq!(expected, CcsdsTimeCode::Cds); assert_eq!(found, 0); - assert_eq!( - err.to_string(), - "invalid raw time code value 0 for time code Cds" - ); + assert_eq!(err.to_string(), "invalid time code, expected Cds, found 0"); } } @@ -2301,7 +2308,7 @@ mod tests { fn test_serialization() { let stamp_now = CdsTime::now_with_u16_days().expect("Error retrieving time"); let val = to_allocvec(&stamp_now).expect("Serializing timestamp failed"); - assert!(val.len() > 0); + assert!(!val.is_empty()); let stamp_deser: CdsTime = from_bytes(&val).expect("Stamp deserialization failed"); assert_eq!(stamp_deser, stamp_now); } diff --git a/src/time/mod.rs b/src/time/mod.rs index 044208e..47e8450 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -3,7 +3,6 @@ use crate::ByteConversionError; #[cfg(feature = "chrono")] use chrono::{TimeZone, Utc}; use core::cmp::Ordering; -use core::fmt::{Display, Formatter}; use core::ops::{Add, AddAssign, Sub}; use core::time::Duration; @@ -14,8 +13,6 @@ use num_traits::float::FloatCore; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -#[cfg(feature = "std")] -use std::error::Error; #[cfg(feature = "std")] use std::time::{SystemTime, SystemTimeError}; #[cfg(feature = "std")] @@ -69,67 +66,23 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result { #[error("date before ccsds epoch: {0:?}")] pub struct DateBeforeCcsdsEpochError(UnixTime); -#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum TimestampError { + #[error("invalid time code, expected {expected:?}, found {found}")] InvalidTimeCode { expected: CcsdsTimeCode, found: u8 }, - ByteConversion(ByteConversionError), - Cds(cds::CdsError), - Cuc(cuc::CucError), + #[error("time stamp: byte conversion error: {0}")] + ByteConversion(#[from] ByteConversionError), + #[error("CDS error: {0}")] + Cds(#[from] cds::CdsError), + #[error("CUC error: {0}")] + Cuc(#[from] cuc::CucError), + #[error("custom epoch not supported")] CustomEpochNotSupported, } -impl Display for TimestampError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self { - TimestampError::InvalidTimeCode { expected, found } => { - write!( - f, - "invalid raw time code value {found} for time code {expected:?}" - ) - } - TimestampError::Cds(e) => { - write!(f, "cds error: {e}") - } - TimestampError::Cuc(e) => { - write!(f, "cuc error: {e}") - } - TimestampError::ByteConversion(e) => { - write!(f, "time stamp: {e}") - } - TimestampError::CustomEpochNotSupported => { - write!(f, "custom epochs are not supported") - } - } - } -} - -#[cfg(feature = "std")] -impl Error for TimestampError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - TimestampError::ByteConversion(e) => Some(e), - TimestampError::Cds(e) => Some(e), - TimestampError::Cuc(e) => Some(e), - _ => None, - } - } -} - -impl From for TimestampError { - fn from(e: cds::CdsError) -> Self { - TimestampError::Cds(e) - } -} - -impl From for TimestampError { - fn from(e: cuc::CucError) -> Self { - TimestampError::Cuc(e) - } -} - #[cfg(feature = "std")] pub mod std_mod { use crate::time::TimestampError; @@ -735,7 +688,7 @@ mod tests { fn test_cuc_error_printout() { let cuc_error = CucError::InvalidCounterWidth(12); let stamp_error = TimestampError::from(cuc_error); - assert_eq!(stamp_error.to_string(), format!("cuc error: {cuc_error}")); + assert_eq!(stamp_error.to_string(), format!("CUC error: {cuc_error}")); } #[test]