From 1761bdd33f28c3dd510ea8ebce5a6495fbc9cf46 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 13 Jan 2023 23:04:53 +0100 Subject: [PATCH 01/31] add first Add impl for TimeProvider --- src/time/cds.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ src/time/mod.rs | 1 + 2 files changed, 72 insertions(+) diff --git a/src/time/cds.rs b/src/time/cds.rs index 6e08eba..3e25853 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -5,6 +5,8 @@ use super::*; use crate::private::Sealed; use core::fmt::Debug; +use core::ops::Add; +use core::time::Duration; /// Base value for the preamble field for a time field parser to determine the time field type. pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::Cds as u8) << 4; @@ -534,6 +536,75 @@ impl TimeProvider { } } +/// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover +/// when they overflow, because addition needs to be infallible. The user needs to check for a +/// days overflow when this is a possibility and might be a problem. +impl Add for TimeProvider { + type Output = Self; + + fn add(self, duration: Duration) -> Self::Output { + let mut next_ccsds_days = self.ccsds_days; + let mut next_ms_of_day = self.ms_of_day; + let mut precision = None; + // Increment CCSDS days by a certain amount while also accounting for overflow. + let increment_days = |ccsds_days: &mut u16, days_inc: u16| { + let days_addition = *ccsds_days as u32 + days_inc as u32; + if days_addition >= (u16::MAX - 1) as u32 { + *ccsds_days = (days_addition - u16::MAX as u32) as u16; + } else { + *ccsds_days += days_inc; + } + }; + // Increment MS of day by a certain amount while also accounting for overflow, where + // the new value exceeds the MS of a day. + let increment_ms_of_day = |ms_of_day: &mut u32, ms_inc: u32, ccsds_days: &mut u16| { + let ms_addition = *ms_of_day + ms_inc; + if ms_addition >= MS_PER_DAY { + *ms_of_day = ms_addition - MS_PER_DAY; + // Re-use existing closure to always amount for overflow. + increment_days(ccsds_days, 1); + } + }; + if let Some(submillis_prec) = self.submillis_precision { + match submillis_prec { + SubmillisPrecision::Absent => {} + SubmillisPrecision::Microseconds(mut us) => { + let micros = duration.as_micros(); + let submilli_micros = (micros % 1000) as u16; + us += submilli_micros; + if us >= 1000 { + let carryover_us = us - 1000; + increment_ms_of_day(&mut next_ms_of_day, 1, &mut next_ccsds_days); + precision = Some(SubmillisPrecision::Microseconds(carryover_us)); + } + } + SubmillisPrecision::Picoseconds(_ps) => {} + SubmillisPrecision::Reserved => {} + } + } + let full_ms = duration.as_millis(); + let ms_of_day = (full_ms % MS_PER_DAY as u128) as u32; + increment_ms_of_day(&mut next_ms_of_day, ms_of_day, &mut next_ccsds_days); + increment_days( + &mut next_ccsds_days, + ((full_ms as u32 - ms_of_day) / MS_PER_DAY) as u16, + ); + let mut provider = Self::new_with_u16_days(next_ccsds_days, next_ms_of_day); + if let Some(prec) = precision { + provider.set_submillis_precision(prec); + } + provider + } +} + +impl Add for TimeProvider { + type Output = Self; + + fn add(self, _rhs: Duration) -> Self::Output { + Self::new_with_u24_days(0, 0).unwrap() + } +} + impl CcsdsTimeProvider for TimeProvider { fn len_as_bytes(&self) -> usize { Self::calc_stamp_len(self.pfield) diff --git a/src/time/mod.rs b/src/time/mod.rs index 26d74b2..da262ca 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -20,6 +20,7 @@ pub mod cuc; pub const DAYS_CCSDS_TO_UNIX: i32 = -4383; pub const SECONDS_PER_DAY: u32 = 86400; +pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000; #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] From 0a774afd4137e945dbe7baf10344f4bc9e35ba33 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 13 Jan 2023 23:24:37 +0100 Subject: [PATCH 02/31] added Add impl for both TimeProviders --- src/time/cds.rs | 123 ++++++++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 51 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 3e25853..142eded 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -11,11 +11,12 @@ use core::time::Duration; /// Base value for the preamble field for a time field parser to determine the time field type. pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::Cds as u8) << 4; pub const MIN_CDS_FIELD_LEN: usize = 7; +pub const MAX_DAYS_24_BITS: u32 = 2_u32.pow(24) - 1; /// Generic trait implemented by token structs to specify the length of day field at type /// level. This trait is only meant to be implemented in this crate and therefore sealed. pub trait ProvidesDaysLength: Sealed { - type FieldType: Copy + Clone + TryFrom; + type FieldType: Copy + Clone + TryFrom + Into + Into; } /// Type level token to be used as a generic parameter to [TimeProvider]. @@ -217,6 +218,10 @@ impl TimeProvider { self.ccsds_days } + fn ccsds_days_as_u32(&self) -> u32 { + self.ccsds_days.into() + } + pub fn submillis_precision(&self) -> Option { self.submillis_precision } @@ -341,7 +346,7 @@ impl TimeProvider { unix_seconds: 0, submillis_precision: None, }; - let unix_days_seconds = ccsds_to_unix_days(ccsds_days.into()) * SECONDS_PER_DAY as i64; + let unix_days_seconds = ccsds_to_unix_days(i64::from(ccsds_days)) * SECONDS_PER_DAY as i64; provider.setup(unix_days_seconds, ms_of_day.into()); Ok(provider) } @@ -536,6 +541,59 @@ impl TimeProvider { } } +fn add_for_max_ccsds_days_val( + time_provider: TimeProvider, + max_days_val: u32, + duration: Duration, +) -> (u32, u32, Option) { + let mut next_ccsds_days = time_provider.ccsds_days_as_u32(); + let mut next_ms_of_day = time_provider.ms_of_day; + let mut precision = None; + // Increment CCSDS days by a certain amount while also accounting for overflow. + let increment_days = |ccsds_days: &mut u32, days_inc: u32| { + let days_addition = *ccsds_days + days_inc; + if days_addition >= (max_days_val - 1) { + *ccsds_days = days_addition - max_days_val; + } else { + *ccsds_days += days_inc; + } + }; + // Increment MS of day by a certain amount while also accounting for overflow, where + // the new value exceeds the MS of a day. + let increment_ms_of_day = |ms_of_day: &mut u32, ms_inc: u32, ccsds_days: &mut u32| { + let ms_addition = *ms_of_day + ms_inc; + if ms_addition >= MS_PER_DAY { + *ms_of_day = ms_addition - MS_PER_DAY; + // Re-use existing closure to always amount for overflow. + increment_days(ccsds_days, 1); + } + }; + if let Some(submillis_prec) = time_provider.submillis_precision { + match submillis_prec { + SubmillisPrecision::Absent => {} + SubmillisPrecision::Microseconds(mut us) => { + let micros = duration.as_micros(); + let submilli_micros = (micros % 1000) as u16; + us += submilli_micros; + if us >= 1000 { + let carryover_us = us - 1000; + increment_ms_of_day(&mut next_ms_of_day, 1, &mut next_ccsds_days); + precision = Some(SubmillisPrecision::Microseconds(carryover_us)); + } + } + SubmillisPrecision::Picoseconds(_ps) => {} + SubmillisPrecision::Reserved => {} + } + } + let full_ms = duration.as_millis(); + let ms_of_day = (full_ms % MS_PER_DAY as u128) as u32; + increment_ms_of_day(&mut next_ms_of_day, ms_of_day, &mut next_ccsds_days); + increment_days( + &mut next_ccsds_days, + (full_ms as u32 - ms_of_day) / MS_PER_DAY, + ); + (next_ccsds_days, next_ms_of_day, precision) +} /// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover /// when they overflow, because addition needs to be infallible. The user needs to check for a /// days overflow when this is a possibility and might be a problem. @@ -543,53 +601,10 @@ impl Add for TimeProvider { type Output = Self; fn add(self, duration: Duration) -> Self::Output { - let mut next_ccsds_days = self.ccsds_days; - let mut next_ms_of_day = self.ms_of_day; - let mut precision = None; - // Increment CCSDS days by a certain amount while also accounting for overflow. - let increment_days = |ccsds_days: &mut u16, days_inc: u16| { - let days_addition = *ccsds_days as u32 + days_inc as u32; - if days_addition >= (u16::MAX - 1) as u32 { - *ccsds_days = (days_addition - u16::MAX as u32) as u16; - } else { - *ccsds_days += days_inc; - } - }; - // Increment MS of day by a certain amount while also accounting for overflow, where - // the new value exceeds the MS of a day. - let increment_ms_of_day = |ms_of_day: &mut u32, ms_inc: u32, ccsds_days: &mut u16| { - let ms_addition = *ms_of_day + ms_inc; - if ms_addition >= MS_PER_DAY { - *ms_of_day = ms_addition - MS_PER_DAY; - // Re-use existing closure to always amount for overflow. - increment_days(ccsds_days, 1); - } - }; - if let Some(submillis_prec) = self.submillis_precision { - match submillis_prec { - SubmillisPrecision::Absent => {} - SubmillisPrecision::Microseconds(mut us) => { - let micros = duration.as_micros(); - let submilli_micros = (micros % 1000) as u16; - us += submilli_micros; - if us >= 1000 { - let carryover_us = us - 1000; - increment_ms_of_day(&mut next_ms_of_day, 1, &mut next_ccsds_days); - precision = Some(SubmillisPrecision::Microseconds(carryover_us)); - } - } - SubmillisPrecision::Picoseconds(_ps) => {} - SubmillisPrecision::Reserved => {} - } - } - let full_ms = duration.as_millis(); - let ms_of_day = (full_ms % MS_PER_DAY as u128) as u32; - increment_ms_of_day(&mut next_ms_of_day, ms_of_day, &mut next_ccsds_days); - increment_days( - &mut next_ccsds_days, - ((full_ms as u32 - ms_of_day) / MS_PER_DAY) as u16, - ); - let mut provider = Self::new_with_u16_days(next_ccsds_days, next_ms_of_day); + #[allow(clippy::suspicious_arithmetic_impl)] + let (next_ccsds_days, next_ms_of_day, precision) = + add_for_max_ccsds_days_val(self, u16::MAX as u32, duration); + let mut provider = Self::new_with_u16_days(next_ccsds_days as u16, next_ms_of_day); if let Some(prec) = precision { provider.set_submillis_precision(prec); } @@ -600,8 +615,14 @@ impl Add for TimeProvider { impl Add for TimeProvider { type Output = Self; - fn add(self, _rhs: Duration) -> Self::Output { - Self::new_with_u24_days(0, 0).unwrap() + fn add(self, duration: Duration) -> Self::Output { + let (next_ccsds_days, next_ms_of_day, precision) = + add_for_max_ccsds_days_val(self, MAX_DAYS_24_BITS, duration); + let mut provider = Self::new_with_u24_days(next_ccsds_days, next_ms_of_day).unwrap(); + if let Some(prec) = precision { + provider.set_submillis_precision(prec); + } + provider } } From b350f8fe03e02938b4117e83a6c7ad0a53eccee5 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 13 Jan 2023 23:25:09 +0100 Subject: [PATCH 03/31] remove lint allowance --- src/time/cds.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 142eded..9c5fb81 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -601,7 +601,6 @@ impl Add for TimeProvider { type Output = Self; fn add(self, duration: Duration) -> Self::Output { - #[allow(clippy::suspicious_arithmetic_impl)] let (next_ccsds_days, next_ms_of_day, precision) = add_for_max_ccsds_days_val(self, u16::MAX as u32, duration); let mut provider = Self::new_with_u16_days(next_ccsds_days as u16, next_ms_of_day); From 3ba68b4e6407951b8585e785d6707ccae7f1fc04 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 14 Jan 2023 13:22:32 +0100 Subject: [PATCH 04/31] add new Conversion helper --- src/time/cds.rs | 156 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 121 insertions(+), 35 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 9c5fb81..26f4b9f 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -4,6 +4,7 @@ //! The core data structure to do this is the [cds::TimeProvider] struct. use super::*; use crate::private::Sealed; +use chrono::Datelike; use core::fmt::Debug; use core::ops::Add; use core::time::Duration; @@ -135,15 +136,56 @@ pub struct TimeProvider { ccsds_days: DaysLen::FieldType, ms_of_day: u32, submillis_precision: Option, + /// This is not strictly necessary but still cached because it significantly simplifies the + /// calculation of [`DateTime`]. unix_seconds: i64, } +/// Helper struct which generates fields for the CDS time provider from a datetime. +struct ConversionFromDatetime { + ccsds_days: u32, + ms_of_day: u32, + /// This is a side-product of the calculation of the CCSDS days. It is useful for + /// re-calculating the datetime at a later point and therefore supplied as well. + unix_days_seconds: i64, + submillis_prec: Option, +} + +fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, i64) { + let secs_of_day = unix_seconds % SECONDS_PER_DAY as i64; + let unix_days = if secs_of_day > 0 { + (unix_seconds - secs_of_day) / SECONDS_PER_DAY as i64 + } else { + (unix_seconds + secs_of_day) / SECONDS_PER_DAY as i64 + }; + (unix_days, secs_of_day) +} + +impl ConversionFromDatetime { + fn new(dt: DateTime) -> Self { + // The CDS timestamp does not support timestamps before the CCSDS epoch. + assert!(dt.year() >= 1958); + let unix_seconds = dt.timestamp(); + let (unix_days, _) = calc_unix_days_and_secs_of_day(unix_seconds); + // This should always be positive now. + let ccsds_days = unix_to_ccsds_days(unix_days) as u32; + let unix_ms = dt.timestamp_millis(); + let mut ms_of_day = unix_ms % MS_PER_DAY as i64; + if ms_of_day < 0 { + ms_of_day = -ms_of_day; + } + Self { + ccsds_days, + ms_of_day: ms_of_day as u32, + unix_days_seconds: unix_days * SECONDS_PER_DAY as i64, + submillis_prec: None, + } + } +} + #[cfg(feature = "std")] struct ConversionFromNow { - ccsds_days: i32, - ms_of_day: u64, - unix_days_seconds: u64, - submillis_prec: Option, + base: ConversionFromDatetime, } #[cfg(feature = "std")] @@ -163,8 +205,8 @@ impl ConversionFromNow { fn new_generic(mut prec: Option) -> Result { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let epoch = now.as_secs(); - let secs_of_day = epoch % SECONDS_PER_DAY as u64; - let unix_days_seconds = epoch - secs_of_day; + // Both values should now be positive + let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(epoch as i64); if let Some(submilli_prec) = prec { match submilli_prec { SubmillisPrecision::Microseconds(_) => { @@ -181,11 +223,12 @@ impl ConversionFromNow { } } Ok(Self { - ms_of_day: secs_of_day * 1000 + now.subsec_millis() as u64, - ccsds_days: unix_to_ccsds_days((unix_days_seconds / SECONDS_PER_DAY as u64) as i64) - as i32, - unix_days_seconds, - submillis_prec: prec, + base: ConversionFromDatetime { + ms_of_day: secs_of_day as u32 * 1000 + now.subsec_millis(), + ccsds_days: unix_to_ccsds_days(unix_days) as u32, + unix_days_seconds: unix_days * SECONDS_PER_DAY as i64, + submillis_prec: prec, + }, }) } } @@ -296,11 +339,11 @@ impl TimeProvider { init_len } - fn setup(&mut self, unix_days_seconds: i64, ms_of_day: u64) { + fn setup(&mut self, unix_days_seconds: i64, ms_of_day: u32) { self.calc_unix_seconds(unix_days_seconds, ms_of_day); } - fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u64) { + fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u32) { self.unix_seconds = unix_days_seconds; let seconds_of_day = (ms_of_day / 1000) as i64; if self.unix_seconds < 0 { @@ -310,10 +353,13 @@ impl TimeProvider { } } - fn calc_date_time(&self, ms_since_last_second: u32) -> Option> { - assert!(ms_since_last_second < 1000, "Invalid MS since last second"); - let ns_since_last_sec = ms_since_last_second * 1e6 as u32; - if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_sec) { + fn calc_date_time(&self, ns_since_last_second: u32) -> Option> { + assert!( + ns_since_last_second < 10_u32.pow(9), + "Invalid MS since last second" + ); + if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_second) + { return Some(val); } None @@ -357,24 +403,27 @@ impl TimeProvider { conversion_from_now: ConversionFromNow, ) -> Result where - >::Error: Debug, + ::FieldType: TryFrom, { - let ccsds_days: ProvidesDaysLen::FieldType = - conversion_from_now.ccsds_days.try_into().map_err(|_| { + let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now + .base + .ccsds_days + .try_into() + .map_err(|_| { StdTimestampError::TimestampError( - CdsError::InvalidCcsdsDays(conversion_from_now.ccsds_days.into()).into(), + CdsError::InvalidCcsdsDays(conversion_from_now.base.ccsds_days.into()).into(), ) })?; let mut provider = Self { - pfield: Self::generate_p_field(days_len, conversion_from_now.submillis_prec), + pfield: Self::generate_p_field(days_len, conversion_from_now.base.submillis_prec), ccsds_days, - ms_of_day: conversion_from_now.ms_of_day as u32, + ms_of_day: conversion_from_now.base.ms_of_day as u32, unix_seconds: 0, - submillis_precision: conversion_from_now.submillis_prec, + submillis_precision: conversion_from_now.base.submillis_prec, }; provider.setup( - conversion_from_now.unix_days_seconds as i64, - conversion_from_now.ms_of_day, + conversion_from_now.base.unix_days_seconds as i64, + conversion_from_now.base.ms_of_day, ); Ok(provider) } @@ -415,26 +464,34 @@ impl TimeProvider { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> where - >::Error: Debug, + ::FieldType: From, { let conversion_from_now = self.generic_conversion_from_now()?; - let ccsds_days: ProvidesDaysLen::FieldType = - conversion_from_now.ccsds_days.try_into().map_err(|_| { + let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now + .base + .ccsds_days + .try_into() + .map_err(|_| { StdTimestampError::TimestampError( - CdsError::InvalidCcsdsDays(conversion_from_now.ccsds_days as i64).into(), + CdsError::InvalidCcsdsDays(conversion_from_now.base.ccsds_days as i64).into(), ) })?; self.ccsds_days = ccsds_days; - self.ms_of_day = conversion_from_now.ms_of_day as u32; + self.ms_of_day = conversion_from_now.base.ms_of_day as u32; self.setup( - conversion_from_now.unix_days_seconds as i64, - conversion_from_now.ms_of_day, + conversion_from_now.base.unix_days_seconds as i64, + conversion_from_now.base.ms_of_day, ); Ok(()) } } impl TimeProvider { + pub fn new_with_u24_days_from_unix_stamp(unix_seconds: i64) { + //let seconds_of_day = unix_seconds % SECONDS_PER_DAY; + //let unix_days = (unix_seconds - seconds_of_day) / SECONDS_PER_DAY; + } + /// Generate a new timestamp provider with the days field width set to 24 bits pub fn new_with_u24_days(ccsds_days: u32, ms_of_day: u32) -> Result { if ccsds_days > 2_u32.pow(24) { @@ -594,6 +651,7 @@ fn add_for_max_ccsds_days_val( ); (next_ccsds_days, next_ms_of_day, precision) } + /// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover /// when they overflow, because addition needs to be infallible. The user needs to check for a /// days overflow when this is a possibility and might be a problem. @@ -625,6 +683,15 @@ impl Add for TimeProvider { } } +impl TryFrom> for TimeProvider { + type Error = (); + + fn try_from(dt: DateTime) -> Result { + //todo!() + //let unix_ms = dt.timestamp_millis(); + Ok(Self::new_with_u16_days(0, 0)) + } +} impl CcsdsTimeProvider for TimeProvider { fn len_as_bytes(&self) -> usize { Self::calc_stamp_len(self.pfield) @@ -643,7 +710,19 @@ impl CcsdsTimeProvider for TimeProvider Option> { - self.calc_date_time(self.ms_of_day % 1000) + let mut ns_since_last_sec = (self.ms_of_day % 1000) * 10_u32.pow(6); + if let Some(precision) = self.submillis_precision { + match precision { + SubmillisPrecision::Microseconds(us) => { + ns_since_last_sec += us as u32 * 1000; + } + SubmillisPrecision::Picoseconds(ps) => { + ns_since_last_sec += ps / 1000; + } + _ => (), + } + } + self.calc_date_time(ns_since_last_sec) } } @@ -710,7 +789,7 @@ mod tests { use chrono::{Datelike, Timelike}; #[cfg(feature = "serde")] use postcard::{from_bytes, to_allocvec}; - use std::format; + use std::{format, println}; #[test] fn test_time_stamp_zero_args() { @@ -980,6 +1059,13 @@ mod tests { assert_eq!(write_buf[7..11], cross_check.to_be_bytes()); } + #[test] + fn test() { + let negative_unix_seconds = -(SECONDS_PER_DAY as i32) - 12; + let test = negative_unix_seconds % SECONDS_PER_DAY as i32; + println!("test: {}", test) + } + #[test] fn read_stamp_with_ps_submillis_precision() { let mut time_stamper = TimeProvider::new_with_u16_days(0, 0); From 8da93443b91c902dd4349a614baee9d090246af9 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 14 Jan 2023 15:37:18 +0100 Subject: [PATCH 05/31] now to test it.. --- src/time/cds.rs | 371 ++++++++++++++++++++++++++++++++++++------------ src/time/mod.rs | 4 + 2 files changed, 281 insertions(+), 94 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 26f4b9f..75ce2e1 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -8,6 +8,7 @@ use chrono::Datelike; use core::fmt::Debug; use core::ops::Add; use core::time::Duration; +use delegate::delegate; /// Base value for the preamble field for a time field parser to determine the time field type. pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::Cds as u8) << 4; @@ -17,7 +18,7 @@ pub const MAX_DAYS_24_BITS: u32 = 2_u32.pow(24) - 1; /// Generic trait implemented by token structs to specify the length of day field at type /// level. This trait is only meant to be implemented in this crate and therefore sealed. pub trait ProvidesDaysLength: Sealed { - type FieldType: Copy + Clone + TryFrom + Into + Into; + type FieldType: Copy + Clone + TryFrom + TryFrom + From + Into + Into; } /// Type level token to be used as a generic parameter to [TimeProvider]. @@ -88,6 +89,7 @@ pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment { } LengthOfDaySegment::Short16Bits } + pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { match pfield & 0b11 { 0b01 => SubmillisPrecision::Microseconds(0), @@ -141,51 +143,134 @@ pub struct TimeProvider { unix_seconds: i64, } -/// Helper struct which generates fields for the CDS time provider from a datetime. -struct ConversionFromDatetime { +/// Private trait which serves as an abstraction for different converters. +trait CdsConverter { + fn submillis_precision(&self) -> Option; + fn ms_of_day(&self) -> u32; + fn ccsds_days(&self) -> u32; + fn unix_days_seconds(&self) -> i64; +} + +struct ConversionFromUnix { ccsds_days: u32, ms_of_day: u32, /// This is a side-product of the calculation of the CCSDS days. It is useful for /// re-calculating the datetime at a later point and therefore supplied as well. unix_days_seconds: i64, +} + +impl ConversionFromUnix { + fn new(unix_seconds: i64, subsec_millis: u32) -> Self { + let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(unix_seconds); + Self { + ccsds_days: unix_to_ccsds_days(unix_days) as u32, + ms_of_day: secs_of_day * 1000 + subsec_millis, + unix_days_seconds: unix_days * SECONDS_PER_DAY as i64, + } + } +} + +impl CdsConverter for ConversionFromUnix { + fn submillis_precision(&self) -> Option { + None + } + + fn ms_of_day(&self) -> u32 { + self.ms_of_day + } + + fn ccsds_days(&self) -> u32 { + self.ccsds_days + } + + fn unix_days_seconds(&self) -> i64 { + self.unix_days_seconds + } +} + +/// Helper struct which generates fields for the CDS time provider from a datetime. +struct ConversionFromDatetime { + unix_conversion: ConversionFromUnix, submillis_prec: Option, } -fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, i64) { - let secs_of_day = unix_seconds % SECONDS_PER_DAY as i64; +impl CdsConverter for ConversionFromDatetime { + fn submillis_precision(&self) -> Option { + self.submillis_prec + } + + delegate! { + to self.unix_conversion { + fn ms_of_day(&self) -> u32; + fn ccsds_days(&self) -> u32; + fn unix_days_seconds(&self) -> i64; + } + } +} + +#[inline] +fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) { + let mut secs_of_day = unix_seconds % SECONDS_PER_DAY as i64; let unix_days = if secs_of_day > 0 { (unix_seconds - secs_of_day) / SECONDS_PER_DAY as i64 } else { (unix_seconds + secs_of_day) / SECONDS_PER_DAY as i64 }; - (unix_days, secs_of_day) + if secs_of_day < 0 { + secs_of_day = -secs_of_day + } + (unix_days, secs_of_day as u32) } impl ConversionFromDatetime { - fn new(dt: DateTime) -> Self { + fn new(dt: DateTime) -> Result> { + Self::new_generic(dt, None) + } + + fn new_with_submillis_us_prec(dt: DateTime) -> Result> { + Self::new_generic(dt, Some(SubmillisPrecision::Microseconds(0))) + } + + fn new_with_submillis_ps_prec(dt: DateTime) -> Result> { + Self::new_generic(dt, Some(SubmillisPrecision::Picoseconds(0))) + } + + fn new_generic( + dt: DateTime, + mut prec: Option, + ) -> Result> { // The CDS timestamp does not support timestamps before the CCSDS epoch. - assert!(dt.year() >= 1958); - let unix_seconds = dt.timestamp(); - let (unix_days, _) = calc_unix_days_and_secs_of_day(unix_seconds); - // This should always be positive now. - let ccsds_days = unix_to_ccsds_days(unix_days) as u32; - let unix_ms = dt.timestamp_millis(); - let mut ms_of_day = unix_ms % MS_PER_DAY as i64; - if ms_of_day < 0 { - ms_of_day = -ms_of_day; + if dt.year() < 1958 { + return Err(dt); } - Self { - ccsds_days, - ms_of_day: ms_of_day as u32, - unix_days_seconds: unix_days * SECONDS_PER_DAY as i64, - submillis_prec: None, + // The contained values in the conversion should be all positive now + let unix_conversion = ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_millis()); + if let Some(submilli_prec) = prec { + match submilli_prec { + SubmillisPrecision::Microseconds(_) => { + prec = Some(SubmillisPrecision::Microseconds( + (dt.timestamp_subsec_micros() % 1000) as u16, + )); + } + SubmillisPrecision::Picoseconds(_) => { + prec = Some(SubmillisPrecision::Microseconds( + (dt.timestamp_subsec_nanos() * 1000) as u16, + )); + } + _ => (), + } } + Ok(Self { + unix_conversion, + submillis_prec: prec, + }) } } #[cfg(feature = "std")] struct ConversionFromNow { - base: ConversionFromDatetime, + unix_conversion: ConversionFromUnix, + submillis_prec: Option, } #[cfg(feature = "std")] @@ -205,8 +290,8 @@ impl ConversionFromNow { fn new_generic(mut prec: Option) -> Result { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let epoch = now.as_secs(); + let unix_conversion = ConversionFromUnix::new(epoch as i64, now.subsec_millis()); // Both values should now be positive - let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(epoch as i64); if let Some(submilli_prec) = prec { match submilli_prec { SubmillisPrecision::Microseconds(_) => { @@ -223,16 +308,26 @@ impl ConversionFromNow { } } Ok(Self { - base: ConversionFromDatetime { - ms_of_day: secs_of_day as u32 * 1000 + now.subsec_millis(), - ccsds_days: unix_to_ccsds_days(unix_days) as u32, - unix_days_seconds: unix_days * SECONDS_PER_DAY as i64, - submillis_prec: prec, - }, + unix_conversion, + submillis_prec: prec, }) } } +impl CdsConverter for ConversionFromNow { + fn submillis_precision(&self) -> Option { + self.submillis_prec + } + + delegate! { + to self.unix_conversion { + fn ms_of_day(&self) -> u32; + fn ccsds_days(&self) -> u32; + fn unix_days_seconds(&self) -> i64; + } + } +} + impl TimeProvider { pub fn set_submillis_precision(&mut self, prec: SubmillisPrecision) { self.pfield &= !(0b11); @@ -343,6 +438,7 @@ impl TimeProvider { self.calc_unix_seconds(unix_days_seconds, ms_of_day); } + #[inline] fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u32) { self.unix_seconds = unix_days_seconds; let seconds_of_day = (ms_of_day / 1000) as i64; @@ -393,38 +489,87 @@ impl TimeProvider { submillis_precision: None, }; let unix_days_seconds = ccsds_to_unix_days(i64::from(ccsds_days)) * SECONDS_PER_DAY as i64; - provider.setup(unix_days_seconds, ms_of_day.into()); + provider.setup(unix_days_seconds, ms_of_day); Ok(provider) } - #[cfg(feature = "std")] - fn generic_from_now( + pub fn from_dt_generic( + dt: DateTime, days_len: LengthOfDaySegment, - conversion_from_now: ConversionFromNow, - ) -> Result - where - ::FieldType: TryFrom, - { - let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now - .base - .ccsds_days - .try_into() - .map_err(|_| { - StdTimestampError::TimestampError( - CdsError::InvalidCcsdsDays(conversion_from_now.base.ccsds_days.into()).into(), - ) + ) -> Result { + let conv_from_dt = + ConversionFromDatetime::new(dt).map_err(TimestampError::DateBeforeCcsdsEpoch)?; + Self::generic_from_conversion(days_len, conv_from_dt) + } + + pub fn from_dt_generic_us_prec( + dt: DateTime, + days_len: LengthOfDaySegment, + ) -> Result { + let conv_from_dt = ConversionFromDatetime::new_with_submillis_us_prec(dt) + .map_err(TimestampError::DateBeforeCcsdsEpoch)?; + Self::generic_from_conversion(days_len, conv_from_dt) + } + + pub fn from_dt_generic_ps_prec( + dt: DateTime, + days_len: LengthOfDaySegment, + ) -> Result { + let conv_from_dt = ConversionFromDatetime::new_with_submillis_ps_prec(dt) + .map_err(TimestampError::DateBeforeCcsdsEpoch)?; + Self::generic_from_conversion(days_len, conv_from_dt) + } + + pub fn from_unix_generic( + unix_seconds: i64, + subsec_millis: u32, + days_len: LengthOfDaySegment, + ) -> Result { + let conv_from_dt = ConversionFromUnix::new(unix_seconds, subsec_millis); + Self::generic_from_conversion(days_len, conv_from_dt) + } + + #[cfg(feature = "std")] + pub 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::TimestampError) + } + + #[cfg(feature = "std")] + pub 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::TimestampError) + } + + #[cfg(feature = "std")] + pub 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::TimestampError) + } + + fn generic_from_conversion( + days_len: LengthOfDaySegment, + converter: C, + ) -> Result { + let ccsds_days: ProvidesDaysLen::FieldType = + converter.ccsds_days().try_into().map_err(|_| { + TimestampError::CdsError(CdsError::InvalidCcsdsDays(converter.ccsds_days().into())) })?; let mut provider = Self { - pfield: Self::generate_p_field(days_len, conversion_from_now.base.submillis_prec), + pfield: Self::generate_p_field(days_len, converter.submillis_precision()), ccsds_days, - ms_of_day: conversion_from_now.base.ms_of_day as u32, + ms_of_day: converter.ms_of_day(), unix_seconds: 0, - submillis_precision: conversion_from_now.base.submillis_prec, + submillis_precision: converter.submillis_precision(), }; - provider.setup( - conversion_from_now.base.unix_days_seconds as i64, - conversion_from_now.base.ms_of_day, - ); + provider.setup(converter.unix_days_seconds(), converter.ms_of_day()); Ok(provider) } @@ -464,64 +609,82 @@ impl TimeProvider { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> where - ::FieldType: From, + ::FieldType: TryFrom, { let conversion_from_now = self.generic_conversion_from_now()?; let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now - .base + .unix_conversion .ccsds_days .try_into() .map_err(|_| { StdTimestampError::TimestampError( - CdsError::InvalidCcsdsDays(conversion_from_now.base.ccsds_days as i64).into(), + CdsError::InvalidCcsdsDays( + conversion_from_now.unix_conversion.ccsds_days as i64, + ) + .into(), ) })?; self.ccsds_days = ccsds_days; - self.ms_of_day = conversion_from_now.base.ms_of_day as u32; + self.ms_of_day = conversion_from_now.unix_conversion.ms_of_day; self.setup( - conversion_from_now.base.unix_days_seconds as i64, - conversion_from_now.base.ms_of_day, + conversion_from_now.unix_conversion.unix_days_seconds, + conversion_from_now.unix_conversion.ms_of_day, ); Ok(()) } } impl TimeProvider { - pub fn new_with_u24_days_from_unix_stamp(unix_seconds: i64) { - //let seconds_of_day = unix_seconds % SECONDS_PER_DAY; - //let unix_days = (unix_seconds - seconds_of_day) / SECONDS_PER_DAY; - } - /// Generate a new timestamp provider with the days field width set to 24 bits - pub fn new_with_u24_days(ccsds_days: u32, ms_of_day: u32) -> Result { - if ccsds_days > 2_u32.pow(24) { + pub fn new(ccsds_days: u32, ms_of_day: u32) -> Result { + if ccsds_days > MAX_DAYS_24_BITS { return Err(CdsError::InvalidCcsdsDays(ccsds_days.into())); } Self::generic_new(LengthOfDaySegment::Long24Bits, ccsds_days, ms_of_day) } + pub fn new_with_u24_days(ccsds_days: u32, ms_of_day: u32) -> Result { + Self::new(ccsds_days, ms_of_day) + } + + pub fn from_unix_seconds_u24_days( + unix_seconds: i64, + subsec_millis: u32, + ) -> Result { + Self::from_unix_generic(unix_seconds, subsec_millis, LengthOfDaySegment::Long24Bits) + } + + pub fn from_dt_u24_days(dt: DateTime) -> Result { + Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits) + } + + pub fn from_dt_with_us_precision_u24_days(dt: DateTime) -> Result { + Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits) + } + + pub fn from_dt_with_ps_precision_u24_days(dt: DateTime) -> Result { + Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits) + } + /// Generate a time stamp from the current time using the system clock. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn from_now_with_u24_days() -> Result { - let conversion_from_now = ConversionFromNow::new()?; - Self::generic_from_now(LengthOfDaySegment::Long24Bits, conversion_from_now) + Self::from_now_generic(LengthOfDaySegment::Long24Bits) } - /// Like [Self::from_now_with_u24_days] but with microsecond sub-millisecond precision. + /// Like [Self::from_now] but with microsecond sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_u24_days_and_us_prec() -> Result { - let conversion_from_now = ConversionFromNow::new_with_submillis_us_prec()?; - Self::generic_from_now(LengthOfDaySegment::Long24Bits, conversion_from_now) + pub fn from_now_with_us_prec() -> Result { + Self::from_now_generic_us_prec(LengthOfDaySegment::Long24Bits) } - /// Like [Self::from_now_with_u24_days] but with picoseconds sub-millisecond precision. + /// Like [Self::from_now] but with picoseconds sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_u24_days_ps_submillis_prec() -> Result { - let conversion_from_now = ConversionFromNow::new_with_submillis_ps_prec()?; - Self::generic_from_now(LengthOfDaySegment::Long24Bits, conversion_from_now) + pub fn from_now_with_ps_prec() -> Result { + Self::from_now_generic_us_prec(LengthOfDaySegment::Long24Bits) } fn from_bytes_24_bit_days(buf: &[u8]) -> Result { @@ -531,7 +694,7 @@ impl TimeProvider { temp_buf[1..4].copy_from_slice(&buf[1..4]); let cccsds_days: u32 = u32::from_be_bytes(temp_buf); let ms_of_day: u32 = u32::from_be_bytes(buf[4..8].try_into().unwrap()); - let mut provider = Self::new_with_u24_days(cccsds_days, ms_of_day)?; + let mut provider = Self::new(cccsds_days, ms_of_day)?; match submillis_precision { SubmillisPrecision::Microseconds(_) => { provider.set_submillis_precision(SubmillisPrecision::Microseconds( @@ -549,33 +712,53 @@ impl TimeProvider { impl TimeProvider { /// Generate a new timestamp provider with the days field width set to 16 bits - pub fn new_with_u16_days(ccsds_days: u16, ms_of_day: u32) -> Self { + pub fn new(ccsds_days: u16, ms_of_day: u32) -> Self { // This should never fail, type system ensures CCSDS can not be negative or too large Self::generic_new(LengthOfDaySegment::Short16Bits, ccsds_days, ms_of_day).unwrap() } + pub fn new_with_u16_days(ccsds_days: u16, ms_of_day: u32) -> Self { + Self::new(ccsds_days, ms_of_day) + } + + pub fn from_unix_seconds_with_u16_days( + unix_seconds: i64, + subsec_millis: u32, + ) -> Result { + Self::from_unix_generic(unix_seconds, subsec_millis, LengthOfDaySegment::Short16Bits) + } + + pub fn from_dt_with_u16_days(dt: DateTime) -> Result { + Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits) + } + + pub fn from_dt_with_u16_days_us_precision(dt: DateTime) -> Result { + Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits) + } + + pub fn from_dt_with_u16_days_ps_precision(dt: DateTime) -> Result { + Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits) + } + /// Generate a time stamp from the current time using the system clock. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn from_now_with_u16_days() -> Result { - let conversion_from_now = ConversionFromNow::new()?; - Self::generic_from_now(LengthOfDaySegment::Short16Bits, conversion_from_now) + Self::from_now_generic(LengthOfDaySegment::Short16Bits) } /// Like [Self::from_now_with_u16_days] but with microsecond sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_u16_days_and_us_prec() -> Result { - let conversion_from_now = ConversionFromNow::new_with_submillis_us_prec()?; - Self::generic_from_now(LengthOfDaySegment::Short16Bits, conversion_from_now) + pub fn from_now_with_u16_days_us_prec() -> Result { + Self::from_now_generic_us_prec(LengthOfDaySegment::Short16Bits) } /// Like [Self::from_now_with_u16_days] but with picosecond sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_u16_days_and_ps_prec() -> Result { - let conversion_from_now = ConversionFromNow::new_with_submillis_ps_prec()?; - Self::generic_from_now(LengthOfDaySegment::Short16Bits, conversion_from_now) + pub fn from_now_with_u16_days_ps_prec() -> Result { + Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits) } fn from_bytes_16_bit_days(buf: &[u8]) -> Result { @@ -583,7 +766,7 @@ impl TimeProvider { Self::generic_raw_read_checks(buf, LengthOfDaySegment::Short16Bits)?; let ccsds_days: u16 = u16::from_be_bytes(buf[1..3].try_into().unwrap()); let ms_of_day: u32 = u32::from_be_bytes(buf[3..7].try_into().unwrap()); - let mut provider = Self::new_with_u16_days(ccsds_days, ms_of_day); + let mut provider = Self::new(ccsds_days, ms_of_day); provider.pfield = buf[0]; match submillis_precision { SubmillisPrecision::Microseconds(_) => provider.set_submillis_precision( @@ -661,7 +844,7 @@ impl Add for TimeProvider { fn add(self, duration: Duration) -> Self::Output { let (next_ccsds_days, next_ms_of_day, precision) = add_for_max_ccsds_days_val(self, u16::MAX as u32, duration); - let mut provider = Self::new_with_u16_days(next_ccsds_days as u16, next_ms_of_day); + let mut provider = Self::new(next_ccsds_days as u16, next_ms_of_day); if let Some(prec) = precision { provider.set_submillis_precision(prec); } @@ -675,7 +858,7 @@ impl Add for TimeProvider { fn add(self, duration: Duration) -> Self::Output { let (next_ccsds_days, next_ms_of_day, precision) = add_for_max_ccsds_days_val(self, MAX_DAYS_24_BITS, duration); - let mut provider = Self::new_with_u24_days(next_ccsds_days, next_ms_of_day).unwrap(); + let mut provider = Self::new(next_ccsds_days, next_ms_of_day).unwrap(); if let Some(prec) = precision { provider.set_submillis_precision(prec); } @@ -684,12 +867,12 @@ impl Add for TimeProvider { } impl TryFrom> for TimeProvider { - type Error = (); + type Error = TimestampError; fn try_from(dt: DateTime) -> Result { - //todo!() - //let unix_ms = dt.timestamp_millis(); - Ok(Self::new_with_u16_days(0, 0)) + let conversion = + ConversionFromDatetime::new(dt).map_err(TimestampError::DateBeforeCcsdsEpoch)?; + Self::generic_from_conversion(LengthOfDaySegment::Short16Bits, conversion) } } impl CcsdsTimeProvider for TimeProvider { @@ -829,7 +1012,7 @@ mod tests { #[test] fn test_large_days_field_write() { - let time_stamper = TimeProvider::new_with_u24_days(0x108020, 0); + let time_stamper = TimeProvider::new_with_u24_days(0x108020_u32, 0); assert!(time_stamper.is_ok()); let time_stamper = time_stamper.unwrap(); assert_eq!(time_stamper.len_as_bytes(), 8); @@ -848,7 +1031,7 @@ mod tests { #[test] fn test_large_days_field_read() { - let time_stamper = TimeProvider::new_with_u24_days(0x108020, 0); + let time_stamper = TimeProvider::new_with_u24_days(0x108020_u32, 0); assert!(time_stamper.is_ok()); let time_stamper = time_stamper.unwrap(); let mut buf = [0; 16]; diff --git a/src/time/mod.rs b/src/time/mod.rs index da262ca..4a7e7fc 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -63,6 +63,7 @@ pub enum TimestampError { ByteConversionError(ByteConversionError), CdsError(cds::CdsError), CucError(cuc::CucError), + DateBeforeCcsdsEpoch(DateTime), CustomEpochNotSupported, } @@ -119,6 +120,9 @@ impl Display for TimestampError { TimestampError::ByteConversionError(e) => { write!(f, "byte conversion error {}", e) } + TimestampError::DateBeforeCcsdsEpoch(e) => { + write!(f, "datetime with date before ccsds epoch: {}", e) + } TimestampError::CustomEpochNotSupported => { write!(f, "custom epochs are not supported") } From 40f64525b6c4f099a1d3af5dd52f83dcc972e1b6 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 14 Jan 2023 15:43:49 +0100 Subject: [PATCH 06/31] some smaller tweaks --- src/time/cds.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 75ce2e1..80d657c 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -530,14 +530,14 @@ impl TimeProvider { } #[cfg(feature = "std")] - pub fn from_now_generic(days_len: LengthOfDaySegment) -> Result { + 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::TimestampError) } #[cfg(feature = "std")] - pub fn from_now_generic_us_prec( + fn from_now_generic_us_prec( days_len: LengthOfDaySegment, ) -> Result { let conversion_from_now = ConversionFromNow::new_with_submillis_us_prec()?; @@ -546,7 +546,7 @@ impl TimeProvider { } #[cfg(feature = "std")] - pub fn from_now_generic_ps_prec( + fn from_now_generic_ps_prec( days_len: LengthOfDaySegment, ) -> Result { let conversion_from_now = ConversionFromNow::new_with_submillis_ps_prec()?; @@ -608,8 +608,6 @@ impl TimeProvider { #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> - where - ::FieldType: TryFrom, { let conversion_from_now = self.generic_conversion_from_now()?; let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now From d071de3a860984c44752c358aced493a1ed28426 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 14 Jan 2023 16:12:02 +0100 Subject: [PATCH 07/31] almost complete --- src/time/cds.rs | 150 ++++++++++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 62 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 80d657c..92434eb 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -160,13 +160,24 @@ struct ConversionFromUnix { } impl ConversionFromUnix { - fn new(unix_seconds: i64, subsec_millis: u32) -> Self { + fn new(unix_seconds: i64, subsec_millis: u32) -> Result { let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(unix_seconds); - Self { + let ccsds_days = unix_to_ccsds_days(unix_days); + if ccsds_days < 0 { + let millis = if unix_seconds < 0 { + unix_seconds * 1000 - subsec_millis as i64 + } else { + unix_seconds * 1000 + subsec_millis as i64 + }; + return Err(TimestampError::DateBeforeCcsdsEpoch( + Utc.timestamp_millis_opt(millis).unwrap(), + )); + } + Ok(Self { ccsds_days: unix_to_ccsds_days(unix_days) as u32, ms_of_day: secs_of_day * 1000 + subsec_millis, unix_days_seconds: unix_days * SECONDS_PER_DAY as i64, - } + }) } } @@ -223,28 +234,29 @@ fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) { } impl ConversionFromDatetime { - fn new(dt: DateTime) -> Result> { + fn new(dt: DateTime) -> Result { Self::new_generic(dt, None) } - fn new_with_submillis_us_prec(dt: DateTime) -> Result> { + fn new_with_submillis_us_prec(dt: DateTime) -> Result { Self::new_generic(dt, Some(SubmillisPrecision::Microseconds(0))) } - fn new_with_submillis_ps_prec(dt: DateTime) -> Result> { + fn new_with_submillis_ps_prec(dt: DateTime) -> Result { Self::new_generic(dt, Some(SubmillisPrecision::Picoseconds(0))) } fn new_generic( dt: DateTime, mut prec: Option, - ) -> Result> { + ) -> Result { // The CDS timestamp does not support timestamps before the CCSDS epoch. if dt.year() < 1958 { - return Err(dt); + return Err(TimestampError::DateBeforeCcsdsEpoch(dt)); } // The contained values in the conversion should be all positive now - let unix_conversion = ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_millis()); + let unix_conversion = + ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_millis())?; if let Some(submilli_prec) = prec { match submilli_prec { SubmillisPrecision::Microseconds(_) => { @@ -290,7 +302,9 @@ impl ConversionFromNow { fn new_generic(mut prec: Option) -> Result { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let epoch = now.as_secs(); - let unix_conversion = ConversionFromUnix::new(epoch as i64, now.subsec_millis()); + // This should always return a value with valid (non-negative) CCSDS days, + // so it is okay to unwrap + let unix_conversion = ConversionFromUnix::new(epoch as i64, now.subsec_millis()).unwrap(); // Both values should now be positive if let Some(submilli_prec) = prec { match submilli_prec { @@ -497,8 +511,7 @@ impl TimeProvider { dt: DateTime, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = - ConversionFromDatetime::new(dt).map_err(TimestampError::DateBeforeCcsdsEpoch)?; + let conv_from_dt = ConversionFromDatetime::new(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -506,8 +519,7 @@ impl TimeProvider { dt: DateTime, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromDatetime::new_with_submillis_us_prec(dt) - .map_err(TimestampError::DateBeforeCcsdsEpoch)?; + let conv_from_dt = ConversionFromDatetime::new_with_submillis_us_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -515,8 +527,7 @@ impl TimeProvider { dt: DateTime, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromDatetime::new_with_submillis_ps_prec(dt) - .map_err(TimestampError::DateBeforeCcsdsEpoch)?; + let conv_from_dt = ConversionFromDatetime::new_with_submillis_ps_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -525,7 +536,7 @@ impl TimeProvider { subsec_millis: u32, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromUnix::new(unix_seconds, subsec_millis); + let conv_from_dt = ConversionFromUnix::new(unix_seconds, subsec_millis)?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -537,18 +548,14 @@ impl TimeProvider { } #[cfg(feature = "std")] - fn from_now_generic_us_prec( - days_len: LengthOfDaySegment, - ) -> Result { + 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::TimestampError) } #[cfg(feature = "std")] - fn from_now_generic_ps_prec( - days_len: LengthOfDaySegment, - ) -> Result { + 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::TimestampError) @@ -607,8 +614,7 @@ impl TimeProvider { #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> - { + pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> { let conversion_from_now = self.generic_conversion_from_now()?; let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now .unix_conversion @@ -634,17 +640,20 @@ impl TimeProvider { impl TimeProvider { /// Generate a new timestamp provider with the days field width set to 24 bits - pub fn new(ccsds_days: u32, ms_of_day: u32) -> Result { + pub fn new_with_u24_days(ccsds_days: u32, ms_of_day: u32) -> Result { if ccsds_days > MAX_DAYS_24_BITS { return Err(CdsError::InvalidCcsdsDays(ccsds_days.into())); } Self::generic_new(LengthOfDaySegment::Long24Bits, ccsds_days, ms_of_day) } - pub fn new_with_u24_days(ccsds_days: u32, ms_of_day: u32) -> Result { - Self::new(ccsds_days, ms_of_day) - } - + /// Create a provider from a generic UNIX timestamp (seconds since 01-01-1970 00:00:00). + /// + /// ## Errors + /// + /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or + /// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or + /// the CCSDS days value exceeds the allowed bit width (24 bits). pub fn from_unix_seconds_u24_days( unix_seconds: i64, subsec_millis: u32, @@ -652,15 +661,24 @@ impl TimeProvider { Self::from_unix_generic(unix_seconds, subsec_millis, LengthOfDaySegment::Long24Bits) } - pub fn from_dt_u24_days(dt: DateTime) -> Result { + /// Create a provider from a [`DateTime`] struct. + /// + /// ## Errors + /// + /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or + /// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or + /// the CCSDS days value exceeds the allowed bit width (24 bits). + pub fn from_dt_with_u24_days(dt: DateTime) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits) } - pub fn from_dt_with_us_precision_u24_days(dt: DateTime) -> Result { + /// Like [Self::from_dt_with_u24_days] but with microsecond sub-millisecond precision. + pub fn from_dt_with_u24_days_us_prec(dt: DateTime) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits) } - pub fn from_dt_with_ps_precision_u24_days(dt: DateTime) -> Result { + /// Like [Self::from_dt_with_u24_days] but with picoseconds sub-millisecond precision. + pub fn from_dt_with_u24_days_ps_prec(dt: DateTime) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits) } @@ -671,28 +689,28 @@ impl TimeProvider { Self::from_now_generic(LengthOfDaySegment::Long24Bits) } - /// Like [Self::from_now] but with microsecond sub-millisecond precision. + /// Like [Self::from_now_with_u24_days] but with microsecond sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_us_prec() -> Result { + pub fn from_now_with_u24_days_us_prec() -> Result { Self::from_now_generic_us_prec(LengthOfDaySegment::Long24Bits) } - /// Like [Self::from_now] but with picoseconds sub-millisecond precision. + /// Like [Self::from_now_with_u24_days] but with picoseconds sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_ps_prec() -> Result { + pub fn from_now_with_u24_days_ps_prec() -> Result { Self::from_now_generic_us_prec(LengthOfDaySegment::Long24Bits) } - fn from_bytes_24_bit_days(buf: &[u8]) -> Result { + fn from_bytes_with_u24_days(buf: &[u8]) -> Result { let submillis_precision = Self::generic_raw_read_checks(buf, LengthOfDaySegment::Long24Bits)?; let mut temp_buf: [u8; 4] = [0; 4]; temp_buf[1..4].copy_from_slice(&buf[1..4]); let cccsds_days: u32 = u32::from_be_bytes(temp_buf); let ms_of_day: u32 = u32::from_be_bytes(buf[4..8].try_into().unwrap()); - let mut provider = Self::new(cccsds_days, ms_of_day)?; + let mut provider = Self::new_with_u24_days(cccsds_days, ms_of_day)?; match submillis_precision { SubmillisPrecision::Microseconds(_) => { provider.set_submillis_precision(SubmillisPrecision::Microseconds( @@ -710,15 +728,11 @@ impl TimeProvider { impl TimeProvider { /// Generate a new timestamp provider with the days field width set to 16 bits - pub fn new(ccsds_days: u16, ms_of_day: u32) -> Self { + pub fn new_with_u16_days(ccsds_days: u16, ms_of_day: u32) -> Self { // This should never fail, type system ensures CCSDS can not be negative or too large Self::generic_new(LengthOfDaySegment::Short16Bits, ccsds_days, ms_of_day).unwrap() } - pub fn new_with_u16_days(ccsds_days: u16, ms_of_day: u32) -> Self { - Self::new(ccsds_days, ms_of_day) - } - pub fn from_unix_seconds_with_u16_days( unix_seconds: i64, subsec_millis: u32, @@ -726,14 +740,21 @@ impl TimeProvider { Self::from_unix_generic(unix_seconds, subsec_millis, LengthOfDaySegment::Short16Bits) } + /// Create a provider from a [`DateTime`] struct. + /// + /// This function will return a [TimestampError::DateBeforeCcsdsEpoch] or a + /// [TimestampError::CdsError] 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). pub fn from_dt_with_u16_days(dt: DateTime) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits) } + /// Like [Self::from_dt_with_u16_days] but with microsecond sub-millisecond precision. pub fn from_dt_with_u16_days_us_precision(dt: DateTime) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits) } + /// Like [Self::from_dt_with_u16_days] but with picoseconds sub-millisecond precision. pub fn from_dt_with_u16_days_ps_precision(dt: DateTime) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits) } @@ -748,23 +769,23 @@ impl TimeProvider { /// Like [Self::from_now_with_u16_days] but with microsecond sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_u16_days_us_prec() -> Result { + pub fn from_now_with_u16_days_us_precision() -> Result { Self::from_now_generic_us_prec(LengthOfDaySegment::Short16Bits) } /// Like [Self::from_now_with_u16_days] but with picosecond sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_u16_days_ps_prec() -> Result { + pub fn from_now_with_u16_days_ps_precision() -> Result { Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits) } - fn from_bytes_16_bit_days(buf: &[u8]) -> Result { + fn from_bytes_with_u16_days(buf: &[u8]) -> Result { let submillis_precision = Self::generic_raw_read_checks(buf, LengthOfDaySegment::Short16Bits)?; let ccsds_days: u16 = u16::from_be_bytes(buf[1..3].try_into().unwrap()); let ms_of_day: u32 = u32::from_be_bytes(buf[3..7].try_into().unwrap()); - let mut provider = Self::new(ccsds_days, ms_of_day); + let mut provider = Self::new_with_u16_days(ccsds_days, ms_of_day); provider.pfield = buf[0]; match submillis_precision { SubmillisPrecision::Microseconds(_) => provider.set_submillis_precision( @@ -842,7 +863,7 @@ impl Add for TimeProvider { fn add(self, duration: Duration) -> Self::Output { let (next_ccsds_days, next_ms_of_day, precision) = add_for_max_ccsds_days_val(self, u16::MAX as u32, duration); - let mut provider = Self::new(next_ccsds_days as u16, next_ms_of_day); + let mut provider = Self::new_with_u16_days(next_ccsds_days as u16, next_ms_of_day); if let Some(prec) = precision { provider.set_submillis_precision(prec); } @@ -850,13 +871,16 @@ impl Add for TimeProvider { } } +/// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover +/// when they overflow, because addition needs to be infallible. The user needs to check for a +/// days overflow when this is a possibility and might be a problem. impl Add for TimeProvider { type Output = Self; fn add(self, duration: Duration) -> Self::Output { let (next_ccsds_days, next_ms_of_day, precision) = add_for_max_ccsds_days_val(self, MAX_DAYS_24_BITS, duration); - let mut provider = Self::new(next_ccsds_days, next_ms_of_day).unwrap(); + let mut provider = Self::new_with_u24_days(next_ccsds_days, next_ms_of_day).unwrap(); if let Some(prec) = precision { provider.set_submillis_precision(prec); } @@ -868,11 +892,20 @@ impl TryFrom> for TimeProvider { type Error = TimestampError; fn try_from(dt: DateTime) -> Result { - let conversion = - ConversionFromDatetime::new(dt).map_err(TimestampError::DateBeforeCcsdsEpoch)?; + let conversion = ConversionFromDatetime::new(dt)?; Self::generic_from_conversion(LengthOfDaySegment::Short16Bits, conversion) } } + +impl TryFrom> for TimeProvider { + type Error = TimestampError; + + fn try_from(dt: DateTime) -> Result { + let conversion = ConversionFromDatetime::new(dt)?; + Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion) + } +} + impl CcsdsTimeProvider for TimeProvider { fn len_as_bytes(&self) -> usize { Self::calc_stamp_len(self.pfield) @@ -909,13 +942,13 @@ impl CcsdsTimeProvider for TimeProvider { fn from_bytes(buf: &[u8]) -> Result { - Self::from_bytes_16_bit_days(buf) + Self::from_bytes_with_u16_days(buf) } } impl TimeReader for TimeProvider { fn from_bytes(buf: &[u8]) -> Result { - Self::from_bytes_24_bit_days(buf) + Self::from_bytes_with_u24_days(buf) } } @@ -970,7 +1003,7 @@ mod tests { use chrono::{Datelike, Timelike}; #[cfg(feature = "serde")] use postcard::{from_bytes, to_allocvec}; - use std::{format, println}; + use std::format; #[test] fn test_time_stamp_zero_args() { @@ -1240,13 +1273,6 @@ mod tests { assert_eq!(write_buf[7..11], cross_check.to_be_bytes()); } - #[test] - fn test() { - let negative_unix_seconds = -(SECONDS_PER_DAY as i32) - 12; - let test = negative_unix_seconds % SECONDS_PER_DAY as i32; - println!("test: {}", test) - } - #[test] fn read_stamp_with_ps_submillis_precision() { let mut time_stamper = TimeProvider::new_with_u16_days(0, 0); From 753be8627238f06c4095eb13af2ec77580182710 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 14 Jan 2023 17:15:48 +0100 Subject: [PATCH 08/31] added first tests --- CHANGELOG.md | 12 +++ src/time/cds.rs | 225 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 203 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d61eea4..23e98d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +## Added + +- CDS timestamp: Added constructor function to create the time provider + from `chrono::DateTime` and a generic UNIX timestamp (`i64` seconds + and subsecond milliseconds). + +## Fixed + +- CDS timestamp: The conversion function from the current time were buggy + when specifying picoseconds precision, which could lead to overflow + multiplications and incorrect precision fields. + # [v0.4.0] 10.01.2023 ## Fixed diff --git a/src/time/cds.rs b/src/time/cds.rs index 92434eb..7f8b7a7 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -163,7 +163,7 @@ impl ConversionFromUnix { fn new(unix_seconds: i64, subsec_millis: u32) -> 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 { + if ccsds_days == 0 && (secs_of_day > 0 || subsec_millis > 0) || ccsds_days < 0 { let millis = if unix_seconds < 0 { unix_seconds * 1000 - subsec_millis as i64 } else { @@ -222,37 +222,36 @@ impl CdsConverter for ConversionFromDatetime { #[inline] fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) { let mut secs_of_day = unix_seconds % SECONDS_PER_DAY as i64; - let unix_days = if secs_of_day > 0 { - (unix_seconds - secs_of_day) / SECONDS_PER_DAY as i64 - } else { - (unix_seconds + secs_of_day) / SECONDS_PER_DAY as i64 - }; + let mut unix_days = (unix_seconds - secs_of_day) / SECONDS_PER_DAY as i64; + // Imagine the CCSDS epoch time minus 5 seconds: We now have the last day in the year + // 1969 (-1 unix days) shortly before midnight (SECONDS_PER_DAY - 5). if secs_of_day < 0 { - secs_of_day = -secs_of_day + unix_days -= 1; + secs_of_day = SECONDS_PER_DAY as i64 + secs_of_day } (unix_days, secs_of_day as u32) } impl ConversionFromDatetime { - fn new(dt: DateTime) -> Result { + fn new(dt: &DateTime) -> Result { Self::new_generic(dt, None) } - fn new_with_submillis_us_prec(dt: DateTime) -> Result { + fn new_with_submillis_us_prec(dt: &DateTime) -> Result { Self::new_generic(dt, Some(SubmillisPrecision::Microseconds(0))) } - fn new_with_submillis_ps_prec(dt: DateTime) -> Result { + fn new_with_submillis_ps_prec(dt: &DateTime) -> Result { Self::new_generic(dt, Some(SubmillisPrecision::Picoseconds(0))) } fn new_generic( - dt: DateTime, + dt: &DateTime, mut prec: Option, ) -> Result { // The CDS timestamp does not support timestamps before the CCSDS epoch. if dt.year() < 1958 { - return Err(TimestampError::DateBeforeCcsdsEpoch(dt)); + return Err(TimestampError::DateBeforeCcsdsEpoch(*dt)); } // The contained values in the conversion should be all positive now let unix_conversion = @@ -265,8 +264,8 @@ impl ConversionFromDatetime { )); } SubmillisPrecision::Picoseconds(_) => { - prec = Some(SubmillisPrecision::Microseconds( - (dt.timestamp_subsec_nanos() * 1000) as u16, + prec = Some(SubmillisPrecision::Picoseconds( + (dt.timestamp_subsec_nanos() * 1000), )); } _ => (), @@ -314,8 +313,8 @@ impl ConversionFromNow { )); } SubmillisPrecision::Picoseconds(_) => { - prec = Some(SubmillisPrecision::Microseconds( - (now.subsec_nanos() * 1000) as u16, + prec = Some(SubmillisPrecision::Picoseconds( + (now.subsec_nanos() * 1000), )); } _ => (), @@ -507,31 +506,31 @@ impl TimeProvider { Ok(provider) } - pub fn from_dt_generic( - dt: DateTime, + fn from_dt_generic( + dt: &DateTime, days_len: LengthOfDaySegment, ) -> Result { let conv_from_dt = ConversionFromDatetime::new(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } - pub fn from_dt_generic_us_prec( - dt: DateTime, + fn from_dt_generic_us_prec( + dt: &DateTime, days_len: LengthOfDaySegment, ) -> Result { let conv_from_dt = ConversionFromDatetime::new_with_submillis_us_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } - pub fn from_dt_generic_ps_prec( - dt: DateTime, + fn from_dt_generic_ps_prec( + dt: &DateTime, days_len: LengthOfDaySegment, ) -> Result { let conv_from_dt = ConversionFromDatetime::new_with_submillis_ps_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } - pub fn from_unix_generic( + fn from_unix_generic( unix_seconds: i64, subsec_millis: u32, days_len: LengthOfDaySegment, @@ -654,7 +653,7 @@ impl TimeProvider { /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or /// the CCSDS days value exceeds the allowed bit width (24 bits). - pub fn from_unix_seconds_u24_days( + pub fn from_unix_secs_with_u24_days( unix_seconds: i64, subsec_millis: u32, ) -> Result { @@ -668,17 +667,17 @@ impl TimeProvider { /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or /// the CCSDS days value exceeds the allowed bit width (24 bits). - pub fn from_dt_with_u24_days(dt: DateTime) -> Result { + pub fn from_dt_with_u24_days(dt: &DateTime) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits) } /// Like [Self::from_dt_with_u24_days] but with microsecond sub-millisecond precision. - pub fn from_dt_with_u24_days_us_prec(dt: DateTime) -> Result { + pub fn from_dt_with_u24_days_us_prec(dt: &DateTime) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits) } /// Like [Self::from_dt_with_u24_days] but with picoseconds sub-millisecond precision. - pub fn from_dt_with_u24_days_ps_prec(dt: DateTime) -> Result { + pub fn from_dt_with_u24_days_ps_prec(dt: &DateTime) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits) } @@ -733,7 +732,14 @@ impl TimeProvider { Self::generic_new(LengthOfDaySegment::Short16Bits, ccsds_days, ms_of_day).unwrap() } - pub fn from_unix_seconds_with_u16_days( + /// Create a provider from a generic UNIX timestamp (seconds since 01-01-1970 00:00:00). + /// + /// ## Errors + /// + /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or + /// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or + /// the CCSDS days value exceeds the allowed bit width (24 bits). + pub fn from_unix_secs_with_u16_days( unix_seconds: i64, subsec_millis: u32, ) -> Result { @@ -745,17 +751,17 @@ impl TimeProvider { /// This function will return a [TimestampError::DateBeforeCcsdsEpoch] or a /// [TimestampError::CdsError] 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). - pub fn from_dt_with_u16_days(dt: DateTime) -> Result { + pub fn from_dt_with_u16_days(dt: &DateTime) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits) } /// Like [Self::from_dt_with_u16_days] but with microsecond sub-millisecond precision. - pub fn from_dt_with_u16_days_us_precision(dt: DateTime) -> Result { + pub fn from_dt_with_u16_days_us_precision(dt: &DateTime) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits) } /// Like [Self::from_dt_with_u16_days] but with picoseconds sub-millisecond precision. - pub fn from_dt_with_u16_days_ps_precision(dt: DateTime) -> Result { + pub fn from_dt_with_u16_days_ps_precision(dt: &DateTime) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits) } @@ -892,7 +898,7 @@ impl TryFrom> for TimeProvider { type Error = TimestampError; fn try_from(dt: DateTime) -> Result { - let conversion = ConversionFromDatetime::new(dt)?; + let conversion = ConversionFromDatetime::new(&dt)?; Self::generic_from_conversion(LengthOfDaySegment::Short16Bits, conversion) } } @@ -901,7 +907,7 @@ impl TryFrom> for TimeProvider { type Error = TimestampError; fn try_from(dt: DateTime) -> Result { - let conversion = ConversionFromDatetime::new(dt)?; + let conversion = ConversionFromDatetime::new(&dt)?; Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion) } } @@ -1000,7 +1006,7 @@ mod tests { use super::*; use crate::time::TimestampError::{ByteConversionError, InvalidTimeCode}; use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall}; - use chrono::{Datelike, Timelike}; + use chrono::{Datelike, NaiveDate, Timelike}; #[cfg(feature = "serde")] use postcard::{from_bytes, to_allocvec}; use std::format; @@ -1317,6 +1323,157 @@ mod tests { } } + #[test] + fn test_creation_from_dt() { + let subsec_millis = 250; + let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) + .unwrap() + .and_hms_milli_opt(16, 49, 30, subsec_millis) + .unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + let time_provider = TimeProvider::from_dt_with_u16_days(&datetime_utc).unwrap(); + // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 + // Leap years need to be accounted for as well. + assert_eq!(time_provider.ccsds_days, 23754); + assert_eq!(time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis); + assert_eq!(time_provider.date_time().unwrap(), datetime_utc); + let time_provider_2: TimeProvider = datetime_utc.try_into().expect("conversion failed"); + // Test the TryInto trait impl + assert_eq!(time_provider, time_provider_2); + } + + #[test] + fn test_creation_from_dt_us_prec() { + // 250 ms + 500 us + let subsec_millis = 250; + let subsec_micros = subsec_millis * 1000 + 500; + let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) + .unwrap() + .and_hms_micro_opt(16, 49, 30, subsec_micros) + .unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + let time_provider = TimeProvider::from_dt_with_u16_days_us_precision(&datetime_utc).unwrap(); + // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 + // Leap years need to be accounted for as well. + assert_eq!(time_provider.ccsds_days, 23754); + assert_eq!(time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis); + assert!(time_provider.submillis_precision.is_some()); + match time_provider.submillis_precision.unwrap() { + SubmillisPrecision::Microseconds(us) => { + assert_eq!(us, 500); + } + _=> panic!("unexpected precision field") + } + assert_eq!(time_provider.date_time().unwrap(), datetime_utc); + } + + #[test] + fn test_creation_from_dt_ps_prec() { + // 250 ms + 500 us + let subsec_millis = 250; + let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000; + let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) + .unwrap() + .and_hms_nano_opt(16, 49, 30, subsec_nanos) + .unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + let time_provider = TimeProvider::from_dt_with_u16_days_ps_precision(&datetime_utc).unwrap(); + // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 + // Leap years need to be accounted for as well. + assert_eq!(time_provider.ccsds_days, 23754); + assert_eq!(time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis); + assert!(time_provider.submillis_precision.is_some()); + match time_provider.submillis_precision.unwrap() { + SubmillisPrecision::Picoseconds(ps) => { + assert_eq!(ps, subsec_nanos * 1000); + } + _=> panic!("unexpected precision field") + } + assert_eq!(time_provider.date_time().unwrap(), datetime_utc); + } + + #[test] + fn test_creation_from_unix_stamp_0() { + let unix_secs = 0; + let subsec_millis = 0; + let time_provider = TimeProvider::from_unix_secs_with_u16_days(unix_secs, subsec_millis) + .expect("creating provider from unix stamp failed"); + assert_eq!(time_provider.ccsds_days, -DAYS_CCSDS_TO_UNIX as u16) + } + + #[test] + fn test_creation_from_unix_stamp_1() { + let subsec_millis = 250; + let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) + .unwrap() + .and_hms_milli_opt(16, 49, 30, subsec_millis) + .unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + let time_provider = TimeProvider::from_unix_secs_with_u16_days(datetime_utc.timestamp(), subsec_millis) + .expect("creating provider from unix stamp failed"); + // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 + // Leap years need to be accounted for as well. + assert_eq!(time_provider.ccsds_days, 23754); + assert_eq!(time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis); + let dt_back = time_provider.date_time().unwrap(); + assert_eq!(datetime_utc, dt_back); + } + + #[test] + fn test_creation_0_ccsds_days() { + let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64; + let subsec_millis = 0; + let time_provider = TimeProvider::from_unix_secs_with_u16_days(unix_secs, subsec_millis) + .expect("creating provider from unix stamp failed"); + assert_eq!(time_provider.ccsds_days, 0) + } + + #[test] + fn test_invalid_creation_from_unix_stamp_days_too_large() { + let invalid_unix_secs: i64 = (u16::MAX as i64 + 1) * SECONDS_PER_DAY as i64; + let subsec_millis = 0; + match TimeProvider::from_unix_secs_with_u16_days(invalid_unix_secs as i64, subsec_millis) { + Ok(_) => { + panic!("creation should not succeed") + } + Err(e) => { + if let TimestampError::CdsError(CdsError::InvalidCcsdsDays(days)) = e { + assert_eq!( + days, + unix_to_ccsds_days(invalid_unix_secs / SECONDS_PER_DAY as i64) + ); + } else { + panic!("unexpected error {}", e) + } + } + } + } + + #[test] + fn test_invalid_creation_from_unix_stamp_before_ccsds_epoch() { + // This is a unix stamp before the CCSDS epoch (01-01-1958 00:00:00), this should be + // precisely 31-12-1957 23:59:55 + let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5; + let subsec_millis = 0; + match TimeProvider::from_unix_secs_with_u16_days(unix_secs as i64, subsec_millis) { + Ok(_) => { + panic!("creation should not succeed") + } + Err(e) => { + if let TimestampError::DateBeforeCcsdsEpoch(dt) = e { + assert_eq!(dt.year(), 1957); + assert_eq!(dt.month(), 12); + assert_eq!(dt.day(), 31); + assert_eq!(dt.hour(), 23); + assert_eq!(dt.minute(), 59); + assert_eq!(dt.second(), 55); + } else { + panic!("unexpected error {}", e) + } + } + } + } + #[test] #[cfg(feature = "serde")] fn test_serialization() { From c284a7a3ccaa3f2ee45c6c32ae757831c6cf6a8e Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 14 Jan 2023 17:43:19 +0100 Subject: [PATCH 09/31] and another dumb logic error for submillis ps prec --- src/time/cds.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index fbb0698..5b1a582 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -264,9 +264,7 @@ impl ConversionFromDatetime { )); } SubmillisPrecision::Picoseconds(_) => { - prec = Some(SubmillisPrecision::Picoseconds( - (dt.timestamp_subsec_nanos() * 1000), - )); + prec = Some(SubmillisPrecision::Picoseconds((dt.timestamp_subsec_nanos() % 10_u32.pow(6)) * 1000)); } _ => (), } @@ -313,7 +311,7 @@ impl ConversionFromNow { )); } SubmillisPrecision::Picoseconds(_) => { - prec = Some(SubmillisPrecision::Picoseconds(now.subsec_nanos() * 1000)); + prec = Some(SubmillisPrecision::Picoseconds((now.subsec_nanos() % 10_u32.pow(6)) * 1000)); } _ => (), } @@ -1212,10 +1210,7 @@ mod tests { assert_eq!(read_stamp.ms_of_day(), u32::MAX - 1); } - #[test] - fn test_time_now() { - let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap(); - let compare_stamp = Utc::now(); + fn generic_now_test(timestamp_now: TimeProvider, compare_stamp: DateTime) { let dt = timestamp_now.date_time().unwrap(); if compare_stamp.year() > dt.year() { assert_eq!(compare_stamp.year() - dt.year(), 1); @@ -1236,6 +1231,19 @@ mod tests { generic_dt_property_equality_check(dt.hour(), compare_stamp.hour(), 0, 23); generic_dt_property_equality_check(dt.minute(), compare_stamp.minute(), 0, 59); } + #[test] + fn test_time_now() { + let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap(); + let compare_stamp = Utc::now(); + generic_now_test(timestamp_now, compare_stamp); + } + + #[test] + fn test_time_now_ps_prec() { + let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap(); + let compare_stamp = Utc::now(); + generic_now_test(timestamp_now, compare_stamp); + } #[test] fn test_submillis_precision_micros() { @@ -1370,6 +1378,7 @@ mod tests { // 250 ms + 500 us let subsec_millis = 250; let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000; + let submilli_nanos = subsec_nanos % 10_u32.pow(6); let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) .unwrap() .and_hms_nano_opt(16, 49, 30, subsec_nanos) @@ -1383,7 +1392,7 @@ mod tests { assert!(time_provider.submillis_precision.is_some()); match time_provider.submillis_precision.unwrap() { SubmillisPrecision::Picoseconds(ps) => { - assert_eq!(ps, subsec_nanos * 1000); + assert_eq!(ps, submilli_nanos * 1000); } _=> panic!("unexpected precision field") } From 67cf9ec0c0b47e5308a8e8b545057d3a6c118d3b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 14 Jan 2023 17:57:15 +0100 Subject: [PATCH 10/31] added some more tests --- src/time/cds.rs | 75 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 6c1d437..326baa7 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -227,7 +227,7 @@ fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) { // 1969 (-1 unix days) shortly before midnight (SECONDS_PER_DAY - 5). if secs_of_day < 0 { unix_days -= 1; - secs_of_day = SECONDS_PER_DAY as i64 + secs_of_day + secs_of_day += SECONDS_PER_DAY as i64 } (unix_days, secs_of_day as u32) } @@ -264,7 +264,9 @@ impl ConversionFromDatetime { )); } SubmillisPrecision::Picoseconds(_) => { - prec = Some(SubmillisPrecision::Picoseconds((dt.timestamp_subsec_nanos() % 10_u32.pow(6)) * 1000)); + prec = Some(SubmillisPrecision::Picoseconds( + (dt.timestamp_subsec_nanos() % 10_u32.pow(6)) * 1000, + )); } _ => (), } @@ -689,14 +691,14 @@ impl TimeProvider { /// Like [Self::from_now_with_u24_days] but with microsecond sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_u24_days_us_prec() -> Result { + pub fn from_now_with_u24_days_us_precision() -> Result { Self::from_now_generic_us_prec(LengthOfDaySegment::Long24Bits) } /// Like [Self::from_now_with_u24_days] but with picoseconds sub-millisecond precision. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now_with_u24_days_ps_prec() -> Result { + pub fn from_now_with_u24_days_ps_precision() -> Result { Self::from_now_generic_us_prec(LengthOfDaySegment::Long24Bits) } @@ -1212,7 +1214,10 @@ mod tests { assert_eq!(read_stamp.ms_of_day(), u32::MAX - 1); } - fn generic_now_test(timestamp_now: TimeProvider, compare_stamp: DateTime) { + fn generic_now_test( + timestamp_now: TimeProvider, + compare_stamp: DateTime, + ) { let dt = timestamp_now.date_time().unwrap(); if compare_stamp.year() > dt.year() { assert_eq!(compare_stamp.year() - dt.year(), 1); @@ -1233,6 +1238,7 @@ mod tests { generic_dt_property_equality_check(dt.hour(), compare_stamp.hour(), 0, 23); generic_dt_property_equality_check(dt.minute(), compare_stamp.minute(), 0, 59); } + #[test] fn test_time_now() { let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap(); @@ -1240,6 +1246,13 @@ mod tests { generic_now_test(timestamp_now, compare_stamp); } + #[test] + fn test_time_now_us_prec() { + let timestamp_now = TimeProvider::from_now_with_u16_days_us_precision().unwrap(); + let compare_stamp = Utc::now(); + generic_now_test(timestamp_now, compare_stamp); + } + #[test] fn test_time_now_ps_prec() { let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap(); @@ -1247,6 +1260,20 @@ mod tests { generic_now_test(timestamp_now, compare_stamp); } + #[test] + fn test_time_now_ps_prec_u16_days() { + let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap(); + let compare_stamp = Utc::now(); + generic_now_test(timestamp_now, compare_stamp); + } + + #[test] + fn test_time_now_ps_prec_u24_days() { + let timestamp_now = TimeProvider::from_now_with_u24_days_ps_precision().unwrap(); + let compare_stamp = Utc::now(); + generic_now_test(timestamp_now, compare_stamp); + } + #[test] fn test_submillis_precision_micros() { let mut time_stamper = TimeProvider::new_with_u16_days(0, 0); @@ -1343,9 +1370,13 @@ mod tests { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. assert_eq!(time_provider.ccsds_days, 23754); - assert_eq!(time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis); + assert_eq!( + time_provider.ms_of_day, + 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis + ); assert_eq!(time_provider.date_time().unwrap(), datetime_utc); - let time_provider_2: TimeProvider = datetime_utc.try_into().expect("conversion failed"); + let time_provider_2: TimeProvider = + datetime_utc.try_into().expect("conversion failed"); // Test the TryInto trait impl assert_eq!(time_provider, time_provider_2); } @@ -1360,17 +1391,21 @@ mod tests { .and_hms_micro_opt(16, 49, 30, subsec_micros) .unwrap(); let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - let time_provider = TimeProvider::from_dt_with_u16_days_us_precision(&datetime_utc).unwrap(); + let time_provider = + TimeProvider::from_dt_with_u16_days_us_precision(&datetime_utc).unwrap(); // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. assert_eq!(time_provider.ccsds_days, 23754); - assert_eq!(time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis); + assert_eq!( + time_provider.ms_of_day, + 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis + ); assert!(time_provider.submillis_precision.is_some()); match time_provider.submillis_precision.unwrap() { SubmillisPrecision::Microseconds(us) => { assert_eq!(us, 500); } - _=> panic!("unexpected precision field") + _ => panic!("unexpected precision field"), } assert_eq!(time_provider.date_time().unwrap(), datetime_utc); } @@ -1386,17 +1421,21 @@ mod tests { .and_hms_nano_opt(16, 49, 30, subsec_nanos) .unwrap(); let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - let time_provider = TimeProvider::from_dt_with_u16_days_ps_precision(&datetime_utc).unwrap(); + let time_provider = + TimeProvider::from_dt_with_u16_days_ps_precision(&datetime_utc).unwrap(); // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. assert_eq!(time_provider.ccsds_days, 23754); - assert_eq!(time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis); + assert_eq!( + time_provider.ms_of_day, + 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis + ); assert!(time_provider.submillis_precision.is_some()); match time_provider.submillis_precision.unwrap() { SubmillisPrecision::Picoseconds(ps) => { assert_eq!(ps, submilli_nanos * 1000); } - _=> panic!("unexpected precision field") + _ => panic!("unexpected precision field"), } assert_eq!(time_provider.date_time().unwrap(), datetime_utc); } @@ -1418,12 +1457,16 @@ mod tests { .and_hms_milli_opt(16, 49, 30, subsec_millis) .unwrap(); let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - let time_provider = TimeProvider::from_unix_secs_with_u16_days(datetime_utc.timestamp(), subsec_millis) - .expect("creating provider from unix stamp failed"); + let time_provider = + TimeProvider::from_unix_secs_with_u16_days(datetime_utc.timestamp(), subsec_millis) + .expect("creating provider from unix stamp failed"); // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. assert_eq!(time_provider.ccsds_days, 23754); - assert_eq!(time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis); + assert_eq!( + time_provider.ms_of_day, + 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis + ); let dt_back = time_provider.date_time().unwrap(); assert_eq!(datetime_utc, dt_back); } From ec452130f9d4c2813cfadb2d44cde96363025418 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 00:38:39 +0100 Subject: [PATCH 11/31] added missing feature gate --- src/time/cds.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/time/cds.rs b/src/time/cds.rs index 326baa7..77fbaf3 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -327,6 +327,7 @@ impl ConversionFromNow { } } +#[cfg(feature = "std")] impl CdsConverter for ConversionFromNow { fn submillis_precision(&self) -> Option { self.submillis_prec From 24e6e50e2f174aff7e1279d1e1c7ea0f6bd4ca19 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 01:15:35 +0100 Subject: [PATCH 12/31] add some more helper methods, improve example --- src/time/cds.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 77fbaf3..26e6809 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -114,7 +114,7 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { /// # Example /// /// ``` -/// use spacepackets::time::cds::{TimeProvider, DaysLen16Bits}; +/// use spacepackets::time::cds::{TimeProvider, DaysLen16Bits, length_of_day_segment_from_pfield, LengthOfDaySegment}; /// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, TimeReader, CcsdsTimeProvider}; /// /// let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap(); @@ -125,7 +125,8 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { /// assert_eq!(written, 7); /// } /// { -/// let read_result = TimeProvider::::from_bytes(&raw_stamp); +/// assert_eq!(length_of_day_segment_from_pfield(raw_stamp[0]), LengthOfDaySegment::Short16Bits); +/// let read_result = TimeProvider::from_bytes_with_u16_days(&raw_stamp); /// assert!(read_result.is_ok()); /// let stamp_deserialized = read_result.unwrap(); /// assert_eq!(stamp_deserialized.len_as_bytes(), 7); @@ -703,7 +704,7 @@ impl TimeProvider { Self::from_now_generic_us_prec(LengthOfDaySegment::Long24Bits) } - fn from_bytes_with_u24_days(buf: &[u8]) -> Result { + pub fn from_bytes_with_u24_days(buf: &[u8]) -> Result { let submillis_precision = Self::generic_raw_read_checks(buf, LengthOfDaySegment::Long24Bits)?; let mut temp_buf: [u8; 4] = [0; 4]; @@ -787,7 +788,7 @@ impl TimeProvider { Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits) } - fn from_bytes_with_u16_days(buf: &[u8]) -> Result { + pub fn from_bytes_with_u16_days(buf: &[u8]) -> Result { let submillis_precision = Self::generic_raw_read_checks(buf, LengthOfDaySegment::Short16Bits)?; let ccsds_days: u16 = u16::from_be_bytes(buf[1..3].try_into().unwrap()); From 3f6c4c6f467cc1bef972862ec0f6efeaf94363a8 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 01:33:26 +0100 Subject: [PATCH 13/31] add dynamic cds time provider function --- src/time/cds.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/time/cds.rs b/src/time/cds.rs index 26e6809..bf6719b 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -4,6 +4,8 @@ //! The core data structure to do this is the [cds::TimeProvider] struct. use super::*; use crate::private::Sealed; +#[cfg(feature = "alloc")] +use alloc::boxed::Box; use chrono::Datelike; use core::fmt::Debug; use core::ops::Add; @@ -343,6 +345,42 @@ impl CdsConverter for ConversionFromNow { } } +#[cfg(feature = "alloc")] +pub trait DynCdsTimeProvider: CcsdsTimeProvider + TimeWriter {} +#[cfg(feature = "alloc")] +impl DynCdsTimeProvider for TimeProvider {} +#[cfg(feature = "alloc")] +impl DynCdsTimeProvider for TimeProvider {} + +/// This function returns the correct [TimeProvider] instance by checking the days of length +/// field. It also checks the CCSDS time code for correctness. +/// +/// The time provider instance is returned as a [DynCdsTimeProvider] trait object. +#[cfg(feature = "alloc")] +pub fn get_dyn_time_provider_from_bytes( + buf: &[u8], +) -> Result, TimestampError> { + let time_code = ccsds_time_code_from_p_field(buf[0]); + if let Err(e) = time_code { + return Err(TimestampError::InvalidTimeCode( + CcsdsTimeCodes::Cds, + e, + )); + } + let time_code = time_code.unwrap(); + if time_code != CcsdsTimeCodes::Cds { + return Err(TimestampError::InvalidTimeCode( + CcsdsTimeCodes::Cds, + time_code as u8, + )); + } + if length_of_day_segment_from_pfield(buf[0]) == LengthOfDaySegment::Short16Bits { + Ok(Box::new(TimeProvider::from_bytes_with_u16_days(buf)?)) + } else { + Ok(Box::new(TimeProvider::from_bytes_with_u24_days(buf)?)) + } +} + impl TimeProvider { pub fn set_submillis_precision(&mut self, prec: SubmillisPrecision) { self.pfield &= !(0b11); From 39bf0c6a61d7213d02b5a9d02db70a61612e22ac Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 02:37:56 +0100 Subject: [PATCH 14/31] Add AddAssign impl and addition unittests --- src/time/cds.rs | 138 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 21 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index bf6719b..481fc20 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -11,6 +11,7 @@ use core::fmt::Debug; use core::ops::Add; use core::time::Duration; use delegate::delegate; +use std::ops::AddAssign; /// Base value for the preamble field for a time field parser to determine the time field type. pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::Cds as u8) << 4; @@ -352,8 +353,8 @@ impl DynCdsTimeProvider for TimeProvider {} #[cfg(feature = "alloc")] impl DynCdsTimeProvider for TimeProvider {} -/// This function returns the correct [TimeProvider] instance by checking the days of length -/// field. It also checks the CCSDS time code for correctness. +/// This function returns the correct [TimeProvider] instance from a raw byte array +/// by checking the days of length field. It also checks the CCSDS time code for correctness. /// /// The time provider instance is returned as a [DynCdsTimeProvider] trait object. #[cfg(feature = "alloc")] @@ -362,10 +363,7 @@ pub fn get_dyn_time_provider_from_bytes( ) -> Result, TimestampError> { let time_code = ccsds_time_code_from_p_field(buf[0]); if let Err(e) = time_code { - return Err(TimestampError::InvalidTimeCode( - CcsdsTimeCodes::Cds, - e, - )); + return Err(TimestampError::InvalidTimeCode(CcsdsTimeCodes::Cds, e)); } let time_code = time_code.unwrap(); if time_code != CcsdsTimeCodes::Cds { @@ -847,7 +845,7 @@ impl TimeProvider { } fn add_for_max_ccsds_days_val( - time_provider: TimeProvider, + time_provider: &TimeProvider, max_days_val: u32, duration: Duration, ) -> (u32, u32, Option) { @@ -856,9 +854,9 @@ fn add_for_max_ccsds_days_val( let mut precision = None; // Increment CCSDS days by a certain amount while also accounting for overflow. let increment_days = |ccsds_days: &mut u32, days_inc: u32| { - let days_addition = *ccsds_days + days_inc; - if days_addition >= (max_days_val - 1) { - *ccsds_days = days_addition - max_days_val; + let days_addition: u64 = *ccsds_days as u64 + days_inc as u64; + if days_addition > max_days_val as u64 { + *ccsds_days = (days_addition - max_days_val as u64) as u32; } else { *ccsds_days += days_inc; } @@ -866,9 +864,9 @@ fn add_for_max_ccsds_days_val( // Increment MS of day by a certain amount while also accounting for overflow, where // the new value exceeds the MS of a day. let increment_ms_of_day = |ms_of_day: &mut u32, ms_inc: u32, ccsds_days: &mut u32| { - let ms_addition = *ms_of_day + ms_inc; - if ms_addition >= MS_PER_DAY { - *ms_of_day = ms_addition - MS_PER_DAY; + *ms_of_day += ms_inc; + if *ms_of_day >= MS_PER_DAY { + *ms_of_day -= MS_PER_DAY; // Re-use existing closure to always amount for overflow. increment_days(ccsds_days, 1); } @@ -877,7 +875,7 @@ fn add_for_max_ccsds_days_val( match submillis_prec { SubmillisPrecision::Absent => {} SubmillisPrecision::Microseconds(mut us) => { - let micros = duration.as_micros(); + let micros = duration.subsec_micros(); let submilli_micros = (micros % 1000) as u16; us += submilli_micros; if us >= 1000 { @@ -886,16 +884,26 @@ fn add_for_max_ccsds_days_val( precision = Some(SubmillisPrecision::Microseconds(carryover_us)); } } - SubmillisPrecision::Picoseconds(_ps) => {} + SubmillisPrecision::Picoseconds(mut ps) => { + let nanos = duration.subsec_nanos(); + let submilli_nanos = nanos % 10_u32.pow(6); + ps += submilli_nanos * 1000; + if ps >= 10_u32.pow(6) { + let carry_over_ps = ps - 10_u32.pow(6); + increment_ms_of_day(&mut next_ms_of_day, 1, &mut next_ccsds_days); + precision = Some(SubmillisPrecision::Picoseconds(carry_over_ps)) + } + } SubmillisPrecision::Reserved => {} } } - let full_ms = duration.as_millis(); - let ms_of_day = (full_ms % MS_PER_DAY as u128) as u32; + let full_seconds = duration.as_secs(); + let secs_of_day = (full_seconds % SECONDS_PER_DAY as u64) as u32; + let ms_of_day = secs_of_day * 1000; increment_ms_of_day(&mut next_ms_of_day, ms_of_day, &mut next_ccsds_days); increment_days( &mut next_ccsds_days, - (full_ms as u32 - ms_of_day) / MS_PER_DAY, + (full_seconds as u32 - secs_of_day) / SECONDS_PER_DAY, ); (next_ccsds_days, next_ms_of_day, precision) } @@ -908,7 +916,7 @@ impl Add for TimeProvider { fn add(self, duration: Duration) -> Self::Output { let (next_ccsds_days, next_ms_of_day, precision) = - add_for_max_ccsds_days_val(self, u16::MAX as u32, duration); + add_for_max_ccsds_days_val(&self, u16::MAX as u32, duration); let mut provider = Self::new_with_u16_days(next_ccsds_days as u16, next_ms_of_day); if let Some(prec) = precision { provider.set_submillis_precision(prec); @@ -925,7 +933,7 @@ impl Add for TimeProvider { fn add(self, duration: Duration) -> Self::Output { let (next_ccsds_days, next_ms_of_day, precision) = - add_for_max_ccsds_days_val(self, MAX_DAYS_24_BITS, duration); + add_for_max_ccsds_days_val(&self, MAX_DAYS_24_BITS, duration); let mut provider = Self::new_with_u24_days(next_ccsds_days, next_ms_of_day).unwrap(); if let Some(prec) = precision { provider.set_submillis_precision(prec); @@ -934,6 +942,32 @@ impl Add for TimeProvider { } } +/// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover +/// when they overflow, because addition needs to be infallible. The user needs to check for a +/// days overflow when this is a possibility and might be a problem. +impl AddAssign for TimeProvider { + fn add_assign(&mut self, duration: Duration) { + let (next_ccsds_days, next_ms_of_day, precision) = + add_for_max_ccsds_days_val(self, u16::MAX as u32, duration); + self.ccsds_days = next_ccsds_days as u16; + self.ms_of_day = next_ms_of_day; + self.submillis_precision = precision; + } +} + +/// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover +/// when they overflow, because addition needs to be infallible. The user needs to check for a +/// days overflow when this is a possibility and might be a problem. +impl AddAssign for TimeProvider { + fn add_assign(&mut self, duration: Duration) { + let (next_ccsds_days, next_ms_of_day, precision) = + add_for_max_ccsds_days_val(self, MAX_DAYS_24_BITS, duration); + self.ccsds_days = next_ccsds_days; + self.ms_of_day = next_ms_of_day; + self.submillis_precision = precision; + } +} + impl TryFrom> for TimeProvider { type Error = TimestampError; @@ -1399,7 +1433,7 @@ mod tests { } #[test] - fn test_creation_from_dt() { + fn test_creation_from_dt_u16_days() { let subsec_millis = 250; let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) .unwrap() @@ -1421,6 +1455,29 @@ mod tests { assert_eq!(time_provider, time_provider_2); } + #[test] + fn test_creation_from_dt_u24_days() { + let subsec_millis = 250; + let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) + .unwrap() + .and_hms_milli_opt(16, 49, 30, subsec_millis) + .unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc).unwrap(); + // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 + // Leap years need to be accounted for as well. + assert_eq!(time_provider.ccsds_days, 23754); + assert_eq!( + time_provider.ms_of_day, + 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis + ); + assert_eq!(time_provider.date_time().unwrap(), datetime_utc); + let time_provider_2: TimeProvider = + datetime_utc.try_into().expect("conversion failed"); + // Test the TryInto trait impl + assert_eq!(time_provider, time_provider_2); + } + #[test] fn test_creation_from_dt_us_prec() { // 250 ms + 500 us @@ -1566,6 +1623,45 @@ mod tests { } } + #[test] + fn test_addition_u16_days_day_increment() { + let mut provider = TimeProvider::new_with_u16_days(0, MS_PER_DAY - 5 * 1000); + let seconds_offset = Duration::from_secs(10); + assert_eq!(provider.ccsds_days, 0); + assert_eq!(provider.ms_of_day, MS_PER_DAY - 5 * 1000); + provider += seconds_offset; + assert_eq!(provider.ccsds_days, 1); + assert_eq!(provider.ms_of_day, 5000); + } + + #[test] + fn test_addition_u16_days() { + let mut provider = TimeProvider::new_with_u16_days(0, 0); + let seconds_offset = Duration::from_secs(5); + assert_eq!(provider.ccsds_days, 0); + assert_eq!(provider.ms_of_day, 0); + provider += seconds_offset; + assert_eq!(provider.ms_of_day, 5000); + // Add one day and test Add operator + let provider2 = provider + Duration::from_secs(60 * 60 * 24); + assert_eq!(provider2.ccsds_days, 1); + assert_eq!(provider2.ms_of_day, 5000); + } + + #[test] + fn test_addition_u24_days() { + let mut provider = TimeProvider::new_with_u24_days(u16::MAX as u32, 0).unwrap(); + let seconds_offset = Duration::from_secs(5); + assert_eq!(provider.ccsds_days, u16::MAX as u32); + assert_eq!(provider.ms_of_day, 0); + provider += seconds_offset; + assert_eq!(provider.ms_of_day, 5000); + // Add one day and test Add operator + let provider2 = provider + Duration::from_secs(60 * 60 * 24); + assert_eq!(provider2.ccsds_days, u16::MAX as u32 + 1); + assert_eq!(provider2.ms_of_day, 5000); + } + #[test] #[cfg(feature = "serde")] fn test_serialization() { From 22f3b72fafead7b3343d788649cfd4e6f600eba4 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 11:54:23 +0100 Subject: [PATCH 15/31] dyn provider works great --- src/time/cds.rs | 143 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 29 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 481fc20..f6dcd59 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -7,6 +7,7 @@ use crate::private::Sealed; #[cfg(feature = "alloc")] use alloc::boxed::Box; use chrono::Datelike; +use core::any::Any; use core::fmt::Debug; use core::ops::Add; use core::time::Duration; @@ -147,11 +148,22 @@ pub struct TimeProvider { unix_seconds: i64, } -/// Private trait which serves as an abstraction for different converters. -trait CdsConverter { +/// Common properties for all CDS time providers. +/// +/// Also exists to encapsulate properties used by private converters. +pub trait CdsCommon { fn submillis_precision(&self) -> Option; fn ms_of_day(&self) -> u32; - fn ccsds_days(&self) -> u32; + fn ccsds_days_as_u32(&self) -> u32; +} + +/// Generic properties for all CDS time providers. +pub trait CdsTimeStamp: CdsCommon { + fn len_of_day_seg(&self) -> LengthOfDaySegment; +} + +/// Private trait which serves as an abstraction for different converters. +trait CdsConverter: CdsCommon { fn unix_days_seconds(&self) -> i64; } @@ -185,7 +197,7 @@ impl ConversionFromUnix { } } -impl CdsConverter for ConversionFromUnix { +impl CdsCommon for ConversionFromUnix { fn submillis_precision(&self) -> Option { None } @@ -194,22 +206,23 @@ impl CdsConverter for ConversionFromUnix { self.ms_of_day } - fn ccsds_days(&self) -> u32 { + fn ccsds_days_as_u32(&self) -> u32 { self.ccsds_days } +} +impl CdsConverter for ConversionFromUnix { fn unix_days_seconds(&self) -> i64 { self.unix_days_seconds } } - /// Helper struct which generates fields for the CDS time provider from a datetime. struct ConversionFromDatetime { unix_conversion: ConversionFromUnix, submillis_prec: Option, } -impl CdsConverter for ConversionFromDatetime { +impl CdsCommon for ConversionFromDatetime { fn submillis_precision(&self) -> Option { self.submillis_prec } @@ -217,12 +230,15 @@ impl CdsConverter for ConversionFromDatetime { delegate! { to self.unix_conversion { fn ms_of_day(&self) -> u32; - fn ccsds_days(&self) -> u32; - fn unix_days_seconds(&self) -> i64; + fn ccsds_days_as_u32(&self) -> u32; } } } +impl CdsConverter for ConversionFromDatetime { + delegate! {to self.unix_conversion { fn unix_days_seconds(&self) -> i64; }} +} + #[inline] fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) { let mut secs_of_day = unix_seconds % SECONDS_PER_DAY as i64; @@ -332,22 +348,25 @@ impl ConversionFromNow { } #[cfg(feature = "std")] -impl CdsConverter for ConversionFromNow { +impl CdsCommon for ConversionFromNow { fn submillis_precision(&self) -> Option { self.submillis_prec } - delegate! { to self.unix_conversion { fn ms_of_day(&self) -> u32; - fn ccsds_days(&self) -> u32; - fn unix_days_seconds(&self) -> i64; + fn ccsds_days_as_u32(&self) -> u32; } } } +#[cfg(feature = "std")] +impl CdsConverter for ConversionFromNow { + delegate! {to self.unix_conversion { fn unix_days_seconds(&self) -> i64; }} +} + #[cfg(feature = "alloc")] -pub trait DynCdsTimeProvider: CcsdsTimeProvider + TimeWriter {} +pub trait DynCdsTimeProvider: CcsdsTimeProvider + CdsTimeStamp + TimeWriter + Any {} #[cfg(feature = "alloc")] impl DynCdsTimeProvider for TimeProvider {} #[cfg(feature = "alloc")] @@ -379,7 +398,31 @@ pub fn get_dyn_time_provider_from_bytes( } } +impl CdsCommon for TimeProvider { + fn submillis_precision(&self) -> Option { + self.submillis_precision + } + + fn ms_of_day(&self) -> u32 { + self.ms_of_day + } + + fn ccsds_days_as_u32(&self) -> u32 { + self.ccsds_days.into() + } +} + impl TimeProvider { + /// This function returns the correct [TimeProvider] instance from a raw byte array + /// by checking the days of length field. It also checks the CCSDS time code for correctness. + /// + /// The time provider instance is returned as a [DynCdsTimeProvider] trait object. + /// This function also simply calls [`get_dyn_time_provider_from_bytes`]. + #[cfg(feature = "alloc")] + pub fn from_bytes_dyn(buf: &[u8]) -> Result, TimestampError> { + get_dyn_time_provider_from_bytes(buf) + } + pub fn set_submillis_precision(&mut self, prec: SubmillisPrecision) { self.pfield &= !(0b11); if let SubmillisPrecision::Absent = prec { @@ -407,18 +450,6 @@ impl TimeProvider { self.ccsds_days } - fn ccsds_days_as_u32(&self) -> u32 { - self.ccsds_days.into() - } - - pub fn submillis_precision(&self) -> Option { - self.submillis_precision - } - - pub fn ms_of_day(&self) -> u32 { - self.ms_of_day - } - fn generic_raw_read_checks( buf: &[u8], days_len: LengthOfDaySegment, @@ -603,8 +634,10 @@ impl TimeProvider { converter: C, ) -> Result { let ccsds_days: ProvidesDaysLen::FieldType = - converter.ccsds_days().try_into().map_err(|_| { - TimestampError::CdsError(CdsError::InvalidCcsdsDays(converter.ccsds_days().into())) + converter.ccsds_days_as_u32().try_into().map_err(|_| { + TimestampError::CdsError(CdsError::InvalidCcsdsDays( + converter.ccsds_days_as_u32().into(), + )) })?; let mut provider = Self { pfield: Self::generate_p_field(days_len, converter.submillis_precision()), @@ -908,6 +941,18 @@ fn add_for_max_ccsds_days_val( (next_ccsds_days, next_ms_of_day, precision) } +impl CdsTimeStamp for TimeProvider { + fn len_of_day_seg(&self) -> LengthOfDaySegment { + LengthOfDaySegment::Short16Bits + } +} + +impl CdsTimeStamp for TimeProvider { + fn len_of_day_seg(&self) -> LengthOfDaySegment { + LengthOfDaySegment::Long24Bits + } +} + /// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover /// when they overflow, because addition needs to be infallible. The user needs to check for a /// days overflow when this is a possibility and might be a problem. @@ -1443,7 +1488,7 @@ mod tests { let time_provider = TimeProvider::from_dt_with_u16_days(&datetime_utc).unwrap(); // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. - assert_eq!(time_provider.ccsds_days, 23754); + assert_eq!(time_provider.ccsds_days(), 23754); assert_eq!( time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis @@ -1662,6 +1707,46 @@ mod tests { assert_eq!(provider2.ms_of_day, 5000); } + #[test] + fn test_dyn_creation_u24_days() { + let stamp = TimeProvider::new_with_u24_days(u16::MAX as u32 + 1, 24).unwrap(); + let mut buf: [u8; 32] = [0; 32]; + stamp.write_to_bytes(&mut buf).unwrap(); + let dyn_provider = get_dyn_time_provider_from_bytes(&buf); + assert!(dyn_provider.is_ok()); + let dyn_provider = dyn_provider.unwrap(); + assert_eq!(dyn_provider.ccdsd_time_code(), CcsdsTimeCodes::Cds); + assert_eq!(dyn_provider.ccsds_days_as_u32(), u16::MAX as u32 + 1); + assert_eq!(dyn_provider.ms_of_day(), 24); + assert_eq!(dyn_provider.submillis_precision(), None); + assert_eq!( + dyn_provider.len_of_day_seg(), + LengthOfDaySegment::Long24Bits + ); + } + + #[test] + fn test_dyn_creation_u16_days_with_precision() { + let mut stamp = TimeProvider::new_with_u16_days(24, 24); + stamp.set_submillis_precision(SubmillisPrecision::Microseconds(666)); + let mut buf: [u8; 32] = [0; 32]; + stamp.write_to_bytes(&mut buf).unwrap(); + let dyn_provider = get_dyn_time_provider_from_bytes(&buf); + assert!(dyn_provider.is_ok()); + let dyn_provider = dyn_provider.unwrap(); + assert_eq!(dyn_provider.ccdsd_time_code(), CcsdsTimeCodes::Cds); + assert_eq!(dyn_provider.ccsds_days_as_u32(), 24); + assert_eq!(dyn_provider.ms_of_day(), 24); + assert_eq!( + dyn_provider.len_of_day_seg(), + LengthOfDaySegment::Short16Bits + ); + assert!(dyn_provider.submillis_precision().is_some()); + if let SubmillisPrecision::Microseconds(us) = dyn_provider.submillis_precision().unwrap() { + assert_eq!(us, 666); + } + } + #[test] #[cfg(feature = "serde")] fn test_serialization() { From 405145496fb2462063daccda45fec5d15c72c8ea Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 11:57:58 +0100 Subject: [PATCH 16/31] typo fix --- src/time/cds.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/time/cds.rs b/src/time/cds.rs index f6dcd59..bd50620 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -110,6 +110,11 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { /// section 3.3 . The width of the days field is configured at compile time via the generic /// [ProvidesDaysLength] trait which is implemented by [DaysLen16Bits] and [DaysLen24Bits]. /// +/// If you do not want to perform a forward check of the days length field with +/// [length_of_day_segment_from_pfield] and you have [alloc] support, you can also +/// use [TimeProvider::from_bytes_dyn] to retrieve the correct instance as a [DynCdsTimeProvider] +/// trait object. +/// /// Custom epochs are not supported yet. /// Furthermore, the preamble field (p-field) is explicitly conveyed. /// That means it will always be present when writing the time stamp to a raw buffer, and it From 4c280b22c814afdf5a14514a9439937e4c13c1f3 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 12:13:39 +0100 Subject: [PATCH 17/31] hmm associated method is tricky --- src/time/cds.rs | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index bd50620..a4dd1bc 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -123,8 +123,8 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { /// # Example /// /// ``` -/// use spacepackets::time::cds::{TimeProvider, DaysLen16Bits, length_of_day_segment_from_pfield, LengthOfDaySegment}; -/// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, TimeReader, CcsdsTimeProvider}; +/// use spacepackets::time::cds::{TimeProvider, length_of_day_segment_from_pfield, LengthOfDaySegment}; +/// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, CcsdsTimeProvider}; /// /// let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap(); /// let mut raw_stamp = [0; 7]; @@ -381,6 +381,30 @@ impl DynCdsTimeProvider for TimeProvider {} /// by checking the days of length field. It also checks the CCSDS time code for correctness. /// /// The time provider instance is returned as a [DynCdsTimeProvider] trait object. +/// This function returns the correct [TimeProvider] instance from a raw byte array +/// by checking the days of length field. It also checks the CCSDS time code for correctness. +/// +/// # Example +/// +/// ``` +/// use spacepackets::time::cds::{TimeProvider, LengthOfDaySegment, get_dyn_time_provider_from_bytes}; +/// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, CcsdsTimeProvider}; +/// +/// let timestamp_now = TimeProvider::new_with_u16_days(24, 24); +/// let mut raw_stamp = [0; 7]; +/// { +/// let written = timestamp_now.write_to_bytes(&mut raw_stamp).unwrap(); +/// assert_eq!((raw_stamp[0] >> 4) & 0b111, CcsdsTimeCodes::Cds as u8); +/// assert_eq!(written, 7); +/// } +/// { +/// let dyn_provider = get_dyn_time_provider_from_bytes(&raw_stamp).unwrap(); +/// assert_eq!(dyn_provider.len_of_day_seg(), LengthOfDaySegment::Short16Bits); +/// assert_eq!(dyn_provider.ccsds_days_as_u32(), 24); +/// assert_eq!(dyn_provider.ms_of_day(), 24); +/// assert_eq!(dyn_provider.submillis_precision(), None); +/// } +/// ``` #[cfg(feature = "alloc")] pub fn get_dyn_time_provider_from_bytes( buf: &[u8], @@ -418,16 +442,6 @@ impl CdsCommon for TimeProvider TimeProvider { - /// This function returns the correct [TimeProvider] instance from a raw byte array - /// by checking the days of length field. It also checks the CCSDS time code for correctness. - /// - /// The time provider instance is returned as a [DynCdsTimeProvider] trait object. - /// This function also simply calls [`get_dyn_time_provider_from_bytes`]. - #[cfg(feature = "alloc")] - pub fn from_bytes_dyn(buf: &[u8]) -> Result, TimestampError> { - get_dyn_time_provider_from_bytes(buf) - } - pub fn set_submillis_precision(&mut self, prec: SubmillisPrecision) { self.pfield &= !(0b11); if let SubmillisPrecision::Absent = prec { From 73575bd00fafcaeaf246bd42e5a42db692cba9bd Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 17:42:23 +0100 Subject: [PATCH 18/31] reaching target coverage --- src/time/cds.rs | 266 ++++++++++++++++++++++++++++++++++++++---------- src/time/cuc.rs | 29 +++++- src/time/mod.rs | 86 ++++++++++++++++ 3 files changed, 326 insertions(+), 55 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index a4dd1bc..b92ba54 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -1,7 +1,9 @@ //! Module to generate or read CCSDS Day Segmented (CDS) timestamps as specified in //! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.3 . //! -//! The core data structure to do this is the [cds::TimeProvider] struct. +//! The core data structure to do this is the [TimeProvider] struct and the +//! [get_dyn_time_provider_from_bytes] function to retrieve correct instances of the +//! struct from a bytestream. use super::*; use crate::private::Sealed; #[cfg(feature = "alloc")] @@ -22,7 +24,16 @@ pub const MAX_DAYS_24_BITS: u32 = 2_u32.pow(24) - 1; /// Generic trait implemented by token structs to specify the length of day field at type /// level. This trait is only meant to be implemented in this crate and therefore sealed. pub trait ProvidesDaysLength: Sealed { - type FieldType: Copy + Clone + TryFrom + TryFrom + From + Into + Into; + type FieldType: Debug + + Copy + + Clone + + PartialEq + + Eq + + TryFrom + + TryFrom + + From + + Into + + Into; } /// Type level token to be used as a generic parameter to [TimeProvider]. @@ -112,7 +123,7 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { /// /// If you do not want to perform a forward check of the days length field with /// [length_of_day_segment_from_pfield] and you have [alloc] support, you can also -/// use [TimeProvider::from_bytes_dyn] to retrieve the correct instance as a [DynCdsTimeProvider] +/// use [get_dyn_time_provider_from_bytes] to retrieve the correct instance as a [DynCdsTimeProvider] /// trait object. /// /// Custom epochs are not supported yet. @@ -123,6 +134,7 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { /// # Example /// /// ``` +/// use core::time::Duration; /// use spacepackets::time::cds::{TimeProvider, length_of_day_segment_from_pfield, LengthOfDaySegment}; /// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, CcsdsTimeProvider}; /// @@ -140,6 +152,11 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { /// let stamp_deserialized = read_result.unwrap(); /// assert_eq!(stamp_deserialized.len_as_bytes(), 7); /// } +/// // It is possible to add a Duration offset to a timestamp provider. Add 5 minutes offset here +/// let offset = Duration::from_secs(60 * 5); +/// let former_unix_seconds = timestamp_now.unix_seconds(); +/// let timestamp_in_5_minutes = timestamp_now + offset; +/// assert_eq!(timestamp_in_5_minutes.unix_seconds(), former_unix_seconds + 5 * 60); /// ``` #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -150,7 +167,7 @@ pub struct TimeProvider { submillis_precision: Option, /// This is not strictly necessary but still cached because it significantly simplifies the /// calculation of [`DateTime`]. - unix_seconds: i64, + unix_stamp: UnixTimeStamp, } /// Common properties for all CDS time providers. @@ -541,12 +558,13 @@ impl TimeProvider { #[inline] fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u32) { - self.unix_seconds = unix_days_seconds; + self.unix_stamp.unix_seconds = unix_days_seconds; + self.unix_stamp.subsecond_millis = Some((ms_of_day % 1000) as u16); let seconds_of_day = (ms_of_day / 1000) as i64; - if self.unix_seconds < 0 { - self.unix_seconds -= seconds_of_day; + if self.unix_stamp.unix_seconds < 0 { + self.unix_stamp.unix_seconds -= seconds_of_day; } else { - self.unix_seconds += seconds_of_day; + self.unix_stamp.unix_seconds += seconds_of_day; } } @@ -555,7 +573,8 @@ impl TimeProvider { ns_since_last_second < 10_u32.pow(9), "Invalid MS since last second" ); - if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_second) + if let LocalResult::Single(val) = + Utc.timestamp_opt(self.unix_stamp.unix_seconds, ns_since_last_second) { return Some(val); } @@ -586,7 +605,7 @@ impl TimeProvider { pfield: Self::generate_p_field(days_len, None), ccsds_days, ms_of_day, - unix_seconds: 0, + unix_stamp: Default::default(), submillis_precision: None, }; let unix_days_seconds = ccsds_to_unix_days(i64::from(ccsds_days)) * SECONDS_PER_DAY as i64; @@ -662,7 +681,7 @@ impl TimeProvider { pfield: Self::generate_p_field(days_len, converter.submillis_precision()), ccsds_days, ms_of_day: converter.ms_of_day(), - unix_seconds: 0, + unix_stamp: Default::default(), submillis_precision: converter.submillis_precision(), }; provider.setup(converter.unix_days_seconds(), converter.ms_of_day()); @@ -762,12 +781,12 @@ impl TimeProvider { } /// Like [Self::from_dt_with_u24_days] but with microsecond sub-millisecond precision. - pub fn from_dt_with_u24_days_us_prec(dt: &DateTime) -> Result { + pub fn from_dt_with_u24_days_us_precision(dt: &DateTime) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits) } /// Like [Self::from_dt_with_u24_days] but with picoseconds sub-millisecond precision. - pub fn from_dt_with_u24_days_ps_prec(dt: &DateTime) -> Result { + pub fn from_dt_with_u24_days_ps_precision(dt: &DateTime) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits) } @@ -1063,8 +1082,18 @@ impl CcsdsTimeProvider for TimeProvider i64 { - self.unix_seconds + self.unix_stamp.unix_seconds + } + #[inline] + fn subsecond_millis(&self) -> Option { + self.unix_stamp.subsecond_millis + } + + #[inline] + fn unix_stamp(&self) -> UnixTimeStamp { + self.unix_stamp } fn date_time(&self) -> Option> { @@ -1152,11 +1181,18 @@ mod tests { #[test] fn test_time_stamp_zero_args() { let time_stamper = TimeProvider::new_with_u16_days(0, 0); + let unix_stamp = time_stamper.unix_stamp(); assert_eq!( - time_stamper.unix_seconds(), + unix_stamp.unix_seconds, (DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64 ); + let subsecond_millis = unix_stamp.subsecond_millis; + assert!(subsecond_millis.is_some()); + + assert_eq!(subsecond_millis.unwrap(), 0); assert_eq!(time_stamper.submillis_precision(), None); + assert!(time_stamper.subsecond_millis().is_some()); + assert_eq!(time_stamper.subsecond_millis().unwrap(), 0); assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds); assert_eq!( time_stamper.p_field(), @@ -1174,7 +1210,7 @@ mod tests { #[test] fn test_time_stamp_unix_epoch() { let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 0); - assert_eq!(time_stamper.unix_seconds(), 0); + assert_eq!(time_stamper.unix_stamp().unix_seconds, 0); assert_eq!(time_stamper.submillis_precision(), None); let date_time = time_stamper.date_time().unwrap(); assert_eq!(date_time.year(), 1970); @@ -1183,11 +1219,17 @@ mod tests { assert_eq!(date_time.hour(), 0); assert_eq!(date_time.minute(), 0); assert_eq!(date_time.second(), 0); + let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 40); + assert!(time_stamper.subsecond_millis().is_some()); + assert_eq!(time_stamper.subsecond_millis().unwrap(), 40); + let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 1040); + assert!(time_stamper.subsecond_millis().is_some()); + assert_eq!(time_stamper.subsecond_millis().unwrap(), 40); } #[test] fn test_large_days_field_write() { - let time_stamper = TimeProvider::new_with_u24_days(0x108020_u32, 0); + let time_stamper = TimeProvider::new_with_u24_days(0x108020_u32, 0x10203040); assert!(time_stamper.is_ok()); let time_stamper = time_stamper.unwrap(); assert_eq!(time_stamper.len_as_bytes(), 8); @@ -1200,7 +1242,7 @@ mod tests { assert_eq!(buf[2], 0x80); assert_eq!(buf[3], 0x20); let ms = u32::from_be_bytes(buf[4..8].try_into().unwrap()); - assert_eq!(ms, 0); + assert_eq!(ms, 0x10203040); assert_eq!((buf[0] >> 2) & 0b1, 1); } @@ -1230,9 +1272,8 @@ mod tests { let faulty_ctor = TimeProvider::::from_bytes(&buf); assert!(faulty_ctor.is_err()); let error = faulty_ctor.unwrap_err(); - if let TimestampError::CdsError(cds::CdsError::InvalidCtorForDaysOfLenInPreamble( - len_of_day, - )) = error + if let TimestampError::CdsError(CdsError::InvalidCtorForDaysOfLenInPreamble(len_of_day)) = + error { assert_eq!(len_of_day, LengthOfDaySegment::Long24Bits); } else { @@ -1497,66 +1538,117 @@ mod tests { } #[test] - fn test_creation_from_dt_u16_days() { - let subsec_millis = 250; + fn read_u24_stamp_with_us_submillis_precision() { + let mut time_stamper = TimeProvider::new_with_u24_days(u16::MAX as u32 + 1, 0).unwrap(); + time_stamper.set_submillis_precision(SubmillisPrecision::Microseconds(500)); + let mut write_buf: [u8; 16] = [0; 16]; + let written = time_stamper + .write_to_bytes(&mut write_buf) + .expect("Writing timestamp failed"); + // 1 byte pfield + 3 bytes days + 4 bytes ms of day + 2 bytes us precision + assert_eq!(written, 10); + let stamp_deserialized = TimeProvider::from_bytes_with_u24_days(&write_buf); + assert!(stamp_deserialized.is_ok()); + let stamp_deserialized = stamp_deserialized.unwrap(); + assert_eq!(stamp_deserialized.len_as_bytes(), 10); + assert_eq!(stamp_deserialized.ccsds_days(), u16::MAX as u32 + 1); + assert!(stamp_deserialized.submillis_precision().is_some()); + let submillis_rec = stamp_deserialized.submillis_precision().unwrap(); + if let SubmillisPrecision::Microseconds(us) = submillis_rec { + assert_eq!(us, 500); + } else { + panic!("Wrong precision field detected"); + } + } + + #[test] + fn read_u24_stamp_with_ps_submillis_precision() { + let mut time_stamper = TimeProvider::new_with_u24_days(u16::MAX as u32 + 1, 0).unwrap(); + time_stamper.set_submillis_precision(SubmillisPrecision::Picoseconds(5e8 as u32)); + let mut write_buf: [u8; 16] = [0; 16]; + let written = time_stamper + .write_to_bytes(&mut write_buf) + .expect("Writing timestamp failed"); + // 1 byte pfield + 3 bytes days + 4 bytes ms of day + 4 bytes us precision + assert_eq!(written, 12); + let stamp_deserialized = TimeProvider::from_bytes_with_u24_days(&write_buf); + assert!(stamp_deserialized.is_ok()); + let stamp_deserialized = stamp_deserialized.unwrap(); + assert_eq!(stamp_deserialized.len_as_bytes(), 12); + assert_eq!(stamp_deserialized.ccsds_days(), u16::MAX as u32 + 1); + assert!(stamp_deserialized.submillis_precision().is_some()); + let submillis_rec = stamp_deserialized.submillis_precision().unwrap(); + if let SubmillisPrecision::Picoseconds(ps) = submillis_rec { + assert_eq!(ps, 5e8 as u32); + } else { + panic!("Wrong precision field detected"); + } + } + + fn generic_dt_case_0_no_prec(subsec_millis: u32) -> DateTime { let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) .unwrap() .and_hms_milli_opt(16, 49, 30, subsec_millis) .unwrap(); - let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - let time_provider = TimeProvider::from_dt_with_u16_days(&datetime_utc).unwrap(); + DateTime::::from_utc(naivedatetime_utc, Utc) + } + + fn generic_check_dt_case_0( + time_provider: &TimeProvider, + subsec_millis: u32, + datetime_utc: DateTime, + ) { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. - assert_eq!(time_provider.ccsds_days(), 23754); + assert_eq!(time_provider.ccsds_days, 23754.into()); assert_eq!( time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis ); assert_eq!(time_provider.date_time().unwrap(), datetime_utc); + } + + #[test] + fn test_creation_from_dt_u16_days() { + let subsec_millis = 250; + let datetime_utc = generic_dt_case_0_no_prec(subsec_millis); + let time_provider = TimeProvider::from_dt_with_u16_days(&datetime_utc).unwrap(); + generic_check_dt_case_0(&time_provider, subsec_millis, datetime_utc); let time_provider_2: TimeProvider = datetime_utc.try_into().expect("conversion failed"); // Test the TryInto trait impl assert_eq!(time_provider, time_provider_2); } - #[test] fn test_creation_from_dt_u24_days() { let subsec_millis = 250; - let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) - .unwrap() - .and_hms_milli_opt(16, 49, 30, subsec_millis) - .unwrap(); - let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + let datetime_utc = generic_dt_case_0_no_prec(subsec_millis); let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc).unwrap(); - // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 - // Leap years need to be accounted for as well. - assert_eq!(time_provider.ccsds_days, 23754); - assert_eq!( - time_provider.ms_of_day, - 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis - ); - assert_eq!(time_provider.date_time().unwrap(), datetime_utc); + generic_check_dt_case_0(&time_provider, subsec_millis, datetime_utc); let time_provider_2: TimeProvider = datetime_utc.try_into().expect("conversion failed"); // Test the TryInto trait impl assert_eq!(time_provider, time_provider_2); } - #[test] - fn test_creation_from_dt_us_prec() { + fn generic_dt_case_1_us_prec(subsec_millis: u32) -> DateTime { // 250 ms + 500 us - let subsec_millis = 250; let subsec_micros = subsec_millis * 1000 + 500; let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) .unwrap() .and_hms_micro_opt(16, 49, 30, subsec_micros) .unwrap(); - let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - let time_provider = - TimeProvider::from_dt_with_u16_days_us_precision(&datetime_utc).unwrap(); + DateTime::::from_utc(naivedatetime_utc, Utc) + } + + fn generic_check_dt_case_1_us_prec( + time_provider: &TimeProvider, + subsec_millis: u32, + datetime_utc: DateTime, + ) { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. - assert_eq!(time_provider.ccsds_days, 23754); + assert_eq!(time_provider.ccsds_days, 23754.into()); assert_eq!( time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis @@ -1572,21 +1664,46 @@ mod tests { } #[test] - fn test_creation_from_dt_ps_prec() { - // 250 ms + 500 us + fn test_creation_from_dt_u16_days_us_prec() { let subsec_millis = 250; + let datetime_utc = generic_dt_case_1_us_prec(subsec_millis); + let time_provider = + TimeProvider::from_dt_with_u16_days_us_precision(&datetime_utc).unwrap(); + generic_check_dt_case_1_us_prec(&time_provider, subsec_millis, datetime_utc); + } + + #[test] + fn test_creation_from_dt_u24_days_us_prec() { + let subsec_millis = 250; + let datetime_utc = generic_dt_case_1_us_prec(subsec_millis); + let time_provider = + TimeProvider::from_dt_with_u24_days_us_precision(&datetime_utc).unwrap(); + generic_check_dt_case_1_us_prec(&time_provider, subsec_millis, datetime_utc); + } + + fn generic_dt_case_2_ps_prec(subsec_millis: u32) -> (DateTime, u32) { + // 250 ms + 500 us let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000; let submilli_nanos = subsec_nanos % 10_u32.pow(6); let naivedatetime_utc = NaiveDate::from_ymd_opt(2023, 01, 14) .unwrap() .and_hms_nano_opt(16, 49, 30, subsec_nanos) .unwrap(); - let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - let time_provider = - TimeProvider::from_dt_with_u16_days_ps_precision(&datetime_utc).unwrap(); + ( + DateTime::::from_utc(naivedatetime_utc, Utc), + submilli_nanos, + ) + } + + fn generic_check_dt_case_2_ps_prec( + time_provider: &TimeProvider, + subsec_millis: u32, + submilli_nanos: u32, + datetime_utc: DateTime, + ) { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. - assert_eq!(time_provider.ccsds_days, 23754); + assert_eq!(time_provider.ccsds_days, 23754.into()); assert_eq!( time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis @@ -1601,6 +1718,34 @@ mod tests { assert_eq!(time_provider.date_time().unwrap(), datetime_utc); } + #[test] + fn test_creation_from_dt_u16_days_ps_prec() { + let subsec_millis = 250; + let (datetime_utc, submilli_nanos) = generic_dt_case_2_ps_prec(subsec_millis); + let time_provider = + TimeProvider::from_dt_with_u16_days_ps_precision(&datetime_utc).unwrap(); + generic_check_dt_case_2_ps_prec( + &time_provider, + subsec_millis, + submilli_nanos, + datetime_utc, + ); + } + + #[test] + fn test_creation_from_dt_u24_days_ps_prec() { + let subsec_millis = 250; + let (datetime_utc, submilli_nanos) = generic_dt_case_2_ps_prec(subsec_millis); + let time_provider = + TimeProvider::from_dt_with_u24_days_ps_precision(&datetime_utc).unwrap(); + generic_check_dt_case_2_ps_prec( + &time_provider, + subsec_millis, + submilli_nanos, + datetime_utc, + ); + } + #[test] fn test_creation_from_unix_stamp_0() { let unix_secs = 0; @@ -1766,6 +1911,21 @@ mod tests { } } + #[test] + fn test_new_u24_days_too_large() { + let time_provider = TimeProvider::new_with_u24_days(2_u32.pow(24), 0); + assert!(time_provider.is_err()); + let e = time_provider.unwrap_err(); + if let CdsError::InvalidCcsdsDays(days) = e { + assert_eq!(days, 2_u32.pow(24) as i64); + } else { + panic!("unexpected error {}", e) + } + } + + #[test] + fn test_dt_u24_days() {} + #[test] #[cfg(feature = "serde")] fn test_serialization() { diff --git a/src/time/cuc.rs b/src/time/cuc.rs index 029a241..dd5c1bf 100644 --- a/src/time/cuc.rs +++ b/src/time/cuc.rs @@ -42,12 +42,14 @@ impl TryFrom for FractionalResolution { /// Please note that this function will panic if the fractional value is not smaller than /// the maximum number of fractions allowed for the particular resolution. /// (e.g. passing 270 when the resolution only allows 255 values). +#[inline] pub fn convert_fractional_part_to_ns(fractional_part: FractionalPart) -> u64 { let div = fractional_res_to_div(fractional_part.0); assert!(fractional_part.1 < div); 10_u64.pow(9) * fractional_part.1 as u64 / div as u64 } +#[inline(always)] pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 { 2_u32.pow(8 * res as u32) - 1 } @@ -351,6 +353,11 @@ impl TimeProviderCcsdsEpoch { pfield & 0b11 } + #[inline] + fn unix_seconds(&self) -> i64 { + ccsds_epoch_to_unix_epoch(self.counter.1 as u64) as i64 + } + /// This returns the length of the individual components of the CUC timestamp in addition /// to the total size. /// @@ -543,10 +550,28 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch { CcsdsTimeCodes::CucCcsdsEpoch } + fn unix_seconds(&self) -> i64 { + self.unix_seconds() + } + + fn subsecond_millis(&self) -> Option { + if let Some(fractions) = self.fractions { + if fractions.0 == FractionalResolution::Seconds { + return None; + } + // Rounding down here is the correct approach. + return Some((convert_fractional_part_to_ns(fractions) / 10_u32.pow(6) as u64) as u16); + } + None + } + /// Please note that this function only works as intended if the time counter resolution /// is one second. - fn unix_seconds(&self) -> i64 { - ccsds_epoch_to_unix_epoch(self.counter.1 as u64) as i64 + fn unix_stamp(&self) -> UnixTimeStamp { + UnixTimeStamp { + unix_seconds: self.unix_seconds(), + subsecond_millis: None, + } } fn date_time(&self) -> Option> { diff --git a/src/time/mod.rs b/src/time/mod.rs index 4a7e7fc..34fa08f 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -204,6 +204,9 @@ pub trait TimeReader { } /// Trait for generic CCSDS time providers. +/// +/// The UNIX helper methods and the [date_time] method are not strictly necessary but extremely +/// practical because they are a very common and simple exchange format for time information. pub trait CcsdsTimeProvider { fn len_as_bytes(&self) -> usize; @@ -213,10 +216,73 @@ pub trait CcsdsTimeProvider { /// in big endian format. fn p_field(&self) -> (usize, [u8; 2]); fn ccdsd_time_code(&self) -> CcsdsTimeCodes; + fn unix_seconds(&self) -> i64; + fn subsecond_millis(&self) -> Option; + fn unix_stamp(&self) -> UnixTimeStamp { + UnixTimeStamp { + unix_seconds: self.unix_seconds(), + subsecond_millis: self.subsecond_millis(), + } + } + fn date_time(&self) -> Option>; } +/// UNIX timestamp: Elapsed seconds since 01-01-1970 00:00:00. +/// +/// Also can optionally include subsecond millisecond for greater accuracy. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct UnixTimeStamp { + pub unix_seconds: i64, + subsecond_millis: Option, +} + +impl UnixTimeStamp { + pub fn new(unix_seconds: i64) -> Self { + Self { + unix_seconds, + subsecond_millis: None, + } + } + + /// Returns none if the subsecond millisecond value is larger than 999. + pub fn new_with_subsecond_millis(unix_seconds: i64, subsec_millis: u16) -> Option { + if subsec_millis > 999 { + return None; + } + Some(Self { + unix_seconds, + subsecond_millis: Some(subsec_millis), + }) + } + + pub fn subsecond_millis(&self) -> Option { + self.subsecond_millis + } + + #[cfg(feature = "std")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] + pub fn from_now() -> Result { + let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; + let epoch = now.as_secs(); + Ok(UnixTimeStamp { + unix_seconds: epoch as i64, + subsecond_millis: Some(now.subsec_millis() as u16), + }) + } + + #[inline] + pub fn unix_seconds_f64(&self) -> f64 { + let mut secs = self.unix_seconds as f64; + if let Some(subsec_millis) = self.subsecond_millis { + secs += subsec_millis as f64 / 1000.0; + } + secs + } +} + #[cfg(all(test, feature = "std"))] mod tests { use super::*; @@ -245,4 +311,24 @@ mod tests { let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64; assert_eq!(days_diff, -DAYS_CCSDS_TO_UNIX as u64); } + + #[test] + fn basic_unix_stamp_test() { + let stamp = UnixTimeStamp::new(-200); + assert_eq!(stamp.unix_seconds, -200); + assert!(stamp.subsecond_millis().is_none()); + let stamp = UnixTimeStamp::new(250); + assert_eq!(stamp.unix_seconds, 250); + assert!(stamp.subsecond_millis().is_none()); + } + + #[test] + fn basic_float_unix_stamp_test() { + let stamp = UnixTimeStamp::new_with_subsecond_millis(500, 600).unwrap(); + assert!(stamp.subsecond_millis.is_some()); + assert_eq!(stamp.unix_seconds, 500); + let subsec_millis = stamp.subsecond_millis().unwrap(); + assert_eq!(subsec_millis, 600); + assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001); + } } From 51e134f031f0a2159f34552dd9f3a83bfbb4f4e4 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 18:24:49 +0100 Subject: [PATCH 19/31] bugfixes for precision handling for additions --- src/time/cds.rs | 65 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index b92ba54..3e00e55 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -922,7 +922,6 @@ fn add_for_max_ccsds_days_val( ) -> (u32, u32, Option) { let mut next_ccsds_days = time_provider.ccsds_days_as_u32(); let mut next_ms_of_day = time_provider.ms_of_day; - let mut precision = None; // Increment CCSDS days by a certain amount while also accounting for overflow. let increment_days = |ccsds_days: &mut u32, days_inc: u32| { let days_addition: u64 = *ccsds_days as u64 + days_inc as u64; @@ -942,9 +941,8 @@ fn add_for_max_ccsds_days_val( increment_days(ccsds_days, 1); } }; - if let Some(submillis_prec) = time_provider.submillis_precision { + let precision = if let Some(submillis_prec) = time_provider.submillis_precision { match submillis_prec { - SubmillisPrecision::Absent => {} SubmillisPrecision::Microseconds(mut us) => { let micros = duration.subsec_micros(); let submilli_micros = (micros % 1000) as u16; @@ -952,22 +950,28 @@ fn add_for_max_ccsds_days_val( if us >= 1000 { let carryover_us = us - 1000; increment_ms_of_day(&mut next_ms_of_day, 1, &mut next_ccsds_days); - precision = Some(SubmillisPrecision::Microseconds(carryover_us)); + us = carryover_us; } + Some(SubmillisPrecision::Microseconds(us)) } SubmillisPrecision::Picoseconds(mut ps) => { let nanos = duration.subsec_nanos(); let submilli_nanos = nanos % 10_u32.pow(6); + // No overflow risk: The maximum value of an u32 is ~4.294e9, and one ms as ps + // is 1e9. The amount ps can now have is always less than 2e9. ps += submilli_nanos * 1000; - if ps >= 10_u32.pow(6) { + if ps >= 10_u32.pow(9) { let carry_over_ps = ps - 10_u32.pow(6); increment_ms_of_day(&mut next_ms_of_day, 1, &mut next_ccsds_days); - precision = Some(SubmillisPrecision::Picoseconds(carry_over_ps)) + ps = carry_over_ps; } + Some(SubmillisPrecision::Picoseconds(ps)) } - SubmillisPrecision::Reserved => {} + _ => None, } - } + } else { + None + }; let full_seconds = duration.as_secs(); let secs_of_day = (full_seconds % SECONDS_PER_DAY as u64) as u32; let ms_of_day = secs_of_day * 1000; @@ -1747,7 +1751,7 @@ mod tests { } #[test] - fn test_creation_from_unix_stamp_0() { + fn test_creation_from_unix_stamp_0_u16_days() { let unix_secs = 0; let subsec_millis = 0; let time_provider = TimeProvider::from_unix_secs_with_u16_days(unix_secs, subsec_millis) @@ -1755,6 +1759,15 @@ mod tests { assert_eq!(time_provider.ccsds_days, -DAYS_CCSDS_TO_UNIX as u16) } + #[test] + fn test_creation_from_unix_stamp_0_u24_days() { + let unix_secs = 0; + let subsec_millis = 0; + let time_provider = TimeProvider::from_unix_secs_with_u24_days(unix_secs, subsec_millis) + .expect("creating provider from unix stamp failed"); + assert_eq!(time_provider.ccsds_days, (-DAYS_CCSDS_TO_UNIX) as u32) + } + #[test] fn test_creation_from_unix_stamp_1() { let subsec_millis = 250; @@ -1889,6 +1902,37 @@ mod tests { ); } + #[test] + fn test_addition_with_us_precision_u16_days() { + let mut provider = TimeProvider::new_with_u16_days(0, 0); + provider.set_submillis_precision(SubmillisPrecision::Microseconds(0)); + let duration = Duration::from_micros(500); + provider += duration; + assert!(provider.submillis_precision().is_some()); + let prec = provider.submillis_precision().unwrap(); + if let SubmillisPrecision::Microseconds(us) = prec { + assert_eq!(us, 500); + } else { + panic!("invalid precision {:?}", prec) + } + } + + #[test] + fn test_addition_with_ps_precision_u16_days() { + let mut provider = TimeProvider::new_with_u16_days(0, 0); + provider.set_submillis_precision(SubmillisPrecision::Picoseconds(0)); + // 500 us as ns + let duration = Duration::from_nanos(500 * 10u32.pow(3) as u64); + provider += duration; + assert!(provider.submillis_precision().is_some()); + let prec = provider.submillis_precision().unwrap(); + if let SubmillisPrecision::Picoseconds(ps) = prec { + assert_eq!(ps, 500 * 10_u32.pow(6)); + } else { + panic!("invalid precision {:?}", prec) + } + } + #[test] fn test_dyn_creation_u16_days_with_precision() { let mut stamp = TimeProvider::new_with_u16_days(24, 24); @@ -1923,9 +1967,6 @@ mod tests { } } - #[test] - fn test_dt_u24_days() {} - #[test] #[cfg(feature = "serde")] fn test_serialization() { From 73dbc80cad66bbf8767ea2fc3aa09a334868ffc3 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 18:26:18 +0100 Subject: [PATCH 20/31] small tweak --- src/time/cds.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 3e00e55..8f37501 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -955,8 +955,8 @@ fn add_for_max_ccsds_days_val( Some(SubmillisPrecision::Microseconds(us)) } SubmillisPrecision::Picoseconds(mut ps) => { - let nanos = duration.subsec_nanos(); - let submilli_nanos = nanos % 10_u32.pow(6); + // 1 ms as ns is 1e6. + let submilli_nanos = duration.subsec_nanos() % 10_u32.pow(6); // No overflow risk: The maximum value of an u32 is ~4.294e9, and one ms as ps // is 1e9. The amount ps can now have is always less than 2e9. ps += submilli_nanos * 1000; From b2def8cd36d91a16538222ae92653ecc53665bdb Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 20:26:26 +0100 Subject: [PATCH 21/31] added one more test --- src/time/cds.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/time/cds.rs b/src/time/cds.rs index 8f37501..1161a65 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -1967,6 +1967,21 @@ mod tests { } } + #[test] + fn test_from_dt_invalid_time() { + // Date before CCSDS epoch + let naivedatetime_utc = NaiveDate::from_ymd_opt(1957, 12, 31) + .unwrap() + .and_hms_milli_opt(23, 59, 59, 999) + .unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc); + assert!(time_provider.is_err()); + if let TimestampError::DateBeforeCcsdsEpoch(dt) = time_provider.unwrap_err() { + assert_eq!(dt, datetime_utc); + } + } + #[test] #[cfg(feature = "serde")] fn test_serialization() { From ed850b1df4c952393016a6614cb64f2e16a877b0 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 20:28:35 +0100 Subject: [PATCH 22/31] use core include for AddAssign --- src/time/cds.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 1161a65..f6401a9 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -11,10 +11,9 @@ use alloc::boxed::Box; use chrono::Datelike; use core::any::Any; use core::fmt::Debug; -use core::ops::Add; +use core::ops::{Add, AddAssign}; use core::time::Duration; use delegate::delegate; -use std::ops::AddAssign; /// Base value for the preamble field for a time field parser to determine the time field type. pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::Cds as u8) << 4; From f1f9f695e536bdccf70659a9b730689ca29c8583 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 20:29:43 +0100 Subject: [PATCH 23/31] put include behind feature gate --- src/time/cds.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index f6401a9..dbf7b6e 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -8,8 +8,9 @@ use super::*; use crate::private::Sealed; #[cfg(feature = "alloc")] use alloc::boxed::Box; -use chrono::Datelike; +#[cfg(feature = "alloc")] use core::any::Any; +use chrono::Datelike; use core::fmt::Debug; use core::ops::{Add, AddAssign}; use core::time::Duration; From 49f3497ca856ba453bcbb495a1fd8a1ab4c867fa Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 20:29:58 +0100 Subject: [PATCH 24/31] cargo fmt --- src/time/cds.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index dbf7b6e..8e9bdad 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -8,9 +8,9 @@ use super::*; use crate::private::Sealed; #[cfg(feature = "alloc")] use alloc::boxed::Box; +use chrono::Datelike; #[cfg(feature = "alloc")] use core::any::Any; -use chrono::Datelike; use core::fmt::Debug; use core::ops::{Add, AddAssign}; use core::time::Duration; From 6f795690fd5760ae761fd1f9676c901ff0bb2729 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 20:48:39 +0100 Subject: [PATCH 25/31] start updating changelog --- CHANGELOG.md | 16 ++++++++++++++++ src/time/cds.rs | 4 ++-- src/time/cuc.rs | 9 --------- src/time/mod.rs | 17 +++++++++-------- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 968fb6c..f02d741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - CDS timestamp: Added constructor function to create the time provider from `chrono::DateTime` and a generic UNIX timestamp (`i64` seconds and subsecond milliseconds). +- New `UnixTimeStamp` abstraction which contains the unix seconds as an `i64` + and an optional subsecond millisecond counter (`u16`) +- `MAX_DAYS_24_BITS` which contains maximum value which can be supplied + to the days field of a CDS time provider with 24 bits days field width. + +## Changed + +- `CcsdsTimeProvider` trait (breaking): + - Add new `unix_stamp` method returning the new `UnixTimeStamp` struct + - Add new `subsecond_millis` method returning counter `Option` + - Default impl for `unix_stamp` which re-uses `subsecond_millis` and + existing `unix_seconds` method +- `TimestampError` (breaking): Add `DateBeforeCcsdsEpoch` error type + because new CDS API allow supplying invalid date times before CCSDS epoch. + Make `TimestampError` with `#[non_exhaustive]` attribute to prevent + future breakages if new error variants are added # [v0.4.2] 14.01.2023 diff --git a/src/time/cds.rs b/src/time/cds.rs index 8e9bdad..1433da2 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -167,7 +167,7 @@ pub struct TimeProvider { submillis_precision: Option, /// This is not strictly necessary but still cached because it significantly simplifies the /// calculation of [`DateTime`]. - unix_stamp: UnixTimeStamp, + unix_stamp: UnixTimestamp, } /// Common properties for all CDS time providers. @@ -1096,7 +1096,7 @@ impl CcsdsTimeProvider for TimeProvider UnixTimeStamp { + fn unix_stamp(&self) -> UnixTimestamp { self.unix_stamp } diff --git a/src/time/cuc.rs b/src/time/cuc.rs index dd5c1bf..21b24a7 100644 --- a/src/time/cuc.rs +++ b/src/time/cuc.rs @@ -565,15 +565,6 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch { None } - /// Please note that this function only works as intended if the time counter resolution - /// is one second. - fn unix_stamp(&self) -> UnixTimeStamp { - UnixTimeStamp { - unix_seconds: self.unix_seconds(), - subsecond_millis: None, - } - } - fn date_time(&self) -> Option> { let unix_seconds = self.unix_seconds(); let ns = if let Some(fractional_part) = self.fractions { diff --git a/src/time/mod.rs b/src/time/mod.rs index 34fa08f..9af0cf5 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -56,6 +56,7 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result { #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[non_exhaustive] pub enum TimestampError { /// Contains tuple where first value is the expected time code and the second /// value is the found raw value @@ -219,8 +220,8 @@ pub trait CcsdsTimeProvider { fn unix_seconds(&self) -> i64; fn subsecond_millis(&self) -> Option; - fn unix_stamp(&self) -> UnixTimeStamp { - UnixTimeStamp { + fn unix_stamp(&self) -> UnixTimestamp { + UnixTimestamp { unix_seconds: self.unix_seconds(), subsecond_millis: self.subsecond_millis(), } @@ -234,12 +235,12 @@ pub trait CcsdsTimeProvider { /// Also can optionally include subsecond millisecond for greater accuracy. #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct UnixTimeStamp { +pub struct UnixTimestamp { pub unix_seconds: i64, subsecond_millis: Option, } -impl UnixTimeStamp { +impl UnixTimestamp { pub fn new(unix_seconds: i64) -> Self { Self { unix_seconds, @@ -267,7 +268,7 @@ impl UnixTimeStamp { pub fn from_now() -> Result { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let epoch = now.as_secs(); - Ok(UnixTimeStamp { + Ok(UnixTimestamp { unix_seconds: epoch as i64, subsecond_millis: Some(now.subsec_millis() as u16), }) @@ -314,17 +315,17 @@ mod tests { #[test] fn basic_unix_stamp_test() { - let stamp = UnixTimeStamp::new(-200); + let stamp = UnixTimestamp::new(-200); assert_eq!(stamp.unix_seconds, -200); assert!(stamp.subsecond_millis().is_none()); - let stamp = UnixTimeStamp::new(250); + let stamp = UnixTimestamp::new(250); assert_eq!(stamp.unix_seconds, 250); assert!(stamp.subsecond_millis().is_none()); } #[test] fn basic_float_unix_stamp_test() { - let stamp = UnixTimeStamp::new_with_subsecond_millis(500, 600).unwrap(); + let stamp = UnixTimestamp::new_with_subsecond_millis(500, 600).unwrap(); assert!(stamp.subsecond_millis.is_some()); assert_eq!(stamp.unix_seconds, 500); let subsec_millis = stamp.subsecond_millis().unwrap(); From 973c54e0deb0edb6e6b536f52ec749b460d2960a Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 21:12:54 +0100 Subject: [PATCH 26/31] extend changelog --- CHANGELOG.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f02d741..c7a943c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Added -- CDS timestamp: Added constructor function to create the time provider +- New `UnixTimestamp` abstraction which contains the unix seconds as an `i64` + and an optional subsecond millisecond counter (`u16`) + +### CDS time module + +- Added constructor function to create the time provider from `chrono::DateTime` and a generic UNIX timestamp (`i64` seconds and subsecond milliseconds). -- New `UnixTimeStamp` abstraction which contains the unix seconds as an `i64` - and an optional subsecond millisecond counter (`u16`) - `MAX_DAYS_24_BITS` which contains maximum value which can be supplied to the days field of a CDS time provider with 24 bits days field width. - +- New `CdsTimestamp` trait which encapsulates common fields for all CDS time providers +- `get_dyn_time_provider_from_bytes`: Requires `alloc` support and returns + the correct `TimeProvider` instance wrapped as a boxed trait object + `Box` by checking the length of days field. +- `from_unix_secs_with_u24_days` and `from_unix_secs_with_u16_days` ## Changed - `CcsdsTimeProvider` trait (breaking): From 2f51420a2960f928cfb9373c25bc32b24ce9e5ce Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 21:13:48 +0100 Subject: [PATCH 27/31] consistency renaming --- src/time/cds.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 1433da2..bbf20ba 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -180,7 +180,7 @@ pub trait CdsCommon { } /// Generic properties for all CDS time providers. -pub trait CdsTimeStamp: CdsCommon { +pub trait CdsTimestamp: CdsCommon { fn len_of_day_seg(&self) -> LengthOfDaySegment; } @@ -388,7 +388,7 @@ impl CdsConverter for ConversionFromNow { } #[cfg(feature = "alloc")] -pub trait DynCdsTimeProvider: CcsdsTimeProvider + CdsTimeStamp + TimeWriter + Any {} +pub trait DynCdsTimeProvider: CcsdsTimeProvider + CdsTimestamp + TimeWriter + Any {} #[cfg(feature = "alloc")] impl DynCdsTimeProvider for TimeProvider {} #[cfg(feature = "alloc")] @@ -983,13 +983,13 @@ fn add_for_max_ccsds_days_val( (next_ccsds_days, next_ms_of_day, precision) } -impl CdsTimeStamp for TimeProvider { +impl CdsTimestamp for TimeProvider { fn len_of_day_seg(&self) -> LengthOfDaySegment { LengthOfDaySegment::Short16Bits } } -impl CdsTimeStamp for TimeProvider { +impl CdsTimestamp for TimeProvider { fn len_of_day_seg(&self) -> LengthOfDaySegment { LengthOfDaySegment::Long24Bits } From 708b68a5cbc243caa4eaf0687563ec8be7900086 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 21:30:31 +0100 Subject: [PATCH 28/31] use new struct in API --- src/time/cds.rs | 54 +++++++++++++++++++++++++++++++------------------ src/time/mod.rs | 41 +++++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index bbf20ba..3c250eb 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -638,11 +638,13 @@ impl TimeProvider { } fn from_unix_generic( - unix_seconds: i64, - subsec_millis: u32, + unix_stamp: &UnixTimestamp, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromUnix::new(unix_seconds, subsec_millis)?; + let conv_from_dt = ConversionFromUnix::new( + unix_stamp.unix_seconds, + unix_stamp.subsecond_millis.unwrap_or(0) as u32, + )?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -763,10 +765,9 @@ impl TimeProvider { /// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or /// the CCSDS days value exceeds the allowed bit width (24 bits). pub fn from_unix_secs_with_u24_days( - unix_seconds: i64, - subsec_millis: u32, + unix_stamp: &UnixTimestamp, ) -> Result { - Self::from_unix_generic(unix_seconds, subsec_millis, LengthOfDaySegment::Long24Bits) + Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits) } /// Create a provider from a [`DateTime`] struct. @@ -849,10 +850,9 @@ impl TimeProvider { /// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or /// the CCSDS days value exceeds the allowed bit width (24 bits). pub fn from_unix_secs_with_u16_days( - unix_seconds: i64, - subsec_millis: u32, + unix_stamp: &UnixTimestamp, ) -> Result { - Self::from_unix_generic(unix_seconds, subsec_millis, LengthOfDaySegment::Short16Bits) + Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits) } /// Create a provider from a [`DateTime`] struct. @@ -1754,8 +1754,11 @@ mod tests { fn test_creation_from_unix_stamp_0_u16_days() { let unix_secs = 0; let subsec_millis = 0; - let time_provider = TimeProvider::from_unix_secs_with_u16_days(unix_secs, subsec_millis) - .expect("creating provider from unix stamp failed"); + let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( + unix_secs, + subsec_millis, + )) + .expect("creating provider from unix stamp failed"); assert_eq!(time_provider.ccsds_days, -DAYS_CCSDS_TO_UNIX as u16) } @@ -1763,8 +1766,11 @@ mod tests { fn test_creation_from_unix_stamp_0_u24_days() { let unix_secs = 0; let subsec_millis = 0; - let time_provider = TimeProvider::from_unix_secs_with_u24_days(unix_secs, subsec_millis) - .expect("creating provider from unix stamp failed"); + let time_provider = TimeProvider::from_unix_secs_with_u24_days(&UnixTimestamp::const_new( + unix_secs, + subsec_millis, + )) + .expect("creating provider from unix stamp failed"); assert_eq!(time_provider.ccsds_days, (-DAYS_CCSDS_TO_UNIX) as u32) } @@ -1776,9 +1782,8 @@ mod tests { .and_hms_milli_opt(16, 49, 30, subsec_millis) .unwrap(); let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); - let time_provider = - TimeProvider::from_unix_secs_with_u16_days(datetime_utc.timestamp(), subsec_millis) - .expect("creating provider from unix stamp failed"); + let time_provider = TimeProvider::from_unix_secs_with_u16_days(&datetime_utc.into()) + .expect("creating provider from unix stamp failed"); // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. assert_eq!(time_provider.ccsds_days, 23754); @@ -1794,8 +1799,11 @@ mod tests { fn test_creation_0_ccsds_days() { let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64; let subsec_millis = 0; - let time_provider = TimeProvider::from_unix_secs_with_u16_days(unix_secs, subsec_millis) - .expect("creating provider from unix stamp failed"); + let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( + unix_secs, + subsec_millis, + )) + .expect("creating provider from unix stamp failed"); assert_eq!(time_provider.ccsds_days, 0) } @@ -1803,7 +1811,10 @@ mod tests { fn test_invalid_creation_from_unix_stamp_days_too_large() { let invalid_unix_secs: i64 = (u16::MAX as i64 + 1) * SECONDS_PER_DAY as i64; let subsec_millis = 0; - match TimeProvider::from_unix_secs_with_u16_days(invalid_unix_secs as i64, subsec_millis) { + match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( + invalid_unix_secs as i64, + subsec_millis, + )) { Ok(_) => { panic!("creation should not succeed") } @@ -1826,7 +1837,10 @@ mod tests { // precisely 31-12-1957 23:59:55 let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5; let subsec_millis = 0; - match TimeProvider::from_unix_secs_with_u16_days(unix_secs as i64, subsec_millis) { + match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( + unix_secs as i64, + subsec_millis, + )) { Ok(_) => { panic!("creation should not succeed") } diff --git a/src/time/mod.rs b/src/time/mod.rs index 9af0cf5..2fadb96 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -241,15 +241,8 @@ pub struct UnixTimestamp { } impl UnixTimestamp { - pub fn new(unix_seconds: i64) -> Self { - Self { - unix_seconds, - subsecond_millis: None, - } - } - /// Returns none if the subsecond millisecond value is larger than 999. - pub fn new_with_subsecond_millis(unix_seconds: i64, subsec_millis: u16) -> Option { + pub fn new(unix_seconds: i64, subsec_millis: u16) -> Option { if subsec_millis > 999 { return None; } @@ -259,6 +252,23 @@ impl UnixTimestamp { }) } + pub const fn const_new(unix_seconds: i64, subsec_millis: u16) -> Self { + if subsec_millis > 999 { + panic!("subsec milliseconds exceeds 999"); + } + Self { + unix_seconds, + subsecond_millis: Some(subsec_millis), + } + } + + pub fn new_only_seconds(unix_seconds: i64) -> Self { + Self { + unix_seconds, + subsecond_millis: None, + } + } + pub fn subsecond_millis(&self) -> Option { self.subsecond_millis } @@ -284,6 +294,15 @@ impl UnixTimestamp { } } +impl From> for UnixTimestamp { + fn from(value: DateTime) -> Self { + Self { + unix_seconds: value.timestamp(), + subsecond_millis: Some(value.timestamp_subsec_millis() as u16), + } + } +} + #[cfg(all(test, feature = "std"))] mod tests { use super::*; @@ -315,17 +334,17 @@ mod tests { #[test] fn basic_unix_stamp_test() { - let stamp = UnixTimestamp::new(-200); + let stamp = UnixTimestamp::new_only_seconds(-200); assert_eq!(stamp.unix_seconds, -200); assert!(stamp.subsecond_millis().is_none()); - let stamp = UnixTimestamp::new(250); + let stamp = UnixTimestamp::new_only_seconds(250); assert_eq!(stamp.unix_seconds, 250); assert!(stamp.subsecond_millis().is_none()); } #[test] fn basic_float_unix_stamp_test() { - let stamp = UnixTimestamp::new_with_subsecond_millis(500, 600).unwrap(); + let stamp = UnixTimestamp::new(500, 600).unwrap(); assert!(stamp.subsecond_millis.is_some()); assert_eq!(stamp.unix_seconds, 500); let subsec_millis = stamp.subsecond_millis().unwrap(); From c8d442690bdebd7f5ee291a4351d0d217eb6bc25 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 21:47:35 +0100 Subject: [PATCH 29/31] changelog should be complete --- CHANGELOG.md | 26 ++++++++++++++++++++------ src/time/mod.rs | 7 +++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7a943c..6a753dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - New `UnixTimestamp` abstraction which contains the unix seconds as an `i64` and an optional subsecond millisecond counter (`u16`) +- `MS_PER_DAY` constant. ### CDS time module @@ -20,22 +21,35 @@ and this project adheres to [Semantic Versioning](http://semver.org/). and subsecond milliseconds). - `MAX_DAYS_24_BITS` which contains maximum value which can be supplied to the days field of a CDS time provider with 24 bits days field width. -- New `CdsTimestamp` trait which encapsulates common fields for all CDS time providers +- New `CdsTimestamp` trait which encapsulates common fields for all CDS time providers. - `get_dyn_time_provider_from_bytes`: Requires `alloc` support and returns the correct `TimeProvider` instance wrapped as a boxed trait object `Box` by checking the length of days field. -- `from_unix_secs_with_u24_days` and `from_unix_secs_with_u16_days` +- `from_unix_secs_with_u24_days` and `from_unix_secs_with_u16_days` which create + the time provider from a `UnixTimestamp` reference. +- `from_dt_with_u16_days`, `from_dt_with_u24_days` and their `..._us_precision` and + `..._ps_precision` variants which allow to create time providers from + a `chrono::DateTime`. +- Add `from_bytes_with_u24_days` and `from_bytes_with_u16_days` associated methods +- Implement `Add` and `AddAssign` for time providers, which allows + easily adding offsets to the providers. +- Implement `TryFrom>` for time providers. + ## Changed +- (breaking): Renamed `from_now_with_u24_days_and_us_prec` to `from_now_with_u24_days_us_precision`. + Also did the same for the `u16` variant. +- (breaking): Renamed `from_now_with_u24_days_and_ps_prec` to `from_now_with_u24_days_ps_precision`. + Also did the same for the `u16` variant. - `CcsdsTimeProvider` trait (breaking): - - Add new `unix_stamp` method returning the new `UnixTimeStamp` struct - - Add new `subsecond_millis` method returning counter `Option` + - Add new `unix_stamp` method returning the new `UnixTimeStamp` struct. + - Add new `subsecond_millis` method returning counter `Option`. - Default impl for `unix_stamp` which re-uses `subsecond_millis` and - existing `unix_seconds` method + existing `unix_seconds` method. - `TimestampError` (breaking): Add `DateBeforeCcsdsEpoch` error type because new CDS API allow supplying invalid date times before CCSDS epoch. Make `TimestampError` with `#[non_exhaustive]` attribute to prevent - future breakages if new error variants are added + future breakages if new error variants are added. # [v0.4.2] 14.01.2023 diff --git a/src/time/mod.rs b/src/time/mod.rs index 2fadb96..b5dd984 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -292,6 +292,13 @@ impl UnixTimestamp { } secs } + + pub fn as_date_time(&self) -> LocalResult> { + Utc.timestamp_opt( + self.unix_seconds, + self.subsecond_millis.unwrap_or(0) as u32 * 10_u32.pow(6), + ) + } } impl From> for UnixTimestamp { From e3f8b4a23be053b92f2ae8ed848ef415dbe64f18 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 21:53:25 +0100 Subject: [PATCH 30/31] re-order changelog --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a753dd..f0e4913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,24 +16,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### CDS time module +- Implement `Add` and `AddAssign` for time providers, which allows + easily adding offsets to the providers. +- Implement `TryFrom>` for time providers. +- `get_dyn_time_provider_from_bytes`: Requires `alloc` support and returns + the correct `TimeProvider` instance wrapped as a boxed trait object + `Box` by checking the length of days field. - Added constructor function to create the time provider from `chrono::DateTime` and a generic UNIX timestamp (`i64` seconds and subsecond milliseconds). - `MAX_DAYS_24_BITS` which contains maximum value which can be supplied to the days field of a CDS time provider with 24 bits days field width. - New `CdsTimestamp` trait which encapsulates common fields for all CDS time providers. -- `get_dyn_time_provider_from_bytes`: Requires `alloc` support and returns - the correct `TimeProvider` instance wrapped as a boxed trait object - `Box` by checking the length of days field. - `from_unix_secs_with_u24_days` and `from_unix_secs_with_u16_days` which create the time provider from a `UnixTimestamp` reference. - `from_dt_with_u16_days`, `from_dt_with_u24_days` and their `..._us_precision` and `..._ps_precision` variants which allow to create time providers from a `chrono::DateTime`. - Add `from_bytes_with_u24_days` and `from_bytes_with_u16_days` associated methods -- Implement `Add` and `AddAssign` for time providers, which allows - easily adding offsets to the providers. -- Implement `TryFrom>` for time providers. ## Changed From 14a971f01c867fe28f1a017b7940f4cfb5974bad Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 15 Jan 2023 21:54:56 +0100 Subject: [PATCH 31/31] remove unnecessary docs --- src/time/cds.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/time/cds.rs b/src/time/cds.rs index 3c250eb..a8d21f2 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -395,11 +395,7 @@ impl DynCdsTimeProvider for TimeProvider {} impl DynCdsTimeProvider for TimeProvider {} /// This function returns the correct [TimeProvider] instance from a raw byte array -/// by checking the days of length field. It also checks the CCSDS time code for correctness. -/// -/// The time provider instance is returned as a [DynCdsTimeProvider] trait object. -/// This function returns the correct [TimeProvider] instance from a raw byte array -/// by checking the days of length field. It also checks the CCSDS time code for correctness. +/// by checking the length of days field. It also checks the CCSDS time code for correctness. /// /// # Example ///