Compare commits
1 Commits
v0.5.1
...
251fcdd6c8
Author | SHA1 | Date | |
---|---|---|---|
251fcdd6c8 |
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -20,21 +20,6 @@ jobs:
|
|||||||
command: check
|
command: check
|
||||||
args: --release
|
args: --release
|
||||||
|
|
||||||
msrv:
|
|
||||||
name: Check with MSRV
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: 1.60.0
|
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: check
|
|
||||||
args: --release
|
|
||||||
|
|
||||||
cross-check:
|
cross-check:
|
||||||
name: Check Cross
|
name: Check Cross
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -73,21 +58,6 @@ jobs:
|
|||||||
command: fmt
|
command: fmt
|
||||||
args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
check-doc:
|
|
||||||
name: Check Documentation Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: nightly
|
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: doc
|
|
||||||
args: --all-features
|
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
name: Clippy
|
name: Clippy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
108
CHANGELOG.md
108
CHANGELOG.md
@@ -8,108 +8,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
# [unreleased]
|
# [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 big 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
|
|
||||||
|
|
||||||
- `PusTmSecondaryHeader`: New `new_simple_no_timestamp` API to create secondary header without
|
|
||||||
timestamp.
|
|
||||||
- `PusTm`: Add `new_simple_no_timestamp` method to create TM without timestamp
|
|
||||||
- New `UnixTimestamp` abstraction which contains the unix seconds as an `i64`
|
|
||||||
and an optional subsecond millisecond counter (`u16`)
|
|
||||||
- `MS_PER_DAY` constant.
|
|
||||||
- CUC: Added `from_date_time` and `from_unix_stamp` constructors for time provider.
|
|
||||||
- CUC: Add `Add<Duration>` and `AddAssign<Duration>` impl for time provider.
|
|
||||||
|
|
||||||
### CDS time module
|
|
||||||
|
|
||||||
- Implement `Add<Duration>` and `AddAssign<Duration>` for time providers, which allows
|
|
||||||
easily adding offsets to the providers.
|
|
||||||
- Implement `TryFrom<DateTime<Utc>>` for time providers.
|
|
||||||
- `get_dyn_time_provider_from_bytes`: Requires `alloc` support and returns
|
|
||||||
the correct `TimeProvider` instance wrapped as a boxed trait object
|
|
||||||
`Box<DynCdsTimeProvider>` by checking the length of days field.
|
|
||||||
- Added constructor function to create the time provider
|
|
||||||
from `chrono::DateTime<Utc>` and a generic UNIX timestamp (`i64` seconds
|
|
||||||
and subsecond milliseconds).
|
|
||||||
- `MAX_DAYS_24_BITS` which contains maximum value which can be supplied
|
|
||||||
to the days field of a CDS time provider with 24 bits days field width.
|
|
||||||
- New `CdsTimestamp` trait which encapsulates common fields for all CDS time providers.
|
|
||||||
- `from_unix_secs_with_u24_days` and `from_unix_secs_with_u16_days` which create
|
|
||||||
the time provider from a `UnixTimestamp` reference.
|
|
||||||
- `from_dt_with_u16_days`, `from_dt_with_u24_days` and their `..._us_precision` and
|
|
||||||
`..._ps_precision` variants which allow to create time providers from
|
|
||||||
a `chrono::DateTime<Utc>`.
|
|
||||||
- Add `from_bytes_with_u24_days` and `from_bytes_with_u16_days` associated methods
|
|
||||||
|
|
||||||
## Changed
|
|
||||||
|
|
||||||
|
|
||||||
- (breaking) `unix_epoch_to_ccsds_epoch`: Expect and return `i64` instead of `u64` now.
|
|
||||||
- (breaking) `ccsds_epoch_to_unix_epoch`: Expect and return `i64` instead of `u64` now.
|
|
||||||
- (breaking) `PusTmSecondaryHeader`: Timestamp is optional now, which translates to a
|
|
||||||
timestamp of size 0.
|
|
||||||
- (breaking): `PusTm`: Renamed `time_stamp` method to `timestamp`, also returns
|
|
||||||
`Optional<&'src_data [u8]>` now.
|
|
||||||
- (breaking): `PusTmSecondaryHeader`: Renamed `time_stamp` field to `timestamp` for consistency.
|
|
||||||
- (breaking): Renamed `from_now_with_u24_days_and_us_prec` to `from_now_with_u24_days_us_precision`.
|
|
||||||
Also did the same for the `u16` variant.
|
|
||||||
- (breaking): Renamed `from_now_with_u24_days_and_ps_prec` to `from_now_with_u24_days_ps_precision`.
|
|
||||||
Also did the same for the `u16` variant.
|
|
||||||
- `CcsdsTimeProvider` trait (breaking):
|
|
||||||
- Add new `unix_stamp` method returning the new `UnixTimeStamp` struct.
|
|
||||||
- Add new `subsecond_millis` method returning counter `Option<u16>`.
|
|
||||||
- Default impl for `unix_stamp` which re-uses `subsecond_millis` and
|
|
||||||
existing `unix_seconds` method.
|
|
||||||
- `TimestampError` (breaking): Add `DateBeforeCcsdsEpoch` error type
|
|
||||||
because new CDS API allow supplying invalid date times before CCSDS epoch.
|
|
||||||
Make `TimestampError` with `#[non_exhaustive]` attribute to prevent
|
|
||||||
future breakages if new error variants are added.
|
|
||||||
|
|
||||||
# [v0.4.2] 14.01.2023
|
|
||||||
|
|
||||||
## Fixed
|
|
||||||
|
|
||||||
- CDS timestamp: Fixed another small logic error for stamp creation from the current
|
|
||||||
time with picosecond precision.
|
|
||||||
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/8
|
|
||||||
|
|
||||||
# [v0.4.1] 14.01.2023
|
|
||||||
|
|
||||||
## Fixed
|
|
||||||
|
|
||||||
- CDS timestamp: The conversion function from the current time were buggy
|
|
||||||
when specifying picoseconds precision, which could lead to overflow
|
|
||||||
multiplications and/or incorrect precision fields.
|
|
||||||
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/7
|
|
||||||
|
|
||||||
# [v0.4.0] 10.01.2023
|
|
||||||
|
|
||||||
## Fixed
|
|
||||||
|
|
||||||
- Remove `Default` derive on CDS time provider. This can lead to uninitialized preamble fields.
|
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
- `serde` support is now optional and behind the `serde` feature.
|
- `serde` support is now optional and behind the `serde` feature.
|
||||||
@@ -121,18 +19,12 @@ The timestamp of `PusTm` is now optional. See Added and Changed section for deta
|
|||||||
The function now returns the remaining slice as well.
|
The function now returns the remaining slice as well.
|
||||||
- All CDS specific functionality was moved into the `cds` submodule of the `time`
|
- All CDS specific functionality was moved into the `cds` submodule of the `time`
|
||||||
module. `CdsShortTimeProvider` was renamed to `TimeProvider`.
|
module. `CdsShortTimeProvider` was renamed to `TimeProvider`.
|
||||||
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/3
|
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
- `SpHeader` getter function `sp_header` added for `PusTc`
|
|
||||||
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/6
|
|
||||||
- Added PFC enumerations: `ecss::UnsignedPfc` and `ecss::RealPfc`.
|
|
||||||
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/5
|
|
||||||
- Added `std::error::Error` implementation for all error enumerations if the `std` feature
|
- Added `std::error::Error` implementation for all error enumerations if the `std` feature
|
||||||
is enabled.
|
is enabled.
|
||||||
- CUC timestamp implementation as specified in CCSDS 301.0-B-4 section 3.2.
|
- CUC timestamp implementation as specified in CCSDS 301.0-B-4 section 3.2.
|
||||||
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/4/files
|
|
||||||
- ACII timestamps as specified in CCSDS 301.0-B-4 section 3.5.
|
- ACII timestamps as specified in CCSDS 301.0-B-4 section 3.5.
|
||||||
- Added MSRV in `Cargo.toml` with the `rust-version` field set to Rust 1.60.
|
- Added MSRV in `Cargo.toml` with the `rust-version` field set to Rust 1.60.
|
||||||
- `serde` `Serialize` and `Deserialize` added to all types.
|
- `serde` `Serialize` and `Deserialize` added to all types.
|
||||||
|
10
Cargo.toml
10
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "spacepackets"
|
name = "spacepackets"
|
||||||
version = "0.5.1"
|
version = "0.3.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
@@ -14,11 +14,11 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zerocopy = "0.6"
|
zerocopy = "0.6"
|
||||||
crc = "3"
|
crc = "3.0"
|
||||||
delegate = "0.8"
|
delegate = "0.8"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1"
|
version = "1.0"
|
||||||
optional = true
|
optional = true
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
@@ -35,9 +35,9 @@ default-features = false
|
|||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std", "dep:serde"]
|
||||||
std = ["chrono/std", "chrono/clock", "alloc"]
|
std = ["chrono/std", "chrono/clock", "alloc"]
|
||||||
serde = ["dep:serde", "chrono/serde"]
|
serde = ["chrono/serde"]
|
||||||
alloc = ["postcard/alloc", "chrono/alloc"]
|
alloc = ["postcard/alloc", "chrono/alloc"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
11
README.md
11
README.md
@@ -15,11 +15,9 @@ Currently, this includes the following components:
|
|||||||
[CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf)
|
[CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf)
|
||||||
- PUS Telecommand and PUS Telemetry implementation according to the
|
- PUS Telecommand and PUS Telemetry implementation according to the
|
||||||
[ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
[ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
||||||
- CUC (CCSDS Unsegmented Time Code) implementation according to
|
- CDS Short Time Code implementation according to
|
||||||
[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)
|
[CCSDS 301.0-B-4 3.3](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
||||||
- Some helper types to support ASCII timecodes as specified in
|
- Some helper types to support ASCII timecodes ad specified in
|
||||||
[CCSDS 301.0-B-4 3.5](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
[CCSDS 301.0-B-4 3.5](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
@@ -30,15 +28,12 @@ It also offers optional support for [`serde`](https://serde.rs/). This allows se
|
|||||||
deserializing them with an appropriate `serde` provider like
|
deserializing them with an appropriate `serde` provider like
|
||||||
[`postcard`](https://github.com/jamesmunns/postcard).
|
[`postcard`](https://github.com/jamesmunns/postcard).
|
||||||
|
|
||||||
## Default features
|
Default features:
|
||||||
|
|
||||||
- [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
|
- [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
|
||||||
- [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers
|
- [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers
|
||||||
like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html).
|
like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html).
|
||||||
Enabled by the `std` feature.
|
Enabled by the `std` feature.
|
||||||
|
|
||||||
## Optional Features
|
|
||||||
|
|
||||||
- [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s
|
- [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
@@ -10,6 +10,6 @@ ARG DEBIAN_FRONTEND=noninteractive
|
|||||||
# set CROSS_CONTAINER_IN_CONTAINER to inform `cross` that it is executed from within a container
|
# set CROSS_CONTAINER_IN_CONTAINER to inform `cross` that it is executed from within a container
|
||||||
ENV CROSS_CONTAINER_IN_CONTAINER=true
|
ENV CROSS_CONTAINER_IN_CONTAINER=true
|
||||||
|
|
||||||
RUN rustup install nightly && \
|
# TODO: installing cross is problematic, permission issues
|
||||||
rustup target add thumbv7em-none-eabihf armv7-unknown-linux-gnueabihf && \
|
RUN rustup target add thumbv7em-none-eabihf armv7-unknown-linux-gnueabihf && \
|
||||||
rustup component add rustfmt clippy
|
rustup component add rustfmt clippy
|
||||||
|
18
automation/Jenkinsfile
vendored
18
automation/Jenkinsfile
vendored
@@ -13,29 +13,19 @@ pipeline {
|
|||||||
sh 'cargo clippy'
|
sh 'cargo clippy'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Docs') {
|
|
||||||
steps {
|
|
||||||
sh 'cargo +nightly doc --all-features'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Rustfmt') {
|
stage('Rustfmt') {
|
||||||
steps {
|
steps {
|
||||||
sh 'cargo fmt --all --check'
|
sh 'cargo fmt'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Test') {
|
stage('Test') {
|
||||||
steps {
|
steps {
|
||||||
sh 'cargo test --all-features'
|
sh 'cargo test'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Check with all features') {
|
stage('Check') {
|
||||||
steps {
|
steps {
|
||||||
sh 'cargo check --all-features'
|
sh 'cargo check'
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Check with no features') {
|
|
||||||
steps {
|
|
||||||
sh 'cargo check --no-default-features'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Check Cross Embedded Bare Metal') {
|
stage('Check Cross Embedded Bare Metal') {
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
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.
|
|
26
src/ecss.rs
26
src/ecss.rs
@@ -15,21 +15,6 @@ pub type CrcType = u16;
|
|||||||
pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
|
pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
|
||||||
pub const CCSDS_HEADER_LEN: usize = size_of::<crate::zc::SpHeader>();
|
pub const CCSDS_HEADER_LEN: usize = size_of::<crate::zc::SpHeader>();
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum PusServiceId {
|
|
||||||
/// Service 1
|
|
||||||
Verification = 1,
|
|
||||||
/// Service 3
|
|
||||||
Housekeeping = 3,
|
|
||||||
/// Service 5
|
|
||||||
Event = 5,
|
|
||||||
/// Service 8
|
|
||||||
Action = 8,
|
|
||||||
/// Service 17
|
|
||||||
Test = 17,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// All PUS versions. Only PUS C is supported by this library.
|
/// All PUS versions. Only PUS C is supported by this library.
|
||||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
@@ -53,7 +38,6 @@ impl TryFrom<u8> for PusVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ECSS Packet Type Codes (PTC)s.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum PacketTypeCodes {
|
pub enum PacketTypeCodes {
|
||||||
@@ -73,7 +57,6 @@ pub enum PacketTypeCodes {
|
|||||||
|
|
||||||
pub type Ptc = PacketTypeCodes;
|
pub type Ptc = PacketTypeCodes;
|
||||||
|
|
||||||
/// ECSS Packet Field Codes (PFC)s for the unsigned [Ptc].
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum UnsignedPfc {
|
pub enum UnsignedPfc {
|
||||||
@@ -89,7 +72,6 @@ pub enum UnsignedPfc {
|
|||||||
ThreeBits = 19,
|
ThreeBits = 19,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ECSS Packet Field Codes (PFC)s for the real (floating point) [Ptc].
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum RealPfc {
|
pub enum RealPfc {
|
||||||
@@ -160,8 +142,6 @@ impl From<ByteConversionError> for PusError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generic trait to describe common attributes for both PUS Telecommands (TC) and PUS Telemetry
|
|
||||||
/// (TM) packets. All PUS packets are also a special type of [CcsdsPacket]s.
|
|
||||||
pub trait PusPacket: CcsdsPacket {
|
pub trait PusPacket: CcsdsPacket {
|
||||||
const PUS_VERSION: PusVersion = PusVersion::PusC;
|
const PUS_VERSION: PusVersion = PusVersion::PusC;
|
||||||
|
|
||||||
@@ -336,13 +316,13 @@ impl<TYPE: ToBeBytes> EcssEnumeration for GenericEcssEnumWrapper<TYPE> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> {
|
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> {
|
||||||
if buf.len() < self.byte_width() {
|
if buf.len() < self.byte_width() as usize {
|
||||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||||
found: buf.len(),
|
found: buf.len(),
|
||||||
expected: self.byte_width(),
|
expected: self.byte_width() as usize,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
buf[0..self.byte_width()].copy_from_slice(self.val.to_be_bytes().as_ref());
|
buf[0..self.byte_width() as usize].copy_from_slice(self.val.to_be_bytes().as_ref());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
src/lib.rs
18
src/lib.rs
@@ -9,11 +9,9 @@
|
|||||||
//! [CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf)
|
//! [CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf)
|
||||||
//! - PUS Telecommand and PUS Telemetry implementation according to the
|
//! - PUS Telecommand and PUS Telemetry implementation according to the
|
||||||
//! [ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
//! [ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
||||||
//! - CUC (CCSDS Unsegmented Time Code) implementation according to
|
//! - CDS Short Time Code implementation according to
|
||||||
//! [CCSDS 301.0-B-4 3.2](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
//! [CCSDS CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
||||||
//! - CDS (CCSDS Day Segmented Time Code) implementation according to
|
//! - Some helper types and functions to support ASCII timecodes ad specified in
|
||||||
//! [CCSDS 301.0-B-4 3.3](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
|
||||||
//! - Some helper types to support ASCII timecodes as specified in
|
|
||||||
//! [CCSDS 301.0-B-4 3.5](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
//! [CCSDS 301.0-B-4 3.5](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
||||||
//!
|
//!
|
||||||
//! ## Features
|
//! ## Features
|
||||||
@@ -24,15 +22,12 @@
|
|||||||
//! deserializing them with an appropriate `serde` provider like
|
//! deserializing them with an appropriate `serde` provider like
|
||||||
//! [`postcard`](https://github.com/jamesmunns/postcard).
|
//! [`postcard`](https://github.com/jamesmunns/postcard).
|
||||||
//!
|
//!
|
||||||
//! ### Default features
|
//! Default features:
|
||||||
//!
|
//!
|
||||||
//! - [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
|
//! - [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
|
||||||
//! - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers
|
//! - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers
|
||||||
//! like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html).
|
//! like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html).
|
||||||
//! Enabled by the `std` feature.
|
//! Enabled by the `std` feature.
|
||||||
//!
|
|
||||||
//! ### Optional features
|
|
||||||
//!
|
|
||||||
//! - [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and
|
//! - [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and
|
||||||
//! `Deserialize` `derive`s
|
//! `Deserialize` `derive`s
|
||||||
//!
|
//!
|
||||||
@@ -129,7 +124,6 @@ impl Display for ByteConversionError {
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl Error for ByteConversionError {}
|
impl Error for ByteConversionError {}
|
||||||
|
|
||||||
/// CCSDS packet type enumeration.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum PacketType {
|
pub enum PacketType {
|
||||||
@@ -178,8 +172,6 @@ impl TryFrom<u8> for SequenceFlags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Abstraction for the CCSDS Packet ID, which forms the last thirteen bits
|
|
||||||
/// of the first two bytes in the CCSDS primary header.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct PacketId {
|
pub struct PacketId {
|
||||||
@@ -263,8 +255,6 @@ impl From<u16> for PacketId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Abstraction for the CCSDS Packet Sequence Control (PSC) field which is the
|
|
||||||
/// third and the fourth byte in the CCSDS primary header.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct PacketSequenceCtrl {
|
pub struct PacketSequenceCtrl {
|
||||||
|
20
src/tc.rs
20
src/tc.rs
@@ -215,19 +215,19 @@ impl PusTcSecondaryHeader {
|
|||||||
/// There is no spare bytes support yet.
|
/// There is no spare bytes support yet.
|
||||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct PusTc<'app_data> {
|
pub struct PusTc<'slice> {
|
||||||
sp_header: SpHeader,
|
sp_header: SpHeader,
|
||||||
pub sec_header: PusTcSecondaryHeader,
|
pub sec_header: PusTcSecondaryHeader,
|
||||||
/// If this is set to false, a manual call to [PusTc::calc_own_crc16] or
|
/// If this is set to false, a manual call to [PusTc::calc_own_crc16] or
|
||||||
/// [PusTc::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
|
/// [PusTc::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
|
||||||
pub calc_crc_on_serialization: bool,
|
pub calc_crc_on_serialization: bool,
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
raw_data: Option<&'app_data [u8]>,
|
raw_data: Option<&'slice [u8]>,
|
||||||
app_data: Option<&'app_data [u8]>,
|
app_data: Option<&'slice [u8]>,
|
||||||
crc16: Option<u16>,
|
crc16: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'app_data> PusTc<'app_data> {
|
impl<'slice> PusTc<'slice> {
|
||||||
/// Generates a new struct instance.
|
/// Generates a new struct instance.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -243,7 +243,7 @@ impl<'app_data> PusTc<'app_data> {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
sp_header: &mut SpHeader,
|
sp_header: &mut SpHeader,
|
||||||
sec_header: PusTcSecondaryHeader,
|
sec_header: PusTcSecondaryHeader,
|
||||||
app_data: Option<&'app_data [u8]>,
|
app_data: Option<&'slice [u8]>,
|
||||||
set_ccsds_len: bool,
|
set_ccsds_len: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
sp_header.set_packet_type(PacketType::Tc);
|
sp_header.set_packet_type(PacketType::Tc);
|
||||||
@@ -268,7 +268,7 @@ impl<'app_data> PusTc<'app_data> {
|
|||||||
sph: &mut SpHeader,
|
sph: &mut SpHeader,
|
||||||
service: u8,
|
service: u8,
|
||||||
subservice: u8,
|
subservice: u8,
|
||||||
app_data: Option<&'app_data [u8]>,
|
app_data: Option<&'slice [u8]>,
|
||||||
set_ccsds_len: bool,
|
set_ccsds_len: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
@@ -279,10 +279,6 @@ impl<'app_data> PusTc<'app_data> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sp_header(&self) -> &SpHeader {
|
|
||||||
&self.sp_header
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len_packed(&self) -> usize {
|
pub fn len_packed(&self) -> usize {
|
||||||
let mut length = PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
|
let mut length = PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
|
||||||
if let Some(app_data) = self.app_data {
|
if let Some(app_data) = self.app_data {
|
||||||
@@ -405,7 +401,7 @@ impl<'app_data> PusTc<'app_data> {
|
|||||||
|
|
||||||
/// Create a [PusTc] instance from a raw slice. On success, it returns a tuple containing
|
/// Create a [PusTc] instance from a raw slice. On success, it returns a tuple containing
|
||||||
/// the instance and the found byte length of the packet.
|
/// the instance and the found byte length of the packet.
|
||||||
pub fn from_bytes(slice: &'app_data [u8]) -> Result<(Self, usize), PusError> {
|
pub fn from_bytes(slice: &'slice [u8]) -> Result<(Self, usize), PusError> {
|
||||||
let raw_data_len = slice.len();
|
let raw_data_len = slice.len();
|
||||||
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||||
@@ -435,7 +431,7 @@ impl<'app_data> PusTc<'app_data> {
|
|||||||
Ok((pus_tc, total_len))
|
Ok((pus_tc, total_len))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn raw(&self) -> Option<&'app_data [u8]> {
|
pub fn raw(&self) -> Option<&'slice [u8]> {
|
||||||
self.raw_data
|
self.raw_data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,10 +3,8 @@
|
|||||||
//! See [chrono::DateTime::format] for a usage example of the generated
|
//! See [chrono::DateTime::format] for a usage example of the generated
|
||||||
//! [chrono::format::DelayedFormat] structs.
|
//! [chrono::format::DelayedFormat] structs.
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
use chrono::{
|
use chrono::format::{DelayedFormat, StrftimeItems};
|
||||||
format::{DelayedFormat, StrftimeItems},
|
use chrono::{DateTime, Utc};
|
||||||
DateTime, Utc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Tuple of format string and formatted size for time code A.
|
/// Tuple of format string and formatted size for time code A.
|
||||||
///
|
///
|
||||||
|
1512
src/time/cds.rs
1512
src/time/cds.rs
File diff suppressed because it is too large
Load Diff
262
src/time/cuc.rs
262
src/time/cuc.rs
@@ -3,15 +3,9 @@
|
|||||||
//!
|
//!
|
||||||
//! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct.
|
//! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct.
|
||||||
use super::*;
|
use super::*;
|
||||||
use chrono::Datelike;
|
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use core::ops::{Add, AddAssign};
|
|
||||||
use core::time::Duration;
|
|
||||||
|
|
||||||
const MIN_CUC_LEN: usize = 2;
|
const MIN_CUC_LEN: usize = 2;
|
||||||
|
|
||||||
/// Base value for the preamble field for a time field parser to determine the time field type.
|
|
||||||
pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::CucCcsdsEpoch as u8) << 4;
|
|
||||||
/// Maximum length if the preamble field is not extended.
|
/// Maximum length if the preamble field is not extended.
|
||||||
pub const MAX_CUC_LEN_SMALL_PREAMBLE: usize = 8;
|
pub const MAX_CUC_LEN_SMALL_PREAMBLE: usize = 8;
|
||||||
|
|
||||||
@@ -45,14 +39,12 @@ 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 value 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]
|
|
||||||
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.0);
|
||||||
assert!(fractional_part.1 < div);
|
assert!(fractional_part.1 < div);
|
||||||
10_u64.pow(9) * fractional_part.1 as u64 / div as u64
|
10_u64.pow(9) * fractional_part.1 as u64 / div as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 {
|
pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 {
|
||||||
2_u32.pow(8 * res as u32) - 1
|
2_u32.pow(8 * res as u32) - 1
|
||||||
}
|
}
|
||||||
@@ -72,20 +64,10 @@ pub fn fractional_part_from_subsec_ns(
|
|||||||
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;
|
|
||||||
// Use integer division because this can reduce code size of really small systems.
|
|
||||||
// First determine the nanoseconds for the smallest segment given the resolution.
|
// First determine the nanoseconds for the smallest segment given the resolution.
|
||||||
// Then divide by that to find out the fractional part. For the calculation of the smallest
|
// Then divide by that to find out the fractional part. An integer division floors
|
||||||
// fraction, we perform a ceiling division. This is because if we would use the default
|
// which is what we want here.
|
||||||
// flooring division, we would divide by a smaller value, thereby allowing the calculation to
|
let fractional_part = ns / (sec_as_ns / fractional_res_to_div(res) as u64);
|
||||||
// invalid fractional parts which are too large. For the division of the nanoseconds by the
|
|
||||||
// smallest fraction, a flooring division is correct.
|
|
||||||
// The multiplication with 100000 is necessary to avoid precision loss during integer division.
|
|
||||||
// TODO: Floating point division might actually be faster option, but requires additional
|
|
||||||
// code on small embedded systems..
|
|
||||||
let fractional_part = ns * 100000 / ((sec_as_ns * 100000 + resolution) / resolution);
|
|
||||||
// Floating point division.
|
|
||||||
//let fractional_part = (ns as f64 / ((sec_as_ns as f64) / resolution as f64)).floor() as u32;
|
|
||||||
Some(FractionalPart(res, fractional_part as u32))
|
Some(FractionalPart(res, fractional_part as u32))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +76,6 @@ pub fn fractional_part_from_subsec_ns(
|
|||||||
pub enum CucError {
|
pub enum CucError {
|
||||||
InvalidCounterWidth(u8),
|
InvalidCounterWidth(u8),
|
||||||
InvalidFractionResolution(FractionalResolution),
|
InvalidFractionResolution(FractionalResolution),
|
||||||
/// Invalid counter supplied.
|
|
||||||
InvalidCounter(u8, u64),
|
InvalidCounter(u8, u64),
|
||||||
InvalidFractions(FractionalResolution, u64),
|
InvalidFractions(FractionalResolution, u64),
|
||||||
}
|
}
|
||||||
@@ -225,7 +206,7 @@ impl TimeProviderCcsdsEpoch {
|
|||||||
WidthCounterPair(4, counter),
|
WidthCounterPair(4, counter),
|
||||||
Some(FractionalPart(
|
Some(FractionalPart(
|
||||||
FractionalResolution::SixtyNs,
|
FractionalResolution::SixtyNs,
|
||||||
subsec_fractions,
|
subsec_fractions as u32,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -234,10 +215,9 @@ impl TimeProviderCcsdsEpoch {
|
|||||||
/// The counter width will always be set to 4 bytes because the normal CCSDS epoch will overflow
|
/// The counter width will always be set to 4 bytes because the normal CCSDS epoch will overflow
|
||||||
/// when using less than that.
|
/// when using less than that.
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
|
||||||
pub fn from_now(fraction_resolution: FractionalResolution) -> Result<Self, StdTimestampError> {
|
pub fn from_now(fraction_resolution: FractionalResolution) -> Result<Self, StdTimestampError> {
|
||||||
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
||||||
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64);
|
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs());
|
||||||
if fraction_resolution == FractionalResolution::Seconds {
|
if fraction_resolution == FractionalResolution::Seconds {
|
||||||
return Ok(Self::new(ccsds_epoch as u32));
|
return Ok(Self::new(ccsds_epoch as u32));
|
||||||
}
|
}
|
||||||
@@ -250,10 +230,9 @@ impl TimeProviderCcsdsEpoch {
|
|||||||
/// Updates the current time stamp from the current time. The fractional field width remains
|
/// Updates the current time stamp from the current time. The fractional field width remains
|
||||||
/// the same and will be updated accordingly.
|
/// the same and will be updated accordingly.
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
|
||||||
pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> {
|
pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> {
|
||||||
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
||||||
self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32;
|
self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs()) as u32;
|
||||||
if self.fractions.is_some() {
|
if self.fractions.is_some() {
|
||||||
self.fractions = fractional_part_from_subsec_ns(
|
self.fractions = fractional_part_from_subsec_ns(
|
||||||
self.fractions.unwrap().0,
|
self.fractions.unwrap().0,
|
||||||
@@ -263,42 +242,6 @@ impl TimeProviderCcsdsEpoch {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_date_time(
|
|
||||||
dt: &DateTime<Utc>,
|
|
||||||
res: FractionalResolution,
|
|
||||||
) -> Result<Self, TimestampError> {
|
|
||||||
// Year before CCSDS epoch is invalid.
|
|
||||||
if dt.year() < 1958 {
|
|
||||||
return Err(TimestampError::DateBeforeCcsdsEpoch(*dt));
|
|
||||||
}
|
|
||||||
Self::new_generic(
|
|
||||||
WidthCounterPair(4, dt.timestamp() as u32),
|
|
||||||
fractional_part_from_subsec_ns(res, dt.timestamp_subsec_nanos() as u64),
|
|
||||||
)
|
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_unix_stamp(
|
|
||||||
unix_stamp: &UnixTimestamp,
|
|
||||||
res: FractionalResolution,
|
|
||||||
) -> Result<Self, TimestampError> {
|
|
||||||
let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds);
|
|
||||||
// Negative CCSDS epoch is invalid.
|
|
||||||
if ccsds_epoch < 0 {
|
|
||||||
return Err(TimestampError::DateBeforeCcsdsEpoch(
|
|
||||||
unix_stamp.as_date_time().unwrap(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if ccsds_epoch > u32::MAX as i64 {
|
|
||||||
return Err(CucError::InvalidCounter(4, ccsds_epoch as u64).into());
|
|
||||||
}
|
|
||||||
let mut fractions = None;
|
|
||||||
if let Some(subsec_millis) = unix_stamp.subsecond_millis {
|
|
||||||
fractions = fractional_part_from_subsec_ns(res, subsec_millis as u64 * 10_u64.pow(6));
|
|
||||||
}
|
|
||||||
Self::new_generic(WidthCounterPair(4, ccsds_epoch as u32), fractions).map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
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), None).unwrap()
|
||||||
@@ -357,7 +300,7 @@ impl TimeProviderCcsdsEpoch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_p_field(counter_width: u8, fractions_width: Option<FractionalResolution>) -> u8 {
|
fn build_p_field(counter_width: u8, fractions_width: Option<FractionalResolution>) -> u8 {
|
||||||
let mut pfield = P_FIELD_BASE;
|
let mut pfield = (CcsdsTimeCodes::CucCcsdsEpoch as u8) << 4;
|
||||||
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
|
||||||
// have been sanitized
|
// have been sanitized
|
||||||
@@ -395,11 +338,6 @@ impl TimeProviderCcsdsEpoch {
|
|||||||
pfield & 0b11
|
pfield & 0b11
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unix_seconds(&self) -> i64 {
|
|
||||||
ccsds_epoch_to_unix_epoch(self.counter.1 as i64)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This returns the length of the individual components of the CUC timestamp in addition
|
/// This returns the length of the individual components of the CUC timestamp in addition
|
||||||
/// to the total size.
|
/// to the total size.
|
||||||
///
|
///
|
||||||
@@ -493,9 +431,9 @@ impl TimeReader for TimeProviderCcsdsEpoch {
|
|||||||
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]);
|
||||||
u32::from_be_bytes(tmp_buf)
|
u32::from_be_bytes(tmp_buf) as u32
|
||||||
}
|
}
|
||||||
4 => u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()),
|
4 => u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()) as u32,
|
||||||
_ => panic!("unreachable match arm"),
|
_ => panic!("unreachable match arm"),
|
||||||
};
|
};
|
||||||
current_idx += cntr_len as usize;
|
current_idx += cntr_len as usize;
|
||||||
@@ -520,7 +458,7 @@ impl TimeReader for TimeProviderCcsdsEpoch {
|
|||||||
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 = Some(FractionalPart(
|
||||||
fractions_len.try_into().unwrap(),
|
fractions_len.try_into().unwrap(),
|
||||||
u32::from_be_bytes(tmp_buf),
|
u32::from_be_bytes(tmp_buf) as u32,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
_ => panic!("unreachable match arm"),
|
_ => panic!("unreachable match arm"),
|
||||||
@@ -592,19 +530,10 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch {
|
|||||||
CcsdsTimeCodes::CucCcsdsEpoch
|
CcsdsTimeCodes::CucCcsdsEpoch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Please note that this function only works as intended if the time counter resolution
|
||||||
|
/// is one second.
|
||||||
fn unix_seconds(&self) -> i64 {
|
fn unix_seconds(&self) -> i64 {
|
||||||
self.unix_seconds()
|
ccsds_epoch_to_unix_epoch(self.counter.1 as u64) as i64
|
||||||
}
|
|
||||||
|
|
||||||
fn subsecond_millis(&self) -> Option<u16> {
|
|
||||||
if let Some(fractions) = self.fractions {
|
|
||||||
if fractions.0 == FractionalResolution::Seconds {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// Rounding down here is the correct approach.
|
|
||||||
return Some((convert_fractional_part_to_ns(fractions) / 10_u32.pow(6) as u64) as u16);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn date_time(&self) -> Option<DateTime<Utc>> {
|
fn date_time(&self) -> Option<DateTime<Utc>> {
|
||||||
@@ -621,95 +550,6 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_provider_values_after_duration_addition(
|
|
||||||
provider: &TimeProviderCcsdsEpoch,
|
|
||||||
duration: Duration,
|
|
||||||
) -> (u32, Option<FractionalPart>) {
|
|
||||||
let mut new_counter = provider.counter.1;
|
|
||||||
let subsec_nanos = duration.subsec_nanos();
|
|
||||||
let mut increment_counter = |amount: u32| {
|
|
||||||
let mut sum: u64 = 0;
|
|
||||||
let mut counter_inc_handler = |max_val: u64| {
|
|
||||||
sum = new_counter as u64 + amount as u64;
|
|
||||||
if sum >= max_val {
|
|
||||||
new_counter = (sum % max_val) as u32;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
new_counter = sum as u32;
|
|
||||||
};
|
|
||||||
match provider.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),
|
|
||||||
4 => counter_inc_handler(u32::MAX as u64),
|
|
||||||
_ => {
|
|
||||||
// Should never happen
|
|
||||||
panic!("invalid counter width")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
increment_counter(duration.as_secs() as u32);
|
|
||||||
(new_counter, fractional_part)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AddAssign<Duration> for TimeProviderCcsdsEpoch {
|
|
||||||
fn add_assign(&mut self, duration: Duration) {
|
|
||||||
let (new_counter, new_fractional_part) =
|
|
||||||
get_provider_values_after_duration_addition(self, duration);
|
|
||||||
self.counter.1 = new_counter;
|
|
||||||
if self.fractions.is_some() {
|
|
||||||
self.fractions = new_fractional_part;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add<Duration> for TimeProviderCcsdsEpoch {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -746,7 +586,6 @@ mod tests {
|
|||||||
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);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
assert!(zero_cuc.subsecond_millis().is_none());
|
|
||||||
assert_eq!(zero_cuc.len_as_bytes(), 5);
|
assert_eq!(zero_cuc.len_as_bytes(), 5);
|
||||||
assert_eq!(pfield_len(buf[0]), 1);
|
assert_eq!(pfield_len(buf[0]), 1);
|
||||||
let written = res.unwrap();
|
let written = res.unwrap();
|
||||||
@@ -1028,39 +867,29 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn fractional_part_formula() {
|
fn fractional_part_formula() {
|
||||||
let fractional_part =
|
let fractional_part =
|
||||||
fractional_part_from_subsec_ns(FractionalResolution::FourMs, 7843138).unwrap();
|
7843137 / (10_u64.pow(9) / fractional_res_to_div(FractionalResolution::FourMs) as u64);
|
||||||
assert_eq!(fractional_part.1, 2);
|
assert_eq!(fractional_part, 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();
|
12000000 / (10_u64.pow(9) / fractional_res_to_div(FractionalResolution::FourMs) as u64);
|
||||||
assert_eq!(fractional_part.1, 3);
|
assert_eq!(fractional_part, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fractional_part_formula_3() {
|
fn fractional_part_formula_3() {
|
||||||
let one_fraction_with_width_two_in_ns =
|
let one_fraction_with_width_two_in_ns = 10_u64.pow(9) / (2_u32.pow(8 * 2) - 1) as u64;
|
||||||
10_u64.pow(9) as f64 / (2_u32.pow(8 * 2) - 1) as f64;
|
assert_eq!(one_fraction_with_width_two_in_ns, 15259);
|
||||||
assert_eq!(one_fraction_with_width_two_in_ns.ceil(), 15260.0);
|
let hundred_fractions_and_some = 100 * one_fraction_with_width_two_in_ns + 7000;
|
||||||
let hundred_fractions_and_some =
|
let fractional_part = hundred_fractions_and_some
|
||||||
(100.0 * one_fraction_with_width_two_in_ns).floor() as u64 + 7000;
|
/ (10_u64.pow(9) / fractional_res_to_div(FractionalResolution::FifteenUs) as u64);
|
||||||
let fractional_part = fractional_part_from_subsec_ns(
|
assert_eq!(fractional_part, 100);
|
||||||
FractionalResolution::FifteenUs,
|
let hundred_and_one_fractions = 101 * one_fraction_with_width_two_in_ns;
|
||||||
hundred_fractions_and_some,
|
let fractional_part = hundred_and_one_fractions
|
||||||
)
|
/ (10_u64.pow(9) / fractional_res_to_div(FractionalResolution::FifteenUs) as u64);
|
||||||
.unwrap();
|
assert_eq!(fractional_part, 101);
|
||||||
assert_eq!(fractional_part.1, 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1085,41 +914,4 @@ mod tests {
|
|||||||
let res = stamp.update_from_now();
|
let res = stamp.update_from_now();
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn assert_largest_fractions() {
|
|
||||||
let fractions =
|
|
||||||
fractional_part_from_subsec_ns(FractionalResolution::SixtyNs, 10u64.pow(9) - 1)
|
|
||||||
.unwrap();
|
|
||||||
// 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) - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_duration_basic() {
|
|
||||||
let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200);
|
|
||||||
cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs);
|
|
||||||
let duration = Duration::from_millis(2500);
|
|
||||||
cuc_stamp += duration;
|
|
||||||
assert_eq!(cuc_stamp.width_counter_pair().1, 202);
|
|
||||||
let fractions = cuc_stamp.width_fractions_pair().unwrap().1;
|
|
||||||
let expected_val =
|
|
||||||
(0.5 * fractional_res_to_div(FractionalResolution::FifteenUs) as f64).floor() 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.subsecond_millis().unwrap() <= 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_duration_overflow() {
|
|
||||||
let mut cuc_stamp =
|
|
||||||
TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 255), None).unwrap();
|
|
||||||
let duration = Duration::from_secs(10);
|
|
||||||
cuc_stamp += duration;
|
|
||||||
assert_eq!(cuc_stamp.counter.1, 10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
327
src/time/mod.rs
327
src/time/mod.rs
@@ -1,10 +1,7 @@
|
|||||||
//! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
//! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
|
||||||
use crate::{ByteConversionError, SizeMissmatch};
|
use crate::{ByteConversionError, SizeMissmatch};
|
||||||
use chrono::{DateTime, LocalResult, TimeZone, Utc};
|
use chrono::{DateTime, LocalResult, TimeZone, Utc};
|
||||||
use core::cmp::Ordering;
|
|
||||||
use core::fmt::{Display, Formatter};
|
use core::fmt::{Display, Formatter};
|
||||||
use core::ops::{Add, AddAssign};
|
|
||||||
use core::time::Duration;
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
@@ -23,7 +20,6 @@ pub mod cuc;
|
|||||||
|
|
||||||
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
|
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
|
||||||
pub const SECONDS_PER_DAY: u32 = 86400;
|
pub const SECONDS_PER_DAY: u32 = 86400;
|
||||||
pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
@@ -59,7 +55,6 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCodes, u8> {
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum TimestampError {
|
pub enum TimestampError {
|
||||||
/// Contains tuple where first value is the expected time code and the second
|
/// Contains tuple where first value is the expected time code and the second
|
||||||
/// value is the found raw value
|
/// value is the found raw value
|
||||||
@@ -67,7 +62,6 @@ pub enum TimestampError {
|
|||||||
ByteConversionError(ByteConversionError),
|
ByteConversionError(ByteConversionError),
|
||||||
CdsError(cds::CdsError),
|
CdsError(cds::CdsError),
|
||||||
CucError(cuc::CucError),
|
CucError(cuc::CucError),
|
||||||
DateBeforeCcsdsEpoch(DateTime<Utc>),
|
|
||||||
CustomEpochNotSupported,
|
CustomEpochNotSupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,9 +118,6 @@ impl Display for TimestampError {
|
|||||||
TimestampError::ByteConversionError(e) => {
|
TimestampError::ByteConversionError(e) => {
|
||||||
write!(f, "byte conversion error {}", e)
|
write!(f, "byte conversion error {}", e)
|
||||||
}
|
}
|
||||||
TimestampError::DateBeforeCcsdsEpoch(e) => {
|
|
||||||
write!(f, "datetime with date before ccsds epoch: {}", e)
|
|
||||||
}
|
|
||||||
TimestampError::CustomEpochNotSupported => {
|
TimestampError::CustomEpochNotSupported => {
|
||||||
write!(f, "custom epochs are not supported")
|
write!(f, "custom epochs are not supported")
|
||||||
}
|
}
|
||||||
@@ -173,12 +164,12 @@ pub const fn ccsds_to_unix_days(ccsds_days: i64) -> i64 {
|
|||||||
|
|
||||||
/// Similar to [unix_to_ccsds_days] but converts the epoch instead, which is the number of elpased
|
/// 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.
|
/// seconds since the CCSDS and UNIX epoch times.
|
||||||
pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: i64) -> i64 {
|
pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: u64) -> u64 {
|
||||||
unix_epoch - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
|
(unix_epoch as i64 - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: i64) -> i64 {
|
pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: u64) -> u64 {
|
||||||
ccsds_epoch + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
|
(ccsds_epoch as i64 + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
@@ -208,9 +199,6 @@ pub trait TimeReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for generic CCSDS time providers.
|
/// Trait for generic CCSDS time providers.
|
||||||
///
|
|
||||||
/// The UNIX helper methods and the [Self::date_time] method are not strictly necessary but extremely
|
|
||||||
/// practical because they are a very common and simple exchange format for time information.
|
|
||||||
pub trait CcsdsTimeProvider {
|
pub trait CcsdsTimeProvider {
|
||||||
fn len_as_bytes(&self) -> usize;
|
fn len_as_bytes(&self) -> usize;
|
||||||
|
|
||||||
@@ -220,209 +208,10 @@ pub trait CcsdsTimeProvider {
|
|||||||
/// in big endian format.
|
/// in big endian format.
|
||||||
fn p_field(&self) -> (usize, [u8; 2]);
|
fn p_field(&self) -> (usize, [u8; 2]);
|
||||||
fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
|
fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
|
||||||
|
|
||||||
fn unix_seconds(&self) -> i64;
|
fn unix_seconds(&self) -> i64;
|
||||||
fn subsecond_millis(&self) -> Option<u16>;
|
|
||||||
fn unix_stamp(&self) -> UnixTimestamp {
|
|
||||||
UnixTimestamp {
|
|
||||||
unix_seconds: self.unix_seconds(),
|
|
||||||
subsecond_millis: self.subsecond_millis(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn date_time(&self) -> Option<DateTime<Utc>>;
|
fn date_time(&self) -> Option<DateTime<Utc>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UNIX timestamp: Elapsed seconds since 01-01-1970 00:00:00.
|
|
||||||
///
|
|
||||||
/// 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 {
|
|
||||||
pub unix_seconds: i64,
|
|
||||||
subsecond_millis: Option<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnixTimestamp {
|
|
||||||
/// 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::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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_only_seconds(unix_seconds: i64) -> Self {
|
|
||||||
Self {
|
|
||||||
unix_seconds,
|
|
||||||
subsecond_millis: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subsecond_millis(&self) -> Option<u16> {
|
|
||||||
self.subsecond_millis
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
|
|
||||||
pub fn from_now() -> Result<Self, SystemTimeError> {
|
|
||||||
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
|
||||||
let epoch = now.as_secs();
|
|
||||||
Ok(UnixTimestamp {
|
|
||||||
unix_seconds: epoch as i64,
|
|
||||||
subsecond_millis: Some(now.subsec_millis() as u16),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn unix_seconds_f64(&self) -> f64 {
|
|
||||||
let mut secs = self.unix_seconds as f64;
|
|
||||||
if let Some(subsec_millis) = self.subsecond_millis {
|
|
||||||
secs += subsec_millis as f64 / 1000.0;
|
|
||||||
}
|
|
||||||
secs
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_date_time(&self) -> LocalResult<DateTime<Utc>> {
|
|
||||||
Utc.timestamp_opt(
|
|
||||||
self.unix_seconds,
|
|
||||||
self.subsecond_millis.unwrap_or(0) as u32 * 10_u32.pow(6),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DateTime<Utc>> for UnixTimestamp {
|
|
||||||
fn from(value: DateTime<Utc>) -> Self {
|
|
||||||
Self {
|
|
||||||
unix_seconds: value.timestamp(),
|
|
||||||
subsecond_millis: Some(value.timestamp_subsec_millis() as u16),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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"))]
|
#[cfg(all(test, feature = "std"))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -445,116 +234,10 @@ mod tests {
|
|||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let unix_epoch = now.as_secs();
|
let unix_epoch = now.as_secs();
|
||||||
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u64;
|
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs());
|
||||||
assert!(ccsds_epoch > unix_epoch);
|
assert!(ccsds_epoch > unix_epoch);
|
||||||
assert_eq!((ccsds_epoch - unix_epoch) % SECONDS_PER_DAY as u64, 0);
|
assert_eq!((ccsds_epoch - unix_epoch) % SECONDS_PER_DAY as u64, 0);
|
||||||
let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64;
|
let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64;
|
||||||
assert_eq!(days_diff, -DAYS_CCSDS_TO_UNIX as u64);
|
assert_eq!(days_diff, -DAYS_CCSDS_TO_UNIX as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_unix_stamp_test() {
|
|
||||||
let stamp = UnixTimestamp::new_only_seconds(-200);
|
|
||||||
assert_eq!(stamp.unix_seconds, -200);
|
|
||||||
assert!(stamp.subsecond_millis().is_none());
|
|
||||||
let stamp = UnixTimestamp::new_only_seconds(250);
|
|
||||||
assert_eq!(stamp.unix_seconds, 250);
|
|
||||||
assert!(stamp.subsecond_millis().is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_float_unix_stamp_test() {
|
|
||||||
let stamp = UnixTimestamp::new(500, 600).unwrap();
|
|
||||||
assert!(stamp.subsecond_millis.is_some());
|
|
||||||
assert_eq!(stamp.unix_seconds, 500);
|
|
||||||
let subsec_millis = stamp.subsecond_millis().unwrap();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
142
src/tm.rs
142
src/tm.rs
@@ -48,7 +48,7 @@ pub mod zc {
|
|||||||
|
|
||||||
pub struct PusTmSecHeader<'slice> {
|
pub struct PusTmSecHeader<'slice> {
|
||||||
pub(crate) zc_header: PusTmSecHeaderWithoutTimestamp,
|
pub(crate) zc_header: PusTmSecHeaderWithoutTimestamp,
|
||||||
pub(crate) timestamp: Option<&'slice [u8]>,
|
pub(crate) timestamp: &'slice [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<crate::tm::PusTmSecondaryHeader<'_>> for PusTmSecHeaderWithoutTimestamp {
|
impl TryFrom<crate::tm::PusTmSecondaryHeader<'_>> for PusTmSecHeaderWithoutTimestamp {
|
||||||
@@ -108,24 +108,27 @@ pub mod zc {
|
|||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct PusTmSecondaryHeader<'stamp> {
|
pub struct PusTmSecondaryHeader<'slice> {
|
||||||
pus_version: PusVersion,
|
pus_version: PusVersion,
|
||||||
pub sc_time_ref_status: u8,
|
pub sc_time_ref_status: u8,
|
||||||
pub service: u8,
|
pub service: u8,
|
||||||
pub subservice: u8,
|
pub subservice: u8,
|
||||||
pub msg_counter: u16,
|
pub msg_counter: u16,
|
||||||
pub dest_id: u16,
|
pub dest_id: u16,
|
||||||
pub timestamp: Option<&'stamp [u8]>,
|
pub time_stamp: &'slice [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'stamp> PusTmSecondaryHeader<'stamp> {
|
impl<'slice> PusTmSecondaryHeader<'slice> {
|
||||||
pub fn new_simple(service: u8, subservice: u8, timestamp: &'stamp [u8]) -> Self {
|
pub fn new_simple(service: u8, subservice: u8, time_stamp: &'slice [u8]) -> Self {
|
||||||
Self::new(service, subservice, 0, 0, Some(timestamp))
|
PusTmSecondaryHeader {
|
||||||
}
|
pus_version: PusVersion::PusC,
|
||||||
|
sc_time_ref_status: 0,
|
||||||
/// Like [Self::new_simple] but without a timestamp.
|
service,
|
||||||
pub fn new_simple_no_timestamp(service: u8, subservice: u8) -> Self {
|
subservice,
|
||||||
Self::new(service, subservice, 0, 0, None)
|
msg_counter: 0,
|
||||||
|
dest_id: 0,
|
||||||
|
time_stamp,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -133,7 +136,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> {
|
|||||||
subservice: u8,
|
subservice: u8,
|
||||||
msg_counter: u16,
|
msg_counter: u16,
|
||||||
dest_id: u16,
|
dest_id: u16,
|
||||||
timestamp: Option<&'stamp [u8]>,
|
time_stamp: &'slice [u8],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PusTmSecondaryHeader {
|
PusTmSecondaryHeader {
|
||||||
pus_version: PusVersion::PusC,
|
pus_version: PusVersion::PusC,
|
||||||
@@ -142,7 +145,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> {
|
|||||||
subservice,
|
subservice,
|
||||||
msg_counter,
|
msg_counter,
|
||||||
dest_id,
|
dest_id,
|
||||||
timestamp,
|
time_stamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +187,7 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
|
|||||||
subservice: sec_header.zc_header.subservice(),
|
subservice: sec_header.zc_header.subservice(),
|
||||||
msg_counter: sec_header.zc_header.msg_counter(),
|
msg_counter: sec_header.zc_header.msg_counter(),
|
||||||
dest_id: sec_header.zc_header.dest_id(),
|
dest_id: sec_header.zc_header.dest_id(),
|
||||||
timestamp: sec_header.timestamp,
|
time_stamp: sec_header.timestamp,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,26 +201,21 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
|
|||||||
/// provider like [postcard](https://docs.rs/postcard/latest/postcard/).
|
/// provider like [postcard](https://docs.rs/postcard/latest/postcard/).
|
||||||
///
|
///
|
||||||
/// There is no spare bytes support yet.
|
/// There is no spare bytes support yet.
|
||||||
///
|
|
||||||
/// # Lifetimes
|
|
||||||
///
|
|
||||||
/// * `'src_data` - Life time of a buffer where the user provided time stamp and source data will
|
|
||||||
/// be serialized into.
|
|
||||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct PusTm<'src_data> {
|
pub struct PusTm<'slice> {
|
||||||
pub sp_header: SpHeader,
|
pub sp_header: SpHeader,
|
||||||
pub sec_header: PusTmSecondaryHeader<'src_data>,
|
pub sec_header: PusTmSecondaryHeader<'slice>,
|
||||||
/// If this is set to false, a manual call to [PusTm::calc_own_crc16] or
|
/// If this is set to false, a manual call to [PusTm::calc_own_crc16] or
|
||||||
/// [PusTm::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
|
/// [PusTm::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
|
||||||
pub calc_crc_on_serialization: bool,
|
pub calc_crc_on_serialization: bool,
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
raw_data: Option<&'src_data [u8]>,
|
raw_data: Option<&'slice [u8]>,
|
||||||
source_data: Option<&'src_data [u8]>,
|
source_data: Option<&'slice [u8]>,
|
||||||
crc16: Option<u16>,
|
crc16: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src_data> PusTm<'src_data> {
|
impl<'slice> PusTm<'slice> {
|
||||||
/// Generates a new struct instance.
|
/// Generates a new struct instance.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -232,8 +230,8 @@ impl<'src_data> PusTm<'src_data> {
|
|||||||
/// the correct value to this field manually
|
/// the correct value to this field manually
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sp_header: &mut SpHeader,
|
sp_header: &mut SpHeader,
|
||||||
sec_header: PusTmSecondaryHeader<'src_data>,
|
sec_header: PusTmSecondaryHeader<'slice>,
|
||||||
source_data: Option<&'src_data [u8]>,
|
source_data: Option<&'slice [u8]>,
|
||||||
set_ccsds_len: bool,
|
set_ccsds_len: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
sp_header.set_packet_type(PacketType::Tm);
|
sp_header.set_packet_type(PacketType::Tm);
|
||||||
@@ -254,20 +252,18 @@ impl<'src_data> PusTm<'src_data> {
|
|||||||
|
|
||||||
pub fn len_packed(&self) -> usize {
|
pub fn len_packed(&self) -> usize {
|
||||||
let mut length = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
|
let mut length = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
|
||||||
if let Some(timestamp) = self.sec_header.timestamp {
|
length += self.sec_header.time_stamp.len();
|
||||||
length += timestamp.len();
|
|
||||||
}
|
|
||||||
if let Some(src_data) = self.source_data {
|
if let Some(src_data) = self.source_data {
|
||||||
length += src_data.len();
|
length += src_data.len();
|
||||||
}
|
}
|
||||||
length
|
length
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timestamp(&self) -> Option<&'src_data [u8]> {
|
pub fn time_stamp(&self) -> &'slice [u8] {
|
||||||
self.sec_header.timestamp
|
self.sec_header.time_stamp
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_data(&self) -> Option<&'src_data [u8]> {
|
pub fn source_data(&self) -> Option<&'slice [u8]> {
|
||||||
self.source_data
|
self.source_data
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,9 +299,7 @@ impl<'src_data> PusTm<'src_data> {
|
|||||||
digest.update(sph_zc.as_bytes());
|
digest.update(sph_zc.as_bytes());
|
||||||
let pus_tc_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
let pus_tc_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||||
digest.update(pus_tc_header.as_bytes());
|
digest.update(pus_tc_header.as_bytes());
|
||||||
if let Some(stamp) = self.sec_header.timestamp {
|
digest.update(self.sec_header.time_stamp);
|
||||||
digest.update(stamp);
|
|
||||||
}
|
|
||||||
if let Some(src_data) = self.source_data {
|
if let Some(src_data) = self.source_data {
|
||||||
digest.update(src_data);
|
digest.update(src_data);
|
||||||
}
|
}
|
||||||
@@ -338,11 +332,9 @@ impl<'src_data> PusTm<'src_data> {
|
|||||||
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
|
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
|
||||||
.ok_or(ByteConversionError::ZeroCopyToError)?;
|
.ok_or(ByteConversionError::ZeroCopyToError)?;
|
||||||
curr_idx += sec_header_len;
|
curr_idx += sec_header_len;
|
||||||
if let Some(timestamp) = self.sec_header.timestamp {
|
let timestamp_len = self.sec_header.time_stamp.len();
|
||||||
let timestamp_len = timestamp.len();
|
slice[curr_idx..curr_idx + timestamp_len].copy_from_slice(self.sec_header.time_stamp);
|
||||||
slice[curr_idx..curr_idx + timestamp_len].copy_from_slice(timestamp);
|
curr_idx += timestamp_len;
|
||||||
curr_idx += timestamp_len;
|
|
||||||
}
|
|
||||||
if let Some(src_data) = self.source_data {
|
if let Some(src_data) = self.source_data {
|
||||||
slice[curr_idx..curr_idx + src_data.len()].copy_from_slice(src_data);
|
slice[curr_idx..curr_idx + src_data.len()].copy_from_slice(src_data);
|
||||||
curr_idx += src_data.len();
|
curr_idx += src_data.len();
|
||||||
@@ -364,10 +356,8 @@ impl<'src_data> PusTm<'src_data> {
|
|||||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||||
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
|
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
|
||||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||||
let mut appended_len = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
|
let mut appended_len =
|
||||||
if let Some(timestamp) = self.sec_header.timestamp {
|
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA + self.sec_header.time_stamp.len();
|
||||||
appended_len += timestamp.len();
|
|
||||||
}
|
|
||||||
if let Some(src_data) = self.source_data {
|
if let Some(src_data) = self.source_data {
|
||||||
appended_len += src_data.len();
|
appended_len += src_data.len();
|
||||||
};
|
};
|
||||||
@@ -379,10 +369,8 @@ impl<'src_data> PusTm<'src_data> {
|
|||||||
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||||
vec.extend_from_slice(sec_header.as_bytes());
|
vec.extend_from_slice(sec_header.as_bytes());
|
||||||
ser_len += sec_header.as_bytes().len();
|
ser_len += sec_header.as_bytes().len();
|
||||||
if let Some(timestamp) = self.sec_header.timestamp {
|
vec.extend_from_slice(self.sec_header.time_stamp);
|
||||||
ser_len += timestamp.len();
|
ser_len += self.sec_header.time_stamp.len();
|
||||||
vec.extend_from_slice(timestamp);
|
|
||||||
}
|
|
||||||
if let Some(src_data) = self.source_data {
|
if let Some(src_data) = self.source_data {
|
||||||
vec.extend_from_slice(src_data);
|
vec.extend_from_slice(src_data);
|
||||||
ser_len += src_data.len();
|
ser_len += src_data.len();
|
||||||
@@ -402,7 +390,7 @@ impl<'src_data> PusTm<'src_data> {
|
|||||||
/// the instance and the found byte length of the packet. The timestamp length needs to be
|
/// the instance and the found byte length of the packet. The timestamp length needs to be
|
||||||
/// known beforehand.
|
/// known beforehand.
|
||||||
pub fn from_bytes(
|
pub fn from_bytes(
|
||||||
slice: &'src_data [u8],
|
slice: &'slice [u8],
|
||||||
timestamp_len: usize,
|
timestamp_len: usize,
|
||||||
) -> Result<(Self, usize), PusError> {
|
) -> Result<(Self, usize), PusError> {
|
||||||
let raw_data_len = slice.len();
|
let raw_data_len = slice.len();
|
||||||
@@ -421,13 +409,9 @@ impl<'src_data> PusTm<'src_data> {
|
|||||||
)
|
)
|
||||||
.ok_or(ByteConversionError::ZeroCopyFromError)?;
|
.ok_or(ByteConversionError::ZeroCopyFromError)?;
|
||||||
current_idx += PUC_TM_MIN_SEC_HEADER_LEN;
|
current_idx += PUC_TM_MIN_SEC_HEADER_LEN;
|
||||||
let mut timestamp = None;
|
|
||||||
if timestamp_len > 0 {
|
|
||||||
timestamp = Some(&slice[current_idx..current_idx + timestamp_len]);
|
|
||||||
}
|
|
||||||
let zc_sec_header_wrapper = zc::PusTmSecHeader {
|
let zc_sec_header_wrapper = zc::PusTmSecHeader {
|
||||||
zc_header: sec_header_zc,
|
zc_header: sec_header_zc,
|
||||||
timestamp,
|
timestamp: &slice[current_idx..current_idx + timestamp_len],
|
||||||
};
|
};
|
||||||
current_idx += timestamp_len;
|
current_idx += timestamp_len;
|
||||||
let raw_data = &slice[0..total_len];
|
let raw_data = &slice[0..total_len];
|
||||||
@@ -484,33 +468,33 @@ mod tests {
|
|||||||
use crate::ecss::PusVersion::PusC;
|
use crate::ecss::PusVersion::PusC;
|
||||||
use crate::SpHeader;
|
use crate::SpHeader;
|
||||||
|
|
||||||
fn base_ping_reply_full_ctor(timestamp: &[u8]) -> PusTm {
|
fn base_ping_reply_full_ctor(time_stamp: &[u8]) -> PusTm {
|
||||||
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
|
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
|
||||||
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, ×tamp);
|
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &time_stamp);
|
||||||
PusTm::new(&mut sph, tc_header, None, true)
|
PusTm::new(&mut sph, tc_header, None, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_hk_reply<'a>(timestamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> {
|
fn base_hk_reply<'a>(time_stamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> {
|
||||||
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
|
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
|
||||||
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, ×tamp);
|
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, &time_stamp);
|
||||||
PusTm::new(&mut sph, tc_header, Some(src_data), true)
|
PusTm::new(&mut sph, tc_header, Some(src_data), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dummy_timestamp() -> &'static [u8] {
|
fn dummy_time_stamp() -> &'static [u8] {
|
||||||
return &[0, 1, 2, 3, 4, 5, 6];
|
return &[0, 1, 2, 3, 4, 5, 6];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic() {
|
fn test_basic() {
|
||||||
let timestamp = dummy_timestamp();
|
let time_stamp = dummy_time_stamp();
|
||||||
let pus_tm = base_ping_reply_full_ctor(×tamp);
|
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
|
||||||
verify_ping_reply(&pus_tm, false, 22, dummy_timestamp());
|
verify_ping_reply(&pus_tm, false, 22, dummy_time_stamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialization_no_source_data() {
|
fn test_serialization_no_source_data() {
|
||||||
let timestamp = dummy_timestamp();
|
let time_stamp = dummy_time_stamp();
|
||||||
let pus_tm = base_ping_reply_full_ctor(×tamp);
|
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
|
||||||
let mut buf: [u8; 32] = [0; 32];
|
let mut buf: [u8; 32] = [0; 32];
|
||||||
let ser_len = pus_tm
|
let ser_len = pus_tm
|
||||||
.write_to_bytes(&mut buf)
|
.write_to_bytes(&mut buf)
|
||||||
@@ -522,7 +506,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_serialization_with_source_data() {
|
fn test_serialization_with_source_data() {
|
||||||
let src_data = [1, 2, 3];
|
let src_data = [1, 2, 3];
|
||||||
let hk_reply = base_hk_reply(dummy_timestamp(), &src_data);
|
let hk_reply = base_hk_reply(dummy_time_stamp(), &src_data);
|
||||||
let mut buf: [u8; 32] = [0; 32];
|
let mut buf: [u8; 32] = [0; 32];
|
||||||
let ser_len = hk_reply
|
let ser_len = hk_reply
|
||||||
.write_to_bytes(&mut buf)
|
.write_to_bytes(&mut buf)
|
||||||
@@ -535,8 +519,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_setters() {
|
fn test_setters() {
|
||||||
let timestamp = dummy_timestamp();
|
let time_stamp = dummy_time_stamp();
|
||||||
let mut pus_tm = base_ping_reply_full_ctor(×tamp);
|
let mut pus_tm = base_ping_reply_full_ctor(&time_stamp);
|
||||||
pus_tm.set_sc_time_ref_status(0b1010);
|
pus_tm.set_sc_time_ref_status(0b1010);
|
||||||
pus_tm.set_dest_id(0x7fff);
|
pus_tm.set_dest_id(0x7fff);
|
||||||
pus_tm.set_msg_counter(0x1f1f);
|
pus_tm.set_msg_counter(0x1f1f);
|
||||||
@@ -549,8 +533,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialization_no_source_data() {
|
fn test_deserialization_no_source_data() {
|
||||||
let timestamp = dummy_timestamp();
|
let time_stamp = dummy_time_stamp();
|
||||||
let pus_tm = base_ping_reply_full_ctor(×tamp);
|
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
|
||||||
let mut buf: [u8; 32] = [0; 32];
|
let mut buf: [u8; 32] = [0; 32];
|
||||||
let ser_len = pus_tm
|
let ser_len = pus_tm
|
||||||
.write_to_bytes(&mut buf)
|
.write_to_bytes(&mut buf)
|
||||||
@@ -558,13 +542,13 @@ mod tests {
|
|||||||
assert_eq!(ser_len, 22);
|
assert_eq!(ser_len, 22);
|
||||||
let (tm_deserialized, size) = PusTm::from_bytes(&buf, 7).expect("Deserialization failed");
|
let (tm_deserialized, size) = PusTm::from_bytes(&buf, 7).expect("Deserialization failed");
|
||||||
assert_eq!(ser_len, size);
|
assert_eq!(ser_len, size);
|
||||||
verify_ping_reply(&tm_deserialized, false, 22, dummy_timestamp());
|
verify_ping_reply(&tm_deserialized, false, 22, dummy_time_stamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_manual_field_update() {
|
fn test_manual_field_update() {
|
||||||
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
|
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
|
||||||
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, dummy_timestamp());
|
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, dummy_time_stamp());
|
||||||
let mut tm = PusTm::new(&mut sph, tc_header, None, false);
|
let mut tm = PusTm::new(&mut sph, tc_header, None, false);
|
||||||
tm.calc_crc_on_serialization = false;
|
tm.calc_crc_on_serialization = false;
|
||||||
assert_eq!(tm.data_len(), 0x00);
|
assert_eq!(tm.data_len(), 0x00);
|
||||||
@@ -584,8 +568,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_target_buf_too_small() {
|
fn test_target_buf_too_small() {
|
||||||
let timestamp = dummy_timestamp();
|
let time_stamp = dummy_time_stamp();
|
||||||
let pus_tm = base_ping_reply_full_ctor(×tamp);
|
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
|
||||||
let mut buf: [u8; 16] = [0; 16];
|
let mut buf: [u8; 16] = [0; 16];
|
||||||
let res = pus_tm.write_to_bytes(&mut buf);
|
let res = pus_tm.write_to_bytes(&mut buf);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
@@ -608,8 +592,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
fn test_append_to_vec() {
|
fn test_append_to_vec() {
|
||||||
let timestamp = dummy_timestamp();
|
let time_stamp = dummy_time_stamp();
|
||||||
let pus_tm = base_ping_reply_full_ctor(×tamp);
|
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
let res = pus_tm.append_to_vec(&mut vec);
|
let res = pus_tm.append_to_vec(&mut vec);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
@@ -621,7 +605,7 @@ mod tests {
|
|||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
fn test_append_to_vec_with_src_data() {
|
fn test_append_to_vec_with_src_data() {
|
||||||
let src_data = [1, 2, 3];
|
let src_data = [1, 2, 3];
|
||||||
let hk_reply = base_hk_reply(dummy_timestamp(), &src_data);
|
let hk_reply = base_hk_reply(dummy_time_stamp(), &src_data);
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
vec.push(4);
|
vec.push(4);
|
||||||
let res = hk_reply.append_to_vec(&mut vec);
|
let res = hk_reply.append_to_vec(&mut vec);
|
||||||
@@ -650,7 +634,7 @@ mod tests {
|
|||||||
assert_eq!(buf[11], 0x00);
|
assert_eq!(buf[11], 0x00);
|
||||||
assert_eq!(buf[12], 0x00);
|
assert_eq!(buf[12], 0x00);
|
||||||
// Timestamp
|
// Timestamp
|
||||||
assert_eq!(&buf[13..20], dummy_timestamp());
|
assert_eq!(&buf[13..20], dummy_time_stamp());
|
||||||
let mut digest = CRC_CCITT_FALSE.digest();
|
let mut digest = CRC_CCITT_FALSE.digest();
|
||||||
digest.update(&buf[0..20]);
|
digest.update(&buf[0..20]);
|
||||||
let crc16 = digest.finalize();
|
let crc16 = digest.finalize();
|
||||||
@@ -662,14 +646,14 @@ mod tests {
|
|||||||
tm: &PusTm,
|
tm: &PusTm,
|
||||||
has_user_data: bool,
|
has_user_data: bool,
|
||||||
exp_full_len: usize,
|
exp_full_len: usize,
|
||||||
exp_timestamp: &[u8],
|
exp_time_stamp: &[u8],
|
||||||
) {
|
) {
|
||||||
assert!(tm.is_tm());
|
assert!(tm.is_tm());
|
||||||
assert_eq!(PusPacket::service(tm), 17);
|
assert_eq!(PusPacket::service(tm), 17);
|
||||||
assert_eq!(PusPacket::subservice(tm), 2);
|
assert_eq!(PusPacket::subservice(tm), 2);
|
||||||
assert!(tm.sec_header_flag());
|
assert!(tm.sec_header_flag());
|
||||||
assert_eq!(tm.len_packed(), exp_full_len);
|
assert_eq!(tm.len_packed(), exp_full_len);
|
||||||
assert_eq!(tm.timestamp().unwrap(), exp_timestamp);
|
assert_eq!(tm.time_stamp(), exp_time_stamp);
|
||||||
if has_user_data {
|
if has_user_data {
|
||||||
assert!(!tm.user_data().is_none());
|
assert!(!tm.user_data().is_none());
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user