time library support #70

Closed
muellerr wants to merge 2 commits from consider-time-support into main
4 changed files with 207 additions and 82 deletions
Showing only changes of commit de1dc16e60 - Show all commits

View File

@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
## Fixed
- CUC timestamp was fixed to include leap second corrections because it is based on the TAI
time reference. Changed the `TimeReader` trait to allow specifying leap seconds which might
be necessary for some timestamps to enable other API.
## Added ## Added
- `From<$EcssEnum$TY> from $TY` for the ECSS enum type definitions. - `From<$EcssEnum$TY> from $TY` for the ECSS enum type definitions.

View File

@ -1171,13 +1171,13 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CcsdsTimeProvider for TimeProvider<Pro
} }
impl TimeReader for TimeProvider<DaysLen16Bits> { impl TimeReader for TimeProvider<DaysLen16Bits> {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> { fn from_bytes_generic(buf: &[u8], _leap_seconds: Option<u32>) -> Result<Self, TimestampError> {
Self::from_bytes_with_u16_days(buf) Self::from_bytes_with_u16_days(buf)
} }
} }
impl TimeReader for TimeProvider<DaysLen24Bits> { impl TimeReader for TimeProvider<DaysLen24Bits> {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> { fn from_bytes_generic(buf: &[u8], _leap_seconds: Option<u32>) -> Result<Self, TimestampError> {
Self::from_bytes_with_u24_days(buf) Self::from_bytes_with_u24_days(buf)
} }
} }

View File

