more improvements for CUC API
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good

This commit is contained in:
Robin Müller 2024-03-18 15:13:19 +01:00
parent 247cc72753
commit dcf720ad67
Signed by: muellerr
GPG Key ID: A649FB78196E3849

View File

@ -61,14 +61,14 @@ impl TryFrom<u8> for FractionalResolution {
} }
} }
/// Please note that this function will panic if the fractional value is not smaller than /// Please note that this function will panic if the fractional counter is not smaller than
/// the maximum number of fractions allowed for the particular resolution. /// the maximum number of fractions allowed for the particular resolution.
/// (e.g. passing 270 when the resolution only allows 255 values). /// (e.g. passing 270 when the resolution only allows 255 values).
#[inline] #[inline]
pub fn convert_fractional_part_to_ns(fractional_part: FractionalPart) -> u64 { pub fn convert_fractional_part_to_ns(fractional_part: FractionalPart) -> u64 {
let div = fractional_res_to_div(fractional_part.0); let div = fractional_res_to_div(fractional_part.resolution);
assert!(fractional_part.1 <= div); assert!(fractional_part.counter <= div);
10_u64.pow(9) * fractional_part.1 as u64 / div as u64 10_u64.pow(9) * fractional_part.counter as u64 / div as u64
} }
#[inline(always)] #[inline(always)]
@ -84,22 +84,22 @@ pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 {
/// Please note that this function will panic if the passed nanoseconds exceeds 1 second /// Please note that this function will panic if the passed nanoseconds exceeds 1 second
/// as a nanosecond (10 to the power of 9). Furthermore, it will return [None] if the /// as a nanosecond (10 to the power of 9). Furthermore, it will return [None] if the
/// given resolution is [FractionalResolution::Seconds]. /// given resolution is [FractionalResolution::Seconds].
pub fn fractional_part_from_subsec_ns( pub fn fractional_part_from_subsec_ns(res: FractionalResolution, ns: u64) -> FractionalPart {
res: FractionalResolution,
ns: u64,
) -> Option<FractionalPart> {
if res == FractionalResolution::Seconds { if res == FractionalResolution::Seconds {
return None; return FractionalPart::new_with_seconds_resolution();
} }
let sec_as_ns = 10_u64.pow(9); let sec_as_ns = 10_u64.pow(9);
if ns > sec_as_ns { if ns > sec_as_ns {
panic!("passed nanosecond value larger than 1 second"); panic!("passed nanosecond value larger than 1 second");
} }
let resolution = fractional_res_to_div(res) as u64; let resolution_divisor = fractional_res_to_div(res) as u64;
// This is probably the cheapest way to calculate the fractional part without using expensive // This is probably the cheapest way to calculate the fractional part without using expensive
// floating point division. // floating point division.
let fractional_part = ns * (resolution + 1) / sec_as_ns; let fractional_counter = ns * (resolution_divisor + 1) / sec_as_ns;
Some(FractionalPart(res, fractional_part as u32)) FractionalPart {
resolution: res,
counter: fractional_counter as u32,
}
} }
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
@ -146,9 +146,57 @@ impl Error for CucError {}
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WidthCounterPair(u8, u32); pub struct WidthCounterPair(u8, u32);
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FractionalPart(FractionalResolution, u32); pub struct FractionalPart {
pub resolution: FractionalResolution,
pub counter: u32,
}
impl FractionalPart {
pub const fn new(resolution: FractionalResolution, counter: u32) -> Self {
let div = fractional_res_to_div(resolution);
assert!(counter <= div, "invalid counter for resolution");
Self {
resolution,
counter,
}
}
/// An empty fractional part for second resolution only.
pub const fn new_with_seconds_resolution() -> Self {
Self::new(FractionalResolution::Seconds, 0)
}
/// Helper method which simply calls [Self::new_with_seconds_resolution].
pub const fn new_empty() -> Self {
Self::new_with_seconds_resolution()
}
pub fn new_checked(resolution: FractionalResolution, counter: u32) -> Option<Self> {
let div = fractional_res_to_div(resolution);
if counter > div {
return None;
}
Some(Self {
resolution,
counter,
})
}
pub fn resolution(&self) -> FractionalResolution {
self.resolution
}
pub fn counter(&self) -> u32 {
self.counter
}
pub fn no_fractional_part(&self) -> bool {
self.resolution == FractionalResolution::Seconds
}
}
/// This object is the abstraction for the CCSDS Unsegmented Time Code (CUC) using the CCSDS epoch /// This object is the abstraction for the CCSDS Unsegmented Time Code (CUC) using the CCSDS epoch
/// and a small preamble field. /// and a small preamble field.
@ -202,7 +250,7 @@ pub struct FractionalPart(FractionalResolution, u32);
pub struct CucTime { pub struct CucTime {
pfield: u8, pfield: u8,
counter: WidthCounterPair, counter: WidthCounterPair,
fractions: Option<FractionalPart>, fractions: FractionalPart,
} }
/// This object is a wrapper object around the [CucTime] object which also tracks /// This object is a wrapper object around the [CucTime] object which also tracks
@ -232,12 +280,16 @@ impl CucTime {
/// 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) -> 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),
FractionalPart::new_with_seconds_resolution(),
)
.unwrap()
} }
/// Like [CucTime::new] but allow to supply a fractional part as well. /// Like [CucTime::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(counter: u32, fractions: FractionalPart) -> Result<Self, CucError> {
Self::new_generic(WidthCounterPair(4, counter), Some(fractions)) Self::new_generic(WidthCounterPair(4, counter), fractions)
} }
/// Fractions with a resolution of ~ 4 ms /// Fractions with a resolution of ~ 4 ms
@ -245,10 +297,10 @@ impl CucTime {
// 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),
Some(FractionalPart( FractionalPart {
FractionalResolution::FourMs, resolution: FractionalResolution::FourMs,
subsec_fractions as u32, counter: subsec_fractions as u32,
)), },
) )
.unwrap() .unwrap()
} }
@ -258,10 +310,10 @@ impl CucTime {
// 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),
Some(FractionalPart( FractionalPart {
FractionalResolution::FifteenUs, resolution: FractionalResolution::FifteenUs,
subsec_fractions as u32, counter: subsec_fractions as u32,
)), },
) )
.unwrap() .unwrap()
} }
@ -272,10 +324,10 @@ impl CucTime {
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) -> Result<Self, CucError> {
Self::new_generic( Self::new_generic(
WidthCounterPair(4, counter), WidthCounterPair(4, counter),
Some(FractionalPart( FractionalPart {
FractionalResolution::SixtyNs, resolution: FractionalResolution::SixtyNs,
subsec_fractions, counter: subsec_fractions,
)), },
) )
} }
@ -308,7 +360,7 @@ impl CucTime {
} }
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(counter, fractions.unwrap()) Self::new_with_fractions(counter, fractions)
.map_err(|e| StdTimestampError::Timestamp(e.into())) .map_err(|e| StdTimestampError::Timestamp(e.into()))
} }
@ -328,11 +380,12 @@ impl CucTime {
.1 .1
.checked_add(leap_seconds) .checked_add(leap_seconds)
.ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?; .ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?;
if self.fractions.is_some() { if let FractionalResolution::Seconds = self.fractions.resolution {
self.fractions = fractional_part_from_subsec_ns( self.fractions = fractional_part_from_subsec_ns(
self.fractions.unwrap().0, self.fractions.resolution,
now.subsec_nanos() as u64, now.subsec_nanos() as u64,
); );
return Ok(());
} }
Ok(()) Ok(())
} }
@ -386,7 +439,11 @@ impl CucTime {
pub fn new_u16_counter(counter: u16) -> Self { pub fn new_u16_counter(counter: u16) -> 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),
FractionalPart::new_with_seconds_resolution(),
)
.unwrap()
} }
pub fn ccsds_time_code(&self) -> CcsdsTimeCode { pub fn ccsds_time_code(&self) -> CcsdsTimeCode {
@ -405,7 +462,8 @@ impl CucTime {
self.counter.1 self.counter.1
} }
pub fn width_fractions_pair(&self) -> Option<FractionalPart> { /// Subsecond fractional part of the CUC time.
pub fn fractions(&self) -> FractionalPart {
self.fractions self.fractions
} }
@ -415,7 +473,7 @@ impl CucTime {
pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> { pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> {
Self::verify_fractions_value(fractions)?; Self::verify_fractions_value(fractions)?;
self.fractions = Some(fractions); self.fractions = fractions;
self.update_p_field_fractions(); self.update_p_field_fractions();
Ok(()) Ok(())
} }
@ -424,22 +482,16 @@ impl CucTime {
/// to 0 if the resolution changes. /// to 0 if the resolution changes.
pub fn set_fractional_resolution(&mut self, res: FractionalResolution) { pub fn set_fractional_resolution(&mut self, res: FractionalResolution) {
if res == FractionalResolution::Seconds { if res == FractionalResolution::Seconds {
self.fractions = None; self.fractions = FractionalPart::new_with_seconds_resolution();
} }
let mut update_fractions = true; if res != self.fractions().resolution() {
if let Some(existing_fractions) = self.fractions { self.fractions = FractionalPart::new(res, 0);
if existing_fractions.0 == res {
update_fractions = false;
}
};
if update_fractions {
self.fractions = Some(FractionalPart(res, 0));
} }
} }
pub fn new_generic( pub fn new_generic(
counter: WidthCounterPair, counter: WidthCounterPair,
fractions: Option<FractionalPart>, fractions: FractionalPart,
) -> 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 {
@ -448,17 +500,15 @@ impl CucTime {
counter: counter.1.into(), counter: counter.1.into(),
}); });
} }
if let Some(fractions) = fractions {
Self::verify_fractions_value(fractions)?; Self::verify_fractions_value(fractions)?;
}
Ok(Self { Ok(Self {
pfield: Self::build_p_field(counter.0, fractions.map(|v| v.0)), pfield: Self::build_p_field(counter.0, fractions.resolution),
counter, counter,
fractions, fractions,
}) })
} }
fn build_p_field(counter_width: u8, fractions_width: Option<FractionalResolution>) -> u8 { fn build_p_field(counter_width: u8, resolution: FractionalResolution) -> u8 {
let mut pfield = P_FIELD_BASE; let mut pfield = P_FIELD_BASE;
if !(1..=4).contains(&counter_width) { if !(1..=4).contains(&counter_width) {
// Okay to panic here, this function is private and all input values should // Okay to panic here, this function is private and all input values should
@ -466,25 +516,20 @@ impl CucTime {
panic!("invalid counter width {} for cuc timestamp", counter_width); panic!("invalid counter width {} for cuc timestamp", counter_width);
} }
pfield |= (counter_width - 1) << 2; pfield |= (counter_width - 1) << 2;
if let Some(fractions_width) = fractions_width { if resolution != FractionalResolution::Seconds {
if !(1..=3).contains(&(fractions_width as u8)) { if !(1..=3).contains(&(resolution as u8)) {
// Okay to panic here, this function is private and all input values should // Okay to panic here, this function is private and all input values should
// have been sanitized // have been sanitized
panic!( panic!("invalid fractions width {:?} for cuc timestamp", resolution);
"invalid fractions width {:?} for cuc timestamp",
fractions_width
);
} }
pfield |= fractions_width as u8; pfield |= resolution as u8;
} }
pfield pfield
} }
fn update_p_field_fractions(&mut self) { fn update_p_field_fractions(&mut self) {
self.pfield &= !(0b11); self.pfield &= !(0b11);
if let Some(fractions) = self.fractions { self.pfield |= self.fractions.resolution() as u8;
self.pfield |= fractions.0 as u8;
}
} }
#[inline] #[inline]
@ -542,10 +587,10 @@ impl CucTime {
} }
fn verify_fractions_value(val: FractionalPart) -> Result<(), CucError> { fn verify_fractions_value(val: FractionalPart) -> Result<(), CucError> {
if val.1 > 2u32.pow((val.0 as u32) * 8) - 1 { if val.counter > 2u32.pow((val.resolution as u32) * 8) - 1 {
return Err(CucError::InvalidFractions { return Err(CucError::InvalidFractions {
resolution: val.0, resolution: val.resolution,
value: val.1 as u64, value: val.counter as u64,
}); });
} }
Ok(()) Ok(())
@ -556,14 +601,11 @@ impl CucTime {
} }
fn subsec_nanos(&self) -> u32 { fn subsec_nanos(&self) -> u32 {
if let Some(fractions) = self.fractions { if self.fractions.resolution() == FractionalResolution::Seconds {
if fractions.0 == FractionalResolution::Seconds {
return 0; return 0;
} }
// Rounding down here is the correct approach. // Rounding down here is the correct approach.
return convert_fractional_part_to_ns(fractions) as u32; convert_fractional_part_to_ns(self.fractions) as u32
}
0
} }
} }
@ -616,29 +658,29 @@ impl TimeReader for CucTime {
_ => panic!("unreachable match arm"), _ => panic!("unreachable match arm"),
}; };
current_idx += cntr_len as usize; current_idx += cntr_len as usize;
let mut fractions = None; let mut fractions = FractionalPart::new_with_seconds_resolution();
if fractions_len > 0 { if fractions_len > 0 {
match fractions_len { match fractions_len {
1 => { 1 => {
fractions = Some(FractionalPart( fractions = FractionalPart::new(
fractions_len.try_into().unwrap(), fractions_len.try_into().unwrap(),
buf[current_idx] as u32, buf[current_idx] as u32,
)) )
} }
2 => { 2 => {
fractions = Some(FractionalPart( fractions = FractionalPart::new(
fractions_len.try_into().unwrap(), fractions_len.try_into().unwrap(),
u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap()) u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap())
as u32, as u32,
)) )
} }
3 => { 3 => {
let mut tmp_buf: [u8; 4] = [0; 4]; let mut tmp_buf: [u8; 4] = [0; 4];
tmp_buf[1..4].copy_from_slice(&buf[current_idx..current_idx + 3]); tmp_buf[1..4].copy_from_slice(&buf[current_idx..current_idx + 3]);
fractions = Some(FractionalPart( fractions = FractionalPart::new(
fractions_len.try_into().unwrap(), fractions_len.try_into().unwrap(),
u32::from_be_bytes(tmp_buf), u32::from_be_bytes(tmp_buf),
)) )
} }
_ => panic!("unreachable match arm"), _ => panic!("unreachable match arm"),
} }
@ -680,18 +722,15 @@ impl TimeWriter for CucTime {
_ => panic!("invalid counter width value"), _ => panic!("invalid counter width value"),
} }
current_idx += self.counter.0 as usize; current_idx += self.counter.0 as usize;
if let Some(fractions) = self.fractions { match self.fractions.resolution() {
match fractions.0 { FractionalResolution::FourMs => bytes[current_idx] = self.fractions.counter as u8,
FractionalResolution::FourMs => bytes[current_idx] = fractions.1 as u8,
FractionalResolution::FifteenUs => bytes[current_idx..current_idx + 2] FractionalResolution::FifteenUs => bytes[current_idx..current_idx + 2]
.copy_from_slice(&(fractions.1 as u16).to_be_bytes()), .copy_from_slice(&(self.fractions.counter as u16).to_be_bytes()),
FractionalResolution::SixtyNs => bytes[current_idx..current_idx + 3] FractionalResolution::SixtyNs => bytes[current_idx..current_idx + 3]
.copy_from_slice(&fractions.1.to_be_bytes()[1..4]), .copy_from_slice(&self.fractions.counter.to_be_bytes()[1..4]),
// Should also never happen _ => (),
_ => panic!("invalid fractions value"),
}
current_idx += fractions.0 as usize;
} }
current_idx += self.fractions.resolution as usize;
Ok(current_idx) Ok(current_idx)
} }
@ -722,11 +761,12 @@ impl CcsdsTimeProvider for CucTimeWithLeapSecs {
} }
} }
fn get_provider_values_after_duration_addition( // TODO: Introduce more overflow checks here.
provider: &CucTime, fn get_time_values_after_duration_addition(
time: &CucTime,
duration: Duration, duration: Duration,
) -> (u32, Option<FractionalPart>) { ) -> (u32, FractionalPart) {
let mut new_counter = provider.counter.1; let mut new_counter = time.counter.1;
let subsec_nanos = duration.subsec_nanos(); let subsec_nanos = duration.subsec_nanos();
let mut increment_counter = |amount: u32| { let mut increment_counter = |amount: u32| {
let mut sum: u64 = 0; let mut sum: u64 = 0;
@ -738,7 +778,7 @@ fn get_provider_values_after_duration_addition(
} }
new_counter = sum as u32; new_counter = sum as u32;
}; };
match provider.counter.0 { match time.counter.0 {
1 => counter_inc_handler(u8::MAX as u64), 1 => counter_inc_handler(u8::MAX as u64),
2 => counter_inc_handler(u16::MAX as u64), 2 => counter_inc_handler(u16::MAX as u64),
3 => counter_inc_handler((2_u32.pow(24) - 1) as u64), 3 => counter_inc_handler((2_u32.pow(24) - 1) as u64),
@ -749,25 +789,21 @@ fn get_provider_values_after_duration_addition(
} }
} }
}; };
let fractional_part = if let Some(fractional_part) = &provider.fractions { let resolution = time.fractions().resolution();
let fractional_increment = let fractional_increment = fractional_part_from_subsec_ns(resolution, subsec_nanos as u64);
fractional_part_from_subsec_ns(fractional_part.0, subsec_nanos as u64).unwrap(); let mut fractional_part = FractionalPart::new_with_seconds_resolution();
let mut increment_fractions = |resolution| { if resolution != FractionalResolution::Seconds {
let mut new_fractions = fractional_part.1 + fractional_increment.1; let mut new_fractions = time.fractions().counter() + fractional_increment.counter;
let max_fractions = fractional_res_to_div(resolution); let max_fractions = fractional_res_to_div(resolution);
if new_fractions > max_fractions { if new_fractions > max_fractions {
increment_counter(1); increment_counter(1);
new_fractions -= max_fractions; new_fractions -= max_fractions;
} }
Some(FractionalPart(resolution, new_fractions)) fractional_part = FractionalPart {
}; resolution,
match fractional_increment.0 { counter: new_fractions,
FractionalResolution::Seconds => None, }
_ => increment_fractions(fractional_increment.0),
} }
} else {
None
};
increment_counter(duration.as_secs() as u32); increment_counter(duration.as_secs() as u32);
(new_counter, fractional_part) (new_counter, fractional_part)
} }
@ -775,12 +811,10 @@ fn get_provider_values_after_duration_addition(
impl AddAssign<Duration> for CucTime { impl AddAssign<Duration> for CucTime {
fn add_assign(&mut self, duration: Duration) { fn add_assign(&mut self, duration: Duration) {
let (new_counter, new_fractional_part) = let (new_counter, new_fractional_part) =
get_provider_values_after_duration_addition(self, duration); get_time_values_after_duration_addition(self, duration);
self.counter.1 = new_counter; self.counter.1 = new_counter;
if self.fractions.is_some() {
self.fractions = new_fractional_part; self.fractions = new_fractional_part;
} }
}
} }
impl Add<Duration> for CucTime { impl Add<Duration> for CucTime {
@ -788,12 +822,9 @@ impl Add<Duration> for CucTime {
fn add(self, duration: Duration) -> Self::Output { fn add(self, duration: Duration) -> Self::Output {
let (new_counter, new_fractional_part) = let (new_counter, new_fractional_part) =
get_provider_values_after_duration_addition(&self, duration); get_time_values_after_duration_addition(&self, duration);
if let Some(fractional_part) = new_fractional_part {
// The generated fractional part should always be valid, so its okay to unwrap here. // The generated fractional part should always be valid, so its okay to unwrap here.
return Self::new_with_fractions(new_counter, fractional_part).unwrap(); Self::new_with_fractions(new_counter, new_fractional_part).unwrap()
}
Self::new(new_counter)
} }
} }
@ -802,12 +833,9 @@ impl Add<Duration> for &CucTime {
fn add(self, duration: Duration) -> Self::Output { fn add(self, duration: Duration) -> Self::Output {
let (new_counter, new_fractional_part) = let (new_counter, new_fractional_part) =
get_provider_values_after_duration_addition(self, duration); get_time_values_after_duration_addition(self, duration);
if let Some(fractional_part) = new_fractional_part {
// The generated fractional part should always be valid, so its okay to unwrap here. // 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(); Self::Output::new_with_fractions(new_counter, new_fractional_part).unwrap()
}
Self::Output::new(new_counter)
} }
} }
@ -835,8 +863,8 @@ mod tests {
let counter = zero_cuc.width_counter_pair(); let counter = zero_cuc.width_counter_pair();
assert_eq!(counter.0, 4); assert_eq!(counter.0, 4);
assert_eq!(counter.1, 0); assert_eq!(counter.1, 0);
let fractions = zero_cuc.width_fractions_pair(); let fractions = zero_cuc.fractions();
assert!(fractions.is_none()); assert_eq!(fractions, FractionalPart::new_with_seconds_resolution());
let dt = ccsds_cuc.chrono_date_time(); let dt = ccsds_cuc.chrono_date_time();
if let chrono::LocalResult::Single(dt) = dt { if let chrono::LocalResult::Single(dt) = dt {
assert_eq!(dt.year(), 1958); assert_eq!(dt.year(), 1958);
@ -851,7 +879,8 @@ mod tests {
#[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 = CucTime::new_generic(WidthCounterPair(4, 0x20102030), None); let zero_cuc =
CucTime::new_generic(WidthCounterPair(4, 0x20102030), FractionalPart::new_empty());
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);
@ -893,12 +922,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 = CucTime::new_generic(WidthCounterPair(4, 0x20102030), None).unwrap(); let zero_cuc = CucTime::new_generic(
WidthCounterPair(4, 0x20102030),
FractionalPart::new_with_seconds_resolution(),
)
.unwrap();
zero_cuc.write_to_bytes(&mut buf).unwrap(); zero_cuc.write_to_bytes(&mut buf).unwrap();
let cuc_read_back = CucTime::from_bytes(&buf).expect("reading cuc timestamp failed"); let cuc_read_back = CucTime::from_bytes(&buf).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.fractions(), FractionalPart::new_empty());
} }
#[test] #[test]
@ -937,7 +970,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 = CucTime::new_generic(WidthCounterPair(1, 200), None); let cuc = CucTime::new_generic(WidthCounterPair(1, 200), FractionalPart::new_empty());
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);
@ -955,7 +988,7 @@ 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 = CucTime::new_generic(WidthCounterPair(2, 40000), None); let cuc = CucTime::new_generic(WidthCounterPair(2, 40000), FractionalPart::new_empty());
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);
@ -976,7 +1009,10 @@ 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 = CucTime::new_generic(WidthCounterPair(3, 2_u32.pow(24) - 2), None); let cuc = CucTime::new_generic(
WidthCounterPair(3, 2_u32.pow(24) - 2),
FractionalPart::new_empty(),
);
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);
@ -1033,9 +1069,8 @@ mod tests {
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 = CucTime::new_with_coarse_fractions(0x30201060, 120); let cuc = CucTime::new_with_coarse_fractions(0x30201060, 120);
assert!(cuc.fractions.is_some()); assert_eq!(cuc.fractions().counter(), 120);
assert_eq!(cuc.fractions.unwrap().1, 120); assert_eq!(cuc.fractions().resolution(), FractionalResolution::FourMs);
assert_eq!(cuc.fractions.unwrap().0, FractionalResolution::FourMs);
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();
@ -1115,44 +1150,49 @@ mod tests {
#[test] #[test]
fn test_fractional_converter() { fn test_fractional_converter() {
let ns = convert_fractional_part_to_ns(FractionalPart(FractionalResolution::FourMs, 2)); let ns = convert_fractional_part_to_ns(FractionalPart {
resolution: FractionalResolution::FourMs,
counter: 2,
});
// The formula for this is 2/255 * 10e9 = 7.843.137. // The formula for this is 2/255 * 10e9 = 7.843.137.
assert_eq!(ns, 7843137); assert_eq!(ns, 7843137);
// This is the largest value we should be able to pass without this function panicking. // This is the largest value we should be able to pass without this function panicking.
let ns = convert_fractional_part_to_ns(FractionalPart( let ns = convert_fractional_part_to_ns(FractionalPart {
FractionalResolution::SixtyNs, resolution: FractionalResolution::SixtyNs,
2_u32.pow(24) - 2, counter: 2_u32.pow(24) - 2,
)); });
assert_eq!(ns, 999999940); assert_eq!(ns, 999999940);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_fractional_converter_invalid_input() { fn test_fractional_converter_invalid_input() {
convert_fractional_part_to_ns(FractionalPart(FractionalResolution::FourMs, 256)); convert_fractional_part_to_ns(FractionalPart {
resolution: FractionalResolution::FourMs,
counter: 256,
});
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_fractional_converter_invalid_input_2() { fn test_fractional_converter_invalid_input_2() {
convert_fractional_part_to_ns(FractionalPart( convert_fractional_part_to_ns(FractionalPart {
FractionalResolution::SixtyNs, resolution: FractionalResolution::SixtyNs,
2_u32.pow(32) - 1, counter: 2_u32.pow(32) - 1,
)); });
} }
#[test] #[test]
fn fractional_part_formula() { fn fractional_part_formula() {
let fractional_part = let fractional_part = fractional_part_from_subsec_ns(FractionalResolution::FourMs, 7843138);
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 7843138).unwrap(); assert_eq!(fractional_part.counter, 2);
assert_eq!(fractional_part.1, 2);
} }
#[test] #[test]
fn fractional_part_formula_2() { fn fractional_part_formula_2() {
let fractional_part = let fractional_part =
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 12000000).unwrap(); fractional_part_from_subsec_ns(FractionalResolution::FourMs, 12000000);
assert_eq!(fractional_part.1, 3); assert_eq!(fractional_part.counter, 3);
} }
#[test] #[test]
@ -1165,86 +1205,86 @@ mod tests {
let fractional_part = fractional_part_from_subsec_ns( let fractional_part = fractional_part_from_subsec_ns(
FractionalResolution::FifteenUs, FractionalResolution::FifteenUs,
hundred_fractions_and_some, hundred_fractions_and_some,
) );
.unwrap(); assert_eq!(fractional_part.counter, 100);
assert_eq!(fractional_part.1, 100);
// Using exactly 101.0 can yield values which will later be rounded down to 100 // Using exactly 101.0 can yield values which will later be rounded down to 100
let hundred_and_one_fractions = let hundred_and_one_fractions =
(101.001 * one_fraction_with_width_two_in_ns).floor() as u64; (101.001 * one_fraction_with_width_two_in_ns).floor() as u64;
let fractional_part = fractional_part_from_subsec_ns( let fractional_part = fractional_part_from_subsec_ns(
FractionalResolution::FifteenUs, FractionalResolution::FifteenUs,
hundred_and_one_fractions, hundred_and_one_fractions,
) );
.unwrap(); assert_eq!(fractional_part.counter, 101);
assert_eq!(fractional_part.1, 101);
} }
#[test] #[test]
fn update_fractions() { fn update_fractions() {
let mut stamp = CucTime::new(2000); let mut stamp = CucTime::new(2000);
let res = stamp.set_fractions(FractionalPart(FractionalResolution::SixtyNs, 5000)); let res = stamp.set_fractions(FractionalPart {
resolution: FractionalResolution::SixtyNs,
counter: 5000,
});
assert!(res.is_ok()); assert!(res.is_ok());
assert!(stamp.fractions.is_some()); assert_eq!(
let fractions = stamp.fractions.unwrap(); stamp.fractions().resolution(),
assert_eq!(fractions.0, FractionalResolution::SixtyNs); FractionalResolution::SixtyNs
assert_eq!(fractions.1, 5000); );
assert_eq!(stamp.fractions().counter(), 5000);
} }
#[test] #[test]
fn set_fract_resolution() { fn set_fract_resolution() {
let mut stamp = CucTime::new(2000); let mut stamp = CucTime::new(2000);
stamp.set_fractional_resolution(FractionalResolution::SixtyNs); stamp.set_fractional_resolution(FractionalResolution::SixtyNs);
assert!(stamp.fractions.is_some()); assert_eq!(
let fractions = stamp.fractions.unwrap(); stamp.fractions().resolution(),
assert_eq!(fractions.0, FractionalResolution::SixtyNs); FractionalResolution::SixtyNs
assert_eq!(fractions.1, 0); );
assert_eq!(stamp.fractions().counter(), 0);
let res = stamp.update_from_now(LEAP_SECONDS); let res = stamp.update_from_now(LEAP_SECONDS);
assert!(res.is_ok()); assert!(res.is_ok());
} }
#[test] #[test]
fn test_small_fraction_floored_to_zero() { fn test_small_fraction_floored_to_zero() {
let fractions = fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 59).unwrap(); let fractions = fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 59);
assert_eq!(fractions.1, 0); assert_eq!(fractions.counter, 0);
} }
#[test] #[test]
fn test_small_fraction_becomes_fractional_part() { fn test_small_fraction_becomes_fractional_part() {
let fractions = fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 61).unwrap(); let fractions = fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 61);
assert_eq!(fractions.1, 1); assert_eq!(fractions.counter, 1);
} }
#[test] #[test]
fn test_smallest_resolution_small_nanoseconds_floored_to_zero() { fn test_smallest_resolution_small_nanoseconds_floored_to_zero() {
let fractions = let fractions =
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 3800 * 1e3 as u64) fractional_part_from_subsec_ns(FractionalResolution::FourMs, 3800 * 1e3 as u64);
.unwrap(); assert_eq!(fractions.counter, 0);
assert_eq!(fractions.1, 0);
} }
#[test] #[test]
fn test_smallest_resolution_small_nanoseconds_becomes_one_fraction() { fn test_smallest_resolution_small_nanoseconds_becomes_one_fraction() {
let fractions = let fractions =
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 4000 * 1e3 as u64) fractional_part_from_subsec_ns(FractionalResolution::FourMs, 4000 * 1e3 as u64);
.unwrap(); assert_eq!(fractions.counter, 1);
assert_eq!(fractions.1, 1);
} }
#[test] #[test]
fn test_smallest_resolution_large_nanoseconds_becomes_largest_fraction() { fn test_smallest_resolution_large_nanoseconds_becomes_largest_fraction() {
let fractions = let fractions =
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 10u64.pow(9) - 1).unwrap(); fractional_part_from_subsec_ns(FractionalResolution::FourMs, 10u64.pow(9) - 1);
assert_eq!(fractions.1, 2_u32.pow(8) - 1); assert_eq!(fractions.counter, 2_u32.pow(8) - 1);
} }
#[test] #[test]
fn test_largest_fractions_with_largest_resolution() { fn test_largest_fractions_with_largest_resolution() {
let fractions = let fractions =
fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 10u64.pow(9) - 1) fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 10u64.pow(9) - 1);
.unwrap();
// The value can not be larger than representable by 3 bytes // The value can not be larger than representable by 3 bytes
// Assert that the maximum resolution can be reached // Assert that the maximum resolution can be reached
assert_eq!(fractions.1, 2_u32.pow(3 * 8) - 1); assert_eq!(fractions.counter, 2_u32.pow(3 * 8) - 1);
} }
fn check_stamp_after_addition(cuc_stamp: &CucTime) { fn check_stamp_after_addition(cuc_stamp: &CucTime) {
@ -1254,14 +1294,14 @@ mod tests {
CcsdsTimeCode::CucCcsdsEpoch CcsdsTimeCode::CucCcsdsEpoch
); );
assert_eq!(cuc_stamp.width_counter_pair().1, 202); assert_eq!(cuc_stamp.width_counter_pair().1, 202);
let fractions = cuc_stamp.width_fractions_pair().unwrap().1; let fractions = cuc_stamp.fractions().counter();
let expected_val = let expected_val =
(0.5 * fractional_res_to_div(FractionalResolution::FifteenUs) as f64).ceil() as u32; (0.5 * fractional_res_to_div(FractionalResolution::FifteenUs) as f64).ceil() as u32;
assert_eq!(fractions, expected_val); assert_eq!(fractions, expected_val);
let cuc_stamp2 = cuc_stamp + Duration::from_millis(501); let cuc_stamp2 = cuc_stamp + Duration::from_millis(501);
// What I would roughly expect // What I would roughly expect
assert_eq!(cuc_stamp2.counter.1, 203); assert_eq!(cuc_stamp2.counter.1, 203);
assert!(cuc_stamp2.fractions.unwrap().1 < 100); assert!(cuc_stamp2.fractions().counter() < 100);
assert!(cuc_stamp2.subsec_millis() <= 1); assert!(cuc_stamp2.subsec_millis() <= 1);
} }
@ -1289,7 +1329,7 @@ mod tests {
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);
assert_eq!(cuc_stamp.width_fractions_pair(), None); assert_eq!(cuc_stamp.fractions(), FractionalPart::new_empty());
} }
#[test] #[test]
@ -1298,11 +1338,12 @@ mod tests {
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);
assert_eq!(new_stamp.width_fractions_pair(), None); assert_eq!(new_stamp.fractions(), FractionalPart::new_empty());
} }
#[test] #[test]
fn add_duration_overflow() { fn add_duration_overflow() {
let mut cuc_stamp = CucTime::new_generic(WidthCounterPair(1, 255), None).unwrap(); let mut cuc_stamp =
CucTime::new_generic(WidthCounterPair(1, 255), FractionalPart::new_empty()).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);
@ -1310,7 +1351,7 @@ mod tests {
#[test] #[test]
fn test_invalid_width_param() { fn test_invalid_width_param() {
let error = CucTime::new_generic(WidthCounterPair(8, 0), None); let error = CucTime::new_generic(WidthCounterPair(8, 0), FractionalPart::new_empty());
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 {
@ -1350,7 +1391,7 @@ mod tests {
#[test] #[test]
fn test_invalid_counter() { fn test_invalid_counter() {
let cuc_error = CucTime::new_generic(WidthCounterPair(1, 256), None); let cuc_error = CucTime::new_generic(WidthCounterPair(1, 256), FractionalPart::new_empty());
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 {