18 Commits

Author SHA1 Message Date
a268903105 update NOTICE file
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
- specify that the code was developed at the IRS
2023-01-24 11:26:47 +01:00
0ce2568028 doc improvements
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 18:26:38 +01:00
b55fe9f443 Merge branch 'main' of https://egit.irs.uni-stuttgart.de/rust/spacepackets
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 16:58:05 +01:00
493a09e1a6 cargo fmt 2023-01-22 16:57:41 +01:00
f54cf69d87 use ISO8601 format in docs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 13:18:51 +01:00
f634a57f93 funny typo
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 13:11:43 +01:00
256407432d create release checklist
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 13:06:22 +01:00
c59b015a20 added additional tests
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 12:53:39 +01:00
e081504b33 Merge branch 'main' of https://egit.irs.uni-stuttgart.de/rust/spacepackets
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-22 12:51:23 +01:00
6eb1b1efbc bugfix, additional test and CHANGELOG bump 2023-01-22 12:50:49 +01:00
51d0a08e7b better document panics and tweak Add/AddAssign impl
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
for UnixTimestamp
2023-01-22 01:54:41 +01:00
6e557c2568 add Add impls for &
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-21 14:50:35 +01:00
fc76a975c1 add Add<Duration> for &UnixTimestamp
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-21 14:28:29 +01:00
74e489bd07 add basic tests and update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-21 13:17:38 +01:00
1e90793072 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-21 11:37:57 +01:00
ef55bc4a6e Improve time module
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-21 01:25:05 +01:00
55862a2433 docs typo
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-20 20:01:27 +01:00
9afc3fc8de Update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-20 19:48:20 +01:00
12 changed files with 457 additions and 32 deletions

View File

@ -8,6 +8,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.5.1] 2023-01-22
## Added
- `time::cds::TimeProvider`
- Add `Ord` and `PartialOrd`, use custom `PartialEq` impl to account for precision correctly.
- Add `precision_as_ns` function which converts microsecond and picosecond precision values
into nanoseconds.
- Add conversion trait to convert `cds::TimeProvider<DaysLen16Bits>` into
`cds::TimeProvider<DaysLen24Bits>` and vice-versa.
- `time::UnixTimestamp`
- Add `Ord` and `PartialOrd` implementations.
- Add `Add<Duration>` and `AddAssign<Duration>` implementations.
## Fixed
- `time::cds::TimeProvider`: Fixed a bug where subsecond milliseconds were not accounted for
when the provider has no submillisecond precision.
# [v0.5.0] 2023-01-20
The timestamp of `PusTm` is now optional. See Added and Changed section for details.
## Added

View File

@ -1,6 +1,6 @@
[package]
name = "spacepackets"
version = "0.5.0"
version = "0.5.1"
edition = "2021"
rust-version = "1.60"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]

2
NOTICE
View File

@ -1,3 +1,3 @@
Generic implementations for various CCSDS and ECSS packet standards.
This software contains code developed at the University of Stuttgart.
This software contains code developed at the University of Stuttgart's Institute of Space Systems.

View File

