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.
/// (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
let div = fractional_res_to_div(fractional_part.resolution);
assert!(fractional_part.counter <= div);
10_u64.pow(9) * fractional_part.counter as u64 / div as u64
}
#[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
/// as a nanosecond (10 to the power of 9). Furthermore, it will return [None] if the
/// given resolution is [FractionalResolution::Seconds].
pub fn fractional_part_from_subsec_ns(
res: FractionalResolution,
ns: u64,
) -> Option<FractionalPart> {
pub fn fractional_part_from_subsec_ns(res: FractionalResolution, ns: u64) -> FractionalPart {
if res == FractionalResolution::Seconds {
return None;
return FractionalPart::new_with_seconds_resolution();
}
let sec_as_ns = 10_u64.pow(9);
if ns > sec_as_ns {
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
// floating point division.
let fractional_part = ns * (resolution + 1) / sec_as_ns;
Some(FractionalPart(res, fractional_part as u32))
let fractional_counter = ns * (resolution_divisor + 1) / sec_as_ns;
FractionalPart {
resolution: res,
counter: fractional_counter as u32,
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@ -146,9 +146,57 @@ impl Error for CucError {}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WidthCounterPair(u8, u32);
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[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
/// and a small preamble field.
@ -202,7 +250,7 @@ pub struct FractionalPart(FractionalResolution, u32);
pub struct CucTime {
pfield: u8,
counter: WidthCounterPair,
fractions: Option<FractionalPart>,
fractions: FractionalPart,
}
/// 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.
pub fn new(counter: u32) -> Self {
// 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.
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
@ -245,10 +297,10 @@ impl CucTime {
// These values are definitely valid, so it is okay to unwrap here.
Self::new_generic(
WidthCounterPair(4, counter),
Some(FractionalPart(
FractionalResolution::FourMs,
subsec_fractions as u32,
)),
FractionalPart {
resolution: FractionalResolution::FourMs,
counter: subsec_fractions as u32,
},
)
.unwrap()
}
@ -258,10 +310,10 @@ impl CucTime {
// These values are definitely valid, so it is okay to unwrap here.
Self::new_generic(
WidthCounterPair(4, counter),
Some(FractionalPart(
FractionalResolution::FifteenUs,
subsec_fractions as u32,
)),
FractionalPart {
resolution: FractionalResolution::FifteenUs,
counter: subsec_fractions as u32,
},
)
.unwrap()
}
@ -272,10 +324,10 @@ impl CucTime {
pub fn new_with_fine_fractions(counter: u32, subsec_fractions: u32) -> Result<Self, CucError> {
Self::new_generic(
WidthCounterPair(4, counter),
Some(FractionalPart(
FractionalResolution::SixtyNs,
subsec_fractions,
)),
FractionalPart {
resolution: FractionalResolution::SixtyNs,
counter: subsec_fractions,
},
)
}
@ -308,7 +360,7 @@ impl CucTime {
}
let fractions =
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()))
}
@ -328,11 +380,12 @@ impl CucTime {
.1
.checked_add(leap_seconds)
.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.unwrap().0,
self.fractions.resolution,
now.subsec_nanos() as u64,
);
return Ok(());
}
Ok(())
}
@ -386,7 +439,11 @@ impl CucTime {
pub fn new_u16_counter(counter: u16) -> Self {
// These values are definitely valid, so it is okay to unwrap here.
Self::new_generic(WidthCounterPair(2, counter as u32), None).unwrap()
Self::new_generic(
WidthCounterPair(2, counter as u32),
FractionalPart::new_with_seconds_resolution(),
)
.unwrap()
}
pub fn ccsds_time_code(&self) -> CcsdsTimeCode {
@ -405,7 +462,8 @@ impl CucTime {
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
}
@ -415,7 +473,7 @@ impl CucTime {
pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> {
Self::verify_fractions_value(fractions)?;
self.fractions = Some(fractions);
self.fractions = fractions;
self.update_p_field_fractions();
Ok(())
}
@ -424,22 +482,16 @@ impl CucTime {
/// to 0 if the resolution changes.
pub fn set_fractional_resolution(&mut self, res: FractionalResolution) {
if res == FractionalResolution::Seconds {
self.fractions = None;
self.fractions = FractionalPart::new_with_seconds_resolution();
}
let mut update_fractions = true;
if let Some(existing_fractions) = self.fractions {
if existing_fractions.0 == res {
update_fractions = false;
}
};
if update_fractions {
self.fractions = Some(FractionalPart(res, 0));
if res != self.fractions().resolution() {
self.fractions = FractionalPart::new(res, 0);
}
}
pub fn new_generic(
counter: WidthCounterPair,
fractions: Option<FractionalPart>,
fractions: FractionalPart,
) -> Result<Self, CucError> {
Self::verify_counter_width(counter.0)?;
if counter.1 > (2u64.pow(counter.0 as u32 * 8) - 1) as u32 {
@ -448,17 +500,15 @@ impl CucTime {
counter: counter.1.into(),
});
}
if let Some(fractions) = fractions {
Self::verify_fractions_value(fractions)?;
}
Self::verify_fractions_value(fractions)?;
Ok(Self {
pfield: Self::build_p_field(counter.0, fractions.map(|v| v.0)),
pfield: Self::build_p_field(counter.0, fractions.resolution),
counter,
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;
if !(1..=4).contains(&counter_width) {
// 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);
}
pfield |= (counter_width - 1) << 2;
if let Some(fractions_width) = fractions_width {
if !(1..=3).contains(&(fractions_width as u8)) {
if resolution != FractionalResolution::Seconds {
if !(1..=3).contains(&(resolution as u8)) {
// Okay to panic here, this function is private and all input values should
// have been sanitized
panic!(
"invalid fractions width {:?} for cuc timestamp",
fractions_width
);
panic!("invalid fractions width {:?} for cuc timestamp", resolution);
}
pfield |= fractions_width as u8;
pfield |= resolution as u8;
}
pfield
}
fn update_p_field_fractions(&mut self) {
self.pfield &= !(0b11);
if let Some(fractions) = self.fractions {
self.pfield |= fractions.0 as u8;
}
self.pfield |= self.fractions.resolution() as u8;
}
#[inline]
@ -542,10 +587,10 @@ impl CucTime {
}
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 {
resolution: val.0,
value: val.1 as u64,
resolution: val.resolution,
value: val.counter as u64,
});
}
Ok(())
@ -556,14 +601,11 @@ impl CucTime {
}
fn subsec_nanos(&self) -> u32 {
if let Some(fractions) = self.fractions {
if fractions.0 == FractionalResolution::Seconds {
return 0;
}
// Rounding down here is the correct approach.
return convert_fractional_part_to_ns(fractions) as u32;
if self.fractions.resolution() == FractionalResolution::Seconds {
return 0;
}
0
// Rounding down here is the correct approach.
convert_fractional_part_to_ns(self.fractions) as u32
}
}
@ -616,29 +658,29 @@ impl TimeReader for CucTime {
_ => panic!("unreachable match arm"),
};
current_idx += cntr_len as usize;
let mut fractions = None;
let mut fractions = FractionalPart::new_with_seconds_resolution();
if fractions_len > 0 {
match fractions_len {
1 => {
fractions = Some(FractionalPart(
fractions = FractionalPart::new(
fractions_len.try_into().unwrap(),
buf[current_idx] as u32,
))
)
}
2 => {
fractions = Some(FractionalPart(
fractions = FractionalPart::new(
fractions_len.try_into().unwrap(),
u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap())
as u32,
))
)
}
3 => {
let mut tmp_buf: [u8; 4] = [0; 4];
tmp_buf[1..4].copy_from_slice(&buf[current_idx..current_idx + 3]);
fractions = Some(FractionalPart(
fractions = FractionalPart::new(
fractions_len.try_into().unwrap(),
u32::from_be_bytes(tmp_buf),
))
)
}
_ => panic!("unreachable match arm"),
}
@ -680,18 +722,15 @@ impl TimeWriter for CucTime {
_ => panic!("invalid counter width value"),
}
current_idx += self.counter.0 as usize;
if let Some(fractions) = self.fractions {
match fractions.0 {
FractionalResolution::FourMs => bytes[current_idx] = fractions.1 as u8,
FractionalResolution::FifteenUs => bytes[current_idx..current_idx + 2]
.copy_from_slice(&(fractions.1 as u16).to_be_bytes()),
FractionalResolution::SixtyNs => bytes[current_idx..current_idx + 3]
.copy_from_slice(&fractions.1.to_be_bytes()[1..4]),
// Should also never happen
_ => panic!("invalid fractions value"),
}
current_idx += fractions.0 as usize;
match self.fractions.resolution() {
FractionalResolution::FourMs => bytes[current_idx] = self.fractions.counter as u8,
FractionalResolution::FifteenUs => bytes[current_idx..current_idx + 2]
.copy_from_slice(&(self.fractions.counter as u16).to_be_bytes()),
FractionalResolution::SixtyNs => bytes[current_idx..current_idx + 3]
.copy_from_slice(&self.fractions.counter.to_be_bytes()[1..4]),
_ => (),
}
current_idx += self.fractions.resolution as usize;
Ok(current_idx)
}
@ -722,11 +761,12 @@ impl CcsdsTimeProvider for CucTimeWithLeapSecs {
}
}
fn get_provider_values_after_duration_addition(
provider: &CucTime,
// TODO: Introduce more overflow checks here.
fn get_time_values_after_duration_addition(
time: &CucTime,
duration: Duration,
) -> (u32, Option<FractionalPart>) {
let mut new_counter = provider.counter.1;
) -> (u32, FractionalPart) {
let mut new_counter = time.counter.1;
let subsec_nanos = duration.subsec_nanos();
let mut increment_counter = |amount: u32| {
let mut sum: u64 = 0;
@ -738,7 +778,7 @@ fn get_provider_values_after_duration_addition(
}
new_counter = sum as u32;
};
match provider.counter.0 {
match time.counter.0 {
1 => counter_inc_handler(u8::MAX as u64),
2 => counter_inc_handler(u16::MAX as u64),
3 => counter_inc_handler((2_u32.pow(24) - 1) as u64),
@ -749,25 +789,21 @@ fn get_provider_values_after_duration_addition(
}
}
};
let fractional_part = if let Some(fractional_part) = &provider.fractions {
let fractional_increment =
fractional_part_from_subsec_ns(fractional_part.0, subsec_nanos as u64).unwrap();
let mut increment_fractions = |resolution| {
let mut new_fractions = fractional_part.1 + fractional_increment.1;
let max_fractions = fractional_res_to_div(resolution);
if new_fractions > max_fractions {
increment_counter(1);
new_fractions -= max_fractions;
}
Some(FractionalPart(resolution, new_fractions))
};
match fractional_increment.0 {
FractionalResolution::Seconds => None,
_ => increment_fractions(fractional_increment.0),
let resolution = time.fractions().resolution();
let fractional_increment = fractional_part_from_subsec_ns(resolution, subsec_nanos as u64);
let mut fractional_part = FractionalPart::new_with_seconds_resolution();
if resolution != FractionalResolution::Seconds {
let mut new_fractions = time.fractions().counter() + fractional_increment.counter;
let max_fractions = fractional_res_to_div(resolution);
if new_fractions > max_fractions {
increment_counter(1);
new_fractions -= max_fractions;
}
} else {
None
};
fractional_part = FractionalPart {
resolution,
counter: new_fractions,
}
}
increment_counter(duration.as_secs() as u32);
(new_counter, fractional_part)
}
@ -775,11 +811,9 @@ fn get_provider_values_after_duration_addition(
impl AddAssign<Duration> for CucTime {
fn add_assign(&mut self, duration: Duration) {
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;
if self.fractions.is_some() {
self.fractions = new_fractional_part;
}
self.fractions = new_fractional_part;
}
}
@ -788,12 +822,9 @@ impl Add<Duration> for CucTime {
fn add(self, duration: Duration) -> Self::Output {
let (new_counter, new_fractional_part) =
get_provider_values_after_duration_addition(&self, duration);
if let Some(fractional_part) = new_fractional_part {
// The generated fractional part should always be valid, so its okay to unwrap here.
return Self::new_with_fractions(new_counter, fractional_part).unwrap();
}
Self::new(new_counter)
get_time_values_after_duration_addition(&self, duration);
// The generated fractional part should always be valid, so its okay to unwrap here.
Self::new_with_fractions(new_counter, new_fractional_part).unwrap()
}
}
@ -802,12 +833,9 @@ impl Add<Duration> for &CucTime {
fn add(self, duration: Duration) -> Self::Output {
let (new_counter, new_fractional_part) =
get_provider_values_after_duration_addition(self, duration);
if let Some(fractional_part) = new_fractional_part {
// The generated fractional part should always be valid, so its okay to unwrap here.
return Self::Output::new_with_fractions(new_counter, fractional_part).unwrap();
}
Self::Output::new(new_counter)
get_time_values_after_duration_addition(self, duration);
// The generated fractional part should always be valid, so its okay to unwrap here.
Self::Output::new_with_fractions(new_counter, new_fractional_part).unwrap()
}
}
@ -835,8 +863,8 @@ mod tests {
let counter = zero_cuc.width_counter_pair();
assert_eq!(counter.0, 4);
assert_eq!(counter.1, 0);
let fractions = zero_cuc.width_fractions_pair();
assert!(fractions.is_none());
let fractions = zero_cuc.fractions();
assert_eq!(fractions, FractionalPart::new_with_seconds_resolution());
let dt = ccsds_cuc.chrono_date_time();
if let chrono::LocalResult::Single(dt) = dt {
assert_eq!(dt.year(), 1958);
@ -851,7 +879,8 @@ mod tests {
#[test]
fn test_write_no_fractions() {
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());
let zero_cuc = zero_cuc.unwrap();
let res = zero_cuc.write_to_bytes(&mut buf);
@ -893,12 +922,16 @@ mod tests {
#[test]
fn test_read_no_fractions() {
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();
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.width_counter_pair().1, 0x20102030);
assert_eq!(cuc_read_back.width_fractions_pair(), None);
assert_eq!(cuc_read_back.fractions(), FractionalPart::new_empty());
}
#[test]
@ -937,7 +970,7 @@ mod tests {
#[test]
fn write_and_read_tiny_stamp() {
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());
let cuc = cuc.unwrap();
assert_eq!(cuc.len_as_bytes(), 2);
@ -955,7 +988,7 @@ mod tests {
#[test]
fn write_slightly_larger_stamp() {
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());
let cuc = cuc.unwrap();
assert_eq!(cuc.len_as_bytes(), 3);
@ -976,7 +1009,10 @@ mod tests {
#[test]
fn write_read_three_byte_cntr_stamp() {
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());
let cuc = cuc.unwrap();
assert_eq!(cuc.len_as_bytes(), 4);
@ -1033,9 +1069,8 @@ mod tests {
fn test_write_with_coarse_fractions() {
let mut buf: [u8; 16] = [0; 16];
let cuc = CucTime::new_with_coarse_fractions(0x30201060, 120);
assert!(cuc.fractions.is_some());
assert_eq!(cuc.fractions.unwrap().1, 120);
assert_eq!(cuc.fractions.unwrap().0, FractionalResolution::FourMs);
assert_eq!(cuc.fractions().counter(), 120);
assert_eq!(cuc.fractions().resolution(), FractionalResolution::FourMs);
let res = cuc.write_to_bytes(&mut buf);
assert!(res.is_ok());
let written = res.unwrap();
@ -1115,44 +1150,49 @@ mod tests {
#[test]
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.
assert_eq!(ns, 7843137);
// This is the largest value we should be able to pass without this function panicking.
let ns = convert_fractional_part_to_ns(FractionalPart(
FractionalResolution::SixtyNs,
2_u32.pow(24) - 2,
));
let ns = convert_fractional_part_to_ns(FractionalPart {
resolution: FractionalResolution::SixtyNs,
counter: 2_u32.pow(24) - 2,
});
assert_eq!(ns, 999999940);
}
#[test]
#[should_panic]
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]
#[should_panic]
fn test_fractional_converter_invalid_input_2() {
convert_fractional_part_to_ns(FractionalPart(
FractionalResolution::SixtyNs,
2_u32.pow(32) - 1,
));
convert_fractional_part_to_ns(FractionalPart {
resolution: FractionalResolution::SixtyNs,
counter: 2_u32.pow(32) - 1,
});
}
#[test]
fn fractional_part_formula() {
let fractional_part =
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 7843138).unwrap();
assert_eq!(fractional_part.1, 2);
let fractional_part = fractional_part_from_subsec_ns(FractionalResolution::FourMs, 7843138);
assert_eq!(fractional_part.counter, 2);
}
#[test]
fn fractional_part_formula_2() {
let fractional_part =
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 12000000).unwrap();
assert_eq!(fractional_part.1, 3);
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 12000000);
assert_eq!(fractional_part.counter, 3);
}
#[test]
@ -1165,86 +1205,86 @@ mod tests {
let fractional_part = fractional_part_from_subsec_ns(
FractionalResolution::FifteenUs,
hundred_fractions_and_some,
)
.unwrap();
assert_eq!(fractional_part.1, 100);
);
assert_eq!(fractional_part.counter, 100);
// Using exactly 101.0 can yield values which will later be rounded down to 100
let hundred_and_one_fractions =
(101.001 * one_fraction_with_width_two_in_ns).floor() as u64;
let fractional_part = fractional_part_from_subsec_ns(
FractionalResolution::FifteenUs,
hundred_and_one_fractions,
)
.unwrap();
assert_eq!(fractional_part.1, 101);
);
assert_eq!(fractional_part.counter, 101);
}
#[test]
fn update_fractions() {
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!(stamp.fractions.is_some());
let fractions = stamp.fractions.unwrap();
assert_eq!(fractions.0, FractionalResolution::SixtyNs);
assert_eq!(fractions.1, 5000);
assert_eq!(
stamp.fractions().resolution(),
FractionalResolution::SixtyNs
);
assert_eq!(stamp.fractions().counter(), 5000);
}
#[test]
fn set_fract_resolution() {
let mut stamp = CucTime::new(2000);
stamp.set_fractional_resolution(FractionalResolution::SixtyNs);
assert!(stamp.fractions.is_some());
let fractions = stamp.fractions.unwrap();
assert_eq!(fractions.0, FractionalResolution::SixtyNs);
assert_eq!(fractions.1, 0);
assert_eq!(
stamp.fractions().resolution(),
FractionalResolution::SixtyNs
);
assert_eq!(stamp.fractions().counter(), 0);
let res = stamp.update_from_now(LEAP_SECONDS);
assert!(res.is_ok());
}
#[test]
fn test_small_fraction_floored_to_zero() {
let fractions = fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 59).unwrap();
assert_eq!(fractions.1, 0);
let fractions = fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 59);
assert_eq!(fractions.counter, 0);
}
#[test]
fn test_small_fraction_becomes_fractional_part() {
let fractions = fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 61).unwrap();
assert_eq!(fractions.1, 1);
let fractions = fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 61);
assert_eq!(fractions.counter, 1);
}
#[test]
fn test_smallest_resolution_small_nanoseconds_floored_to_zero() {
let fractions =
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 3800 * 1e3 as u64)
.unwrap();
assert_eq!(fractions.1, 0);
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 3800 * 1e3 as u64);
assert_eq!(fractions.counter, 0);
}
#[test]
fn test_smallest_resolution_small_nanoseconds_becomes_one_fraction() {
let fractions =
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 4000 * 1e3 as u64)
.unwrap();
assert_eq!(fractions.1, 1);
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 4000 * 1e3 as u64);
assert_eq!(fractions.counter, 1);
}
#[test]
fn test_smallest_resolution_large_nanoseconds_becomes_largest_fraction() {
let fractions =
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 10u64.pow(9) - 1).unwrap();
assert_eq!(fractions.1, 2_u32.pow(8) - 1);
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 10u64.pow(9) - 1);
assert_eq!(fractions.counter, 2_u32.pow(8) - 1);
}
#[test]
fn test_largest_fractions_with_largest_resolution() {
let fractions =
fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 10u64.pow(9) - 1)
.unwrap();
fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 10u64.pow(9) - 1);
// The value can not be larger than representable by 3 bytes
// 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) {
@ -1254,14 +1294,14 @@ mod tests {
CcsdsTimeCode::CucCcsdsEpoch
);
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 =
(0.5 * fractional_res_to_div(FractionalResolution::FifteenUs) as f64).ceil() as u32;
assert_eq!(fractions, expected_val);
let cuc_stamp2 = cuc_stamp + Duration::from_millis(501);
// What I would roughly expect
assert_eq!(cuc_stamp2.counter.1, 203);
assert!(cuc_stamp2.fractions.unwrap().1 < 100);
assert!(cuc_stamp2.fractions().counter() < 100);
assert!(cuc_stamp2.subsec_millis() <= 1);
}
@ -1289,7 +1329,7 @@ mod tests {
let duration = Duration::from_millis(2000);
cuc_stamp += duration;
assert_eq!(cuc_stamp.counter(), 202);
assert_eq!(cuc_stamp.width_fractions_pair(), None);
assert_eq!(cuc_stamp.fractions(), FractionalPart::new_empty());
}
#[test]
@ -1298,11 +1338,12 @@ mod tests {
let duration = Duration::from_millis(2000);
let new_stamp = cuc_stamp + duration;
assert_eq!(new_stamp.counter(), 202);
assert_eq!(new_stamp.width_fractions_pair(), None);
assert_eq!(new_stamp.fractions(), FractionalPart::new_empty());
}
#[test]
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);
cuc_stamp += duration;
assert_eq!(cuc_stamp.counter.1, 10);
@ -1310,7 +1351,7 @@ mod tests {
#[test]
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());
let error = error.unwrap_err();
if let CucError::InvalidCounterWidth(width) = error {
@ -1350,7 +1391,7 @@ mod tests {
#[test]
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());
let cuc_error = cuc_error.unwrap_err();
if let CucError::InvalidCounter { width, counter } = cuc_error {