Add TM builder API

This commit is contained in:
Robin Mueller
2025-09-10 17:01:49 +02:00
parent 89788c1341
commit aaac15e3d0
9 changed files with 379 additions and 176 deletions

View File

@@ -1,4 +1,4 @@
all: check clippy fmt build docs all: check build test clippy fmt docs
clippy: clippy:
cargo clippy -- -D warnings cargo clippy -- -D warnings
@@ -9,6 +9,10 @@ fmt:
check: check:
cargo check --all-features cargo check --all-features
test:
cargo nextest r --all-features
cargo test --doc
build: build:
cargo build --all-features cargo build --all-features

View File

@@ -260,21 +260,6 @@ pub fn verify_crc16_ccitt_false_from_raw_no_table(raw_data: &[u8]) -> bool {
false 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 { macro_rules! sp_header_impls {
() => { () => {
delegate!(to self.sp_header { delegate!(to self.sp_header {
@@ -289,7 +274,6 @@ macro_rules! sp_header_impls {
} }
use crate::util::{GenericUnsignedByteField, ToBeBytes, UnsignedEnum}; use crate::util::{GenericUnsignedByteField, ToBeBytes, UnsignedEnum};
pub(crate) use ccsds_impl;
pub(crate) use sp_header_impls; pub(crate) use sp_header_impls;
/// Generic trait for ECSS enumeration which consist of a PFC field denoting their bit length /// Generic trait for ECSS enumeration which consist of a PFC field denoting their bit length

View File

@@ -5,7 +5,6 @@
//! //!
//! ```rust //! ```rust
//! use spacepackets::SpHeader; //! use spacepackets::SpHeader;
//! use spacepackets::ecss::WritablePusPacket;
//! use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader, CreatorConfig}; //! use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader, CreatorConfig};
//! use arbitrary_int::u11; //! use arbitrary_int::u11;
//! //!
@@ -45,7 +44,7 @@
use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE}; use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE};
pub use crate::ecss::CreatorConfig; pub use crate::ecss::CreatorConfig;
use crate::ecss::{ 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, verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, PusVersion,
WritablePusPacket, WritablePusPacket,
}; };
@@ -497,7 +496,16 @@ impl WritablePusPacket for PusTcCreator<'_> {
} }
impl CcsdsPacket 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<'_> { impl PusPacket for PusTcCreator<'_> {
@@ -708,6 +716,7 @@ impl<'buf> PusTcCreatorWithReservedAppData<'buf> {
} }
} }
#[derive(Debug)]
pub struct PusTcBuilder<'a> { pub struct PusTcBuilder<'a> {
sp_header: SpHeader, sp_header: SpHeader,
sec_header: PusTcSecondaryHeader, sec_header: PusTcSecondaryHeader,
@@ -948,7 +957,16 @@ impl PartialEq for PusTcReader<'_> {
} }
impl CcsdsPacket 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<'_> { impl PusPacket for PusTcReader<'_> {

View File

@@ -36,7 +36,7 @@
use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE}; use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE};
use crate::ecss::tc::{AckFlags, ACK_ALL}; use crate::ecss::tc::{AckFlags, ACK_ALL};
use crate::ecss::{ 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, verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, PusVersion,
WritablePusPacket, WritablePusPacket,
}; };
@@ -483,7 +483,16 @@ impl WritablePusPacket for PusTcCreator<'_> {
} }
impl CcsdsPacket 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<'_> { impl PusPacket for PusTcCreator<'_> {
@@ -770,7 +779,16 @@ impl PartialEq for PusTcReader<'_> {
} }
impl CcsdsPacket 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<'_> { impl PusPacket for PusTcReader<'_> {

View File

@@ -4,10 +4,8 @@
//! # Examples //! # Examples
//! //!
//! ```rust //! ```rust
//! use spacepackets::time::TimeWriter;
//! use spacepackets::time::cds::CdsTime; //! use spacepackets::time::cds::CdsTime;
//! use spacepackets::{CcsdsPacket, SpHeader}; //! use spacepackets::SpHeader;
//! use spacepackets::ecss::{PusPacket, WritablePusPacket};
//! use spacepackets::ecss::tm::{PusTmCreator, PusTmReader, PusTmSecondaryHeader, CreatorConfig}; //! use spacepackets::ecss::tm::{PusTmCreator, PusTmReader, PusTmSecondaryHeader, CreatorConfig};
//! use arbitrary_int::u11; //! use arbitrary_int::u11;
//! //!
@@ -42,17 +40,26 @@
//! assert_eq!(ping_tm_reader.subservice(), 2); //! assert_eq!(ping_tm_reader.subservice(), 2);
//! assert_eq!(ping_tm_reader.apid().value(), 0x02); //! assert_eq!(ping_tm_reader.apid().value(), 0x02);
//! assert_eq!(ping_tm_reader.timestamp(), &time_buf); //! 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}; use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE};
pub use crate::ecss::CreatorConfig; pub use crate::ecss::CreatorConfig;
use crate::ecss::{ 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, verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, PusVersion,
WritablePusPacket, WritablePusPacket,
}; };
use crate::{ use crate::{
ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SpHeader, CCSDS_HEADER_LEN, ByteConversionError, CcsdsPacket, PacketId, PacketSequenceControl, PacketType, SequenceFlags,
MAX_APID, SpHeader, CCSDS_HEADER_LEN, MAX_APID,
}; };
use arbitrary_int::traits::Integer; use arbitrary_int::traits::Integer;
use arbitrary_int::{u11, u14, u3, u4}; use arbitrary_int::{u11, u14, u3, u4};
@@ -344,8 +351,12 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> {
Self::new(sp_header, sec_header, &[], packet_config) Self::new(sp_header, sec_header, &[], packet_config)
} }
pub fn builder() -> PusTmBuilder<'time, 'src_data> {
PusTmBuilder::new()
}
#[inline] #[inline]
fn has_checksum(&self) -> bool { pub fn has_checksum(&self) -> bool {
self.has_checksum self.has_checksum
} }
@@ -359,6 +370,21 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> {
self.source_data self.source_data
} }
#[inline]
pub fn service(&self) -> u8 {
self.sec_header.service
}
#[inline]
pub fn subservice(&self) -> u8 {
self.sec_header.subservice
}
#[inline]
pub fn apid(&self) -> u11 {
self.sp_header.packet_id.apid
}
#[inline] #[inline]
pub fn set_dest_id(&mut self, dest_id: u16) { pub fn set_dest_id(&mut self, dest_id: u16) {
self.sec_header.dest_id = dest_id; self.sec_header.dest_id = dest_id;
@@ -516,7 +542,16 @@ impl PartialEq for PusTmCreator<'_, '_> {
} }
impl CcsdsPacket 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<'_, '_> { impl PusPacket for PusTmCreator<'_, '_> {
@@ -570,6 +605,126 @@ impl GenericPusTmSecondaryHeader for PusTmCreator<'_, '_> {
impl IsPusTelemetry for PusTmCreator<'_, '_> {} impl IsPusTelemetry for PusTmCreator<'_, '_> {}
#[derive(Debug)]
pub struct PusTmBuilder<'time, 'src_data> {
sp_header: SpHeader,
sec_header: PusTmSecondaryHeader<'time>,
source_data: &'src_data [u8],
has_checksum: bool,
}
impl Default for PusTmBuilder<'_, '_> {
fn default() -> Self {
Self::new()
}
}
impl PusTmBuilder<'_, '_> {
pub fn new() -> Self {
Self {
sp_header: SpHeader::new(
PacketId::new_for_tm(true, u11::new(0)),
PacketSequenceControl::new(SequenceFlags::Unsegmented, u14::new(0)),
0,
),
sec_header: PusTmSecondaryHeader::new(0, 0, 0, 0, &[]),
source_data: &[],
has_checksum: true,
}
}
#[inline]
pub fn with_apid(mut self, apid: u11) -> Self {
self.sp_header.packet_id.set_apid(apid);
self
}
#[inline]
pub fn with_packet_id(mut self, mut packet_id: PacketId) -> Self {
packet_id.packet_type = PacketType::Tc;
self.sp_header.packet_id = packet_id;
self
}
#[inline]
pub fn with_packet_sequence_control(mut self, psc: PacketSequenceControl) -> Self {
self.sp_header.psc = psc;
self
}
#[inline]
pub fn with_sequence_count(mut self, seq_count: u14) -> Self {
self.sp_header.psc.seq_count = seq_count;
self
}
#[inline]
pub fn with_service(mut self, service: u8) -> Self {
self.sec_header.service = service;
self
}
#[inline]
pub fn with_subservice(mut self, service: u8) -> Self {
self.sec_header.subservice = service;
self
}
#[inline]
pub fn with_dest_id(mut self, dest_id: u16) -> Self {
self.sec_header.dest_id = dest_id;
self
}
#[inline]
pub fn with_msg_counter(mut self, msg_counter: u16) -> Self {
self.sec_header.msg_counter = msg_counter;
self
}
#[inline]
pub fn with_sc_time_ref_status(mut self, sc_time_ref_status: u4) -> Self {
self.sec_header.sc_time_ref_status = sc_time_ref_status;
self
}
#[inline]
pub fn with_checksum(mut self, has_checksum: bool) -> Self {
self.has_checksum = has_checksum;
self
}
}
impl<'src_data> PusTmBuilder<'_, 'src_data> {
#[inline]
pub fn with_source_data(mut self, source_data: &'src_data [u8]) -> Self {
self.source_data = source_data;
self
}
}
impl<'time> PusTmBuilder<'time, '_> {
#[inline]
pub fn with_timestamp(mut self, timestamp: &'time [u8]) -> Self {
self.sec_header.timestamp = timestamp;
self
}
}
impl<'time, 'src_data> PusTmBuilder<'time, 'src_data> {
pub fn build(self) -> PusTmCreator<'time, 'src_data> {
PusTmCreator::new(
self.sp_header,
self.sec_header,
self.source_data,
CreatorConfig {
has_checksum: self.has_checksum,
set_ccsds_len: true,
},
)
}
}
/// A specialized variant of [PusTmCreator] designed for efficiency when handling large source /// A specialized variant of [PusTmCreator] designed for efficiency when handling large source
/// data. /// data.
/// ///
@@ -882,6 +1037,26 @@ impl<'raw_data> PusTmReader<'raw_data> {
self.user_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] #[inline]
pub fn timestamp(&self) -> &[u8] { pub fn timestamp(&self) -> &[u8] {
self.sec_header.timestamp self.sec_header.timestamp
@@ -909,7 +1084,16 @@ impl PartialEq for PusTmReader<'_> {
} }
impl CcsdsPacket 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<'_> { impl PusPacket for PusTmReader<'_> {
@@ -1171,14 +1355,13 @@ impl GenericPusTmSecondaryHeader for PusTmZeroCopyWriter<'_> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::string::ToString;
use super::*; use super::*;
use crate::time::cds::CdsTime; use crate::time::cds::CdsTime;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::time::CcsdsTimeProvider; use crate::time::CcsdsTimeProvider;
use crate::SpHeader; use crate::SpHeader;
use crate::{ecss::PusVersion::PusC, MAX_SEQ_COUNT}; use crate::{ecss::PusVersion::PusC, MAX_SEQ_COUNT};
use alloc::string::ToString;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use postcard::{from_bytes, to_allocvec}; use postcard::{from_bytes, to_allocvec};
@@ -1190,6 +1373,28 @@ mod tests {
PusTmCreator::new_no_source_data(sph, tm_header, CreatorConfig::default()) PusTmCreator::new_no_source_data(sph, tm_header, CreatorConfig::default())
} }
fn base_ping_reply_full_ctor_builder<'a, 'b>(
timestamp: &'a [u8],
alt_api: bool,
) -> PusTmCreator<'a, 'b> {
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))
.with_service(17)
.with_subservice(2)
.with_timestamp(timestamp)
.build()
}
fn base_ping_reply_full_ctor_no_checksum<'a, 'b>(timestamp: &'a [u8]) -> PusTmCreator<'a, 'b> { fn base_ping_reply_full_ctor_no_checksum<'a, 'b>(timestamp: &'a [u8]) -> PusTmCreator<'a, 'b> {
let sph = SpHeader::new_for_unseg_tm(u11::new(0x123), u14::new(0x234), 0); let sph = SpHeader::new_for_unseg_tm(u11::new(0x123), u14::new(0x234), 0);
let tm_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp); let tm_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
@@ -1938,4 +2143,41 @@ mod tests {
let output_converted_back: PusTmReader = from_bytes(&output).unwrap(); let output_converted_back: PusTmReader = from_bytes(&output).unwrap();
assert_eq!(output_converted_back, tm_reader); assert_eq!(output_converted_back, tm_reader);
} }
#[test]
fn test_builder() {
assert_eq!(
base_ping_reply_full_ctor_builder(dummy_timestamp(), false),
base_ping_reply_full_ctor(dummy_timestamp())
);
}
#[test]
fn test_builder_2() {
assert_eq!(
base_ping_reply_full_ctor_builder(dummy_timestamp(), true),
base_ping_reply_full_ctor(dummy_timestamp())
);
}
#[test]
fn test_builder_3() {
let tm = PusTmBuilder::new()
.with_packet_id(PacketId::new_for_tc(true, u11::new(0x02)))
.with_packet_sequence_control(PacketSequenceControl::new(
SequenceFlags::Unsegmented,
u14::new(0x34),
))
.with_service(17)
.with_subservice(2)
.with_dest_id(0x2f2f)
.with_checksum(false)
.build();
assert_eq!(tm.seq_count().value(), 0x34);
assert_eq!(tm.sequence_flags(), SequenceFlags::Unsegmented);
assert_eq!(tm.apid().value(), 0x02);
assert_eq!(tm.packet_type(), PacketType::Tm);
assert_eq!(tm.service(), 17);
assert_eq!(tm.subservice(), 2);
}
} }