@ -19,7 +19,7 @@ Currently, this includes the following components:
[CCSDS 301.0-B-4 3.2](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
- CDS (CCSDS Day Segmented Time Code) implementation according to
[CCSDS 301.0-B-4 3.3](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
- Some helper types to support ASCII timecodes ad specified in
- Some helper types to support ASCII timecodes as specified in
[CCSDS 301.0-B-4 3.5](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
# Features

18
release_checklist.md Normal file
View File

@ -0,0 +1,18 @@
Checklist for new releases
=======
# Pre-Release
1. Make sure any new modules are documented sufficiently enough and check docs with
`cargo doc --all-features --open`.
2. Bump version specifier in `Cargo.toml`.
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
`unreleased` section.
4. Run `cargo test --all-features`.
5. Run `cargo fmt` and `cargo clippy`. Check `cargo msrv` against MSRV in `Cargo.toml`.
6. Wait for CI/CD results for EGit and Github. These also check cross-compilation for bare-metal
targets.
# Post-Release
1. Create a new release on `EGit` based on the release branch.

View File

@ -1,5 +1,8 @@
//! Common definitions and helpers required to create PUS TMTC packets according to
//! [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/)
//!
//! You can find the PUS telecommand definitions in the [crate::tc] module and ithe PUS telemetry definitions
//! inside the [crate::tm] module.
use crate::{ByteConversionError, CcsdsPacket, SizeMissmatch};
use core::fmt::{Debug, Display, Formatter};
use core::mem::size_of;

View File

@ -13,7 +13,7 @@
//! [CCSDS 301.0-B-4 3.2](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
//! - CDS (CCSDS Day Segmented Time Code) implementation according to
//! [CCSDS 301.0-B-4 3.3](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
//! - Some helper types to support ASCII timecodes ad specified in
//! - Some helper types to support ASCII timecodes as specified in
//! [CCSDS 301.0-B-4 3.5](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
//!
//! ## Features

View File

@ -205,8 +205,8 @@ impl PusTcSecondaryHeader {
}
}
/// This class models a PUS telecommand. It is the primary data structure to generate the raw byte
/// representation of a PUS telecommand or to deserialize from one from raw bytes.
/// This class models the PUS C telecommand packet. It is the primary data structure to generate the
/// raw byte representation of a PUS telecommand or to deserialize from one from raw bytes.
///
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the
/// [serde] feature is used, which allows to send around TC packets in a raw byte format using a

View File

@ -11,6 +11,7 @@ use alloc::boxed::Box;
use chrono::Datelike;
#[cfg(feature = "alloc")]
use core::any::Any;
use core::cmp::Ordering;
use core::fmt::Debug;
use core::ops::{Add, AddAssign};
use core::time::Duration;
@ -158,7 +159,7 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision {
/// let timestamp_in_5_minutes = timestamp_now + offset;
/// assert_eq!(timestamp_in_5_minutes.unix_seconds(), former_unix_seconds + 5 * 60);
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TimeProvider<DaysLen: ProvidesDaysLength = DaysLen16Bits> {
pfield: u8,
@ -459,6 +460,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsCommon for TimeProvider<ProvidesDay
}
impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
/// Please note that a precision value of 0 will be converted to [None] (no precision).
pub fn set_submillis_precision(&mut self, prec: SubmillisPrecision) {
self.pfield &= !(0b11);
if let SubmillisPrecision::Absent = prec {
@ -486,6 +488,25 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
self.ccsds_days
}
/// Maps the submillisecond precision to a nanosecond value. This will reduce precision when
/// using picosecond resolution, but significantly simplifies comparison of timestamps.
pub fn precision_as_ns(&self) -> Option<u32> {
if let Some(prec) = self.submillis_precision {
match prec {
SubmillisPrecision::Microseconds(us) => {
return Some(us as u32 * 1000);
}
SubmillisPrecision::Picoseconds(ps) => {
return Some(ps / 1000);
}
_ => {
return None;
}
}
}
None
}
fn generic_raw_read_checks(
buf: &[u8],
days_len: LengthOfDaySegment,
@ -769,19 +790,19 @@ impl TimeProvider<DaysLen24Bits> {
/// ## Errors
///
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or
/// the CCSDS days value exceeds the allowed bit width (24 bits).
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
/// or the CCSDS days value exceeds the allowed bit width (24 bits).
pub fn from_dt_with_u24_days(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits)
}
/// Create a provider from a generic UNIX timestamp (seconds since 01-01-1970 00:00:00).
/// Create a provider from a generic UNIX timestamp (seconds since 1970-01-01T00:00:00+00:00).
///
/// ## Errors
///
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or
/// the CCSDS days value exceeds the allowed bit width (24 bits).
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
/// or the CCSDS days value exceeds the allowed bit width (24 bits).
pub fn from_unix_secs_with_u24_days(
unix_stamp: &UnixTimestamp,
) -> Result<Self, TimestampError> {
@ -858,13 +879,13 @@ impl TimeProvider<DaysLen16Bits> {
Self::from_now_generic(LengthOfDaySegment::Short16Bits)
}
/// Create a provider from a generic UNIX timestamp (seconds since 01-01-1970 00:00:00).
/// Create a provider from a generic UNIX timestamp (seconds since 1970-01-01T00:00:00+00:00).
///
/// ## Errors
///
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or
/// the CCSDS days value exceeds the allowed bit width (24 bits).
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
/// or the CCSDS days value exceeds the allowed bit width (24 bits).
pub fn from_unix_secs_with_u16_days(
unix_stamp: &UnixTimestamp,
) -> Result<Self, TimestampError> {
@ -975,8 +996,14 @@ fn add_for_max_ccsds_days_val<T: ProvidesDaysLength>(
_ => None,
}
} else {
increment_ms_of_day(
&mut next_ms_of_day,
duration.subsec_millis(),
&mut next_ccsds_days,
);
None
};
// The subsecond millisecond were already handled.
let full_seconds = duration.as_secs();
let secs_of_day = (full_seconds % SECONDS_PER_DAY as u64) as u32;
let ms_of_day = secs_of_day * 1000;
@ -1017,6 +1044,20 @@ impl Add<Duration> for TimeProvider<DaysLen16Bits> {
}
}
impl Add<Duration> for &TimeProvider<DaysLen16Bits> {
type Output = TimeProvider<DaysLen16Bits>;
fn add(self, duration: Duration) -> Self::Output {
let (next_ccsds_days, next_ms_of_day, precision) =
add_for_max_ccsds_days_val(self, u16::MAX as u32, duration);
let mut provider = Self::Output::new_with_u16_days(next_ccsds_days as u16, next_ms_of_day);
if let Some(prec) = precision {
provider.set_submillis_precision(prec);
}
provider
}
}
/// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover
/// when they overflow, because addition needs to be infallible. The user needs to check for a
/// days overflow when this is a possibility and might be a problem.
@ -1034,6 +1075,20 @@ impl Add<Duration> for TimeProvider<DaysLen24Bits> {
}
}
impl Add<Duration> for &TimeProvider<DaysLen24Bits> {
type Output = TimeProvider<DaysLen24Bits>;
fn add(self, duration: Duration) -> Self::Output {
let (next_ccsds_days, next_ms_of_day, precision) =
add_for_max_ccsds_days_val(self, MAX_DAYS_24_BITS, duration);
let mut provider =
Self::Output::new_with_u24_days(next_ccsds_days, next_ms_of_day).unwrap();
if let Some(prec) = precision {
provider.set_submillis_precision(prec);
}
provider
}
}
/// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover
/// when they overflow, because addition needs to be infallible. The user needs to check for a
/// days overflow when this is a possibility and might be a problem.
@ -1177,6 +1232,75 @@ impl TimeWriter for TimeProvider<DaysLen24Bits> {
}
}
impl<DaysLenProvider: ProvidesDaysLength> PartialEq for TimeProvider<DaysLenProvider> {
fn eq(&self, other: &Self) -> bool {
if self.ccsds_days == other.ccsds_days
&& self.ms_of_day == other.ms_of_day
&& self.precision_as_ns().unwrap_or(0) == other.precision_as_ns().unwrap_or(0)
{
return true;
}
false
}
}
impl<DaysLenProvider: ProvidesDaysLength> PartialOrd for TimeProvider<DaysLenProvider> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
return Some(Ordering::Equal);
}
match self.ccsds_days_as_u32().cmp(&other.ccsds_days_as_u32()) {
Ordering::Less => return Some(Ordering::Less),
Ordering::Greater => return Some(Ordering::Greater),
_ => (),
}
match self.ms_of_day().cmp(&other.ms_of_day()) {
Ordering::Less => return Some(Ordering::Less),
Ordering::Greater => return Some(Ordering::Greater),
_ => (),
}
match self
.precision_as_ns()
.unwrap_or(0)
.cmp(&other.precision_as_ns().unwrap_or(0))
{
Ordering::Less => return Some(Ordering::Less),
Ordering::Greater => return Some(Ordering::Greater),
_ => (),
}
Some(Ordering::Equal)
}
}
impl<DaysLenProvider: ProvidesDaysLength + Eq> Ord for TimeProvider<DaysLenProvider> {
fn cmp(&self, other: &Self) -> Ordering {
PartialOrd::partial_cmp(self, other).unwrap()
}
}
impl From<TimeProvider<DaysLen16Bits>> for TimeProvider<DaysLen24Bits> {
fn from(value: TimeProvider<DaysLen16Bits>) -> Self {
// This function only fails if the days value exceeds 24 bits, which is not possible here,
// so it is okay to unwrap.
Self::new_with_u24_days(value.ccsds_days_as_u32(), value.ms_of_day()).unwrap()
}
}
/// This conversion can fail if the days value exceeds 16 bits.
impl TryFrom<TimeProvider<DaysLen24Bits>> for TimeProvider<DaysLen16Bits> {
type Error = CdsError;
fn try_from(value: TimeProvider<DaysLen24Bits>) -> Result<Self, CdsError> {
let ccsds_days = value.ccsds_days_as_u32();
if ccsds_days > u16::MAX as u32 {
return Err(CdsError::InvalidCcsdsDays(ccsds_days as i64));
}
Ok(Self::new_with_u16_days(
ccsds_days as u16,
value.ms_of_day(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -1984,6 +2108,16 @@ mod tests {
}
}
#[test]
fn test_addition_on_ref() {
// This test case also tests the case where there is no submillis precision but subsecond
// milliseconds.
let provider_ref = &TimeProvider::new_with_u16_days(2, 500);
let new_stamp = provider_ref + Duration::from_millis(2 * 24 * 60 * 60 * 1000 + 500);
assert_eq!(new_stamp.ccsds_days_as_u32(), 4);
assert_eq!(new_stamp.ms_of_day, 1000);
}
fn check_ps_and_carryover(prec: SubmillisPrecision, ms_of_day: u32, val: u32) {
if let SubmillisPrecision::Picoseconds(ps) = prec {
assert_eq!(ps, val);
@ -2066,6 +2200,43 @@ mod tests {
}
}
#[test]
fn test_eq() {
let stamp0 = TimeProvider::new_with_u16_days(0, 0);
let mut buf: [u8; 7] = [0; 7];
stamp0.write_to_bytes(&mut buf).unwrap();
let stamp1 = TimeProvider::from_bytes_with_u16_days(&buf).unwrap();
assert_eq!(stamp0, stamp1);
assert!(!(stamp0 < stamp1));
assert!(!(stamp1 > stamp0));
}
#[test]
fn test_ord() {
let stamp0 = TimeProvider::new_with_u24_days(0, 0).unwrap();
let stamp1 = TimeProvider::new_with_u24_days(0, 50000).unwrap();
let mut stamp2 = TimeProvider::new_with_u24_days(0, 50000).unwrap();
stamp2.set_submillis_precision(SubmillisPrecision::Microseconds(500));
let stamp3 = TimeProvider::new_with_u24_days(1, 0).unwrap();
assert!(stamp1 > stamp0);
assert!(stamp2 > stamp0);
assert!(stamp2 > stamp1);
assert!(stamp3 > stamp0);
assert!(stamp3 > stamp1);
assert!(stamp3 > stamp2);
}
#[test]
fn test_conversion() {
let mut stamp_small = TimeProvider::new_with_u16_days(u16::MAX, 500);
let stamp_larger: TimeProvider<DaysLen24Bits> = stamp_small.into();
assert_eq!(stamp_larger.ccsds_days_as_u32(), u16::MAX as u32);
assert_eq!(stamp_larger.ms_of_day(), 500);
stamp_small = stamp_larger.try_into().unwrap();
assert_eq!(stamp_small.ccsds_days_as_u32(), u16::MAX as u32);
assert_eq!(stamp_small.ms_of_day(), 500);
}
#[test]
#[cfg(feature = "serde")]
fn test_serialization() {

View File

@ -134,8 +134,8 @@ pub struct FractionalPart(FractionalResolution, u32);
/// It has the capability to generate and read timestamps as specified in the CCSDS 301.0-B-4
/// section 3.2 . The preamble field only has one byte, which allows a time code representation
/// through the year 2094. The time is represented as a simple binary counter starting from the
/// fixed CCSDS epoch (1958-01-01 00:00:00). It is possible to provide subsecond accuracy using the
/// fractional field with various available [resolutions][FractionalResolution].
/// fixed CCSDS epoch (1958-01-01T00:00:00+00:00). It is possible to provide subsecond accuracy
/// using the fractional field with various available [resolutions][FractionalResolution].
///
/// Having a preamble field of one byte limits the width of the counter
/// type (generally seconds) to 4 bytes and the width of the fractions type to 3 bytes. This limits
@ -696,6 +696,20 @@ impl Add<Duration> for TimeProviderCcsdsEpoch {
}
}
impl Add<Duration> for &TimeProviderCcsdsEpoch {
type Output = TimeProviderCcsdsEpoch;
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)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -1,7 +1,10 @@
//! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
use crate::{ByteConversionError, SizeMissmatch};
use chrono::{DateTime, LocalResult, TimeZone, Utc};
use core::cmp::Ordering;
use core::fmt::{Display, Formatter};
use core::ops::{Add, AddAssign};
use core::time::Duration;
#[allow(unused_imports)]
#[cfg(not(feature = "std"))]
@ -154,16 +157,16 @@ pub fn seconds_since_epoch() -> f64 {
/// Convert UNIX days to CCSDS days
///
/// - CCSDS epoch: 1958 January 1
/// - UNIX Epoch: 1970 January 1
/// - CCSDS epoch: 1958-01-01T00:00:00+00:00
/// - UNIX Epoch: 1970-01-01T00:00:00+00:00
pub const fn unix_to_ccsds_days(unix_days: i64) -> i64 {
unix_days - DAYS_CCSDS_TO_UNIX as i64
}
/// Convert CCSDS days to UNIX days
///
/// - CCSDS epoch: 1958 January 1
/// - UNIX Epoch: 1970 January 1
/// - CCSDS epoch: 1958-01-01T00:00:00+00:00
/// - UNIX Epoch: 1970-01-01T00:00:00+00:00
pub const fn ccsds_to_unix_days(ccsds_days: i64) -> i64 {
ccsds_days + DAYS_CCSDS_TO_UNIX as i64
}
@ -230,9 +233,10 @@ pub trait CcsdsTimeProvider {
fn date_time(&self) -> Option<DateTime<Utc>>;
}
/// UNIX timestamp: Elapsed seconds since 01-01-1970 00:00:00.
/// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00:00+00:00.
///
/// Also can optionally include subsecond millisecond for greater accuracy.
/// Also can optionally include subsecond millisecond for greater accuracy. Please note that a
/// subsecond millisecond value of 0 gets converted to [None].
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UnixTimestamp {
@ -241,24 +245,28 @@ pub struct UnixTimestamp {
}
impl UnixTimestamp {
/// Returns none if the subsecond millisecond value is larger than 999.
/// Returns none if the subsecond millisecond value is larger than 999. 0 is converted to
/// a [None] value.
pub fn new(unix_seconds: i64, subsec_millis: u16) -> Option<Self> {
if subsec_millis > 999 {
return None;
}
Some(Self {
unix_seconds,
subsecond_millis: Some(subsec_millis),
})
Some(Self::const_new(unix_seconds, subsec_millis))
}
/// Like [Self::new] but const. Panics if the subsecond value is larger than 999.
pub const fn const_new(unix_seconds: i64, subsec_millis: u16) -> Self {
if subsec_millis > 999 {
panic!("subsec milliseconds exceeds 999");
}
let subsecond_millis = if subsec_millis == 0 {
None
} else {
Some(subsec_millis)
};
Self {
unix_seconds,
subsecond_millis: Some(subsec_millis),
subsecond_millis,
}
}
@ -310,6 +318,111 @@ impl From<DateTime<Utc>> for UnixTimestamp {
}
}
impl PartialOrd for UnixTimestamp {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
return Some(Ordering::Equal);
}
match self.unix_seconds.cmp(&other.unix_seconds) {
Ordering::Less => return Some(Ordering::Less),
Ordering::Greater => return Some(Ordering::Greater),
_ => (),
}
match self
.subsecond_millis()
.unwrap_or(0)
.cmp(&other.subsecond_millis().unwrap_or(0))
{
Ordering::Less => {
return if self.unix_seconds < 0 {
Some(Ordering::Greater)
} else {
Some(Ordering::Less)
}
}
Ordering::Greater => {
return if self.unix_seconds < 0 {
Some(Ordering::Less)
} else {
Some(Ordering::Greater)
}
}
Ordering::Equal => (),
}
Some(Ordering::Equal)
}
}
impl Ord for UnixTimestamp {
fn cmp(&self, other: &Self) -> Ordering {
PartialOrd::partial_cmp(self, other).unwrap()
}
}
fn get_new_stamp_after_addition(
current_stamp: &UnixTimestamp,
duration: Duration,
) -> UnixTimestamp {
let mut new_subsec_millis =
current_stamp.subsecond_millis().unwrap_or(0) + duration.subsec_millis() as u16;
let mut new_unix_seconds = current_stamp.unix_seconds;
let mut increment_seconds = |value: u32| {
if new_unix_seconds < 0 {
new_unix_seconds = new_unix_seconds
.checked_sub(value.into())
.expect("new unix seconds would exceed i64::MIN");
} else {
new_unix_seconds = new_unix_seconds
.checked_add(value.into())
.expect("new unix seconds would exceed i64::MAX");
}
};
if new_subsec_millis >= 1000 {
new_subsec_millis -= 1000;
increment_seconds(1);
}
increment_seconds(
duration
.as_secs()
.try_into()
.expect("duration seconds exceeds u32::MAX"),
);
UnixTimestamp::const_new(new_unix_seconds, new_subsec_millis)
}
/// Please note that this operation will panic on the following conditions:
///
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
/// - Unix seconds after addition exceeds [i64::MAX].
/// - Seconds from duration to add exceeds [u32::MAX].
impl AddAssign<Duration> for UnixTimestamp {
fn add_assign(&mut self, duration: Duration) {
*self = get_new_stamp_after_addition(self, duration);
}
}
/// Please note that this operation will panic for the following conditions:
///
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
/// - Unix seconds after addition exceeds [i64::MAX].
/// - Unix seconds exceeds [u32::MAX].
impl Add<Duration> for UnixTimestamp {
type Output = Self;
fn add(self, duration: Duration) -> Self::Output {
get_new_stamp_after_addition(&self, duration)
}
}
impl Add<Duration> for &UnixTimestamp {
type Output = UnixTimestamp;
fn add(self, duration: Duration) -> Self::Output {
get_new_stamp_after_addition(self, duration)
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
@ -358,4 +471,90 @@ mod tests {
assert_eq!(subsec_millis, 600);
assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001);
}
#[test]
fn test_ord_larger() {
let stamp0 = UnixTimestamp::new_only_seconds(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6);
assert!(stamp1 > stamp0);
assert!(stamp2 > stamp0);
assert!(stamp2 > stamp1);
}
#[test]
fn test_ord_smaller() {
let stamp0 = UnixTimestamp::new_only_seconds(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6);
assert!(stamp0 < stamp1);
assert!(stamp0 < stamp2);
assert!(stamp1 < stamp2);
}
#[test]
fn test_ord_larger_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6);
assert!(stamp0 > stamp1);
assert!(stamp0 > stamp2);
assert!(stamp1 > stamp2);
assert!(stamp1 >= stamp2);
assert!(stamp0 >= stamp1);
}
#[test]
fn test_ord_smaller_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6);
assert!(stamp2 < stamp1);
assert!(stamp2 < stamp0);
assert!(stamp1 < stamp0);
assert!(stamp1 <= stamp0);
assert!(stamp2 <= stamp1);
}
#[test]
fn test_eq() {
let stamp0 = UnixTimestamp::new(5, 0).unwrap();
let stamp1 = UnixTimestamp::new_only_seconds(5);
assert_eq!(stamp0, stamp1);
assert!(stamp0 <= stamp1);
assert!(stamp0 >= stamp1);
assert!(!(stamp0 < stamp1));
assert!(!(stamp0 > stamp1));
}
#[test]
fn test_addition() {
let mut stamp0 = UnixTimestamp::new_only_seconds(1);
stamp0 += Duration::from_secs(5);
assert_eq!(stamp0.unix_seconds, 6);
assert!(stamp0.subsecond_millis().is_none());
let stamp1 = stamp0 + Duration::from_millis(500);
assert_eq!(stamp1.unix_seconds, 6);
assert!(stamp1.subsecond_millis().is_some());
assert_eq!(stamp1.subsecond_millis().unwrap(), 500);
}
#[test]
fn test_addition_on_ref() {
let stamp0 = &UnixTimestamp::new(20, 500).unwrap();
let stamp1 = stamp0 + Duration::from_millis(2500);
assert_eq!(stamp1.unix_seconds, 23);
assert!(stamp1.subsecond_millis().is_none());
}
#[test]
fn test_addition_spillover() {
let mut stamp0 = UnixTimestamp::new(1, 900).unwrap();
stamp0 += Duration::from_millis(100);
assert_eq!(stamp0.unix_seconds, 2);
assert!(stamp0.subsecond_millis().is_none());
stamp0 += Duration::from_millis(1100);
assert_eq!(stamp0.unix_seconds, 3);
assert_eq!(stamp0.subsecond_millis().unwrap(), 100);
}
}

View File

@ -189,9 +189,8 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
}
}
/// This class models a PUS telemetry and which can also be used. It is the primary data
/// structure to generate the raw byte representation of PUS telemetry or to
/// deserialize from one from raw bytes.
/// This class models the PUS C telemetry packet. It is the primary data structure to generate the
/// raw byte representation of PUS telemetry or to deserialize from one from raw bytes.
///
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the [serde]
/// feature is used which allows to send around TM packets in a raw byte format using a serde