From 85a8eb3f4ae004a598e75f0ef9fbf9e81c27ca4c Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 25 Mar 2024 13:42:18 +0100 Subject: [PATCH] more granular error handling --- CHANGELOG.md | 3 ++ src/ecss/mod.rs | 10 ++--- src/ecss/tc.rs | 16 +++----- src/ecss/tm.rs | 80 +++++++++++++++++++------------------- src/time/cds.rs | 100 +++++++++++++++++++++++++++--------------------- src/time/cuc.rs | 37 +++++++++++++----- src/time/mod.rs | 18 +++++++-- 7 files changed, 150 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4300d4e..f2ef33a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,9 @@ to check all the API changes in the **Changed** chapter. - `CcsdsTimeProvider::date_time` renamed to `CcsdsTimeProvider::chrono_date_time`. - Added `UnixTime::MIN`, `UnixTime::MAX` and `UnixTime::EPOCH`. - Added `UnixTime::timelib_date_time`. +- Error handling for ECSS and time module is more granular now, with a new + `DateBeforeCcsdsEpochError` error and a `DateBeforeCcsdsEpoch` enum variant for both + `CdsError` and `CucError`. # [v0.11.0-rc.0] 2024-03-04 diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index 5838436..8aab2a1 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -205,13 +205,12 @@ pub trait PusPacket: CcsdsPacket { fn crc16(&self) -> Option; } -pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result { +pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result { if raw_data.len() < 2 { return Err(ByteConversionError::FromSliceTooSmall { found: raw_data.len(), expected: 2, - } - .into()); + }); } Ok(u16::from_be_bytes( raw_data[raw_data.len() - 2..raw_data.len()] @@ -248,13 +247,12 @@ pub(crate) fn user_data_from_raw( current_idx: usize, total_len: usize, slice: &[u8], -) -> Result<&[u8], PusError> { +) -> Result<&[u8], ByteConversionError> { match current_idx { _ if current_idx > total_len - 2 => Err(ByteConversionError::FromSliceTooSmall { found: total_len - 2, expected: current_idx, - } - .into()), + }), _ => Ok(&slice[current_idx..total_len - 2]), } } diff --git a/src/ecss/tc.rs b/src/ecss/tc.rs index d923162..5fb36a6 100644 --- a/src/ecss/tc.rs +++ b/src/ecss/tc.rs @@ -654,8 +654,7 @@ impl<'raw_data> PusTcCreator<'raw_data> { } #[cfg(feature = "alloc")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] - pub fn append_to_vec(&self, vec: &mut Vec) -> Result { + pub fn append_to_vec(&self, vec: &mut Vec) -> usize { let sph_zc = crate::zc::SpHeader::from(self.sp_header); let mut appended_len = PUS_TC_MIN_LEN_WITHOUT_APP_DATA; appended_len += self.app_data.len(); @@ -668,7 +667,7 @@ impl<'raw_data> PusTcCreator<'raw_data> { let mut digest = CRC_CCITT_FALSE.digest(); digest.update(&vec[start_idx..start_idx + appended_len - 2]); vec.extend_from_slice(&digest.finalize().to_be_bytes()); - Ok(appended_len) + appended_len } } @@ -763,7 +762,8 @@ pub struct PusTcReader<'raw_data> { impl<'raw_data> PusTcReader<'raw_data> { /// Create a [PusTcReader] instance from a raw slice. On success, it returns a tuple containing - /// the instance and the found byte length of the packet. + /// the instance and the found byte length of the packet. This function also performs a CRC + /// check and will return an appropriate [PusError] if the check fails. pub fn new(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> { let raw_data_len = slice.len(); if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA { @@ -1010,9 +1010,7 @@ mod tests { fn test_vec_ser_deser() { let pus_tc = base_ping_tc_simple_ctor(); let mut test_vec = Vec::new(); - let size = pus_tc - .append_to_vec(&mut test_vec) - .expect("Error writing TC to vector"); + let size = pus_tc.append_to_vec(&mut test_vec); assert_eq!(size, 13); verify_test_tc_raw(&test_vec.as_slice()); verify_crc_no_app_data(&test_vec.as_slice()); @@ -1058,9 +1056,7 @@ mod tests { let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]); verify_test_tc(&pus_tc, true, 16); let mut test_vec = Vec::new(); - let size = pus_tc - .append_to_vec(&mut test_vec) - .expect("Error writing TC to vector"); + let size = pus_tc.append_to_vec(&mut test_vec); assert_eq!(test_vec[11], 1); assert_eq!(test_vec[12], 2); assert_eq!(test_vec[13], 3); diff --git a/src/ecss/tm.rs b/src/ecss/tm.rs index 837a6dc..5199e52 100644 --- a/src/ecss/tm.rs +++ b/src/ecss/tm.rs @@ -660,6 +660,37 @@ impl<'raw_data> PusTmCreator<'raw_data> { self.update_ccsds_data_len(); } + /// Write the raw PUS byte representation to a provided buffer. + pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result { + let mut curr_idx = 0; + let total_size = self.len_written(); + if total_size > slice.len() { + return Err(ByteConversionError::ToSliceTooSmall { + found: slice.len(), + expected: total_size, + }); + } + 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_bytes(&mut slice[curr_idx..curr_idx + sec_header_len]) + .ok_or(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(); + 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) + } + /// Append the raw PUS byte representation to a provided [alloc::vec::Vec] #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] @@ -689,34 +720,7 @@ impl WritablePusPacket for PusTmCreator<'_> { } /// Write the raw PUS byte representation to a provided buffer. fn write_to_bytes(&self, slice: &mut [u8]) -> Result { - let mut curr_idx = 0; - 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(&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_bytes(&mut slice[curr_idx..curr_idx + sec_header_len]) - .ok_or(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(); - 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) + Ok(Self::write_to_bytes(self, slice)?) } } @@ -788,6 +792,9 @@ impl<'raw_data> PusTmReader<'raw_data> { /// Create a [PusTmReader] instance from a raw slice. On success, it returns a tuple containing /// the instance and the found byte length of the packet. The timestamp length needs to be /// known beforehand. + /// + /// This function will check the CRC-16 of the PUS packet and will return an appropriate + /// [PusError] if the check fails. pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<(Self, usize), PusError> { let raw_data_len = slice.len(); if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA { @@ -1260,18 +1267,11 @@ mod tests { let res = pus_tm.write_to_bytes(&mut buf); assert!(res.is_err()); let error = res.unwrap_err(); - assert!(matches!(error, PusError::ByteConversion { .. })); - match error { - PusError::ByteConversion(err) => match err { - ByteConversionError::ToSliceTooSmall { found, expected } => { - assert_eq!(expected, 22); - assert_eq!(found, 16); - } - _ => panic!("Invalid PUS error {:?}", err), - }, - _ => { - panic!("Invalid error {:?}", error); - } + if let ByteConversionError::ToSliceTooSmall { found, expected } = error { + assert_eq!(expected, 22); + assert_eq!(found, 16); + } else { + panic!("Invalid error {:?}", error); } } diff --git a/src/time/cds.rs b/src/time/cds.rs index 120d27a..3a2a3b4 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -34,8 +34,9 @@ use core::any::Any; use serde::{Deserialize, Serialize}; use super::{ - ccsds_to_unix_days, unix_to_ccsds_days, CcsdsTimeCode, CcsdsTimeProvider, TimeReader, - TimeWriter, TimestampError, UnixTime, MS_PER_DAY, SECONDS_PER_DAY, + ccsds_to_unix_days, unix_to_ccsds_days, CcsdsTimeCode, CcsdsTimeProvider, + DateBeforeCcsdsEpochError, TimeReader, TimeWriter, TimestampError, UnixTime, MS_PER_DAY, + SECONDS_PER_DAY, }; /// Base value for the preamble field for a time field parser to determine the time field type. @@ -99,6 +100,7 @@ pub enum CdsError { /// There are distinct constructors depending on the days field width detected in the preamble /// field. This error will be returned if there is a missmatch. InvalidCtorForDaysOfLenInPreamble(LengthOfDaySegment), + DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError), } impl Display for CdsError { @@ -113,12 +115,27 @@ impl Display for CdsError { "wrong constructor for length of day {length_of_day:?} detected in preamble", ) } + CdsError::DateBeforeCcsdsEpoch(e) => write!(f, "date before CCSDS epoch: {e}"), } } } #[cfg(feature = "std")] -impl Error for CdsError {} +impl Error for CdsError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + CdsError::DateBeforeCcsdsEpoch(e) => Some(e), + _ => None, + } + } +} + +impl From for CdsError { + fn from(value: DateBeforeCcsdsEpochError) -> Self { + Self::DateBeforeCcsdsEpoch(value) + } +} + pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment { if (pfield >> 2) & 0b1 == 1 { return LengthOfDaySegment::Long24Bits; @@ -226,11 +243,11 @@ impl ConversionFromUnix { unix_seconds: i64, subsec_nanos: u32, precision: SubmillisPrecision, - ) -> Result { + ) -> Result { let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(unix_seconds); let ccsds_days = unix_to_ccsds_days(unix_days); if ccsds_days == 0 && (secs_of_day > 0 || subsec_nanos > 0) || ccsds_days < 0 { - return Err(TimestampError::DateBeforeCcsdsEpoch( + return Err(DateBeforeCcsdsEpochError( UnixTime::new_checked(unix_seconds, subsec_nanos) .expect("unix timestamp creation failed"), )); @@ -318,19 +335,19 @@ fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) { #[cfg(feature = "chrono")] impl ConversionFromChronoDatetime { - fn new(dt: &chrono::DateTime) -> Result { + fn new(dt: &chrono::DateTime) -> Result { Self::new_generic(dt, SubmillisPrecision::Absent) } fn new_with_submillis_us_prec( dt: &chrono::DateTime, - ) -> Result { + ) -> Result { Self::new_generic(dt, SubmillisPrecision::Microseconds) } fn new_with_submillis_ps_prec( dt: &chrono::DateTime, - ) -> Result { + ) -> Result { Self::new_generic(dt, SubmillisPrecision::Picoseconds) } @@ -338,10 +355,10 @@ impl ConversionFromChronoDatetime { fn new_generic( dt: &chrono::DateTime, prec: SubmillisPrecision, - ) -> Result { + ) -> Result { // The CDS timestamp does not support timestamps before the CCSDS epoch. if dt.year() < 1958 { - return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTime::from(*dt))); + return Err(DateBeforeCcsdsEpochError(UnixTime::from(*dt))); } // The contained values in the conversion should be all positive now let unix_conversion = @@ -680,7 +697,7 @@ impl CdsTime { fn from_dt_generic( dt: &chrono::DateTime, days_len: LengthOfDaySegment, - ) -> Result { + ) -> Result { let conv_from_dt = ConversionFromChronoDatetime::new(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -689,7 +706,7 @@ impl CdsTime { fn from_dt_generic_us_prec( dt: &chrono::DateTime, days_len: LengthOfDaySegment, - ) -> Result { + ) -> Result { let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_us_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -698,7 +715,7 @@ impl CdsTime { fn from_dt_generic_ps_prec( dt: &chrono::DateTime, days_len: LengthOfDaySegment, - ) -> Result { + ) -> Result { let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_ps_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -707,7 +724,7 @@ impl CdsTime { unix_stamp: &UnixTime, days_len: LengthOfDaySegment, submillis_prec: SubmillisPrecision, - ) -> Result { + ) -> Result { let conv_from_dt = ConversionFromUnix::new(unix_stamp.secs, unix_stamp.subsec_nanos, submillis_prec)?; Self::generic_from_conversion(days_len, conv_from_dt) @@ -717,33 +734,31 @@ impl CdsTime { fn from_now_generic(days_len: LengthOfDaySegment) -> Result { let conversion_from_now = ConversionFromNow::new()?; Self::generic_from_conversion(days_len, conversion_from_now) - .map_err(StdTimestampError::Timestamp) + .map_err(|e| StdTimestampError::Timestamp(TimestampError::from(e))) } #[cfg(feature = "std")] fn from_now_generic_us_prec(days_len: LengthOfDaySegment) -> Result { let conversion_from_now = ConversionFromNow::new_with_submillis_us_prec()?; Self::generic_from_conversion(days_len, conversion_from_now) - .map_err(StdTimestampError::Timestamp) + .map_err(|e| StdTimestampError::Timestamp(TimestampError::from(e))) } #[cfg(feature = "std")] fn from_now_generic_ps_prec(days_len: LengthOfDaySegment) -> Result { let conversion_from_now = ConversionFromNow::new_with_submillis_ps_prec()?; Self::generic_from_conversion(days_len, conversion_from_now) - .map_err(StdTimestampError::Timestamp) + .map_err(|e| StdTimestampError::Timestamp(TimestampError::from(e))) } fn generic_from_conversion( days_len: LengthOfDaySegment, converter: C, - ) -> Result { - let ccsds_days: ProvidesDaysLen::FieldType = - converter.ccsds_days_as_u32().try_into().map_err(|_| { - TimestampError::Cds(CdsError::InvalidCcsdsDays( - converter.ccsds_days_as_u32().into(), - )) - })?; + ) -> Result { + let ccsds_days: ProvidesDaysLen::FieldType = converter + .ccsds_days_as_u32() + .try_into() + .map_err(|_| CdsError::InvalidCcsdsDays(converter.ccsds_days_as_u32().into()))?; let mut provider = Self { pfield: Self::generate_p_field(days_len, converter.submillis_precision()), ccsds_days, @@ -825,9 +840,7 @@ impl CdsTime { /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// or the CCSDS days value exceeds the allowed bit width (24 bits). #[cfg(feature = "chrono")] - pub fn from_dt_with_u24_days( - dt: &chrono::DateTime, - ) -> Result { + pub fn from_dt_with_u24_days(dt: &chrono::DateTime) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits) } @@ -841,7 +854,7 @@ impl CdsTime { pub fn from_unix_stamp_with_u24_days( unix_stamp: &UnixTime, submillis_prec: SubmillisPrecision, - ) -> Result { + ) -> Result { Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits, submillis_prec) } @@ -849,7 +862,7 @@ impl CdsTime { #[cfg(feature = "chrono")] pub fn from_dt_with_u24_days_us_precision( dt: &chrono::DateTime, - ) -> Result { + ) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits) } @@ -857,7 +870,7 @@ impl CdsTime { #[cfg(feature = "chrono")] pub fn from_dt_with_u24_days_ps_precision( dt: &chrono::DateTime, - ) -> Result { + ) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits) } @@ -915,9 +928,7 @@ impl CdsTime { /// [TimestampError::Cds] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or /// the CCSDS days value exceeds the allowed bit width (16 bits). #[cfg(feature = "chrono")] - pub fn from_dt_with_u16_days( - dt: &chrono::DateTime, - ) -> Result { + pub fn from_dt_with_u16_days(dt: &chrono::DateTime) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits) } @@ -938,7 +949,7 @@ impl CdsTime { pub fn from_unix_stamp_with_u16_days( unix_stamp: &UnixTime, submillis_prec: SubmillisPrecision, - ) -> Result { + ) -> Result { Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits, submillis_prec) } @@ -946,7 +957,7 @@ impl CdsTime { #[cfg(feature = "chrono")] pub fn from_dt_with_u16_days_us_precision( dt: &chrono::DateTime, - ) -> Result { + ) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits) } @@ -954,7 +965,7 @@ impl CdsTime { #[cfg(feature = "chrono")] pub fn from_dt_with_u16_days_ps_precision( dt: &chrono::DateTime, - ) -> Result { + ) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits) } @@ -1168,7 +1179,7 @@ impl AddAssign for CdsTime { #[cfg(feature = "chrono")] impl TryFrom> for CdsTime { - type Error = TimestampError; + type Error = CdsError; fn try_from(dt: chrono::DateTime) -> Result { let conversion = ConversionFromChronoDatetime::new(&dt)?; @@ -1178,8 +1189,7 @@ impl TryFrom> for CdsTime { #[cfg(feature = "chrono")] impl TryFrom> for CdsTime { - type Error = TimestampError; - + type Error = CdsError; fn try_from(dt: chrono::DateTime) -> Result { let conversion = ConversionFromChronoDatetime::new(&dt)?; Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion) @@ -1991,12 +2001,12 @@ mod tests { panic!("creation should not succeed") } Err(e) => { - if let TimestampError::Cds(CdsError::InvalidCcsdsDays(days)) = e { + if let CdsError::InvalidCcsdsDays(days) = e { assert_eq!( days, unix_to_ccsds_days(invalid_unix_secs / SECONDS_PER_DAY as i64) ); - assert_eq!(e.to_string(), "cds error: invalid ccsds days 69919"); + assert_eq!(e.to_string(), "invalid ccsds days 69919"); } else { panic!("unexpected error {}", e) } @@ -2018,8 +2028,8 @@ mod tests { panic!("creation should not succeed") } Err(e) => { - if let TimestampError::DateBeforeCcsdsEpoch(dt) = e { - let dt = dt.chrono_date_time(); + if let CdsError::DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError(unix_dt)) = e { + let dt = unix_dt.chrono_date_time(); if let chrono::LocalResult::Single(dt) = dt { assert_eq!(dt.year(), 1957); assert_eq!(dt.month(), 12); @@ -2249,7 +2259,9 @@ mod tests { .unwrap(); let time_provider = CdsTime::from_dt_with_u24_days(&datetime_utc); assert!(time_provider.is_err()); - if let TimestampError::DateBeforeCcsdsEpoch(dt) = time_provider.unwrap_err() { + if let CdsError::DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError(dt)) = + time_provider.unwrap_err() + { assert_eq!(dt, datetime_utc.into()); } } diff --git a/src/time/cuc.rs b/src/time/cuc.rs index 856dfad..fa31f31 100644 --- a/src/time/cuc.rs +++ b/src/time/cuc.rs @@ -17,7 +17,8 @@ use crate::ByteConversionError; use super::StdTimestampError; use super::{ ccsds_epoch_to_unix_epoch, ccsds_time_code_from_p_field, unix_epoch_to_ccsds_epoch, - CcsdsTimeCode, CcsdsTimeProvider, TimeReader, TimeWriter, TimestampError, UnixTime, + CcsdsTimeCode, CcsdsTimeProvider, DateBeforeCcsdsEpochError, TimeReader, TimeWriter, + TimestampError, UnixTime, }; #[cfg(feature = "std")] use std::error::Error; @@ -116,6 +117,7 @@ pub enum CucError { value: u64, }, LeapSecondCorrectionError, + DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError), } impl Display for CucError { @@ -136,12 +138,28 @@ impl Display for CucError { 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 {} +impl Error for CucError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + CucError::DateBeforeCcsdsEpoch(e) => Some(e), + _ => None, + } + } +} + +impl From 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 /// is the counter value. @@ -398,20 +416,19 @@ impl CucTime { dt: &chrono::DateTime, res: FractionalResolution, leap_seconds: u32, - ) -> Result { + ) -> Result { // Year before CCSDS epoch is invalid. if dt.year() < 1958 { - return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTime::from(*dt))); + return Err(DateBeforeCcsdsEpochError(UnixTime::from(*dt)).into()); } let counter = dt .timestamp() .checked_add(i64::from(leap_seconds)) - .ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?; + .ok_or(CucError::LeapSecondCorrectionError)?; Self::new_generic( WidthCounterPair(4, counter as u32), fractional_part_from_subsec_ns(res, dt.timestamp_subsec_nanos() as u64), ) - .map_err(|e| e.into()) } /// Generates a CUC timestamp from a UNIX timestamp with a width of 4. This width is able @@ -420,11 +437,11 @@ impl CucTime { unix_stamp: &UnixTime, res: FractionalResolution, leap_seconds: u32, - ) -> Result { + ) -> Result { let counter = unix_epoch_to_ccsds_epoch(unix_stamp.secs); // Negative CCSDS epoch is invalid. if counter < 0 { - return Err(TimestampError::DateBeforeCcsdsEpoch(*unix_stamp)); + return Err(DateBeforeCcsdsEpochError(*unix_stamp).into()); } // We already excluded negative values, so the conversion to u64 should always work. let mut counter = u32::try_from(counter).map_err(|_| CucError::InvalidCounter { @@ -433,10 +450,10 @@ impl CucTime { })?; counter = counter .checked_add(leap_seconds) - .ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?; + .ok_or(CucError::LeapSecondCorrectionError)?; let fractions = fractional_part_from_subsec_ns(res, unix_stamp.subsec_millis() as u64 * 10_u64.pow(6)); - Self::new_generic(WidthCounterPair(4, counter as u32), fractions).map_err(|e| e.into()) + Self::new_generic(WidthCounterPair(4, counter as u32), fractions) } /// Most generic constructor which allows full configurability for the counter and for the diff --git a/src/time/mod.rs b/src/time/mod.rs index 96f6094..eeddb16 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -63,6 +63,19 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result { CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits) } +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct DateBeforeCcsdsEpochError(UnixTime); + +impl Display for DateBeforeCcsdsEpochError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "date before ccsds epoch: {:?}", self.0) + } +} + +#[cfg(feature = "std")] +impl Error for DateBeforeCcsdsEpochError {} + #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[non_exhaustive] @@ -71,7 +84,6 @@ pub enum TimestampError { ByteConversion(ByteConversionError), Cds(cds::CdsError), Cuc(cuc::CucError), - DateBeforeCcsdsEpoch(UnixTime), CustomEpochNotSupported, } @@ -93,9 +105,6 @@ impl Display for TimestampError { TimestampError::ByteConversion(e) => { write!(f, "time stamp: {e}") } - TimestampError::DateBeforeCcsdsEpoch(e) => { - write!(f, "datetime with date before ccsds epoch: {e:?}") - } TimestampError::CustomEpochNotSupported => { write!(f, "custom epochs are not supported") } @@ -114,6 +123,7 @@ impl Error for TimestampError { } } } + impl From for TimestampError { fn from(e: cds::CdsError) -> Self { TimestampError::Cds(e)