@ -103,6 +103,7 @@ pub enum CucError {
resolution: FractionalResolution, resolution: FractionalResolution,
value: u64, value: u64,
}, },
LeapSecondCorrectionError,
} }
impl Display for CucError { impl Display for CucError {
@ -120,6 +121,9 @@ impl Display for CucError {
"invalid cuc fractional part {value} for resolution {resolution:?}" "invalid cuc fractional part {value} for resolution {resolution:?}"
) )
} }
CucError::LeapSecondCorrectionError => {
write!(f, "error while correcting for leap seconds")
}
} }
} }
} }
@ -153,8 +157,10 @@ pub struct FractionalPart(FractionalResolution, u32);
/// use spacepackets::time::cuc::{FractionalResolution, TimeProviderCcsdsEpoch}; /// use spacepackets::time::cuc::{FractionalResolution, TimeProviderCcsdsEpoch};
/// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, TimeReader, CcsdsTimeProvider}; /// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, TimeReader, CcsdsTimeProvider};
/// ///
/// const LEAP_SECONDS: u32 = 37;
/// // Highest fractional resolution /// // Highest fractional resolution
/// let timestamp_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs).expect("creating cuc stamp failed"); /// let timestamp_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS)
/// .expect("creating cuc stamp failed");
/// let mut raw_stamp = [0; 16]; /// let mut raw_stamp = [0; 16];
/// { /// {
/// let written = timestamp_now.write_to_bytes(&mut raw_stamp).expect("writing timestamp failed"); /// let written = timestamp_now.write_to_bytes(&mut raw_stamp).expect("writing timestamp failed");
@ -163,7 +169,7 @@ pub struct FractionalPart(FractionalResolution, u32);
/// assert_eq!(written, 8); /// assert_eq!(written, 8);
/// } /// }
/// { /// {
/// let read_result = TimeProviderCcsdsEpoch::from_bytes(&raw_stamp); /// let read_result = TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&raw_stamp, LEAP_SECONDS);
/// assert!(read_result.is_ok()); /// assert!(read_result.is_ok());
/// let stamp_deserialized = read_result.unwrap(); /// let stamp_deserialized = read_result.unwrap();
/// assert_eq!(stamp_deserialized, timestamp_now); /// assert_eq!(stamp_deserialized, timestamp_now);
@ -175,6 +181,7 @@ pub struct TimeProviderCcsdsEpoch {
pfield: u8, pfield: u8,
counter: WidthCounterPair, counter: WidthCounterPair,
fractions: Option<FractionalPart>, fractions: Option<FractionalPart>,
leap_seconds: u32,
} }
#[inline] #[inline]
@ -187,18 +194,26 @@ pub fn pfield_len(pfield: u8) -> usize {
impl TimeProviderCcsdsEpoch { impl TimeProviderCcsdsEpoch {
/// Create a time provider with a four byte counter and no fractional part. /// Create a time provider with a four byte counter and no fractional part.
pub fn new(counter: u32) -> Self { pub fn new(counter: u32, leap_seconds: u32) -> Self {
// These values are definitely valid, so it is okay to unwrap here. // These values are definitely valid, so it is okay to unwrap here.
Self::new_generic(WidthCounterPair(4, counter), None).unwrap() Self::new_generic(WidthCounterPair(4, counter), None, leap_seconds).unwrap()
} }
/// Like [TimeProviderCcsdsEpoch::new] but allow to supply a fractional part as well. /// Like [TimeProviderCcsdsEpoch::new] but allow to supply a fractional part as well.
pub fn new_with_fractions(counter: u32, fractions: FractionalPart) -> Result<Self, CucError> { pub fn new_with_fractions(
Self::new_generic(WidthCounterPair(4, counter), Some(fractions)) counter: u32,
fractions: FractionalPart,
leap_seconds: u32,
) -> Result<Self, CucError> {
Self::new_generic(WidthCounterPair(4, counter), Some(fractions), leap_seconds)
} }
/// Fractions with a resolution of ~ 4 ms /// Fractions with a resolution of ~ 4 ms
pub fn new_with_coarse_fractions(counter: u32, subsec_fractions: u8) -> Self { pub fn new_with_coarse_fractions(
counter: u32,
subsec_fractions: u8,
leap_seconds: u32,
) -> Self {
// These values are definitely valid, so it is okay to unwrap here. // These values are definitely valid, so it is okay to unwrap here.
Self::new_generic( Self::new_generic(
WidthCounterPair(4, counter), WidthCounterPair(4, counter),
@ -206,12 +221,17 @@ impl TimeProviderCcsdsEpoch {
FractionalResolution::FourMs, FractionalResolution::FourMs,
subsec_fractions as u32, subsec_fractions as u32,
)), )),
leap_seconds,
) )
.unwrap() .unwrap()
} }
/// Fractions with a resolution of ~ 16 us /// Fractions with a resolution of ~ 16 us
pub fn new_with_medium_fractions(counter: u32, subsec_fractions: u16) -> Self { pub fn new_with_medium_fractions(
counter: u32,
subsec_fractions: u16,
leap_seconds: u32,
) -> Self {
// These values are definitely valid, so it is okay to unwrap here. // These values are definitely valid, so it is okay to unwrap here.
Self::new_generic( Self::new_generic(
WidthCounterPair(4, counter), WidthCounterPair(4, counter),
@ -219,6 +239,7 @@ impl TimeProviderCcsdsEpoch {
FractionalResolution::FifteenUs, FractionalResolution::FifteenUs,
subsec_fractions as u32, subsec_fractions as u32,
)), )),
leap_seconds,
) )
.unwrap() .unwrap()
} }
@ -226,40 +247,63 @@ impl TimeProviderCcsdsEpoch {
/// Fractions with a resolution of ~ 60 ns. The fractional part value is limited by the /// Fractions with a resolution of ~ 60 ns. The fractional part value is limited by the
/// 24 bits of the fractional field, so this function will fail with /// 24 bits of the fractional field, so this function will fail with
/// [CucError::InvalidFractions] if the fractional value exceeds the value. /// [CucError::InvalidFractions] if the fractional value exceeds the value.
pub fn new_with_fine_fractions(counter: u32, subsec_fractions: u32) -> Result<Self, CucError> { pub fn new_with_fine_fractions(
counter: u32,
subsec_fractions: u32,
leap_seconds: u32,
) -> Result<Self, CucError> {
Self::new_generic( Self::new_generic(
WidthCounterPair(4, counter), WidthCounterPair(4, counter),
Some(FractionalPart( Some(FractionalPart(
FractionalResolution::SixtyNs, FractionalResolution::SixtyNs,
subsec_fractions, subsec_fractions,
)), )),
leap_seconds,
) )
} }
/// This function will return the current time as a CUC timestamp. /// This function will return the current time as a CUC timestamp.
/// The counter width will always be set to 4 bytes because the normal CCSDS epoch will overflow /// The counter width will always be set to 4 bytes because the normal CCSDS epoch will overflow
/// when using less than that. /// when using less than that.
///
/// The CUC timestamp uses TAI as the reference time system. Therefore, leap second corrections
/// must be applied on top of the UTC based time retrieved from the system in addition to the
/// conversion to the CCSDS epoch.
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn from_now(fraction_resolution: FractionalResolution) -> Result<Self, StdTimestampError> { pub fn from_now(
fraction_resolution: FractionalResolution,
leap_seconds: u32,
) -> Result<Self, StdTimestampError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64); let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64);
ccsds_epoch
.checked_add(i64::from(leap_seconds))
.ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?;
if fraction_resolution == FractionalResolution::Seconds { if fraction_resolution == FractionalResolution::Seconds {
return Ok(Self::new(ccsds_epoch as u32)); return Ok(Self::new(ccsds_epoch as u32, leap_seconds));
} }
let fractions = let fractions =
fractional_part_from_subsec_ns(fraction_resolution, now.subsec_nanos() as u64); fractional_part_from_subsec_ns(fraction_resolution, now.subsec_nanos() as u64);
Self::new_with_fractions(ccsds_epoch as u32, fractions.unwrap()) Self::new_with_fractions(ccsds_epoch as u32, fractions.unwrap(), leap_seconds)
.map_err(|e| StdTimestampError::Timestamp(e.into())) .map_err(|e| StdTimestampError::Timestamp(e.into()))
} }
/// Updates the current time stamp from the current time. The fractional field width remains /// Updates the current time stamp from the current time. The fractional field width remains
/// the same and will be updated accordingly. /// the same and will be updated accordingly.
///
/// The CUC timestamp uses TAI as the reference time system. Therefore, leap second corrections
/// must be applied on top of the UTC based time retrieved from the system in addition to the
/// conversion to the CCSDS epoch.
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(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 now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32; self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32;
self.counter
.1
.checked_add(self.leap_seconds)
.ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?;
if self.fractions.is_some() { if self.fractions.is_some() {
self.fractions = fractional_part_from_subsec_ns( self.fractions = fractional_part_from_subsec_ns(
self.fractions.unwrap().0, self.fractions.unwrap().0,
@ -272,14 +316,20 @@ impl TimeProviderCcsdsEpoch {
pub fn from_date_time( pub fn from_date_time(
dt: &DateTime<Utc>, dt: &DateTime<Utc>,
res: FractionalResolution, res: FractionalResolution,
leap_seconds: u32,
) -> Result<Self, TimestampError> { ) -> Result<Self, TimestampError> {
// Year before CCSDS epoch is invalid. // Year before CCSDS epoch is invalid.
if dt.year() < 1958 { if dt.year() < 1958 {
return Err(TimestampError::DateBeforeCcsdsEpoch(*dt)); return Err(TimestampError::DateBeforeCcsdsEpoch(*dt));
} }
let counter = dt
.timestamp()
.checked_add(i64::from(leap_seconds))
.ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?;
Self::new_generic( Self::new_generic(
WidthCounterPair(4, dt.timestamp() as u32), WidthCounterPair(4, counter as u32),
fractional_part_from_subsec_ns(res, dt.timestamp_subsec_nanos() as u64), fractional_part_from_subsec_ns(res, dt.timestamp_subsec_nanos() as u64),
leap_seconds,
) )
.map_err(|e| e.into()) .map_err(|e| e.into())
} }
@ -289,6 +339,7 @@ impl TimeProviderCcsdsEpoch {
pub fn from_unix_stamp( pub fn from_unix_stamp(
unix_stamp: &UnixTimestamp, unix_stamp: &UnixTimestamp,
res: FractionalResolution, res: FractionalResolution,
leap_seconds: u32,
) -> Result<Self, TimestampError> { ) -> Result<Self, TimestampError> {
let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds); let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds);
// Negative CCSDS epoch is invalid. // Negative CCSDS epoch is invalid.
@ -297,16 +348,24 @@ impl TimeProviderCcsdsEpoch {
unix_stamp.as_date_time().unwrap(), unix_stamp.as_date_time().unwrap(),
)); ));
} }
ccsds_epoch
.checked_add(i64::from(leap_seconds))
.ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?;
let fractions = fractional_part_from_subsec_ns( let fractions = fractional_part_from_subsec_ns(
res, res,
unix_stamp.subsecond_millis() as u64 * 10_u64.pow(6), unix_stamp.subsecond_millis() as u64 * 10_u64.pow(6),
); );
Self::new_generic(WidthCounterPair(4, ccsds_epoch as u32), fractions).map_err(|e| e.into()) Self::new_generic(
WidthCounterPair(4, ccsds_epoch as u32),
fractions,
leap_seconds,
)
.map_err(|e| e.into())
} }
pub fn new_u16_counter(counter: u16) -> Self { pub fn new_u16_counter(counter: u16, leap_seconds: u32) -> Self {
// These values are definitely valid, so it is okay to unwrap here. // These values are definitely valid, so it is okay to unwrap here.
Self::new_generic(WidthCounterPair(2, counter as u32), None).unwrap() Self::new_generic(WidthCounterPair(2, counter as u32), None, leap_seconds).unwrap()
} }
pub fn width_counter_pair(&self) -> WidthCounterPair { pub fn width_counter_pair(&self) -> WidthCounterPair {
@ -352,6 +411,7 @@ impl TimeProviderCcsdsEpoch {
pub fn new_generic( pub fn new_generic(
counter: WidthCounterPair, counter: WidthCounterPair,
fractions: Option<FractionalPart>, fractions: Option<FractionalPart>,
leap_seconds: u32,
) -> Result<Self, CucError> { ) -> Result<Self, CucError> {
Self::verify_counter_width(counter.0)?; Self::verify_counter_width(counter.0)?;
if counter.1 > (2u64.pow(counter.0 as u32 * 8) - 1) as u32 { if counter.1 > (2u64.pow(counter.0 as u32 * 8) - 1) as u32 {
@ -367,6 +427,7 @@ impl TimeProviderCcsdsEpoch {
pfield: Self::build_p_field(counter.0, fractions.map(|v| v.0)), pfield: Self::build_p_field(counter.0, fractions.map(|v| v.0)),
counter, counter,
fractions, fractions,
leap_seconds,
}) })
} }
@ -412,6 +473,8 @@ impl TimeProviderCcsdsEpoch {
#[inline] #[inline]
fn unix_seconds(&self) -> i64 { fn unix_seconds(&self) -> i64 {
ccsds_epoch_to_unix_epoch(self.counter.1 as i64) ccsds_epoch_to_unix_epoch(self.counter.1 as i64)
.checked_sub(self.leap_seconds as i64)
.unwrap()
} }
/// This returns the length of the individual components of the CUC timestamp in addition /// This returns the length of the individual components of the CUC timestamp in addition
@ -458,10 +521,7 @@ impl TimeProviderCcsdsEpoch {
} }
impl TimeReader for TimeProviderCcsdsEpoch { impl TimeReader for TimeProviderCcsdsEpoch {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> fn from_bytes_generic(buf: &[u8], leap_seconds: Option<u32>) -> Result<Self, TimestampError> {
where
Self: Sized,
{
if buf.len() < MIN_CUC_LEN { if buf.len() < MIN_CUC_LEN {
return Err(TimestampError::ByteConversion( return Err(TimestampError::ByteConversion(
ByteConversionError::FromSliceTooSmall { ByteConversionError::FromSliceTooSmall {
@ -470,6 +530,9 @@ impl TimeReader for TimeProviderCcsdsEpoch {
}, },
)); ));
} }
if leap_seconds.is_none() {
return Err(TimestampError::Cuc(CucError::LeapSecondCorrectionError));
}
match ccsds_time_code_from_p_field(buf[0]) { match ccsds_time_code_from_p_field(buf[0]) {
Ok(code) => { Ok(code) => {
if code != CcsdsTimeCodes::CucCcsdsEpoch { if code != CcsdsTimeCodes::CucCcsdsEpoch {
@ -536,7 +599,11 @@ impl TimeReader for TimeProviderCcsdsEpoch {
_ => panic!("unreachable match arm"), _ => panic!("unreachable match arm"),
} }
} }
let provider = Self::new_generic(WidthCounterPair(cntr_len, counter), fractions)?; let provider = Self::new_generic(
WidthCounterPair(cntr_len, counter),
fractions,
leap_seconds.unwrap(),
)?;
Ok(provider) Ok(provider)
} }
} }
@ -704,9 +771,10 @@ impl Add<Duration> for TimeProviderCcsdsEpoch {
get_provider_values_after_duration_addition(&self, duration); get_provider_values_after_duration_addition(&self, duration);
if let Some(fractional_part) = new_fractional_part { if let Some(fractional_part) = new_fractional_part {
// The generated fractional part should always be valid, so its okay to unwrap here. // The generated fractional part should always be valid, so its okay to unwrap here.
return Self::new_with_fractions(new_counter, fractional_part).unwrap(); return Self::new_with_fractions(new_counter, fractional_part, self.leap_seconds)
.unwrap();
} }
Self::new(new_counter) Self::new(new_counter, self.leap_seconds)
} }
} }
@ -718,9 +786,14 @@ impl Add<Duration> for &TimeProviderCcsdsEpoch {
get_provider_values_after_duration_addition(self, duration); get_provider_values_after_duration_addition(self, duration);
if let Some(fractional_part) = new_fractional_part { if let Some(fractional_part) = new_fractional_part {
// The generated fractional part should always be valid, so its okay to unwrap here. // The generated fractional part should always be valid, so its okay to unwrap here.
return Self::Output::new_with_fractions(new_counter, fractional_part).unwrap(); return Self::Output::new_with_fractions(
new_counter,
fractional_part,
self.leap_seconds,
)
.unwrap();
} }
Self::Output::new(new_counter) Self::Output::new(new_counter, self.leap_seconds)
} }
} }
@ -732,9 +805,12 @@ mod tests {
#[allow(unused_imports)] #[allow(unused_imports)]
use std::println; use std::println;
const LEAP_SECONDS: u32 = 37;
#[test] #[test]
fn test_basic_zero_epoch() { fn test_basic_zero_epoch() {
let zero_cuc = TimeProviderCcsdsEpoch::new(0); // Include leap second correction for this.
let zero_cuc = TimeProviderCcsdsEpoch::new(0, LEAP_SECONDS);
assert_eq!(zero_cuc.len_as_bytes(), 5); assert_eq!(zero_cuc.len_as_bytes(), 5);
assert_eq!(zero_cuc.width(), zero_cuc.width_counter_pair().0); assert_eq!(zero_cuc.width(), zero_cuc.width_counter_pair().0);
assert_eq!(zero_cuc.counter(), zero_cuc.width_counter_pair().1); assert_eq!(zero_cuc.counter(), zero_cuc.width_counter_pair().1);
@ -747,18 +823,22 @@ mod tests {
let dt = zero_cuc.date_time(); let dt = zero_cuc.date_time();
assert!(dt.is_some()); assert!(dt.is_some());
let dt = dt.unwrap(); let dt = dt.unwrap();
assert_eq!(dt.year(), 1958); assert_eq!(dt.year(), 1957);
assert_eq!(dt.month(), 1); assert_eq!(dt.month(), 12);
assert_eq!(dt.day(), 1); assert_eq!(dt.day(), 31);
assert_eq!(dt.hour(), 0); assert_eq!(dt.hour(), 23);
assert_eq!(dt.minute(), 0); assert_eq!(dt.minute(), 59);
assert_eq!(dt.second(), 0); assert_eq!(dt.second(), 23);
} }
#[test] #[test]
fn test_write_no_fractions() { fn test_write_no_fractions() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let zero_cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(4, 0x20102030), None); let zero_cuc = TimeProviderCcsdsEpoch::new_generic(
WidthCounterPair(4, 0x20102030),
None,
LEAP_SECONDS,
);
assert!(zero_cuc.is_ok()); assert!(zero_cuc.is_ok());
let zero_cuc = zero_cuc.unwrap(); let zero_cuc = zero_cuc.unwrap();
let res = zero_cuc.write_to_bytes(&mut buf); let res = zero_cuc.write_to_bytes(&mut buf);
@ -782,7 +862,7 @@ mod tests {
#[test] #[test]
fn test_datetime_now() { fn test_datetime_now() {
let now = Utc::now(); let now = Utc::now();
let cuc_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs); let cuc_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS);
assert!(cuc_now.is_ok()); assert!(cuc_now.is_ok());
let cuc_now = cuc_now.unwrap(); let cuc_now = cuc_now.unwrap();
let dt_opt = cuc_now.date_time(); let dt_opt = cuc_now.date_time();
@ -797,11 +877,16 @@ mod tests {
#[test] #[test]
fn test_read_no_fractions() { fn test_read_no_fractions() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let zero_cuc = let zero_cuc = TimeProviderCcsdsEpoch::new_generic(
TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(4, 0x20102030), None).unwrap(); WidthCounterPair(4, 0x20102030),
None,
LEAP_SECONDS,
)
.unwrap();
zero_cuc.write_to_bytes(&mut buf).unwrap(); zero_cuc.write_to_bytes(&mut buf).unwrap();
let cuc_read_back = let cuc_read_back =
TimeProviderCcsdsEpoch::from_bytes(&buf).expect("reading cuc timestamp failed"); TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf, LEAP_SECONDS)
.expect("reading cuc timestamp failed");
assert_eq!(cuc_read_back, zero_cuc); assert_eq!(cuc_read_back, zero_cuc);
assert_eq!(cuc_read_back.width_counter_pair().1, 0x20102030); assert_eq!(cuc_read_back.width_counter_pair().1, 0x20102030);
assert_eq!(cuc_read_back.width_fractions_pair(), None); assert_eq!(cuc_read_back.width_fractions_pair(), None);
@ -811,7 +896,8 @@ mod tests {
fn invalid_read_len() { fn invalid_read_len() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
for i in 0..2 { for i in 0..2 {
let res = TimeProviderCcsdsEpoch::from_bytes(&buf[0..i]); let res =
TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf[0..i], LEAP_SECONDS);
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();
if let TimestampError::ByteConversion(ByteConversionError::FromSliceTooSmall { if let TimestampError::ByteConversion(ByteConversionError::FromSliceTooSmall {
@ -823,10 +909,12 @@ mod tests {
assert_eq!(expected, 2); assert_eq!(expected, 2);
} }
} }
let large_stamp = TimeProviderCcsdsEpoch::new_with_fine_fractions(22, 300).unwrap(); let large_stamp =
TimeProviderCcsdsEpoch::new_with_fine_fractions(22, 300, LEAP_SECONDS).unwrap();
large_stamp.write_to_bytes(&mut buf).unwrap(); large_stamp.write_to_bytes(&mut buf).unwrap();
for i in 2..large_stamp.len_as_bytes() - 1 { for i in 2..large_stamp.len_as_bytes() - 1 {
let res = TimeProviderCcsdsEpoch::from_bytes(&buf[0..i]); let res =
TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf[0..i], LEAP_SECONDS);
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();
if let TimestampError::ByteConversion(ByteConversionError::FromSliceTooSmall { if let TimestampError::ByteConversion(ByteConversionError::FromSliceTooSmall {
@ -843,7 +931,7 @@ mod tests {
#[test] #[test]
fn write_and_read_tiny_stamp() { fn write_and_read_tiny_stamp() {
let mut buf = [0; 2]; let mut buf = [0; 2];
let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 200), None); let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 200), None, LEAP_SECONDS);
assert!(cuc.is_ok()); assert!(cuc.is_ok());
let cuc = cuc.unwrap(); let cuc = cuc.unwrap();
assert_eq!(cuc.len_as_bytes(), 2); assert_eq!(cuc.len_as_bytes(), 2);
@ -852,7 +940,8 @@ mod tests {
let written = res.unwrap(); let written = res.unwrap();
assert_eq!(written, 2); assert_eq!(written, 2);
assert_eq!(buf[1], 200); assert_eq!(buf[1], 200);
let cuc_read_back = TimeProviderCcsdsEpoch::from_bytes(&buf); let cuc_read_back =
TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf, LEAP_SECONDS);
assert!(cuc_read_back.is_ok()); assert!(cuc_read_back.is_ok());
let cuc_read_back = cuc_read_back.unwrap(); let cuc_read_back = cuc_read_back.unwrap();
assert_eq!(cuc_read_back, cuc); assert_eq!(cuc_read_back, cuc);
@ -861,7 +950,8 @@ mod tests {
#[test] #[test]
fn write_slightly_larger_stamp() { fn write_slightly_larger_stamp() {
let mut buf = [0; 4]; let mut buf = [0; 4];
let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(2, 40000), None); let cuc =
TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(2, 40000), None, LEAP_SECONDS);
assert!(cuc.is_ok()); assert!(cuc.is_ok());
let cuc = cuc.unwrap(); let cuc = cuc.unwrap();
assert_eq!(cuc.len_as_bytes(), 3); assert_eq!(cuc.len_as_bytes(), 3);
@ -870,7 +960,8 @@ mod tests {
let written = res.unwrap(); let written = res.unwrap();
assert_eq!(written, 3); assert_eq!(written, 3);
assert_eq!(u16::from_be_bytes(buf[1..3].try_into().unwrap()), 40000); assert_eq!(u16::from_be_bytes(buf[1..3].try_into().unwrap()), 40000);
let cuc_read_back = TimeProviderCcsdsEpoch::from_bytes(&buf); let cuc_read_back =
TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf, LEAP_SECONDS);
assert!(cuc_read_back.is_ok()); assert!(cuc_read_back.is_ok());
let cuc_read_back = cuc_read_back.unwrap(); let cuc_read_back = cuc_read_back.unwrap();
assert_eq!(cuc_read_back, cuc); assert_eq!(cuc_read_back, cuc);
@ -882,7 +973,11 @@ mod tests {
#[test] #[test]
fn write_read_three_byte_cntr_stamp() { fn write_read_three_byte_cntr_stamp() {
let mut buf = [0; 4]; let mut buf = [0; 4];
let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(3, 2_u32.pow(24) - 2), None); let cuc = TimeProviderCcsdsEpoch::new_generic(
WidthCounterPair(3, 2_u32.pow(24) - 2),
None,
LEAP_SECONDS,
);
assert!(cuc.is_ok()); assert!(cuc.is_ok());
let cuc = cuc.unwrap(); let cuc = cuc.unwrap();
assert_eq!(cuc.len_as_bytes(), 4); assert_eq!(cuc.len_as_bytes(), 4);
@ -893,7 +988,8 @@ mod tests {
let mut temp_buf = [0; 4]; let mut temp_buf = [0; 4];
temp_buf[1..4].copy_from_slice(&buf[1..4]); temp_buf[1..4].copy_from_slice(&buf[1..4]);
assert_eq!(u32::from_be_bytes(temp_buf), 2_u32.pow(24) - 2); assert_eq!(u32::from_be_bytes(temp_buf), 2_u32.pow(24) - 2);
let cuc_read_back = TimeProviderCcsdsEpoch::from_bytes(&buf); let cuc_read_back =
TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf, LEAP_SECONDS);
assert!(cuc_read_back.is_ok()); assert!(cuc_read_back.is_ok());
let cuc_read_back = cuc_read_back.unwrap(); let cuc_read_back = cuc_read_back.unwrap();
assert_eq!(cuc_read_back, cuc); assert_eq!(cuc_read_back, cuc);
@ -902,7 +998,7 @@ mod tests {
#[test] #[test]
fn test_write_invalid_buf() { fn test_write_invalid_buf() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let res = TimeProviderCcsdsEpoch::new_with_fine_fractions(0, 0); let res = TimeProviderCcsdsEpoch::new_with_fine_fractions(0, 0, LEAP_SECONDS);
let cuc = res.unwrap(); let cuc = res.unwrap();
for i in 0..cuc.len_as_bytes() - 1 { for i in 0..cuc.len_as_bytes() - 1 {
let err = cuc.write_to_bytes(&mut buf[0..i]); let err = cuc.write_to_bytes(&mut buf[0..i]);
@ -924,7 +1020,7 @@ mod tests {
fn invalid_ccsds_stamp_type() { fn invalid_ccsds_stamp_type() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
buf[0] |= (CcsdsTimeCodes::CucAgencyEpoch as u8) << 4; buf[0] |= (CcsdsTimeCodes::CucAgencyEpoch as u8) << 4;
let res = TimeProviderCcsdsEpoch::from_bytes(&buf); let res = TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf, LEAP_SECONDS);
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();
if let TimestampError::InvalidTimeCode { expected, found } = err { if let TimestampError::InvalidTimeCode { expected, found } = err {
@ -938,7 +1034,7 @@ mod tests {
#[test] #[test]
fn test_write_with_coarse_fractions() { fn test_write_with_coarse_fractions() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let cuc = TimeProviderCcsdsEpoch::new_with_coarse_fractions(0x30201060, 120); let cuc = TimeProviderCcsdsEpoch::new_with_coarse_fractions(0x30201060, 120, LEAP_SECONDS);
assert!(cuc.fractions.is_some()); assert!(cuc.fractions.is_some());
assert_eq!(cuc.fractions.unwrap().1, 120); assert_eq!(cuc.fractions.unwrap().1, 120);
assert_eq!(cuc.fractions.unwrap().0, FractionalResolution::FourMs); assert_eq!(cuc.fractions.unwrap().0, FractionalResolution::FourMs);
@ -957,10 +1053,10 @@ mod tests {
#[test] #[test]
fn test_read_with_coarse_fractions() { fn test_read_with_coarse_fractions() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let cuc = TimeProviderCcsdsEpoch::new_with_coarse_fractions(0x30201060, 120); let cuc = TimeProviderCcsdsEpoch::new_with_coarse_fractions(0x30201060, 120, LEAP_SECONDS);
let res = cuc.write_to_bytes(&mut buf); let res = cuc.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
let res = TimeProviderCcsdsEpoch::from_bytes(&buf); let res = TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf, LEAP_SECONDS);
assert!(res.is_ok()); assert!(res.is_ok());
let read_back = res.unwrap(); let read_back = res.unwrap();
assert_eq!(read_back, cuc); assert_eq!(read_back, cuc);
@ -969,7 +1065,8 @@ mod tests {
#[test] #[test]
fn test_write_with_medium_fractions() { fn test_write_with_medium_fractions() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let cuc = TimeProviderCcsdsEpoch::new_with_medium_fractions(0x30303030, 30000); let cuc =
TimeProviderCcsdsEpoch::new_with_medium_fractions(0x30303030, 30000, LEAP_SECONDS);
let res = cuc.write_to_bytes(&mut buf); let res = cuc.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
let written = res.unwrap(); let written = res.unwrap();
@ -981,10 +1078,11 @@ mod tests {
#[test] #[test]
fn test_read_with_medium_fractions() { fn test_read_with_medium_fractions() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let cuc = TimeProviderCcsdsEpoch::new_with_medium_fractions(0x30303030, 30000); let cuc =
TimeProviderCcsdsEpoch::new_with_medium_fractions(0x30303030, 30000, LEAP_SECONDS);
let res = cuc.write_to_bytes(&mut buf); let res = cuc.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
let res = TimeProviderCcsdsEpoch::from_bytes(&buf); let res = TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf, LEAP_SECONDS);
assert!(res.is_ok()); assert!(res.is_ok());
let cuc_read_back = res.unwrap(); let cuc_read_back = res.unwrap();
assert_eq!(cuc_read_back, cuc); assert_eq!(cuc_read_back, cuc);
@ -993,8 +1091,11 @@ mod tests {
#[test] #[test]
fn test_write_with_fine_fractions() { fn test_write_with_fine_fractions() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let cuc = let cuc = TimeProviderCcsdsEpoch::new_with_fine_fractions(
TimeProviderCcsdsEpoch::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000); 0x30303030,
u16::MAX as u32 + 60000,
LEAP_SECONDS,
);
assert!(cuc.is_ok()); assert!(cuc.is_ok());
let cuc = cuc.unwrap(); let cuc = cuc.unwrap();
let res = cuc.write_to_bytes(&mut buf); let res = cuc.write_to_bytes(&mut buf);
@ -1009,13 +1110,16 @@ mod tests {
#[test] #[test]
fn test_read_with_fine_fractions() { fn test_read_with_fine_fractions() {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let cuc = let cuc = TimeProviderCcsdsEpoch::new_with_fine_fractions(
TimeProviderCcsdsEpoch::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000); 0x30303030,
u16::MAX as u32 + 60000,
LEAP_SECONDS,
);
assert!(cuc.is_ok()); assert!(cuc.is_ok());
let cuc = cuc.unwrap(); let cuc = cuc.unwrap();
let res = cuc.write_to_bytes(&mut buf); let res = cuc.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
let res = TimeProviderCcsdsEpoch::from_bytes(&buf); let res = TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&buf, LEAP_SECONDS);
assert!(res.is_ok()); assert!(res.is_ok());
let cuc_read_back = res.unwrap(); let cuc_read_back = res.unwrap();
assert_eq!(cuc_read_back, cuc); assert_eq!(cuc_read_back, cuc);
@ -1089,7 +1193,7 @@ mod tests {
#[test] #[test]
fn update_fractions() { fn update_fractions() {
let mut stamp = TimeProviderCcsdsEpoch::new(2000); let mut stamp = TimeProviderCcsdsEpoch::new(2000, LEAP_SECONDS);
let res = stamp.set_fractions(FractionalPart(FractionalResolution::SixtyNs, 5000)); let res = stamp.set_fractions(FractionalPart(FractionalResolution::SixtyNs, 5000));
assert!(res.is_ok()); assert!(res.is_ok());
assert!(stamp.fractions.is_some()); assert!(stamp.fractions.is_some());
@ -1100,7 +1204,7 @@ mod tests {
#[test] #[test]
fn set_fract_resolution() { fn set_fract_resolution() {
let mut stamp = TimeProviderCcsdsEpoch::new(2000); let mut stamp = TimeProviderCcsdsEpoch::new(2000, LEAP_SECONDS);
stamp.set_fractional_resolution(FractionalResolution::SixtyNs); stamp.set_fractional_resolution(FractionalResolution::SixtyNs);
assert!(stamp.fractions.is_some()); assert!(stamp.fractions.is_some());
let fractions = stamp.fractions.unwrap(); let fractions = stamp.fractions.unwrap();
@ -1135,7 +1239,7 @@ mod tests {
#[test] #[test]
fn add_duration_basic() { fn add_duration_basic() {
let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200); let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200, LEAP_SECONDS);
cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs); cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs);
let duration = Duration::from_millis(2500); let duration = Duration::from_millis(2500);
cuc_stamp += duration; cuc_stamp += duration;
@ -1144,7 +1248,7 @@ mod tests {
#[test] #[test]
fn add_duration_basic_on_ref() { fn add_duration_basic_on_ref() {
let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200); let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200, LEAP_SECONDS);
cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs); cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs);
let duration = Duration::from_millis(2500); let duration = Duration::from_millis(2500);
let new_stamp = cuc_stamp + duration; let new_stamp = cuc_stamp + duration;
@ -1153,7 +1257,7 @@ mod tests {
#[test] #[test]
fn add_duration_basic_no_fractions() { fn add_duration_basic_no_fractions() {
let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200); let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200, LEAP_SECONDS);
let duration = Duration::from_millis(2000); let duration = Duration::from_millis(2000);
cuc_stamp += duration; cuc_stamp += duration;
assert_eq!(cuc_stamp.counter(), 202); assert_eq!(cuc_stamp.counter(), 202);
@ -1162,7 +1266,7 @@ mod tests {
#[test] #[test]
fn add_duration_basic_on_ref_no_fractions() { fn add_duration_basic_on_ref_no_fractions() {
let cuc_stamp = TimeProviderCcsdsEpoch::new(200); let cuc_stamp = TimeProviderCcsdsEpoch::new(200, LEAP_SECONDS);
let duration = Duration::from_millis(2000); let duration = Duration::from_millis(2000);
let new_stamp = cuc_stamp + duration; let new_stamp = cuc_stamp + duration;
assert_eq!(new_stamp.counter(), 202); assert_eq!(new_stamp.counter(), 202);
@ -1171,7 +1275,8 @@ mod tests {
#[test] #[test]
fn add_duration_overflow() { fn add_duration_overflow() {
let mut cuc_stamp = let mut cuc_stamp =
TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 255), None).unwrap(); TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 255), None, LEAP_SECONDS)
.unwrap();
let duration = Duration::from_secs(10); let duration = Duration::from_secs(10);
cuc_stamp += duration; cuc_stamp += duration;
assert_eq!(cuc_stamp.counter.1, 10); assert_eq!(cuc_stamp.counter.1, 10);
@ -1179,7 +1284,7 @@ mod tests {
#[test] #[test]
fn test_invalid_width_param() { fn test_invalid_width_param() {
let error = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(8, 0), None); let error = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(8, 0), None, LEAP_SECONDS);
assert!(error.is_err()); assert!(error.is_err());
let error = error.unwrap_err(); let error = error.unwrap_err();
if let CucError::InvalidCounterWidth(width) = error { if let CucError::InvalidCounterWidth(width) = error {
@ -1193,14 +1298,18 @@ mod tests {
#[test] #[test]
fn test_from_dt() { fn test_from_dt() {
let dt = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(); let dt = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
let cuc = let cuc = TimeProviderCcsdsEpoch::from_date_time(
TimeProviderCcsdsEpoch::from_date_time(&dt, FractionalResolution::Seconds).unwrap(); &dt,
assert_eq!(cuc.counter(), dt.timestamp() as u32); FractionalResolution::Seconds,
LEAP_SECONDS,
)
.unwrap();
assert_eq!(cuc.counter(), dt.timestamp() as u32 + LEAP_SECONDS);
} }
#[test] #[test]
fn test_new_u16_width() { fn test_new_u16_width() {
let cuc = TimeProviderCcsdsEpoch::new_u16_counter(0); let cuc = TimeProviderCcsdsEpoch::new_u16_counter(0, LEAP_SECONDS);
assert_eq!(cuc.width(), 2); assert_eq!(cuc.width(), 2);
assert_eq!(cuc.counter(), 0); assert_eq!(cuc.counter(), 0);
} }
@ -1208,9 +1317,12 @@ mod tests {
#[test] #[test]
fn from_unix_stamp() { fn from_unix_stamp() {
let unix_stamp = UnixTimestamp::new(0, 0).unwrap(); let unix_stamp = UnixTimestamp::new(0, 0).unwrap();
let cuc = let cuc = TimeProviderCcsdsEpoch::from_unix_stamp(
TimeProviderCcsdsEpoch::from_unix_stamp(&unix_stamp, FractionalResolution::Seconds) &unix_stamp,
.expect("failed to create cuc from unix stamp"); FractionalResolution::Seconds,
LEAP_SECONDS,
)
.expect("failed to create cuc from unix stamp");
assert_eq!( assert_eq!(
cuc.counter(), cuc.counter(),
(-DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as u32 (-DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as u32
@ -1219,7 +1331,8 @@ mod tests {
#[test] #[test]
fn test_invalid_counter() { fn test_invalid_counter() {
let cuc_error = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 256), None); let cuc_error =
TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 256), None, LEAP_SECONDS);
assert!(cuc_error.is_err()); assert!(cuc_error.is_err());
let cuc_error = cuc_error.unwrap_err(); let cuc_error = cuc_error.unwrap_err();
if let CucError::InvalidCounter { width, counter } = cuc_error { if let CucError::InvalidCounter { width, counter } = cuc_error {
@ -1233,7 +1346,7 @@ mod tests {
#[test] #[test]
fn test_stamp_to_vec() { fn test_stamp_to_vec() {
let stamp = TimeProviderCcsdsEpoch::new_u16_counter(100); let stamp = TimeProviderCcsdsEpoch::new_u16_counter(100, LEAP_SECONDS);
let stamp_vec = stamp.to_vec().unwrap(); let stamp_vec = stamp.to_vec().unwrap();
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
stamp.write_to_bytes(&mut buf).unwrap(); stamp.write_to_bytes(&mut buf).unwrap();

View File

@ -204,10 +204,16 @@ pub trait TimeWriter {
} }
} }
pub trait TimeReader { pub trait TimeReader: Sized {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> {
where Self::from_bytes_generic(buf, None)
Self: Sized; }
fn from_bytes_with_leap_seconds(buf: &[u8], leap_seconds: u32) -> Result<Self, TimestampError> {
Self::from_bytes_generic(buf, Some(leap_seconds))
}
fn from_bytes_generic(buf: &[u8], leap_seconds: Option<u32>) -> Result<Self, TimestampError>;
} }
/// Trait for generic CCSDS time providers. /// Trait for generic CCSDS time providers.