From c4f8eee8daf354524d7ebdd273b006818fc0e6c8 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 8 Dec 2022 14:59:12 +0100 Subject: [PATCH] base line cuc impl done --- src/time.rs | 340 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 260 insertions(+), 80 deletions(-) diff --git a/src/time.rs b/src/time.rs index 923066d..894295e 100644 --- a/src/time.rs +++ b/src/time.rs @@ -7,15 +7,14 @@ use core::fmt::{Display, Formatter}; #[cfg(not(feature = "std"))] use num_traits::float::FloatCore; +use crate::time::cuc::CucError; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "std")] use std::time::{SystemTime, SystemTimeError}; -use crate::time::cds::LengthOfDaySegment; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - pub const DAYS_CCSDS_TO_UNIX: i32 = -4383; pub const SECONDS_PER_DAY: u32 = 86400; @@ -51,36 +50,6 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result { CcsdsTimeCodes::try_from(raw_bits).map_err(|_| raw_bits) } -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum CdsError { - /// CCSDS days value exceeds maximum allowed size or is negative - InvalidCcsdsDays(i64), - /// There are distinct constructors depending on the days field width detected in the preamble - /// field. This error will be returned if there is a missmatch. - InvalidCtorForDaysOfLenInPreamble(LengthOfDaySegment), -} - -impl Display for CdsError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self { - CdsError::InvalidCcsdsDays(days) => { - write!(f, "invalid ccsds days {}", days) - } - CdsError::InvalidCtorForDaysOfLenInPreamble(length_of_day) => { - write!( - f, - "wrong constructor for length of day {:?} detected in preamble", - length_of_day - ) - } - } - } -} - -#[cfg(feature = "std")] -impl Error for CdsError {} - #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TimestampError { @@ -88,13 +57,20 @@ pub enum TimestampError { /// value is the found raw value InvalidTimeCode(CcsdsTimeCodes, u8), ByteConversionError(ByteConversionError), - CdsError(CdsError), + CdsError(cds::CdsError), + CucError(cuc::CucError), CustomEpochNotSupported, } -impl From for TimestampError { - fn from(v: CdsError) -> Self { - TimestampError::CdsError(v) +impl From for TimestampError { + fn from(e: cds::CdsError) -> Self { + TimestampError::CdsError(e) + } +} + +impl From for TimestampError { + fn from(e: CucError) -> Self { + TimestampError::CucError(e) } } @@ -133,6 +109,9 @@ impl Display for TimestampError { TimestampError::CdsError(e) => { write!(f, "cds error {}", e) } + TimestampError::CucError(e) => { + write!(f, "cuc error {}", e) + } TimestampError::ByteConversionError(e) => { write!(f, "byte conversion error {}", e) } @@ -149,6 +128,7 @@ impl Error for TimestampError { match self { TimestampError::ByteConversionError(e) => Some(e), TimestampError::CdsError(e) => Some(e), + TimestampError::CucError(e) => Some(e), _ => None, } } @@ -270,6 +250,35 @@ pub mod cds { Reserved, } + #[derive(Debug, PartialEq, Eq, Copy, Clone)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + pub enum CdsError { + /// CCSDS days value exceeds maximum allowed size or is negative + InvalidCcsdsDays(i64), + /// There are distinct constructors depending on the days field width detected in the preamble + /// field. This error will be returned if there is a missmatch. + InvalidCtorForDaysOfLenInPreamble(LengthOfDaySegment), + } + + impl Display for CdsError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + CdsError::InvalidCcsdsDays(days) => { + write!(f, "invalid ccsds days {}", days) + } + CdsError::InvalidCtorForDaysOfLenInPreamble(length_of_day) => { + write!( + f, + "wrong constructor for length of day {:?} detected in preamble", + length_of_day + ) + } + } + } + } + + #[cfg(feature = "std")] + impl Error for CdsError {} pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment { if (pfield >> 2) & 0b1 == 1 { return LengthOfDaySegment::Long24Bits; @@ -816,8 +825,41 @@ pub mod cds { } pub mod cuc { - use core::fmt::Debug; use super::*; + 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 CucError { + InvalidCounterWidth(u8), + InvalidFractionWidth(u8), + InvalidCounter(u8, u64), + InvalidFractions(u8, u64), + } + + impl Display for CucError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + CucError::InvalidCounterWidth(w) => { + write!(f, "invalid cuc counter byte width {}", w) + } + CucError::InvalidFractionWidth(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) + } + } + } + } + + #[cfg(feature = "std")] + impl Error for CucError {} #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -833,7 +875,7 @@ pub mod cuc { pub struct TimeProviderCcsdsEpoch { pfield: u8, counter: WidthCounterPair, - fractions: Option + fractions: Option, } #[inline] @@ -841,53 +883,183 @@ pub mod cuc { if ((pfield >> 7) & 0b1) == 1 { return 2; } - return 1 + 1 } impl TimeProviderCcsdsEpoch { fn build_p_field(counter_width: u8, fractions_width: Option) -> u8 { let mut pfield = (CcsdsTimeCodes::CucCcsdsEpoch as u8) << 4; - if counter_width < 1 || counter_width > 4 { + if !(1..=4).contains(&counter_width) { + // Okay to panic here, this function is private and all input values should + // have been sanitized panic!("invalid counter width {} for cuc timestamp", counter_width); } - pfield |= counter_width << 3; + pfield |= (counter_width - 1) << 3; if let Some(fractions_width) = fractions_width { - if fractions_width < 1 || fractions_width > 3 { - panic!("invalid fractions width {} for cuc timestamp", fractions_width); + if !(1..=3).contains(&fractions_width) { + // 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 + ); } pfield |= fractions_width; } pfield } - pub fn len_packed(&self) -> usize { - // TODO: Implement - todo!() + Self::len_packed_from_pfield(self.pfield) + } + + #[inline] + pub fn len_cntr_from_pfield(pfield: u8) -> u8 { + ((pfield >> 2) & 0b11) + 1 + } + + #[inline] + pub fn len_fractions_from_pfield(pfield: u8) -> u8 { + pfield & 0b11 + } + + /// This returns the length of the individual components of the CUC timestamp in addition + /// to the total size. + /// + /// This function will return a tuple where the first value is the byte width of the + /// counter, the second value is the byte width of the fractional part, and the third + /// components is the total size. + pub fn len_components_and_total_from_pfield(pfield: u8) -> (u8, u8, usize) { + let base_len: usize = 1; + let cntr_len = Self::len_cntr_from_pfield(pfield); + let fractions_len = Self::len_fractions_from_pfield(pfield); + ( + cntr_len, + fractions_len, + base_len + cntr_len as usize + fractions_len as usize, + ) + } + + pub fn len_packed_from_pfield(pfield: u8) -> usize { + let mut base_len: usize = 1; + base_len += Self::len_cntr_from_pfield(pfield) as usize; + base_len += Self::len_fractions_from_pfield(pfield) as usize; + base_len + } + + /// Verifies the raw width parameter and returns the actual length, which is the raw + /// value plus 1. + fn verify_counter_width(width: u8) -> Result<(), CucError> { + if width == 0 || width > 4 { + return Err(CucError::InvalidCounterWidth(width)); + } + Ok(()) + } + + fn verify_fractions_width(width: u8) -> Result<(), CucError> { + if width > 3 { + return Err(CucError::InvalidFractionWidth(width)); + } + Ok(()) } } impl TimeProviderCcsdsEpoch { pub fn new_default(counter: u32) -> Self { - Self::new(WidthCounterPair(4, counter), None) + // 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 new(counter: WidthCounterPair, fractions: Option) -> Self { - let fractions_width = match fractions { - None => 0, - Some(fractions) => fractions.0 - }; - Self { - pfield: Self::build_p_field(counter.0.into(), fractions_width.into()), - counter, - fractions + pub fn new( + counter: WidthCounterPair, + fractions: Option, + ) -> Result { + Self::verify_counter_width(counter.0)?; + if counter.1 > 2u32.pow(counter.0 as u32 * 8) - 1 { + return Err(CucError::InvalidCounter(counter.0, counter.1 as u64)); } + if let Some(fractions) = fractions { + Self::verify_fractions_width(fractions.0)?; + if fractions.1 > 2u32.pow((fractions.0 as u32) * 8) - 1 { + return Err(CucError::InvalidFractions(fractions.0, fractions.1 as u64)); + } + } + Ok(Self { + pfield: Self::build_p_field(counter.0, fractions.map(|v| v.0)), + counter, + fractions, + }) } } impl TimeReader for TimeProviderCcsdsEpoch { - fn from_bytes(_buf: &[u8]) -> Result where Self: Sized { - todo!() + fn from_bytes(buf: &[u8]) -> Result + where + Self: Sized, + { + if buf.len() < MIN_CUC_LEN { + return Err(TimestampError::ByteConversionError( + ByteConversionError::FromSliceTooSmall(SizeMissmatch { + expected: MIN_CUC_LEN, + found: buf.len(), + }), + )); + } + let (cntr_len, fractions_len, total_len) = + Self::len_components_and_total_from_pfield(buf[0]); + if buf.len() < total_len { + return Err(TimestampError::ByteConversionError( + ByteConversionError::FromSliceTooSmall(SizeMissmatch { + expected: total_len, + found: buf.len(), + }), + )); + } + let mut current_idx = 1; + let counter = + match cntr_len { + 1 => buf[current_idx] as u32, + 2 => 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]); + u32::from_be_bytes(tmp_buf) as u32 + } + 4 => u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()) + as u32, + _ => panic!("unreachable match arm"), + }; + current_idx += cntr_len as usize; + let mut fractions = None; + if fractions_len > 0 { + match fractions_len { + 1 => fractions = Some(WidthCounterPair(fractions_len, buf[current_idx] as u32)), + 2 => { + fractions = Some(WidthCounterPair( + fractions_len, + 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(WidthCounterPair( + fractions_len, + u32::from_be_bytes(tmp_buf) as u32, + )) + } + _ => panic!("unreachable match arm"), + } + } + let provider = Self::new(WidthCounterPair(cntr_len, counter), fractions)?; + Ok(provider) } } @@ -895,45 +1067,52 @@ pub mod cuc { fn write_to_bytes(&self, bytes: &mut [u8]) -> Result { // Cross check the sizes of the counters against byte widths in the ctor if bytes.len() < self.len_packed() { - return Err(TimestampError::ByteConversionError(ByteConversionError::ToSliceTooSmall(SizeMissmatch { - found: bytes.len(), - expected: self.len_packed() - }))) + return Err(TimestampError::ByteConversionError( + ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: bytes.len(), + expected: self.len_packed(), + }), + )); } bytes[0] = self.pfield; let mut current_idx: usize = 1; match self.counter.0 { 1 => { - bytes[1] = self.counter.1 as u8; - }, + bytes[current_idx] = self.counter.1 as u8; + } 2 => { - bytes[1..3].copy_from_slice(&(self.counter.1 as u16).to_be_bytes()); - }, + bytes[current_idx..current_idx + 2] + .copy_from_slice(&(self.counter.1 as u16).to_be_bytes()); + } 3 => { - bytes[1..4].copy_from_slice(&self.counter.1.to_be_bytes()[1..4]); - }, + bytes[current_idx..current_idx + 3] + .copy_from_slice(&self.counter.1.to_be_bytes()[1..4]); + } 4 => { - bytes[1..5].copy_from_slice(&self.counter.1.to_be_bytes()); - }, + bytes[current_idx..current_idx + 4] + .copy_from_slice(&self.counter.1.to_be_bytes()); + } // Should never happen - _ => panic!("invalid counter width value") + _ => panic!("invalid counter width value"), } 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].copy_from_slice(&(fractions.1 as u16).to_be_bytes()), - 3 => bytes[current_idx..current_idx + 3].copy_from_slice(&fractions.1.to_be_bytes()[1..4]), + 2 => bytes[current_idx..current_idx + 2] + .copy_from_slice(&(fractions.1 as u16).to_be_bytes()), + 3 => bytes[current_idx..current_idx + 3] + .copy_from_slice(&fractions.1.to_be_bytes()[1..4]), // Should also never happen - _ => panic!("invalid fractions value") + _ => panic!("invalid fractions value"), } current_idx += fractions.0 as usize; } Ok(current_idx) } } - } + /// Module to generate the ASCII timecodes specified in /// [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.5 . /// See [chrono::DateTime::format] for a usage example of the generated @@ -1006,7 +1185,7 @@ pub mod ascii { mod tests { use super::cds::TimeProvider; use super::*; - use crate::time::cds::{DaysLen16Bits, DaysLen24Bits, SubmillisPrecision}; + use crate::time::cds::{DaysLen16Bits, DaysLen24Bits, LengthOfDaySegment, SubmillisPrecision}; use crate::time::TimestampError::{ByteConversionError, InvalidTimeCode}; use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall}; use alloc::format; @@ -1163,8 +1342,9 @@ mod tests { let faulty_ctor = TimeProvider::::from_bytes(&buf); assert!(faulty_ctor.is_err()); let error = faulty_ctor.unwrap_err(); - if let TimestampError::CdsError(CdsError::InvalidCtorForDaysOfLenInPreamble(len_of_day)) = - error + if let TimestampError::CdsError(cds::CdsError::InvalidCtorForDaysOfLenInPreamble( + len_of_day, + )) = error { assert_eq!(len_of_day, LengthOfDaySegment::Long24Bits); } else {