From 1652567b8cd23dfce005163fa8db39ec80f02b8c Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 16 Jan 2023 00:47:24 +0100 Subject: [PATCH 1/2] add first Add and AddAssign impl for CUC --- src/time/cuc.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/time/cuc.rs b/src/time/cuc.rs index 21b24a7..e3aadad 100644 --- a/src/time/cuc.rs +++ b/src/time/cuc.rs @@ -4,6 +4,8 @@ //! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct. use super::*; use core::fmt::Debug; +use core::ops::{Add, AddAssign}; +use core::time::Duration; const MIN_CUC_LEN: usize = 2; @@ -579,6 +581,80 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch { } } +fn get_provider_values_after_duration_addition( + provider: &TimeProviderCcsdsEpoch, + duration: Duration, +) -> (u32, Option) { + let mut new_counter = provider.counter.1; + let subsec_nanos = duration.subsec_nanos(); + let mut increment_counter = |amount: u32| { + let mut sum: u64 = 0; + let mut counter_inc_handler = |max_val: u64| { + sum = new_counter as u64 + amount as u64; + if sum >= max_val { + new_counter = (sum % max_val) as u32; + return; + } + new_counter = sum as u32; + }; + match provider.counter.0 { + 1 => counter_inc_handler(u8::MAX as u64), + 2 => counter_inc_handler(u16::MAX as u64), + 3 => counter_inc_handler((2_u32.pow(24) - 1) as u64), + 4 => counter_inc_handler(u32::MAX as u64), + _ => { + // Should never happen + panic!("invalid counter width") + } + } + }; + let fractional_part = if let Some(fractional_part) = &provider.fractions { + let fractional_increment = + fractional_part_from_subsec_ns(fractional_part.0, subsec_nanos as u64).unwrap(); + let mut increment_fractions = |resolution| { + let mut new_fractions = fractional_part.1 + fractional_increment.1; + let max_fractions = fractional_res_to_div(resolution); + if new_fractions > max_fractions { + increment_counter(1); + new_fractions -= max_fractions; + } + Some(FractionalPart(resolution, new_fractions)) + }; + match fractional_increment.0 { + FractionalResolution::Seconds => None, + _ => increment_fractions(fractional_increment.0), + } + } else { + None + }; + (new_counter, fractional_part) +} + +impl AddAssign for TimeProviderCcsdsEpoch { + fn add_assign(&mut self, duration: Duration) { + let (new_counter, new_fractional_part) = + get_provider_values_after_duration_addition(self, duration); + self.counter.1 = new_counter; + if self.fractions.is_some() { + self.fractions = new_fractional_part; + } + } +} + +impl Add for TimeProviderCcsdsEpoch { + type Output = Self; + + fn add(self, duration: Duration) -> Self::Output { + let (new_counter, new_fractional_part) = + get_provider_values_after_duration_addition(&self, duration); + if let Some(fractional_part) = new_fractional_part { + // The generated fractional part should always be valid, so its okay to unwrap here. + return Self::new_with_fractions(new_counter, fractional_part).unwrap(); + } + Self::new(new_counter) + } +} + #[cfg(test)] mod tests { use super::*; -- 2.43.0 From 80e1be676eaeed6dd5e4ac26d3397d09935d980a Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 17 Jan 2023 01:16:07 +0100 Subject: [PATCH 2/2] that should do it --- CHANGELOG.md | 5 ++++ src/time/cuc.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++-- src/time/mod.rs | 10 +++---- 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 944ec13..7057668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ The timestamp of `PusTm` is now optional. See Added and Changed section for deta - New `UnixTimestamp` abstraction which contains the unix seconds as an `i64` and an optional subsecond millisecond counter (`u16`) - `MS_PER_DAY` constant. +- CUC: Added `from_date_time` and `from_unix_stamp` constructors for time provider. +- CUC: Add `Add` and `AddAssign` impl for time provider. ### CDS time module @@ -42,6 +44,9 @@ The timestamp of `PusTm` is now optional. See Added and Changed section for deta ## Changed + +- (breaking) `unix_epoch_to_ccsds_epoch`: Expect and return `i64` instead of `u64` now. +- (breaking) `ccsds_epoch_to_unix_epoch`: Expect and return `i64` instead of `u64` now. - (breaking) `PusTmSecondaryHeader`: Timestamp is optional now, which translates to a timestamp of size 0. - (breaking): `PusTm`: Renamed `time_stamp` method to `timestamp`, also returns diff --git a/src/time/cuc.rs b/src/time/cuc.rs index e3aadad..bbacb47 100644 --- a/src/time/cuc.rs +++ b/src/time/cuc.rs @@ -3,6 +3,7 @@ //! //! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct. use super::*; +use chrono::Datelike; use core::fmt::Debug; use core::ops::{Add, AddAssign}; use core::time::Duration; @@ -93,6 +94,7 @@ pub fn fractional_part_from_subsec_ns( pub enum CucError { InvalidCounterWidth(u8), InvalidFractionResolution(FractionalResolution), + /// Invalid counter supplied. InvalidCounter(u8, u64), InvalidFractions(FractionalResolution, u64), } @@ -234,7 +236,7 @@ impl TimeProviderCcsdsEpoch { #[cfg(feature = "std")] pub fn from_now(fraction_resolution: FractionalResolution) -> Result { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; - let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs()); + let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64); if fraction_resolution == FractionalResolution::Seconds { return Ok(Self::new(ccsds_epoch as u32)); } @@ -249,7 +251,7 @@ impl TimeProviderCcsdsEpoch { #[cfg(feature = "std")] pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; - self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs()) as u32; + self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32; if self.fractions.is_some() { self.fractions = fractional_part_from_subsec_ns( self.fractions.unwrap().0, @@ -259,6 +261,42 @@ impl TimeProviderCcsdsEpoch { Ok(()) } + pub fn from_date_time( + dt: &DateTime, + res: FractionalResolution, + ) -> Result { + // Year before CCSDS epoch is invalid. + if dt.year() < 1958 { + return Err(TimestampError::DateBeforeCcsdsEpoch(*dt)); + } + Self::new_generic( + WidthCounterPair(4, dt.timestamp() as u32), + fractional_part_from_subsec_ns(res, dt.timestamp_subsec_nanos() as u64), + ) + .map_err(|e| e.into()) + } + + pub fn from_unix_stamp( + unix_stamp: &UnixTimestamp, + res: FractionalResolution, + ) -> Result { + let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds); + // Negative CCSDS epoch is invalid. + if ccsds_epoch < 0 { + return Err(TimestampError::DateBeforeCcsdsEpoch( + unix_stamp.as_date_time().unwrap(), + )); + } + if ccsds_epoch > u32::MAX as i64 { + return Err(CucError::InvalidCounter(4, ccsds_epoch as u64).into()); + } + let mut fractions = None; + if let Some(subsec_millis) = unix_stamp.subsecond_millis { + fractions = fractional_part_from_subsec_ns(res, subsec_millis as u64 * 10_u64.pow(6)); + } + Self::new_generic(WidthCounterPair(4, ccsds_epoch as u32), fractions).map_err(|e| e.into()) + } + pub fn new_u16_counter(counter: u16) -> Self { // These values are definitely valid, so it is okay to unwrap here. Self::new_generic(WidthCounterPair(2, counter as u32), None).unwrap() @@ -357,7 +395,7 @@ impl TimeProviderCcsdsEpoch { #[inline] fn unix_seconds(&self) -> i64 { - ccsds_epoch_to_unix_epoch(self.counter.1 as u64) as i64 + ccsds_epoch_to_unix_epoch(self.counter.1 as i64) } /// This returns the length of the individual components of the CUC timestamp in addition @@ -627,6 +665,7 @@ fn get_provider_values_after_duration_addition( } else { None }; + increment_counter(duration.as_secs() as u32); (new_counter, fractional_part) } @@ -691,6 +730,7 @@ mod tests { let zero_cuc = zero_cuc.unwrap(); let res = zero_cuc.write_to_bytes(&mut buf); assert!(res.is_ok()); + assert!(zero_cuc.subsecond_millis().is_none()); assert_eq!(zero_cuc.len_as_bytes(), 5); assert_eq!(pfield_len(buf[0]), 1); let written = res.unwrap(); @@ -1039,4 +1079,31 @@ mod tests { // Assert that the maximum resolution can be reached assert_eq!(fractions.1, 2_u32.pow(3 * 8) - 2); } + + #[test] + fn add_duration_basic() { + let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200); + cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs); + let duration = Duration::from_millis(2500); + cuc_stamp += duration; + assert_eq!(cuc_stamp.width_counter_pair().1, 202); + let fractions = cuc_stamp.width_fractions_pair().unwrap().1; + let expected_val = + (0.5 * fractional_res_to_div(FractionalResolution::FifteenUs) as f64).floor() as u32; + assert_eq!(fractions, expected_val); + let cuc_stamp2 = cuc_stamp + Duration::from_millis(501); + // What I would roughly expect + assert_eq!(cuc_stamp2.counter.1, 203); + assert!(cuc_stamp2.fractions.unwrap().1 < 100); + assert!(cuc_stamp2.subsecond_millis().unwrap() <= 1); + } + + #[test] + fn add_duration_overflow() { + let mut cuc_stamp = + TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 255), None).unwrap(); + let duration = Duration::from_secs(10); + cuc_stamp += duration; + assert_eq!(cuc_stamp.counter.1, 10); + } } diff --git a/src/time/mod.rs b/src/time/mod.rs index b5dd984..70d7121 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -170,12 +170,12 @@ pub const fn ccsds_to_unix_days(ccsds_days: i64) -> i64 { /// Similar to [unix_to_ccsds_days] but converts the epoch instead, which is the number of elpased /// seconds since the CCSDS and UNIX epoch times. -pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: u64) -> u64 { - (unix_epoch as i64 - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)) as u64 +pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: i64) -> i64 { + unix_epoch - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64) } -pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: u64) -> u64 { - (ccsds_epoch as i64 + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)) as u64 +pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: i64) -> i64 { + ccsds_epoch + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64) } #[cfg(feature = "std")] @@ -332,7 +332,7 @@ mod tests { .duration_since(SystemTime::UNIX_EPOCH) .unwrap(); let unix_epoch = now.as_secs(); - let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs()); + let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u64; assert!(ccsds_epoch > unix_epoch); assert_eq!((ccsds_epoch - unix_epoch) % SECONDS_PER_DAY as u64, 0); let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64; -- 2.43.0