98 Commits

Author SHA1 Message Date
d86f1e8b88 prep v0.5.4
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-12 17:23:37 +01:00
61c2042e35 Copy and Clone derivations for CDS type level support
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-12 15:48:51 +01:00
7dddeb8743 prepare v0.5.3
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 23:46:07 +01:00
16bd0f0956 use regular UnixTimestamp ctor
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 20:27:50 +01:00
8cf6f72cf3 use proper UnixTimestamp ctor
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 18:58:32 +01:00
ac2936460f minor fix for UnixTimestamp
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 18:47:46 +01:00
0f6595afd7 fix spacepackets no_std build
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 01:15:34 +01:00
7aa3432f16 add more HK subservices
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-02-04 15:45:23 +01:00
cfa5f8099c added HK subservices
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-02-04 15:16:57 +01:00
8054f4091d extend ecss module definitions
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-02-04 15:13:59 +01:00
effef4609b added some generic scheduling definitions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-04 14:41:59 +01:00
5e208e7c23 ecss module as folder
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-04 14:06:11 +01:00
a203f49e5d update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 22:03:43 +01:00
6a9bd8135d fixes for clippy 1.67
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:57:45 +01:00
b6df5fb4d1 allow delegate 0.8 and 0.9
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:50:57 +01:00
9108a4ec68 update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:31:29 +01:00
2b33f811eb no need to deprecate this function
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:30:25 +01:00
5d39cef6a0 Various smaller changes 2023-01-26 21:28:39 +01:00
8970ac7bc5 Merge pull request 'Fixed PartialEq Implementation for PusTm Struct, branched from fix of PusTc Implementation.' (#13) from partial_eq_fix_tm into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #13
Reviewed-by: Robin Müller <muellerr@irs.uni-stuttgart.de>
2023-01-26 21:12:42 +01:00
6c5f454728 clippy fixes
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-26 21:11:03 +01:00
ea4b6c9cba Merge remote-tracking branch 'origin/main' into partial_eq_fix_tm
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:05:13 +01:00
5e9af9c226 Merge pull request 'Fixed PartialEq implementation to PusTc struct' (#12) from partial_eq_fix into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #12
Reviewed-by: Robin Müller <muellerr@irs.uni-stuttgart.de>
2023-01-26 21:02:39 +01:00
ad8e50c614 CHANGELOG clarification, gitignore comments
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-26 21:00:32 +01:00
db1918e2ca updated changelog
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2023-01-26 19:25:12 +01:00
0079e5d758 fixed implementation of partialeq 2023-01-26 19:24:41 +01:00
34bf9780af fmt and clippy
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-26 19:08:29 +01:00
5b021fec22 updated changelog
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-26 19:07:35 +01:00
be1c97b75a added partial equal implementation to pustc + unit tests
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-26 19:04:16 +01:00
1db64256fc added gitignore 2023-01-26 18:50:50 +01:00
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
368331cc60 some more doc fixes
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-20 19:46:26 +01:00
416b5c1805 Some doc improvements 2023-01-20 19:42:28 +01:00
fa53be0934 bump version to v0.5.0 2023-01-20 19:08:12 +01:00
65440ada35 Merge pull request 'add first Add and AddAssign impl for CUC' (#11) from cuc_time_extensions into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #11
2023-01-18 21:53:13 +01:00
80e1be676e that should do it
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-17 01:16:07 +01:00
419d3e2c56 Merge remote-tracking branch 'origin/main' into cuc_time_extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-17 00:06:56 +01:00
37af989a03 Merge remote-tracking branch 'origin/main' into time_extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-17 00:05:23 +01:00
904354abc0 last bugfix
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-17 00:04:31 +01:00
264b1a514a more bugs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-16 23:27:42 +01:00
ab1179ed5f Merge pull request 'ECSS TM timestamp updates' (#10) from ecss_tm_timestamp_updates into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #10
2023-01-16 18:39:56 +01:00
1300923273 Merge branch 'main' into cuc_time_extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-16 18:01:03 +01:00
030c8daf45 bump changelog again
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-16 18:00:23 +01:00
50edd8b3b4 Merge remote-tracking branch 'origin/main' into ecss_tm_timestamp_updates
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-16 17:57:19 +01:00
6e7eabe18f Merge pull request 'Time module extensions' (#9) from time_extensions into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #9
2023-01-16 17:51:27 +01:00
9fb445d89a Merge remote-tracking branch 'origin/main' into time_extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-16 16:54:49 +01:00
e545cf11ee bump version
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2023-01-16 11:57:30 +01:00
00f399c23a timestamp is optional now
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-16 11:56:25 +01:00
1652567b8c add first Add and AddAssign impl for CUC
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-16 00:47:24 +01:00
14a971f01c remove unnecessary docs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-15 21:54:56 +01:00
e3f8b4a23b re-order changelog
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 21:53:25 +01:00
c8d442690b changelog should be complete
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 21:47:35 +01:00
708b68a5cb use new struct in API 2023-01-15 21:30:31 +01:00
2f51420a29 consistency renaming 2023-01-15 21:13:48 +01:00
973c54e0de extend changelog 2023-01-15 21:12:54 +01:00
6f795690fd start updating changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-15 20:48:39 +01:00
49f3497ca8 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 20:29:58 +01:00
f1f9f695e5 put include behind feature gate
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 20:29:43 +01:00
ed850b1df4 use core include for AddAssign
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 20:28:35 +01:00
b2def8cd36 added one more test
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 20:26:26 +01:00
73dbc80cad small tweak
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 18:26:18 +01:00
51e134f031 bugfixes for precision handling for additions
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 18:24:49 +01:00
73575bd00f reaching target coverage
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 17:42:23 +01:00
4c280b22c8 hmm associated method is tricky
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 12:13:39 +01:00
405145496f typo fix
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 11:57:58 +01:00
22f3b72faf dyn provider works great
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 11:54:23 +01:00
39bf0c6a61 Add AddAssign impl and addition unittests
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 02:37:56 +01:00
3f6c4c6f46 add dynamic cds time provider function 2023-01-15 01:33:26 +01:00
24e6e50e2f add some more helper methods, improve example
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 01:15:35 +01:00
ec452130f9 added missing feature gate
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 00:38:39 +01:00
67cf9ec0c0 added some more tests
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 17:57:15 +01:00
a24c9fc042 Merge branch 'another_logic_fix_cds_stamp' into add_time_helpers 2023-01-14 17:50:10 +01:00
c284a7a3cc and another dumb logic error for submillis ps prec
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 17:43:19 +01:00
5c6c016e13 Merge remote-tracking branch 'origin/main' into add_time_helpers 2023-01-14 17:29:56 +01:00
753be86272 added first tests
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 17:15:48 +01:00
d071de3a86 almost complete
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 16:12:02 +01:00
40f64525b6 some smaller tweaks 2023-01-14 15:43:49 +01:00
8da93443b9 now to test it..
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 15:37:18 +01:00
3ba68b4e64 add new Conversion helper 2023-01-14 13:22:32 +01:00
b350f8fe03 remove lint allowance
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-13 23:25:09 +01:00
0a774afd41 added Add impl for both TimeProviders 2023-01-13 23:24:37 +01:00
1761bdd33f add first Add<Duration> impl for TimeProvider<DaysLen16Bits> 2023-01-13 23:04:53 +01:00
17 changed files with 2490 additions and 221 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Rust
/target
/Cargo.lock
# CLion
/.idea/*
!/.idea/runConfigurations

View File

@ -8,6 +8,129 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.5.4] 2023-02-12
## Added
- `Clone` trait requirement for `time::cds::ProvidesDaysLen` trait.
- Added `Copy` and `Clone` derives for `DaysLen16Bits` and `DaysLen24Bits`.
# [v0.5.3] 2023-02-05
## Added
- `num_enum` dependency to avoid boilerplate code for primtive to enum conversions, for example
for the PUS subservices.
- `ecss.event` module containing a `Subservice` enum.
- `ecss.verification` module containing a `Subservice` enum.
- `ecss.scheduling` module containing a `Subservice` enum and some other helper enumerations.
- `ecss.hk` module containing a `Subservice` enum.
## Changed
- Added missing Service IDs to `ecss.PusServiceId` and marked in `#[non_exhaustive]`.
## Fixed
- `time.UnixTimestamp`: All constructors and `From` conversions now use the `new` constructor,
which should cause a correct conversion of 0 subsecond milliseconds to a `None` value.
# [v0.5.2] 2023-01-26
## Added
- Added `.gitignore`.
## Fixed
- Correct implementation of Trait `PartialEq` for `PusTc` and `PusTm`. The previous auto-derivation
were incorrect because they also compared fields unrelated to the raw byte representation.
## Changed
- Renamed `PusTc` `raw` method to `raw_bytes` and add better docs to avoid confusion.
Deprecate `raw` to avoid breaking change.
- Added `raw_bytes` method to `PusTm`.
# [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
- `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

View File

@ -1,6 +1,6 @@
[package]
name = "spacepackets"
version = "0.4.2"
version = "0.5.4"
edition = "2021"
rust-version = "1.60"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
@ -15,7 +15,11 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
[dependencies]
zerocopy = "0.6"
crc = "3"
delegate = "0.8"
delegate = ">=0.8, <0.10"
[dependencies.num_enum]
version = "0.5"
default-features = false
[dependencies.serde]
version = "1"

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.

43
src/ecss/event.rs Normal file
View File

@ -0,0 +1,43 @@
//! PUS Service 5 Events
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum Subservice {
TmInfoReport = 1,
TmLowSeverityReport = 2,
TmMediumSeverityReport = 3,
TmHighSeverityReport = 4,
TcEnableEventGeneration = 5,
TcDisableEventGeneration = 6,
TcReportDisabledList = 7,
TmDisabledEventsReport = 8,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conv_into_u8() {
let subservice: u8 = Subservice::TmLowSeverityReport.into();
assert_eq!(subservice, 2);
}
#[test]
fn test_conv_from_u8() {
let subservice: Subservice = 2.try_into().unwrap();
assert_eq!(subservice, Subservice::TmLowSeverityReport);
}
#[test]
fn test_conv_fails() {
let conversion = Subservice::try_from(9);
assert!(conversion.is_err());
let err = conversion.unwrap_err();
assert_eq!(err.number, 9);
}
}

31
src/ecss/hk.rs Normal file
View File

@ -0,0 +1,31 @@
//! PUS Service 3 Housekeeping
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum Subservice {
// Regular HK
TcCreateHkReportStructure = 1,
TcDeleteHkReportStructures = 3,
TcEnableHkGeneration = 5,
TcDisableHkGeneration = 6,
TcReportHkReportStructures = 9,
TmHkPacket = 25,
TcGenerateOneShotHk = 27,
TcModifyHkCollectionInterval = 31,
// Diagnostics HK
TcCreateDiagReportStructure = 2,
TcDeleteDiagReportStructures = 4,
TcEnableDiagGeneration = 7,
TcDisableDiagGeneration = 8,
TmHkStructuresReport = 10,
TcReportDiagReportStructures = 11,
TmDiagStructuresReport = 12,
TmDiagPacket = 26,
TcGenerateOneShotDiag = 28,
TcModifyDiagCollectionInterval = 32,
}

View File

@ -1,33 +1,74 @@
//! 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;
use crc::{Crc, CRC_16_IBM_3740};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
pub mod event;
pub mod hk;
pub mod scheduling;
pub mod verification;
pub type CrcType = u16;
/// CRC algorithm used by the PUS standard.
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>();
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
#[non_exhaustive]
pub enum PusServiceId {
/// Service 1
Verification = 1,
/// Service 2
DeviceAccess = 2,
/// Service 3
Housekeeping = 3,
/// Service 4
ParameterStatistics = 4,
/// Service 5
Event = 5,
/// Service 6
MemoryManagement = 6,
/// Service 8
Action = 8,
/// Service 9
TimeManagement = 9,
/// Service 11
Scheduling = 11,
/// Service 12
OnBoardMonitoring = 12,
/// Service 13
LargePacketTransfer = 13,
/// Service 14
RealTimeForwardingControl = 14,
/// Service 15
StorageAndRetrival = 15,
/// Service 17
Test = 17,
/// Service 18
OpsAndProcedures = 18,
/// Service 19
EventAction = 19,
/// Service 20
Parameter = 20,
/// Service 21
RequestSequencing = 21,
/// Service 22
PositionBasedScheduling = 22,
/// Service 23
FileManagement = 23,
}
/// All PUS versions. Only PUS C is supported by this library.
@ -119,16 +160,15 @@ impl Display for PusError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
PusError::VersionNotSupported(v) => {
write!(f, "PUS version {:?} not supported", v)
write!(f, "PUS version {v:?} not supported")
}
PusError::IncorrectCrc(crc) => {
write!(f, "crc16 {:#04x} is incorrect", crc)
write!(f, "crc16 {crc:#04x} is incorrect")
}
PusError::RawDataTooShort(size) => {
write!(
f,
"deserialization error, provided raw data with size {} too short",
size
"deserialization error, provided raw data with size {size} too short"
)
}
PusError::NoRawData => {
@ -138,7 +178,7 @@ impl Display for PusError {
write!(f, "crc16 was not calculated")
}
PusError::ByteConversionError(e) => {
write!(f, "low level byte conversion error: {}", e)
write!(f, "low level byte conversion error: {e}")
}
}
}

105
src/ecss/scheduling.rs Normal file
View File

@ -0,0 +1,105 @@
//! PUS Service 11 Scheduling
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum Subservice {
// Core subservices
TcEnableScheduling = 1,
TcDisableScheduling = 2,
TcResetScheduling = 3,
TcInsertActivity = 4,
TcDeleteActivityByRequestId = 5,
TcDeleteActivitiesByFilter = 6,
// Time shift subservices
TcTimeShiftActivityWithRequestId = 7,
TcTimeShiftActivitiesByFilter = 8,
TcTimeShiftAll = 15,
// Reporting subservices
TcDetailReportByRequestId = 9,
TmDetailReport = 10,
TcDetailReportByFilter = 11,
TcSummaryReportByRequestId = 12,
TmSummaryReport = 13,
TcSummaryReportByFilter = 14,
TcDetailReportAll = 16,
TcSummaryReportAll = 17,
// Subschedule subservices
TcReportSubscheduleStatus = 18,
TmReportSubscheduleStatus = 19,
TcEnableSubschedule = 20,
TcDisableSubschedule = 21,
// Group subservices
TcCreateScheduleGroup = 22,
TcDeleteScheduleGroup = 23,
TcEnableScheduleGroup = 24,
TcDisableScheduleGroup = 25,
TcReportAllGroupsStatus = 26,
TmReportAllGroupsStatus = 27,
}
/// This status applies to sub-schedules and groups as well as specified in ECSS-E-ST-70-41C 8.11.3
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SchedStatus {
Disabled = 0,
Enabled = 1,
}
impl From<bool> for SchedStatus {
fn from(value: bool) -> Self {
if value {
SchedStatus::Enabled
} else {
SchedStatus::Disabled
}
}
}
/// Time window types as specified in ECSS-E-ST-70-41C 8.11.3
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TimeWindowType {
SelectAll = 0,
TimeTagToTimeTag = 1,
FromTimeTag = 2,
ToTimeTag = 3,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bool_conv_0() {
let enabled = true;
let status: SchedStatus = enabled.into();
assert_eq!(status, SchedStatus::Enabled)
}
#[test]
fn test_bool_conv_1() {
let enabled = false;
let status: SchedStatus = enabled.into();
assert_eq!(status, SchedStatus::Disabled)
}
#[test]
fn test_conv_into_u8() {
let subservice: u8 = Subservice::TcCreateScheduleGroup.into();
assert_eq!(subservice, 22);
}
#[test]
fn test_conv_from_u8() {
let subservice: Subservice = 22u8.try_into().unwrap();
assert_eq!(subservice, Subservice::TcCreateScheduleGroup);
}
}

35
src/ecss/verification.rs Normal file
View File

@ -0,0 +1,35 @@
//! PUS Service 1 Verification
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum Subservice {
TmAcceptanceSuccess = 1,
TmAcceptanceFailure = 2,
TmStartSuccess = 3,
TmStartFailure = 4,
TmStepSuccess = 5,
TmStepFailure = 6,
TmCompletionSuccess = 7,
TmCompletionFailure = 8,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conv_into_u8() {
let subservice: u8 = Subservice::TmCompletionSuccess.into();
assert_eq!(subservice, 7);
}
#[test]
fn test_conv_from_u8() {
let subservice: Subservice = 7.try_into().unwrap();
assert_eq!(subservice, Subservice::TmCompletionSuccess);
}
}

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,29 +205,35 @@ 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
/// serde provider like [postcard](https://docs.rs/postcard/latest/postcard/).
///
/// There is no spare bytes support yet.
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
///
/// # Lifetimes
///
/// * `'raw_data` - If the TC is not constructed from a raw slice, this will be the life time of
/// a buffer where the user provided application data will be serialized into. If it
/// is, this is the lifetime of the raw byte slice it is constructed from.
#[derive(Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTc<'app_data> {
pub struct PusTc<'raw_data> {
sp_header: SpHeader,
pub sec_header: PusTcSecondaryHeader,
/// 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.
pub calc_crc_on_serialization: bool,
#[cfg_attr(feature = "serde", serde(skip))]
raw_data: Option<&'app_data [u8]>,
app_data: Option<&'app_data [u8]>,
raw_data: Option<&'raw_data [u8]>,
app_data: Option<&'raw_data [u8]>,
crc16: Option<u16>,
}
impl<'app_data> PusTc<'app_data> {
impl<'raw_data> PusTc<'raw_data> {
/// Generates a new struct instance.
///
/// # Arguments
@ -243,7 +249,7 @@ impl<'app_data> PusTc<'app_data> {
pub fn new(
sp_header: &mut SpHeader,
sec_header: PusTcSecondaryHeader,
app_data: Option<&'app_data [u8]>,
app_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool,
) -> Self {
sp_header.set_packet_type(PacketType::Tc);
@ -268,7 +274,7 @@ impl<'app_data> PusTc<'app_data> {
sph: &mut SpHeader,
service: u8,
subservice: u8,
app_data: Option<&'app_data [u8]>,
app_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool,
) -> Self {
Self::new(
@ -405,7 +411,7 @@ impl<'app_data> PusTc<'app_data> {
/// 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.
pub fn from_bytes(slice: &'app_data [u8]) -> Result<(Self, usize), PusError> {
pub fn from_bytes(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> {
let raw_data_len = slice.len();
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
return Err(PusError::RawDataTooShort(raw_data_len));
@ -435,11 +441,26 @@ impl<'app_data> PusTc<'app_data> {
Ok((pus_tc, total_len))
}
pub fn raw(&self) -> Option<&'app_data [u8]> {
#[deprecated(since = "0.5.2", note = "use raw_bytes() instead")]
pub fn raw(&self) -> Option<&'raw_data [u8]> {
self.raw_bytes()
}
/// If [Self] was constructed [Self::from_bytes], this function will return the slice it was
/// constructed from. Otherwise, [None] will be returned.
pub fn raw_bytes(&self) -> Option<&'raw_data [u8]> {
self.raw_data
}
}
impl PartialEq for PusTc<'_> {
fn eq(&self, other: &Self) -> bool {
self.sp_header == other.sp_header
&& self.sec_header == other.sec_header
&& self.app_data == other.app_data
}
}
//noinspection RsTraitImplementation
impl CcsdsPacket for PusTc<'_> {
ccsds_impl!();
@ -736,4 +757,20 @@ mod tests {
assert_eq!(slice[11], 0xee);
assert_eq!(slice[12], 0x63);
}
#[test]
fn partial_eq_pus_tc() {
// new vs new simple
let pus_tc_1 = base_ping_tc_simple_ctor();
let pus_tc_2 = base_ping_tc_full_ctor();
assert_eq!(pus_tc_1, pus_tc_2);
}
#[test]
fn partial_eq_serialized_vs_derialized() {
let pus_tc = base_ping_tc_simple_ctor();
let mut buf = [0; 32];
pus_tc.write_to_bytes(&mut buf).unwrap();
assert_eq!(pus_tc, PusTc::from_bytes(&buf).unwrap().0);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,10 @@
//!
//! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct.
use super::*;
use chrono::Datelike;
use core::fmt::Debug;
use core::ops::{Add, AddAssign};
use core::time::Duration;
const MIN_CUC_LEN: usize = 2;
@ -42,12 +45,14 @@ impl TryFrom<u8> for FractionalResolution {
/// Please note that this function will panic if the fractional value is not smaller than
/// the maximum number of fractions allowed for the particular resolution.
/// (e.g. passing 270 when the resolution only allows 255 values).
#[inline]
pub fn convert_fractional_part_to_ns(fractional_part: FractionalPart) -> u64 {
let div = fractional_res_to_div(fractional_part.0);
assert!(fractional_part.1 < div);
10_u64.pow(9) * fractional_part.1 as u64 / div as u64
}
#[inline(always)]
pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 {
2_u32.pow(8 * res as u32) - 1
}
@ -89,6 +94,7 @@ pub fn fractional_part_from_subsec_ns(
pub enum CucError {
InvalidCounterWidth(u8),
InvalidFractionResolution(FractionalResolution),
/// Invalid counter supplied.
InvalidCounter(u8, u64),
InvalidFractions(FractionalResolution, u64),
}
@ -97,16 +103,16 @@ impl Display for CucError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
CucError::InvalidCounterWidth(w) => {
write!(f, "invalid cuc counter byte width {}", w)
write!(f, "invalid cuc counter byte width {w}")
}
CucError::InvalidFractionResolution(w) => {
write!(f, "invalid cuc fractional part byte width {:?}", w)
write!(f, "invalid cuc fractional part byte width {w:?}")
}
CucError::InvalidCounter(w, c) => {
write!(f, "invalid cuc counter {} for width {}", c, w)
write!(f, "invalid cuc counter {c} for width {w}")
}
CucError::InvalidFractions(w, c) => {
write!(f, "invalid cuc fractional part {} for width {:?}", c, w)
write!(f, "invalid cuc fractional part {c} for width {w:?}")
}
}
}
@ -128,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
@ -228,9 +234,10 @@ impl TimeProviderCcsdsEpoch {
/// The counter width will always be set to 4 bytes because the normal CCSDS epoch will overflow
/// when using less than that.
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn from_now(fraction_resolution: FractionalResolution) -> Result<Self, StdTimestampError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs());
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64);
if fraction_resolution == FractionalResolution::Seconds {
return Ok(Self::new(ccsds_epoch as u32));
}
@ -243,9 +250,10 @@ impl TimeProviderCcsdsEpoch {
/// Updates the current time stamp from the current time. The fractional field width remains
/// the same and will be updated accordingly.
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs()) as u32;
self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32;
if self.fractions.is_some() {
self.fractions = fractional_part_from_subsec_ns(
self.fractions.unwrap().0,
@ -255,6 +263,42 @@ impl TimeProviderCcsdsEpoch {
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 {
// These values are definitely valid, so it is okay to unwrap here.
Self::new_generic(WidthCounterPair(2, counter as u32), None).unwrap()
@ -351,6 +395,11 @@ impl TimeProviderCcsdsEpoch {
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
/// to the total size.
///
@ -543,10 +592,19 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch {
CcsdsTimeCodes::CucCcsdsEpoch
}
/// Please note that this function only works as intended if the time counter resolution
/// is one second.
fn unix_seconds(&self) -> i64 {
ccsds_epoch_to_unix_epoch(self.counter.1 as u64) as i64
self.unix_seconds()
}
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>> {
@ -563,6 +621,95 @@ 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)]
mod tests {
use super::*;
@ -599,6 +746,7 @@ mod tests {
let zero_cuc = zero_cuc.unwrap();
let res = zero_cuc.write_to_bytes(&mut buf);
assert!(res.is_ok());
assert!(zero_cuc.subsecond_millis().is_none());
assert_eq!(zero_cuc.len_as_bytes(), 5);
assert_eq!(pfield_len(buf[0]), 1);
let written = res.unwrap();
@ -947,4 +1095,31 @@ mod tests {
// 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);
}
}

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"))]
@ -20,6 +23,7 @@ pub mod cuc;
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
pub const SECONDS_PER_DAY: u32 = 86400;
pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -55,6 +59,7 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCodes, u8> {
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum TimestampError {
/// Contains tuple where first value is the expected time code and the second
/// value is the found raw value
@ -62,6 +67,7 @@ pub enum TimestampError {
ByteConversionError(ByteConversionError),
CdsError(cds::CdsError),
CucError(cuc::CucError),
DateBeforeCcsdsEpoch(DateTime<Utc>),
CustomEpochNotSupported,
}
@ -105,18 +111,20 @@ impl Display for TimestampError {
TimestampError::InvalidTimeCode(time_code, raw_val) => {
write!(
f,
"invalid raw time code value {} for time code {:?}",
raw_val, time_code
"invalid raw time code value {raw_val} for time code {time_code:?}"
)
}
TimestampError::CdsError(e) => {
write!(f, "cds error {}", e)
write!(f, "cds error {e}")
}
TimestampError::CucError(e) => {
write!(f, "cuc error {}", e)
write!(f, "cuc error {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 => {
write!(f, "custom epochs are not supported")
@ -148,28 +156,28 @@ 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
}
/// Similar to [unix_to_ccsds_days] but converts the epoch instead, which is the number of elpased
/// seconds since the CCSDS and UNIX epoch times.
pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: u64) -> u64 {
(unix_epoch as i64 - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)) as u64
pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: i64) -> i64 {
unix_epoch - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
}
pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: u64) -> u64 {
(ccsds_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 {
ccsds_epoch + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
}
#[cfg(feature = "std")]
@ -199,6 +207,9 @@ pub trait TimeReader {
}
/// 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 {
fn len_as_bytes(&self) -> usize;
@ -208,10 +219,203 @@ pub trait CcsdsTimeProvider {
/// in big endian format.
fn p_field(&self) -> (usize, [u8; 2]);
fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
fn unix_seconds(&self) -> i64;
fn subsecond_millis(&self) -> Option<u16>;
fn unix_stamp(&self) -> UnixTimestamp {
if self.subsecond_millis().is_none() {
return UnixTimestamp::new_only_seconds(self.unix_seconds());
}
UnixTimestamp::const_new(self.unix_seconds(), self.subsecond_millis().unwrap())
}
fn date_time(&self) -> Option<DateTime<Utc>>;
}
/// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00: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(Self::const_new(epoch as i64, 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::const_new(value.timestamp(), 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"))]
mod tests {
use super::*;
@ -234,10 +438,116 @@ mod tests {
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
let unix_epoch = now.as_secs();
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs());
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u64;
assert!(ccsds_epoch > unix_epoch);
assert_eq!((ccsds_epoch - unix_epoch) % SECONDS_PER_DAY as u64, 0);
let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64;
assert_eq!(days_diff, -DAYS_CCSDS_TO_UNIX as u64);
}
#[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);
}
}

178
src/tm.rs
View File

@ -48,7 +48,7 @@ pub mod zc {
pub struct PusTmSecHeader<'slice> {
pub(crate) zc_header: PusTmSecHeaderWithoutTimestamp,
pub(crate) timestamp: &'slice [u8],
pub(crate) timestamp: Option<&'slice [u8]>,
}
impl TryFrom<crate::tm::PusTmSecondaryHeader<'_>> for PusTmSecHeaderWithoutTimestamp {
@ -115,20 +115,17 @@ pub struct PusTmSecondaryHeader<'stamp> {
pub subservice: u8,
pub msg_counter: u16,
pub dest_id: u16,
pub time_stamp: &'stamp [u8],
pub timestamp: Option<&'stamp [u8]>,
}
impl<'stamp> PusTmSecondaryHeader<'stamp> {
pub fn new_simple(service: u8, subservice: u8, time_stamp: &'stamp [u8]) -> Self {
PusTmSecondaryHeader {
pus_version: PusVersion::PusC,
sc_time_ref_status: 0,
service,
subservice,
msg_counter: 0,
dest_id: 0,
time_stamp,
}
pub fn new_simple(service: u8, subservice: u8, timestamp: &'stamp [u8]) -> Self {
Self::new(service, subservice, 0, 0, Some(timestamp))
}
/// Like [Self::new_simple] but without a timestamp.
pub fn new_simple_no_timestamp(service: u8, subservice: u8) -> Self {
Self::new(service, subservice, 0, 0, None)
}
pub fn new(
@ -136,7 +133,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> {
subservice: u8,
msg_counter: u16,
dest_id: u16,
time_stamp: &'stamp [u8],
timestamp: Option<&'stamp [u8]>,
) -> Self {
PusTmSecondaryHeader {
pus_version: PusVersion::PusC,
@ -145,7 +142,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> {
subservice,
msg_counter,
dest_id,
time_stamp,
timestamp,
}
}
}
@ -187,14 +184,13 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
subservice: sec_header.zc_header.subservice(),
msg_counter: sec_header.zc_header.msg_counter(),
dest_id: sec_header.zc_header.dest_id(),
time_stamp: sec_header.timestamp,
timestamp: sec_header.timestamp,
})
}
}
/// 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
@ -204,23 +200,24 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
///
/// # 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)]
/// * `'raw_data` - If the TM is not constructed from a raw slice, this will be the life time of
/// a buffer where the user provided time stamp and source data will be serialized into. If it
/// is, this is the lifetime of the raw byte slice it is constructed from.
#[derive(Eq, Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTm<'src_data> {
pub struct PusTm<'raw_data> {
pub sp_header: SpHeader,
pub sec_header: PusTmSecondaryHeader<'src_data>,
pub sec_header: PusTmSecondaryHeader<'raw_data>,
/// 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.
pub calc_crc_on_serialization: bool,
#[cfg_attr(feature = "serde", serde(skip))]
raw_data: Option<&'src_data [u8]>,
source_data: Option<&'src_data [u8]>,
raw_data: Option<&'raw_data [u8]>,
source_data: Option<&'raw_data [u8]>,
crc16: Option<u16>,
}
impl<'src_data> PusTm<'src_data> {
impl<'raw_data> PusTm<'raw_data> {
/// Generates a new struct instance.
///
/// # Arguments
@ -235,8 +232,8 @@ impl<'src_data> PusTm<'src_data> {
/// the correct value to this field manually
pub fn new(
sp_header: &mut SpHeader,
sec_header: PusTmSecondaryHeader<'src_data>,
source_data: Option<&'src_data [u8]>,
sec_header: PusTmSecondaryHeader<'raw_data>,
source_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool,
) -> Self {
sp_header.set_packet_type(PacketType::Tm);
@ -257,18 +254,20 @@ impl<'src_data> PusTm<'src_data> {
pub fn len_packed(&self) -> usize {
let mut length = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
length += self.sec_header.time_stamp.len();
if let Some(timestamp) = self.sec_header.timestamp {
length += timestamp.len();
}
if let Some(src_data) = self.source_data {
length += src_data.len();
}
length
}
pub fn time_stamp(&self) -> &'src_data [u8] {
self.sec_header.time_stamp
pub fn timestamp(&self) -> Option<&'raw_data [u8]> {
self.sec_header.timestamp
}
pub fn source_data(&self) -> Option<&'src_data [u8]> {
pub fn source_data(&self) -> Option<&'raw_data [u8]> {
self.source_data
}
@ -304,7 +303,9 @@ impl<'src_data> PusTm<'src_data> {
digest.update(sph_zc.as_bytes());
let pus_tc_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
digest.update(pus_tc_header.as_bytes());
digest.update(self.sec_header.time_stamp);
if let Some(stamp) = self.sec_header.timestamp {
digest.update(stamp);
}
if let Some(src_data) = self.source_data {
digest.update(src_data);
}
@ -337,9 +338,11 @@ impl<'src_data> PusTm<'src_data> {
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
.ok_or(ByteConversionError::ZeroCopyToError)?;
curr_idx += sec_header_len;
let timestamp_len = self.sec_header.time_stamp.len();
slice[curr_idx..curr_idx + timestamp_len].copy_from_slice(self.sec_header.time_stamp);
curr_idx += timestamp_len;
if let Some(timestamp) = self.sec_header.timestamp {
let timestamp_len = timestamp.len();
slice[curr_idx..curr_idx + timestamp_len].copy_from_slice(timestamp);
curr_idx += timestamp_len;
}
if let Some(src_data) = self.source_data {
slice[curr_idx..curr_idx + src_data.len()].copy_from_slice(src_data);
curr_idx += src_data.len();
@ -361,8 +364,10 @@ impl<'src_data> PusTm<'src_data> {
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
let mut appended_len =
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA + self.sec_header.time_stamp.len();
let mut appended_len = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
if let Some(timestamp) = self.sec_header.timestamp {
appended_len += timestamp.len();
}
if let Some(src_data) = self.source_data {
appended_len += src_data.len();
};
@ -374,8 +379,10 @@ impl<'src_data> PusTm<'src_data> {
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
vec.extend_from_slice(sec_header.as_bytes());
ser_len += sec_header.as_bytes().len();
vec.extend_from_slice(self.sec_header.time_stamp);
ser_len += self.sec_header.time_stamp.len();
if let Some(timestamp) = self.sec_header.timestamp {
ser_len += timestamp.len();
vec.extend_from_slice(timestamp);
}
if let Some(src_data) = self.source_data {
vec.extend_from_slice(src_data);
ser_len += src_data.len();
@ -395,7 +402,7 @@ impl<'src_data> PusTm<'src_data> {
/// the instance and the found byte length of the packet. The timestamp length needs to be
/// known beforehand.
pub fn from_bytes(
slice: &'src_data [u8],
slice: &'raw_data [u8],
timestamp_len: usize,
) -> Result<(Self, usize), PusError> {
let raw_data_len = slice.len();
@ -414,9 +421,13 @@ impl<'src_data> PusTm<'src_data> {
)
.ok_or(ByteConversionError::ZeroCopyFromError)?;
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 {
zc_header: sec_header_zc,
timestamp: &slice[current_idx..current_idx + timestamp_len],
timestamp,
};
current_idx += timestamp_len;
let raw_data = &slice[0..total_len];
@ -431,6 +442,20 @@ impl<'src_data> PusTm<'src_data> {
verify_crc16_from_raw(raw_data, pus_tm.crc16.expect("CRC16 invalid"))?;
Ok((pus_tm, total_len))
}
/// If [Self] was constructed [Self::from_bytes], this function will return the slice it was
/// constructed from. Otherwise, [None] will be returned.
pub fn raw_bytes(&self) -> Option<&'raw_data [u8]> {
self.raw_data
}
}
impl PartialEq for PusTm<'_> {
fn eq(&self, other: &Self) -> bool {
self.sp_header == other.sp_header
&& self.sec_header == other.sec_header
&& self.source_data == other.source_data
}
}
//noinspection RsTraitImplementation
@ -473,33 +498,33 @@ mod tests {
use crate::ecss::PusVersion::PusC;
use crate::SpHeader;
fn base_ping_reply_full_ctor(time_stamp: &[u8]) -> PusTm {
fn base_ping_reply_full_ctor(timestamp: &[u8]) -> PusTm {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &time_stamp);
PusTm::new(&mut sph, tc_header, None, true)
let tm_header = PusTmSecondaryHeader::new_simple(17, 2, &timestamp);
PusTm::new(&mut sph, tm_header, None, true)
}
fn base_hk_reply<'a>(time_stamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> {
fn base_hk_reply<'a>(timestamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, &time_stamp);
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, &timestamp);
PusTm::new(&mut sph, tc_header, Some(src_data), true)
}
fn dummy_time_stamp() -> &'static [u8] {
fn dummy_timestamp() -> &'static [u8] {
return &[0, 1, 2, 3, 4, 5, 6];
}
#[test]
fn test_basic() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
verify_ping_reply(&pus_tm, false, 22, dummy_time_stamp());
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
verify_ping_reply(&pus_tm, false, 22, dummy_timestamp());
}
#[test]
fn test_serialization_no_source_data() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm
.write_to_bytes(&mut buf)
@ -511,7 +536,7 @@ mod tests {
#[test]
fn test_serialization_with_source_data() {
let src_data = [1, 2, 3];
let hk_reply = base_hk_reply(dummy_time_stamp(), &src_data);
let hk_reply = base_hk_reply(dummy_timestamp(), &src_data);
let mut buf: [u8; 32] = [0; 32];
let ser_len = hk_reply
.write_to_bytes(&mut buf)
@ -524,8 +549,8 @@ mod tests {
#[test]
fn test_setters() {
let time_stamp = dummy_time_stamp();
let mut pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let mut pus_tm = base_ping_reply_full_ctor(&timestamp);
pus_tm.set_sc_time_ref_status(0b1010);
pus_tm.set_dest_id(0x7fff);
pus_tm.set_msg_counter(0x1f1f);
@ -538,8 +563,8 @@ mod tests {
#[test]
fn test_deserialization_no_source_data() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm
.write_to_bytes(&mut buf)
@ -547,13 +572,13 @@ mod tests {
assert_eq!(ser_len, 22);
let (tm_deserialized, size) = PusTm::from_bytes(&buf, 7).expect("Deserialization failed");
assert_eq!(ser_len, size);
verify_ping_reply(&tm_deserialized, false, 22, dummy_time_stamp());
verify_ping_reply(&tm_deserialized, false, 22, dummy_timestamp());
}
#[test]
fn test_manual_field_update() {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, dummy_time_stamp());
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, dummy_timestamp());
let mut tm = PusTm::new(&mut sph, tc_header, None, false);
tm.calc_crc_on_serialization = false;
assert_eq!(tm.data_len(), 0x00);
@ -573,8 +598,8 @@ mod tests {
#[test]
fn test_target_buf_too_small() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
let mut buf: [u8; 16] = [0; 16];
let res = pus_tm.write_to_bytes(&mut buf);
assert!(res.is_err());
@ -597,8 +622,8 @@ mod tests {
#[test]
#[cfg(feature = "alloc")]
fn test_append_to_vec() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
let mut vec = Vec::new();
let res = pus_tm.append_to_vec(&mut vec);
assert!(res.is_ok());
@ -610,7 +635,7 @@ mod tests {
#[cfg(feature = "alloc")]
fn test_append_to_vec_with_src_data() {
let src_data = [1, 2, 3];
let hk_reply = base_hk_reply(dummy_time_stamp(), &src_data);
let hk_reply = base_hk_reply(dummy_timestamp(), &src_data);
let mut vec = Vec::new();
vec.push(4);
let res = hk_reply.append_to_vec(&mut vec);
@ -639,7 +664,7 @@ mod tests {
assert_eq!(buf[11], 0x00);
assert_eq!(buf[12], 0x00);
// Timestamp
assert_eq!(&buf[13..20], dummy_time_stamp());
assert_eq!(&buf[13..20], dummy_timestamp());
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&buf[0..20]);
let crc16 = digest.finalize();
@ -651,14 +676,14 @@ mod tests {
tm: &PusTm,
has_user_data: bool,
exp_full_len: usize,
exp_time_stamp: &[u8],
exp_timestamp: &[u8],
) {
assert!(tm.is_tm());
assert_eq!(PusPacket::service(tm), 17);
assert_eq!(PusPacket::subservice(tm), 2);
assert!(tm.sec_header_flag());
assert_eq!(tm.len_packed(), exp_full_len);
assert_eq!(tm.time_stamp(), exp_time_stamp);
assert_eq!(tm.timestamp().unwrap(), exp_timestamp);
if has_user_data {
assert!(!tm.user_data().is_none());
}
@ -670,4 +695,21 @@ mod tests {
assert_eq!(tm.msg_counter(), 0x0000);
assert_eq!(tm.sc_time_ref_status(), 0b0000);
}
#[test]
fn partial_eq_pus_tm() {
let timestamp = dummy_timestamp();
let pus_tm_1 = base_ping_reply_full_ctor(timestamp);
let pus_tm_2 = base_ping_reply_full_ctor(timestamp);
assert_eq!(pus_tm_1, pus_tm_2);
}
#[test]
fn partial_eq_serialized_vs_derialized() {
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf = [0; 32];
pus_tm.write_to_bytes(&mut buf).unwrap();
assert_eq!(pus_tm, PusTm::from_bytes(&buf, timestamp.len()).unwrap().0);
}
}