From 6ea2489e1c5814d3fff5c9db7fdd126b4e731268 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 25 Mar 2024 11:44:06 +0100 Subject: [PATCH] more granular error handling --- src/ecss/mod.rs | 4 +- src/ecss/tc.rs | 16 +++---- src/ecss/tm.rs | 3 ++ src/time/cds.rs | 108 +++++++++++++++++++++++++++--------------------- src/time/cuc.rs | 37 ++++++++++++----- src/time/mod.rs | 18 ++++++-- 6 files changed, 114 insertions(+), 72 deletions(-) diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index 5838436..7389459 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -205,7 +205,7 @@ 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(), @@ -248,7 +248,7 @@ 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, 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..b0ff1b2 100644 --- a/src/ecss/tm.rs +++ b/src/ecss/tm.rs @@ -788,6 +788,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 { diff --git a/src/time/cds.rs b/src/time/cds.rs index 120d27a..8e1a902 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,21 +1179,26 @@ 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)?; - Self::generic_from_conversion(LengthOfDaySegment::Short16Bits, conversion) + Ok(Self::generic_from_conversion( + LengthOfDaySegment::Short16Bits, + conversion, + )?) } } #[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) + Ok(Self::generic_from_conversion( + LengthOfDaySegment::Long24Bits, + conversion, + )?) } } @@ -1991,12 +2007,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 +2034,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 +2265,7 @@ 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)