View File

@@ -49,7 +49,7 @@
//! ``` //! ```
use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE}; use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE};
use crate::ecss::{ 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, verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion,
WritablePusPacket, WritablePusPacket,
}; };
@@ -541,7 +541,16 @@ impl PartialEq for PusTmCreator<'_, '_> {
} }
impl CcsdsPacket 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<'_, '_> { impl PusPacket for PusTmCreator<'_, '_> {
@@ -840,7 +849,16 @@ impl PartialEq for PusTmReader<'_> {
} }
impl CcsdsPacket 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<'_> { impl PusPacket for PusTmReader<'_> {

View File

@@ -642,14 +642,12 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
self.unix_time = UnixTime::new(unix_days_seconds, subsec_nanos); 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 { if buf.len() < len_as_bytes {
return Err(TimestampError::ByteConversion( return Err(ByteConversionError::ToSliceTooSmall {
ByteConversionError::ToSliceTooSmall { expected: len_as_bytes,
expected: len_as_bytes, found: buf.len(),
found: buf.len(), });
},
));
} }
Ok(()) Ok(())
} }
@@ -957,6 +955,23 @@ impl CdsTime<DaysLen16Bits> {
Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits) Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits)
} }
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
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<Self, TimestampError> { pub fn from_bytes_with_u16_days(buf: &[u8]) -> Result<Self, TimestampError> {
let submillis_precision = let submillis_precision =
Self::generic_raw_read_checks(buf, LengthOfDaySegment::Short16Bits)?; Self::generic_raw_read_checks(buf, LengthOfDaySegment::Short16Bits)?;
@@ -1212,20 +1227,7 @@ impl TimeReader for CdsTime<DaysLen24Bits> {
impl TimeWriter for CdsTime<DaysLen16Bits> { impl TimeWriter for CdsTime<DaysLen16Bits> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, TimestampError> { fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, TimestampError> {
self.length_check(buf, self.len_as_bytes())?; self.write_to_bytes(buf).map_err(TimestampError::from)
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())
} }
fn len_written(&self) -> usize { fn len_written(&self) -> usize {
@@ -1233,8 +1235,8 @@ impl TimeWriter for CdsTime<DaysLen16Bits> {
} }
} }
impl TimeWriter for CdsTime<DaysLen24Bits> { impl CdsTime<DaysLen24Bits> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, TimestampError> { pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.length_check(buf, self.len_as_bytes())?; self.length_check(buf, self.len_as_bytes())?;
buf[0] = self.pfield; buf[0] = self.pfield;
let be_days = self.ccsds_days.to_be_bytes(); let be_days = self.ccsds_days.to_be_bytes();
@@ -1251,6 +1253,12 @@ impl TimeWriter for CdsTime<DaysLen24Bits> {
} }
Ok(self.len_as_bytes()) Ok(self.len_as_bytes())
} }
}
impl TimeWriter for CdsTime<DaysLen24Bits> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, TimestampError> {
self.write_to_bytes(buf).map_err(TimestampError::from)
}
fn len_written(&self) -> usize { fn len_written(&self) -> usize {
self.len_as_bytes() self.len_as_bytes()
@@ -1331,7 +1339,7 @@ mod tests {
use super::*; use super::*;
use crate::time::TimestampError::{ByteConversion, InvalidTimeCode}; use crate::time::TimestampError::{ByteConversion, InvalidTimeCode};
use crate::time::{UnixTime, DAYS_CCSDS_TO_UNIX, MS_PER_DAY}; 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 alloc::string::ToString;
use chrono::{Datelike, NaiveDate, Timelike}; use chrono::{Datelike, NaiveDate, Timelike};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@@ -1486,12 +1494,14 @@ mod tests {
assert!(res.is_err()); assert!(res.is_err());
let error = res.unwrap_err(); let error = res.unwrap_err();
match error { match error {
ByteConversion(ToSliceTooSmall { found, expected }) => { ByteConversionError::ToSliceTooSmall { found, expected } => {
assert_eq!(found, i); assert_eq!(found, i);
assert_eq!(expected, 7); assert_eq!(expected, 7);
assert_eq!( assert_eq!(
error.to_string(), 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!( _ => panic!(
@@ -1537,10 +1547,7 @@ mod tests {
if let InvalidTimeCode { expected, found } = err { if let InvalidTimeCode { expected, found } = err {
assert_eq!(expected, CcsdsTimeCode::Cds); assert_eq!(expected, CcsdsTimeCode::Cds);
assert_eq!(found, 0); assert_eq!(found, 0);
assert_eq!( assert_eq!(err.to_string(), "invalid time code, expected Cds, found 0");
err.to_string(),
"invalid raw time code value 0 for time code Cds"
);
} }
} }
@@ -2301,7 +2308,7 @@ mod tests {
fn test_serialization() { fn test_serialization() {
let stamp_now = CdsTime::now_with_u16_days().expect("Error retrieving time"); let stamp_now = CdsTime::now_with_u16_days().expect("Error retrieving time");
let val = to_allocvec(&stamp_now).expect("Serializing timestamp failed"); 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"); let stamp_deser: CdsTime = from_bytes(&val).expect("Stamp deserialization failed");
assert_eq!(stamp_deser, stamp_now); assert_eq!(stamp_deser, stamp_now);
} }

View File

@@ -6,7 +6,7 @@
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use core::fmt::{Debug, Display, Formatter}; use core::fmt::Debug;
use core::ops::{Add, AddAssign}; use core::ops::{Add, AddAssign};
use core::time::Duration; use core::time::Duration;
@@ -20,8 +20,6 @@ use super::{
TimestampError, UnixTime, TimestampError, UnixTime,
}; };
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::time::SystemTime; use std::time::SystemTime;
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
@@ -103,63 +101,24 @@ pub fn fractional_part_from_subsec_ns(res: FractionalResolution, ns: u64) -> Fra
} }
} }
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CucError { pub enum CucError {
#[error("invalid cuc counter byte width {0}")]
InvalidCounterWidth(u8), InvalidCounterWidth(u8),
/// Invalid counter supplied. /// Invalid counter supplied.
InvalidCounter { #[error("invalid cuc counter {counter} for width {width}")]
width: u8, InvalidCounter { width: u8, counter: u64 },
counter: u64, #[error("invalid cuc fractional part {value} for resolution {resolution:?}")]
},
InvalidFractions { InvalidFractions {
resolution: FractionalResolution, resolution: FractionalResolution,
value: u64, value: u64,
}, },
#[error("error while correcting for leap seconds")]
LeapSecondCorrectionError, LeapSecondCorrectionError,
DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError), #[error("date before ccsds epoch: {0}")]
} DateBeforeCcsdsEpoch(#[from] DateBeforeCcsdsEpochError),
impl Display for CucError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
CucError::InvalidCounterWidth(w) => {
write!(f, "invalid cuc counter byte width {w}")
}
CucError::InvalidCounter { width, counter } => {
write!(f, "invalid cuc counter {counter} for width {width}")
}
CucError::InvalidFractions { resolution, value } => {
write!(
f,
"invalid cuc fractional part {value} for resolution {resolution:?}"
)
}
CucError::LeapSecondCorrectionError => {
write!(f, "error while correcting for leap seconds")
}
CucError::DateBeforeCcsdsEpoch(e) => {
write!(f, "date before ccsds epoch: {e}")
}
}
}
}
#[cfg(feature = "std")]
impl Error for CucError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
CucError::DateBeforeCcsdsEpoch(e) => Some(e),
_ => None,
}
}
}
impl From<DateBeforeCcsdsEpochError> for CucError {
fn from(e: DateBeforeCcsdsEpochError) -> Self {
Self::DateBeforeCcsdsEpoch(e)
}
} }
/// Tuple object where the first value is the width of the counter and the second value /// Tuple object where the first value is the width of the counter and the second value

View File

@@ -3,7 +3,6 @@ use crate::ByteConversionError;
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use core::cmp::Ordering; use core::cmp::Ordering;
use core::fmt::{Display, Formatter};
use core::ops::{Add, AddAssign, Sub}; use core::ops::{Add, AddAssign, Sub};
use core::time::Duration; use core::time::Duration;
@@ -14,8 +13,6 @@ use num_traits::float::FloatCore;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::time::{SystemTime, SystemTimeError}; use std::time::{SystemTime, SystemTimeError};
#[cfg(feature = "std")] #[cfg(feature = "std")]
@@ -69,67 +66,23 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCode, u8> {
#[error("date before ccsds epoch: {0:?}")] #[error("date before ccsds epoch: {0:?}")]
pub struct DateBeforeCcsdsEpochError(UnixTime); 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 = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum TimestampError { pub enum TimestampError {
#[error("invalid time code, expected {expected:?}, found {found}")]
InvalidTimeCode { expected: CcsdsTimeCode, found: u8 }, InvalidTimeCode { expected: CcsdsTimeCode, found: u8 },
ByteConversion(ByteConversionError), #[error("time stamp: byte conversion error: {0}")]
Cds(cds::CdsError), ByteConversion(#[from] ByteConversionError),
Cuc(cuc::CucError), #[error("CDS error: {0}")]
Cds(#[from] cds::CdsError),
#[error("CUC error: {0}")]
Cuc(#[from] cuc::CucError),
#[error("custom epoch not supported")]
CustomEpochNotSupported, 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<cds::CdsError> for TimestampError {
fn from(e: cds::CdsError) -> Self {
TimestampError::Cds(e)
}
}
impl From<cuc::CucError> for TimestampError {
fn from(e: cuc::CucError) -> Self {
TimestampError::Cuc(e)
}
}
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod std_mod { pub mod std_mod {
use crate::time::TimestampError; use crate::time::TimestampError;
@@ -735,7 +688,7 @@ mod tests {
fn test_cuc_error_printout() { fn test_cuc_error_printout() {
let cuc_error = CucError::InvalidCounterWidth(12); let cuc_error = CucError::InvalidCounterWidth(12);
let stamp_error = TimestampError::from(cuc_error); 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] #[test]