Add CUC impl #4

Merged
muellerr merged 28 commits from add_cuc_time_impl into main 2023-01-09 11:14:42 +01:00
2 changed files with 157 additions and 25 deletions
Showing only changes of commit 5958d19eb4 - Show all commits

View File

@ -3,13 +3,53 @@ use core::fmt::Debug;
const MIN_CUC_LEN: usize = 2;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FractionalResolution {
/// No fractional part, only second resolution
Seconds = 0,
/// 256 fractional parts, resulting in 1/255 ~= 4 ms fractional resolution
FourMs = 1,
/// 65535 fractional parts, resulting in 1/65535 ~= 15 us fractional resolution
FifteenUs = 2,
/// 16777215 fractional parts, resulting in 1/16777215 ~= 60 ns fractional resolution
SixtyNs = 3,
}
impl TryFrom<u8> for FractionalResolution {
type Error = ();
fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
0 => Ok(FractionalResolution::Seconds),
1 => Ok(FractionalResolution::FourMs),
2 => Ok(FractionalResolution::FifteenUs),
3 => Ok(FractionalResolution::SixtyNs),
_ => Err(()),
}
}
}
/// Please note that this function will panic if the fractional value is not smaller than
/// the maximum number of fractions allowed for the particular resolution.
/// (e.g. passing 270 when the resolution only allows 255 values).
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
}
pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 {
2_u32.pow(8 * res as u32) - 1
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CucError {
InvalidCounterWidth(u8),
InvalidFractionWidth(u8),
InvalidFractionResolution(FractionalResolution),
InvalidCounter(u8, u64),
InvalidFractions(u8, u64),
InvalidFractions(FractionalResolution, u64),
}
impl Display for CucError {
@ -18,14 +58,14 @@ impl Display for CucError {
CucError::InvalidCounterWidth(w) => {
write!(f, "invalid cuc counter byte width {}", w)
}
CucError::InvalidFractionWidth(w) => {
write!(f, "invalid cuc fractional part byte width {}", w)
CucError::InvalidFractionResolution(w) => {
write!(f, "invalid cuc fractional part byte width {:?}", w)
}
CucError::InvalidCounter(w, c) => {
write!(f, "invalid cuc counter {} for width {}", c, w)
}
CucError::InvalidFractions(w, c) => {
write!(f, "invalid cuc fractional part {} for width {}", c, w)
write!(f, "invalid cuc fractional part {} for width {:?}", c, w)
}
}
}
@ -37,6 +77,9 @@ 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);
/// This provider uses the CCSDS epoch. Furthermore the preamble field only has one byte,
/// which allows a time code representation through the year 2094.
@ -48,7 +91,7 @@ pub struct WidthCounterPair(u8, u32);
pub struct TimeProviderCcsdsEpoch {
pfield: u8,
counter: WidthCounterPair,
fractions: Option<WidthCounterPair>,
fractions: Option<FractionalPart>,
}
#[inline]
@ -60,7 +103,7 @@ pub fn pfield_len(pfield: u8) -> usize {
}
impl TimeProviderCcsdsEpoch {
fn build_p_field(counter_width: u8, fractions_width: Option<u8>) -> u8 {
fn build_p_field(counter_width: u8, fractions_width: Option<FractionalResolution>) -> u8 {
let mut pfield = (CcsdsTimeCodes::CucCcsdsEpoch as u8) << 4;
if !(1..=4).contains(&counter_width) {
// Okay to panic here, this function is private and all input values should
@ -69,15 +112,15 @@ impl TimeProviderCcsdsEpoch {
}
pfield |= (counter_width - 1) << 3;
if let Some(fractions_width) = fractions_width {
if !(1..=3).contains(&fractions_width) {
if !(1..=3).contains(&(fractions_width 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",
"invalid fractions width {:?} for cuc timestamp",
fractions_width
);
}
pfield |= fractions_width;
pfield |= fractions_width as u8;
}
pfield
}
@ -85,7 +128,7 @@ impl TimeProviderCcsdsEpoch {
fn update_p_field_fractions(&mut self) {
self.pfield &= !(0b11);
if let Some(fractions) = self.fractions {
self.pfield |= fractions.0;
self.pfield |= fractions.0 as u8;
}
}
@ -136,14 +179,14 @@ impl TimeProviderCcsdsEpoch {
Ok(())
}
fn verify_fractions_width(width: u8) -> Result<(), CucError> {
if width > 3 {
return Err(CucError::InvalidFractionWidth(width));
fn verify_fractions_width(width: FractionalResolution) -> Result<(), CucError> {
if width as u8 > 3 {
return Err(CucError::InvalidFractionResolution(width));
}
Ok(())
}
fn verify_fractions_value(val: WidthCounterPair) -> Result<(), CucError> {
fn verify_fractions_value(val: FractionalPart) -> Result<(), CucError> {
if val.1 > 2u32.pow((val.0 as u32) * 8) - 1 {
return Err(CucError::InvalidFractions(val.0, val.1 as u64));
}
@ -156,12 +199,41 @@ impl TimeProviderCcsdsEpoch {
// These values are definitely valid, so it is okay to unwrap here.
Self::new(WidthCounterPair(4, counter), None).unwrap()
}
pub fn new_u16_counter(counter: u16) -> Self {
// These values are definitely valid, so it is okay to unwrap here.
Self::new(WidthCounterPair(2, counter as u32), None).unwrap()
}
pub fn set_fractions(&mut self, fractions: WidthCounterPair) -> Result<(), CucError> {
/// 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
/// when using less than that.
#[cfg(feature = "std")]
pub fn from_now(fraction_resolution: FractionalResolution) -> Result<Self, StdTimestampError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs());
let mut fractions = None;
if fraction_resolution != FractionalResolution::Seconds {
let fractional_part = 10_u64.pow(9) * now.subsec_nanos() as u64
/ fractional_res_to_div(fraction_resolution) as u64;
fractions = Some(FractionalPart(fraction_resolution, fractional_part as u32));
}
Ok(Self {
pfield: 0,
counter: WidthCounterPair(4, ccsds_epoch as u32),
fractions,
})
}
pub fn width_counter_pair(&self) -> WidthCounterPair {
self.counter
}
pub fn width_fractions_pair(&self) -> Option<FractionalPart> {
self.fractions
}
pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> {
Self::verify_fractions_width(fractions.0)?;
Self::verify_fractions_value(fractions)?;
self.fractions = Some(fractions);
@ -171,7 +243,7 @@ impl TimeProviderCcsdsEpoch {
pub fn new(
counter: WidthCounterPair,
fractions: Option<WidthCounterPair>,
fractions: Option<FractionalPart>,
) -> Result<Self, CucError> {
Self::verify_counter_width(counter.0)?;
if counter.1 > 2u32.pow(counter.0 as u32 * 8) - 1 {
@ -228,10 +300,15 @@ impl TimeReader for TimeProviderCcsdsEpoch {
let mut fractions = None;
if fractions_len > 0 {
match fractions_len {
1 => fractions = Some(WidthCounterPair(fractions_len, buf[current_idx] as u32)),
1 => {
fractions = Some(FractionalPart(
fractions_len.try_into().unwrap(),
buf[current_idx] as u32,
))
}
2 => {
fractions = Some(WidthCounterPair(
fractions_len,
fractions = Some(FractionalPart(
fractions_len.try_into().unwrap(),
u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap())
as u32,
))
@ -239,8 +316,8 @@ impl TimeReader for TimeProviderCcsdsEpoch {
3 => {
let mut tmp_buf: [u8; 4] = [0; 4];
tmp_buf[1..4].copy_from_slice(&buf[current_idx..current_idx + 3]);
fractions = Some(WidthCounterPair(
fractions_len,
fractions = Some(FractionalPart(
fractions_len.try_into().unwrap(),
u32::from_be_bytes(tmp_buf) as u32,
))
}
@ -286,10 +363,10 @@ impl TimeWriter for TimeProviderCcsdsEpoch {
current_idx += self.counter.0 as usize;
if let Some(fractions) = self.fractions {
match fractions.0 {
1 => bytes[current_idx] = fractions.1 as u8,
2 => bytes[current_idx..current_idx + 2]
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()),
3 => bytes[current_idx..current_idx + 3]
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"),
@ -299,3 +376,39 @@ impl TimeWriter for TimeProviderCcsdsEpoch {
Ok(current_idx)
}
}
#[cfg(test)]
mod tests {
use crate::time::cuc::{convert_fractional_part_to_ns, FractionalPart, FractionalResolution};
#[test]
fn test_basic() {}
#[test]
fn test_fractional_converter() {
let ns = convert_fractional_part_to_ns(FractionalPart(FractionalResolution::FourMs, 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,
));
assert_eq!(ns, 999999940);
}
#[test]
#[should_panic]
fn test_fractional_converter_invalid_input() {
convert_fractional_part_to_ns(FractionalPart(FractionalResolution::FourMs, 256));
}
#[test]
#[should_panic]
fn test_fractional_converter_invalid_input_2() {
convert_fractional_part_to_ns(FractionalPart(
FractionalResolution::SixtyNs,
2_u32.pow(32) - 1,
));
}
}

View File

@ -163,6 +163,12 @@ pub const fn ccsds_to_unix_days(ccsds_days: i64) -> i64 {
ccsds_days + DAYS_CCSDS_TO_UNIX as i64
}
/// Similar to [unix_to_ccsds_days] but converts the epoch instead, which is the number of elpased
/// seconds since the CCSDS and UNIX epoch times.
pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: u64) -> u64 {
(unix_epoch as i64 - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)) as u64
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn ms_of_day_using_sysclock() -> u32 {
@ -218,4 +224,17 @@ mod tests {
let sec_floats = seconds_since_epoch();
assert!(sec_floats > 0.0);
}
#[test]
fn test_ccsds_epoch() {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
let unix_epoch = now.as_secs();
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs());
assert!(ccsds_epoch > unix_epoch);
assert_eq!((ccsds_epoch - unix_epoch) % SECONDS_PER_DAY as u64, 0);
let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64;
assert_eq!(days_diff, -DAYS_CCSDS_TO_UNIX as u64);
}
}