6 Commits

Author SHA1 Message Date
179e8b73b6 refactor CUC again
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2024-03-14 12:49:59 +01:00
83c84bb31d make the API even more similar
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2024-03-14 10:50:31 +01:00
05b6503851 UnixTimestamp now has nanosecond precision
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2024-03-14 10:13:29 +01:00
aae246dca6 correction for unittest 2024-03-13 18:20:24 +01:00
aacdd9f364 add support for the time library
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2024-03-13 18:12:12 +01:00
de1dc16e60 this works, need to think about where this is the best solution
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2024-03-13 17:37:51 +01:00
23 changed files with 1226 additions and 730 deletions

View File

@ -8,8 +8,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.11.0-rc.1] 2024-04-03
Major API changes for the time API. If you are using the time API, it is strongly recommended Major API changes for the time API. If you are using the time API, it is strongly recommended
to check all the API changes in the **Changed** chapter. to check all the API changes in the **Changed** chapter.
@ -26,14 +24,9 @@ to check all the API changes in the **Changed** chapter.
- Added basic support conversions to the `time` library. Introduce new `chrono` and `timelib` - Added basic support conversions to the `time` library. Introduce new `chrono` and `timelib`
feature gate. feature gate.
- Added `CcsdsTimeProvider::timelib_date_time`. - Added `CcsdsTimeProvider::timelib_date_time`.
- Optional support for `defmt` by adding optional `defmt::Format` derives for common types.
## Changed ## Changed
- `PusTcCreator::new_simple` now expects a valid slice for the source data instead of an optional
slice. For telecommands without application data, `&[]` can be passed.
- `PusTmSecondaryHeader` constructors now expects a valid slice for the time stamp instead of an
optional slice.
- Renamed `CcsdsTimeProvider::date_time` to `CcsdsTimeProvider::chrono_date_time` - Renamed `CcsdsTimeProvider::date_time` to `CcsdsTimeProvider::chrono_date_time`
- Renamed `CcsdsTimeCodes` to `CcsdsTimeCode` - Renamed `CcsdsTimeCodes` to `CcsdsTimeCode`
- Renamed `cds::TimeProvider` to `cds::CdsTime` - Renamed `cds::TimeProvider` to `cds::CdsTime`
@ -41,7 +34,6 @@ to check all the API changes in the **Changed** chapter.
- `UnixTimestamp` renamed to `UnixTime` - `UnixTimestamp` renamed to `UnixTime`
- `UnixTime` seconds are now private and can be retrieved using the `secs` member method. - `UnixTime` seconds are now private and can be retrieved using the `secs` member method.
- `UnixTime::new` renamed to `UnixTime::new_checked`. - `UnixTime::new` renamed to `UnixTime::new_checked`.
- `UnixTime::secs` renamed to `UnixTime::as_secs`.
- `UnixTime` now has a nanosecond subsecond precision. The `new` constructor now expects - `UnixTime` now has a nanosecond subsecond precision. The `new` constructor now expects
nanoseconds as the second argument. nanoseconds as the second argument.
- Added new `UnixTime::new_subsec_millis` and `UnixTime::new_subsec_millis_checked` API - Added new `UnixTime::new_subsec_millis` and `UnixTime::new_subsec_millis_checked` API
@ -51,16 +43,6 @@ to check all the API changes in the **Changed** chapter.
- `CcsdsTimeProvider::date_time` renamed to `CcsdsTimeProvider::chrono_date_time`. - `CcsdsTimeProvider::date_time` renamed to `CcsdsTimeProvider::chrono_date_time`.
- Added `UnixTime::MIN`, `UnixTime::MAX` and `UnixTime::EPOCH`. - Added `UnixTime::MIN`, `UnixTime::MAX` and `UnixTime::EPOCH`.
- Added `UnixTime::timelib_date_time`. - Added `UnixTime::timelib_date_time`.
- Error handling for ECSS and time module is more granular now, with a new
`DateBeforeCcsdsEpochError` error and a `DateBeforeCcsdsEpoch` enum variant for both
`CdsError` and `CucError`.
- `PusTmCreator` now has two lifetimes: One for the raw source data buffer and one for the
raw timestamp.
- Time API `from_now*` API renamed to `now*`.
## Removed
- Legacy `PusTm` and `PusTc` objects.
# [v0.11.0-rc.0] 2024-03-04 # [v0.11.0-rc.0] 2024-03-04

View File

@ -1,6 +1,6 @@
[package] [package]
name = "spacepackets" name = "spacepackets"
version = "0.11.0-rc.1" version = "0.11.0-rc.0"
edition = "2021" edition = "2021"
rust-version = "1.61" rust-version = "1.61"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
@ -48,10 +48,6 @@ optional = true
version = "0.2" version = "0.2"
default-features = false default-features = false
[dependencies.defmt]
version = "0.3"
optional = true
[dev-dependencies] [dev-dependencies]
postcard = "1" postcard = "1"
chrono = "0.4" chrono = "0.4"
@ -63,8 +59,7 @@ serde = ["dep:serde", "chrono/serde"]
alloc = ["postcard/alloc", "chrono/alloc"] alloc = ["postcard/alloc", "chrono/alloc"]
chrono = ["dep:chrono"] chrono = ["dep:chrono"]
timelib = ["dep:time"] timelib = ["dep:time"]
defmt = ["dep:defmt"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"] rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"]

View File

@ -45,8 +45,6 @@ deserializing them with an appropriate `serde` provider like
- [`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
- [`chrono`](https://crates.io/crates/chrono): Add basic support for the `chrono` time library. - [`chrono`](https://crates.io/crates/chrono): Add basic support for the `chrono` time library.
- [`timelib`](https://crates.io/crates/time): Add basic support for the `time` time library. - [`timelib`](https://crates.io/crates/time): Add basic support for the `time` time library.
- [`defmt`](https://defmt.ferrous-systems.com/): Add support for the `defmt` by adding the
[`defmt::Format`](https://defmt.ferrous-systems.com/format) derive on many types.
# Examples # Examples

View File

@ -4,11 +4,11 @@ Checklist for new releases
# Pre-Release # Pre-Release
1. Make sure any new modules are documented sufficiently enough and check docs with 1. Make sure any new modules are documented sufficiently enough and check docs with
`cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]' --open`. `cargo +nightly doc --all-features --config 'rustdocflags=["--cfg", "doc_cfg"]' --open`.
2. Bump version specifier in `Cargo.toml`. 2. Bump version specifier in `Cargo.toml`.
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new 3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
`unreleased` section. `unreleased` section.
4. Run `cargo test --all-features` or `cargo nextest r --all-features`. 4. Run `cargo test --all-features`.
5. Run `cargo fmt` and `cargo clippy`. Check `cargo msrv` against MSRV in `Cargo.toml`. 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 6. Wait for CI/CD results for EGit and Github. These also check cross-compilation for bare-metal
targets. targets.

View File

@ -20,7 +20,6 @@ pub const MIN_LV_LEN: usize = 1;
/// this will be the lifetime of that data reference. /// this will be the lifetime of that data reference.
#[derive(Debug, Copy, Clone, Eq)] #[derive(Debug, Copy, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Lv<'data> { pub struct Lv<'data> {
data: &'data [u8], data: &'data [u8],
// If the LV was generated from a raw bytestream, this will contain the start of the // If the LV was generated from a raw bytestream, this will contain the start of the
@ -89,6 +88,7 @@ impl<'data> Lv<'data> {
/// Helper function to build a string LV. This is especially useful for the file or directory /// Helper function to build a string LV. This is especially useful for the file or directory
/// path LVs /// path LVs
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn new_from_string(string: &'data String) -> Result<Lv<'data>, TlvLvError> { pub fn new_from_string(string: &'data String) -> Result<Lv<'data>, TlvLvError> {
Self::new(string.as_bytes()) Self::new(string.as_bytes())
} }

View File

@ -18,7 +18,6 @@ pub const CFDP_VERSION_2: u8 = 0b001;
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum PduType { pub enum PduType {
FileDirective = 0, FileDirective = 0,
@ -27,7 +26,6 @@ pub enum PduType {
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum Direction { pub enum Direction {
TowardsReceiver = 0, TowardsReceiver = 0,
@ -36,7 +34,6 @@ pub enum Direction {
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum TransmissionMode { pub enum TransmissionMode {
Acknowledged = 0, Acknowledged = 0,
@ -45,7 +42,6 @@ pub enum TransmissionMode {
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum CrcFlag { pub enum CrcFlag {
NoCrc = 0, NoCrc = 0,
@ -73,7 +69,6 @@ impl From<CrcFlag> for bool {
/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75) /// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75)
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum SegmentMetadataFlag { pub enum SegmentMetadataFlag {
NotPresent = 0, NotPresent = 0,
@ -83,7 +78,6 @@ pub enum SegmentMetadataFlag {
/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75) /// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75)
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum SegmentationControl { pub enum SegmentationControl {
NoRecordBoundaryPreservation = 0, NoRecordBoundaryPreservation = 0,
@ -92,7 +86,6 @@ pub enum SegmentationControl {
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum FaultHandlerCode { pub enum FaultHandlerCode {
NoticeOfCancellation = 0b0001, NoticeOfCancellation = 0b0001,
@ -103,7 +96,6 @@ pub enum FaultHandlerCode {
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum ConditionCode { pub enum ConditionCode {
/// This is not an error condition for which a faulty handler override can be specified /// This is not an error condition for which a faulty handler override can be specified
@ -126,7 +118,6 @@ pub enum ConditionCode {
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum LargeFileFlag { pub enum LargeFileFlag {
/// 32 bit maximum file size and FSS size /// 32 bit maximum file size and FSS size
@ -138,7 +129,6 @@ pub enum LargeFileFlag {
/// Transaction status for the ACK PDU field according to chapter 5.2.4 of the CFDP standard. /// Transaction status for the ACK PDU field according to chapter 5.2.4 of the CFDP standard.
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum TransactionStatus { pub enum TransactionStatus {
/// Transaction is not currently active and the CFDP implementation does not retain a /// Transaction is not currently active and the CFDP implementation does not retain a
@ -156,7 +146,6 @@ pub enum TransactionStatus {
/// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/) /// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/)
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum ChecksumType { pub enum ChecksumType {
/// Modular legacy checksum /// Modular legacy checksum
@ -178,7 +167,6 @@ pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4];
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TlvLvError { pub enum TlvLvError {
DataTooLarge(usize), DataTooLarge(usize),
ByteConversion(ByteConversionError), ByteConversion(ByteConversionError),

View File

@ -15,7 +15,6 @@ use serde::{Deserialize, Serialize};
/// For more information, refer to CFDP chapter 5.2.4. /// For more information, refer to CFDP chapter 5.2.4.
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AckPdu { pub struct AckPdu {
pdu_header: PduHeader, pdu_header: PduHeader,
directive_code_of_acked_pdu: FileDirectiveType, directive_code_of_acked_pdu: FileDirectiveType,

View File

@ -15,7 +15,6 @@ use super::{CfdpPdu, WritablePduPacket};
/// For more information, refer to CFDP chapter 5.2.2. /// For more information, refer to CFDP chapter 5.2.2.
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EofPdu { pub struct EofPdu {
pdu_header: PduHeader, pdu_header: PduHeader,
condition_code: ConditionCode, condition_code: ConditionCode,

View File

@ -14,7 +14,6 @@ use super::{CfdpPdu, WritablePduPacket};
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum DeliveryCode { pub enum DeliveryCode {
Complete = 0, Complete = 0,
@ -23,7 +22,6 @@ pub enum DeliveryCode {
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum FileStatus { pub enum FileStatus {
DiscardDeliberately = 0b00, DiscardDeliberately = 0b00,
@ -36,7 +34,6 @@ pub enum FileStatus {
/// ///
/// For more information, refer to CFDP chapter 5.2.3. /// For more information, refer to CFDP chapter 5.2.3.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct FinishedPduCreator<'fs_responses> { pub struct FinishedPduCreator<'fs_responses> {
pdu_header: PduHeader, pdu_header: PduHeader,
condition_code: ConditionCode, condition_code: ConditionCode,
@ -222,7 +219,6 @@ impl<'buf> Iterator for FilestoreResponseIterator<'buf> {
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct FinishedPduReader<'buf> { pub struct FinishedPduReader<'buf> {
pdu_header: PduHeader, pdu_header: PduHeader,
condition_code: ConditionCode, condition_code: ConditionCode,

View File

@ -15,7 +15,6 @@ use super::{CfdpPdu, WritablePduPacket};
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MetadataGenericParams { pub struct MetadataGenericParams {
pub closure_requested: bool, pub closure_requested: bool,
pub checksum_type: ChecksumType, pub checksum_type: ChecksumType,
@ -56,7 +55,6 @@ pub fn build_metadata_opts_from_vec(
/// This abstraction exposes a specialized API for creating metadata PDUs as specified in /// This abstraction exposes a specialized API for creating metadata PDUs as specified in
/// CFDP chapter 5.2.5. /// CFDP chapter 5.2.5.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MetadataPduCreator<'src_name, 'dest_name, 'opts> { pub struct MetadataPduCreator<'src_name, 'dest_name, 'opts> {
pdu_header: PduHeader, pdu_header: PduHeader,
metadata_params: MetadataGenericParams, metadata_params: MetadataGenericParams,
@ -243,7 +241,6 @@ impl<'opts> Iterator for OptionsIter<'opts> {
/// involved. /// involved.
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MetadataPduReader<'buf> { pub struct MetadataPduReader<'buf> {
pdu_header: PduHeader, pdu_header: PduHeader,
metadata_params: MetadataGenericParams, metadata_params: MetadataGenericParams,

View File

@ -18,7 +18,6 @@ pub mod nak;
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum FileDirectiveType { pub enum FileDirectiveType {
EofPdu = 0x04, EofPdu = 0x04,
@ -221,7 +220,6 @@ pub trait CfdpPdu {
/// same. /// same.
#[derive(Debug, Copy, Clone, Eq)] #[derive(Debug, Copy, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CommonPduConfig { pub struct CommonPduConfig {
source_entity_id: UnsignedByteField, source_entity_id: UnsignedByteField,
dest_entity_id: UnsignedByteField, dest_entity_id: UnsignedByteField,
@ -360,7 +358,6 @@ pub const FIXED_HEADER_LEN: usize = 4;
/// For detailed information, refer to chapter 5.1 of the CFDP standard. /// For detailed information, refer to chapter 5.1 of the CFDP standard.
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PduHeader { pub struct PduHeader {
pdu_type: PduType, pdu_type: PduType,
pdu_conf: CommonPduConfig, pdu_conf: CommonPduConfig,

View File

@ -12,7 +12,6 @@ use super::{
/// Helper type to encapsulate both normal file size segment requests and large file size segment /// Helper type to encapsulate both normal file size segment requests and large file size segment
/// requests. /// requests.
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SegmentRequests<'a> { pub enum SegmentRequests<'a> {
U32Pairs(&'a [(u32, u32)]), U32Pairs(&'a [(u32, u32)]),
U64Pairs(&'a [(u64, u64)]), U64Pairs(&'a [(u64, u64)]),
@ -32,7 +31,6 @@ impl SegmentRequests<'_> {
/// It exposes a specialized API which simplifies to generate these NAK PDUs with the /// It exposes a specialized API which simplifies to generate these NAK PDUs with the
/// format according to CFDP chapter 5.2.6. /// format according to CFDP chapter 5.2.6.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NakPduCreator<'seg_reqs> { pub struct NakPduCreator<'seg_reqs> {
pdu_header: PduHeader, pdu_header: PduHeader,
start_of_scope: u64, start_of_scope: u64,
@ -355,7 +353,6 @@ impl<T: SegReqFromBytes> SegmentRequestIter<'_, T> {
/// ///
/// The NAK format is expected to be conforming to CFDP chapter 5.2.6. /// The NAK format is expected to be conforming to CFDP chapter 5.2.6.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NakPduReader<'seg_reqs> { pub struct NakPduReader<'seg_reqs> {
pdu_header: PduHeader, pdu_header: PduHeader,
start_of_scope: u64, start_of_scope: u64,

View File

@ -52,7 +52,6 @@ pub trait WritableTlv {
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum TlvType { pub enum TlvType {
FilestoreRequest = 0x00, FilestoreRequest = 0x00,
@ -65,7 +64,6 @@ pub enum TlvType {
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TlvTypeField { pub enum TlvTypeField {
Standard(TlvType), Standard(TlvType),
Custom(u8), Custom(u8),
@ -73,7 +71,6 @@ pub enum TlvTypeField {
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum FilestoreActionCode { pub enum FilestoreActionCode {
CreateFile = 0b0000, CreateFile = 0b0000,
@ -121,7 +118,6 @@ impl From<TlvTypeField> for u8 {
/// this will be the lifetime of that data reference. /// this will be the lifetime of that data reference.
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Tlv<'data> { pub struct Tlv<'data> {
tlv_type_field: TlvTypeField, tlv_type_field: TlvTypeField,
#[cfg_attr(feature = "serde", serde(borrow))] #[cfg_attr(feature = "serde", serde(borrow))]
@ -228,7 +224,6 @@ pub(crate) fn verify_tlv_type(raw_type: u8, expected_tlv_type: TlvType) -> Resul
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EntityIdTlv { pub struct EntityIdTlv {
entity_id: UnsignedByteField, entity_id: UnsignedByteField,
} }
@ -353,7 +348,6 @@ pub fn fs_request_has_second_filename(action_code: FilestoreActionCode) -> bool
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct FilestoreTlvBase<'first_name, 'second_name> { struct FilestoreTlvBase<'first_name, 'second_name> {
pub action_code: FilestoreActionCode, pub action_code: FilestoreActionCode,
#[cfg_attr(feature = "serde", serde(borrow))] #[cfg_attr(feature = "serde", serde(borrow))]
@ -567,7 +561,6 @@ impl GenericTlv for FilestoreRequestTlv<'_, '_> {
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct FilestoreResponseTlv<'first_name, 'second_name, 'fs_msg> { pub struct FilestoreResponseTlv<'first_name, 'second_name, 'fs_msg> {
#[cfg_attr(feature = "serde", serde(borrow))] #[cfg_attr(feature = "serde", serde(borrow))]
base: FilestoreTlvBase<'first_name, 'second_name>, base: FilestoreTlvBase<'first_name, 'second_name>,

View File

@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum Subservice { pub enum Subservice {
// Regular HK // Regular HK

View File

@ -74,7 +74,6 @@ pub enum PusServiceId {
/// 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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum PusVersion { pub enum PusVersion {
EsaPus = 0, EsaPus = 0,
@ -151,7 +150,6 @@ pub enum PfcReal {
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PusError { pub enum PusError {
VersionNotSupported(PusVersion), VersionNotSupported(PusVersion),
ChecksumFailure(u16), ChecksumFailure(u16),
@ -207,12 +205,13 @@ pub trait PusPacket: CcsdsPacket {
fn crc16(&self) -> Option<u16>; fn crc16(&self) -> Option<u16>;
} }
pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> { pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, PusError> {
if raw_data.len() < 2 { if raw_data.len() < 2 {
return Err(ByteConversionError::FromSliceTooSmall { return Err(ByteConversionError::FromSliceTooSmall {
found: raw_data.len(), found: raw_data.len(),
expected: 2, expected: 2,
}); }
.into());
} }
Ok(u16::from_be_bytes( Ok(u16::from_be_bytes(
raw_data[raw_data.len() - 2..raw_data.len()] raw_data[raw_data.len() - 2..raw_data.len()]
@ -227,16 +226,35 @@ pub(crate) fn calc_pus_crc16(bytes: &[u8]) -> u16 {
digest.finalize() digest.finalize()
} }
pub(crate) fn crc_procedure(
calc_on_serialization: bool,
cached_crc16: &Option<u16>,
start_idx: usize,
curr_idx: usize,
slice: &[u8],
) -> Result<u16, PusError> {
let crc16;
if calc_on_serialization {
crc16 = calc_pus_crc16(&slice[start_idx..curr_idx])
} else if cached_crc16.is_none() {
return Err(PusError::CrcCalculationMissing);
} else {
crc16 = cached_crc16.unwrap();
}
Ok(crc16)
}
pub(crate) fn user_data_from_raw( pub(crate) fn user_data_from_raw(
current_idx: usize, current_idx: usize,
total_len: usize, total_len: usize,
slice: &[u8], slice: &[u8],
) -> Result<&[u8], ByteConversionError> { ) -> Result<&[u8], PusError> {
match current_idx { match current_idx {
_ if current_idx > total_len - 2 => Err(ByteConversionError::FromSliceTooSmall { _ if current_idx > total_len - 2 => Err(ByteConversionError::FromSliceTooSmall {
found: total_len - 2, found: total_len - 2,
expected: current_idx, expected: current_idx,
}), }
.into()),
_ => Ok(&slice[current_idx..total_len - 2]), _ => Ok(&slice[current_idx..total_len - 2]),
} }
} }

View File

@ -48,6 +48,8 @@ use zerocopy::AsBytes;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
pub use legacy_tc::*;
/// PUS C secondary header length is fixed /// PUS C secondary header length is fixed
pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::<zc::PusTcSecondaryHeader>(); pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::<zc::PusTcSecondaryHeader>();
pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize = pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize =
@ -59,7 +61,6 @@ pub trait IsPusTelecommand {}
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
enum AckOpts { enum AckOpts {
Acceptance = 0b1000, Acceptance = 0b1000,
@ -145,7 +146,6 @@ 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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PusTcSecondaryHeader { pub struct PusTcSecondaryHeader {
pub service: u8, pub service: u8,
pub subservice: u8, pub subservice: u8,
@ -212,6 +212,333 @@ impl PusTcSecondaryHeader {
} }
} }
pub mod legacy_tc {
use crate::ecss::tc::{
zc, GenericPusTcSecondaryHeader, IsPusTelecommand, PusTcSecondaryHeader, ACK_ALL,
PUC_TC_SECONDARY_HEADER_LEN, PUS_TC_MIN_LEN_WITHOUT_APP_DATA,
};
use crate::ecss::{
ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls,
verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, WritablePusPacket,
CCSDS_HEADER_LEN,
};
use crate::ecss::{user_data_from_raw, PusVersion};
use crate::SequenceFlags;
use crate::{ByteConversionError, CcsdsPacket, PacketType, SpHeader, CRC_CCITT_FALSE};
use core::mem::size_of;
use delegate::delegate;
use zerocopy::AsBytes;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// 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.
///
/// # 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<'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<&'raw_data [u8]>,
app_data: &'raw_data [u8],
crc16: Option<u16>,
}
impl<'raw_data> PusTc<'raw_data> {
/// Generates a new struct instance.
///
/// # Arguments
///
/// * `sp_header` - Space packet header information. The correct packet type will be set
/// automatically
/// * `sec_header` - Information contained in the data field header, including the service
/// and subservice type
/// * `app_data` - Custom application data
/// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length
/// field. If this is not set to true, [PusTc::update_ccsds_data_len] can be called to set
/// the correct value to this field manually
#[deprecated(
since = "0.7.0",
note = "Use specialized PusTcCreator or PusTcReader classes instead"
)]
pub fn new(
sp_header: &mut SpHeader,
sec_header: PusTcSecondaryHeader,
app_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool,
) -> Self {
sp_header.set_packet_type(PacketType::Tc);
sp_header.set_sec_header_flag();
let mut pus_tc = Self {
sp_header: *sp_header,
raw_data: None,
app_data: app_data.unwrap_or(&[]),
sec_header,
calc_crc_on_serialization: true,
crc16: None,
};
if set_ccsds_len {
pus_tc.update_ccsds_data_len();
}
pus_tc
}
/// Simplified version of the [PusTc::new] function which allows to only specify service and
/// subservice instead of the full PUS TC secondary header.
#[deprecated(
since = "0.7.0",
note = "Use specialized PusTcCreator or PusTcReader classes instead"
)]
pub fn new_simple(
sph: &mut SpHeader,
service: u8,
subservice: u8,
app_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool,
) -> Self {
#[allow(deprecated)]
Self::new(
sph,
PusTcSecondaryHeader::new(service, subservice, ACK_ALL, 0),
app_data,
set_ccsds_len,
)
}
pub fn sp_header(&self) -> &SpHeader {
&self.sp_header
}
pub fn set_ack_field(&mut self, ack: u8) -> bool {
if ack > 0b1111 {
return false;
}
self.sec_header.ack = ack & 0b1111;
true
}
pub fn set_source_id(&mut self, source_id: u16) {
self.sec_header.source_id = source_id;
}
sp_header_impls!();
/// Calculate the CCSDS space packet data length field and sets it
/// This is called automatically if the `set_ccsds_len` argument in the [PusTc::new] call was
/// used.
/// If this was not done or the application data is set or changed after construction,
/// this function needs to be called to ensure that the data length field of the CCSDS header
/// is set correctly.
pub fn update_ccsds_data_len(&mut self) {
self.sp_header.data_len =
self.len_written() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
}
/// This function should be called before the TC packet is serialized if
/// [PusTc::calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16.
pub fn calc_own_crc16(&mut self) {
let mut digest = CRC_CCITT_FALSE.digest();
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
digest.update(sph_zc.as_bytes());
let pus_tc_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
digest.update(pus_tc_header.as_bytes());
if !self.app_data.is_empty() {
digest.update(self.app_data);
}
self.crc16 = Some(digest.finalize())
}
/// This helper function calls both [PusTc::update_ccsds_data_len] and [PusTc::calc_own_crc16].
pub fn update_packet_fields(&mut self) {
self.update_ccsds_data_len();
self.calc_own_crc16();
}
#[cfg(feature = "alloc")]
#[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 appended_len = PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len();
let start_idx = vec.len();
let mut ser_len = 0;
vec.extend_from_slice(sph_zc.as_bytes());
ser_len += sph_zc.as_bytes().len();
// The PUS version is hardcoded to PUS C
let pus_tc_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
vec.extend_from_slice(pus_tc_header.as_bytes());
ser_len += pus_tc_header.as_bytes().len();
vec.extend_from_slice(self.app_data);
ser_len += self.app_data.len();
let crc16 = crc_procedure(
self.calc_crc_on_serialization,
&self.crc16,
start_idx,
ser_len,
&vec[start_idx..ser_len],
)?;
vec.extend_from_slice(crc16.to_be_bytes().as_slice());
Ok(appended_len)
}
/// 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.
#[deprecated(
since = "0.7.0",
note = "Use specialized PusTcCreator or PusTcReader classes instead"
)]
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(ByteConversionError::FromSliceTooSmall {
found: raw_data_len,
expected: PUS_TC_MIN_LEN_WITHOUT_APP_DATA,
}
.into());
}
let mut current_idx = 0;
let (sp_header, _) = SpHeader::from_be_bytes(&slice[0..CCSDS_HEADER_LEN])?;
current_idx += CCSDS_HEADER_LEN;
let total_len = sp_header.total_len();
if raw_data_len < total_len || total_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
return Err(ByteConversionError::FromSliceTooSmall {
found: raw_data_len,
expected: total_len,
}
.into());
}
let sec_header = zc::PusTcSecondaryHeader::from_bytes(
&slice[current_idx..current_idx + PUC_TC_SECONDARY_HEADER_LEN],
)
.ok_or(ByteConversionError::ZeroCopyFromError)?;
current_idx += PUC_TC_SECONDARY_HEADER_LEN;
let raw_data = &slice[0..total_len];
let pus_tc = Self {
sp_header,
sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(),
raw_data: Some(raw_data),
app_data: user_data_from_raw(current_idx, total_len, slice)?,
calc_crc_on_serialization: false,
crc16: Some(crc_from_raw_data(raw_data)?),
};
verify_crc16_ccitt_false_from_raw_to_pus_error(
raw_data,
pus_tc.crc16.expect("CRC16 invalid"),
)?;
Ok((pus_tc, total_len))
}
#[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 WritablePusPacket for PusTc<'_> {
fn len_written(&self) -> usize {
PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len()
}
/// Write the raw PUS byte representation to a provided buffer.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = 0;
let tc_header_len = size_of::<zc::PusTcSecondaryHeader>();
let total_size = self.len_written();
if total_size > slice.len() {
return Err(ByteConversionError::ToSliceTooSmall {
found: slice.len(),
expected: total_size,
}
.into());
}
self.sp_header.write_to_be_bytes(slice)?;
curr_idx += CCSDS_HEADER_LEN;
let sec_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
sec_header
.write_to_bytes(&mut slice[curr_idx..curr_idx + tc_header_len])
.ok_or(ByteConversionError::ZeroCopyToError)?;
curr_idx += tc_header_len;
slice[curr_idx..curr_idx + self.app_data.len()].copy_from_slice(self.app_data);
curr_idx += self.app_data.len();
let crc16 = crc_procedure(
self.calc_crc_on_serialization,
&self.crc16,
0,
curr_idx,
slice,
)?;
slice[curr_idx..curr_idx + 2].copy_from_slice(crc16.to_be_bytes().as_slice());
curr_idx += 2;
Ok(curr_idx)
}
}
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
}
}
impl CcsdsPacket for PusTc<'_> {
ccsds_impl!();
}
impl PusPacket for PusTc<'_> {
delegate!(to self.sec_header {
fn pus_version(&self) -> PusVersion;
fn service(&self) -> u8;
fn subservice(&self) -> u8;
});
fn user_data(&self) -> &[u8] {
self.app_data
}
fn crc16(&self) -> Option<u16> {
self.crc16
}
}
impl GenericPusTcSecondaryHeader for PusTc<'_> {
delegate!(to self.sec_header {
fn pus_version(&self) -> PusVersion;
fn service(&self) -> u8;
fn subservice(&self) -> u8;
fn source_id(&self) -> u16;
fn ack_flags(&self) -> u8;
});
}
impl IsPusTelecommand for PusTc<'_> {}
}
/// This class can be used to create PUS C telecommand packet. It is the primary data structure to /// This class can be used to create PUS C telecommand packet. It is the primary data structure to
/// generate the raw byte representation of a PUS telecommand. /// generate the raw byte representation of a PUS telecommand.
/// ///
@ -222,7 +549,6 @@ impl PusTcSecondaryHeader {
/// There is no spare bytes support yet. /// There is no spare bytes support yet.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PusTcCreator<'raw_data> { pub struct PusTcCreator<'raw_data> {
sp_header: SpHeader, sp_header: SpHeader,
pub sec_header: PusTcSecondaryHeader, pub sec_header: PusTcSecondaryHeader,
@ -240,7 +566,7 @@ impl<'raw_data> PusTcCreator<'raw_data> {
/// and subservice type /// and subservice type
/// * `app_data` - Custom application data /// * `app_data` - Custom application data
/// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length /// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length
/// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set /// field. If this is not set to true, [PusTc::update_ccsds_data_len] can be called to set
/// 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,
@ -261,19 +587,19 @@ impl<'raw_data> PusTcCreator<'raw_data> {
pus_tc pus_tc
} }
/// Simplified version of the [Self::new] function which allows to only specify service /// Simplified version of the [PusTcCreator::new] function which allows to only specify service
/// and subservice instead of the full PUS TC secondary header. /// and subservice instead of the full PUS TC secondary header.
pub fn new_simple( pub fn new_simple(
sph: &mut SpHeader, sph: &mut SpHeader,
service: u8, service: u8,
subservice: u8, subservice: u8,
app_data: &'raw_data [u8], app_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool, set_ccsds_len: bool,
) -> Self { ) -> Self {
Self::new( Self::new(
sph, sph,
PusTcSecondaryHeader::new(service, subservice, ACK_ALL, 0), PusTcSecondaryHeader::new(service, subservice, ACK_ALL, 0),
app_data, app_data.unwrap_or(&[]),
set_ccsds_len, set_ccsds_len,
) )
} }
@ -305,7 +631,7 @@ impl<'raw_data> PusTcCreator<'raw_data> {
sp_header_impls!(); sp_header_impls!();
/// Calculate the CCSDS space packet data length field and sets it /// Calculate the CCSDS space packet data length field and sets it
/// This is called automatically if the `set_ccsds_len` argument in the [Self::new] call was /// This is called automatically if the `set_ccsds_len` argument in the [PusTc::new] call was
/// used. /// used.
/// If this was not done or the application data is set or changed after construction, /// If this was not done or the application data is set or changed after construction,
/// this function needs to be called to ensure that the data length field of the CCSDS header /// this function needs to be called to ensure that the data length field of the CCSDS header
@ -315,7 +641,8 @@ impl<'raw_data> PusTcCreator<'raw_data> {
self.len_written() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1; self.len_written() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
} }
/// This function calculates and returns the CRC16 for the current packet. /// This function should be called before the TC packet is serialized if
/// [PusTc::calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16.
pub fn calc_own_crc16(&self) -> u16 { pub fn calc_own_crc16(&self) -> u16 {
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
let sph_zc = crate::zc::SpHeader::from(self.sp_header); let sph_zc = crate::zc::SpHeader::from(self.sp_header);
@ -327,7 +654,8 @@ impl<'raw_data> PusTcCreator<'raw_data> {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> usize { #[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 sph_zc = crate::zc::SpHeader::from(self.sp_header);
let mut appended_len = PUS_TC_MIN_LEN_WITHOUT_APP_DATA; let mut appended_len = PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
appended_len += self.app_data.len(); appended_len += self.app_data.len();
@ -340,7 +668,7 @@ impl<'raw_data> PusTcCreator<'raw_data> {
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&vec[start_idx..start_idx + appended_len - 2]); digest.update(&vec[start_idx..start_idx + appended_len - 2]);
vec.extend_from_slice(&digest.finalize().to_be_bytes()); vec.extend_from_slice(&digest.finalize().to_be_bytes());
appended_len Ok(appended_len)
} }
} }
@ -424,7 +752,6 @@ impl IsPusTelecommand for PusTcCreator<'_> {}
/// * `'raw_data` - Lifetime of the provided raw slice. /// * `'raw_data` - Lifetime of the provided raw slice.
#[derive(Eq, Copy, Clone, Debug)] #[derive(Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PusTcReader<'raw_data> { pub struct PusTcReader<'raw_data> {
#[cfg_attr(feature = "serde", serde(skip))] #[cfg_attr(feature = "serde", serde(skip))]
raw_data: &'raw_data [u8], raw_data: &'raw_data [u8],
@ -436,8 +763,7 @@ pub struct PusTcReader<'raw_data> {
impl<'raw_data> PusTcReader<'raw_data> { impl<'raw_data> PusTcReader<'raw_data> {
/// Create a [PusTcReader] instance from a raw slice. On success, it returns a tuple containing /// Create a [PusTcReader] instance from a raw slice. On success, it returns a tuple containing
/// the instance and the found byte length of the packet. This function also performs a CRC /// the instance and the found byte length of the packet.
/// check and will return an appropriate [PusError] if the check fails.
pub fn new(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> { pub fn new(slice: &'raw_data [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 {
@ -575,12 +901,12 @@ mod tests {
fn base_ping_tc_simple_ctor() -> PusTcCreator<'static> { fn base_ping_tc_simple_ctor() -> PusTcCreator<'static> {
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap(); let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
PusTcCreator::new_simple(&mut sph, 17, 1, &[], true) PusTcCreator::new_simple(&mut sph, 17, 1, None, true)
} }
fn base_ping_tc_simple_ctor_with_app_data(app_data: &'static [u8]) -> PusTcCreator<'static> { fn base_ping_tc_simple_ctor_with_app_data(app_data: &'static [u8]) -> PusTcCreator<'static> {
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap(); let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
PusTcCreator::new_simple(&mut sph, 17, 1, app_data, true) PusTcCreator::new_simple(&mut sph, 17, 1, Some(app_data), true)
} }
#[test] #[test]
@ -637,7 +963,7 @@ mod tests {
#[test] #[test]
fn test_update_func() { fn test_update_func() {
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap(); let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
let mut tc = PusTcCreator::new_simple(&mut sph, 17, 1, &[], false); let mut tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, false);
assert_eq!(tc.data_len(), 0); assert_eq!(tc.data_len(), 0);
tc.update_ccsds_data_len(); tc.update_ccsds_data_len();
assert_eq!(tc.data_len(), 6); assert_eq!(tc.data_len(), 6);
@ -684,7 +1010,9 @@ mod tests {
fn test_vec_ser_deser() { fn test_vec_ser_deser() {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
let mut test_vec = Vec::new(); let mut test_vec = Vec::new();
let size = pus_tc.append_to_vec(&mut test_vec); let size = pus_tc
.append_to_vec(&mut test_vec)
.expect("Error writing TC to vector");
assert_eq!(size, 13); assert_eq!(size, 13);
verify_test_tc_raw(&test_vec.as_slice()); verify_test_tc_raw(&test_vec.as_slice());
verify_crc_no_app_data(&test_vec.as_slice()); verify_crc_no_app_data(&test_vec.as_slice());
@ -730,7 +1058,9 @@ mod tests {
let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]); let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]);
verify_test_tc(&pus_tc, true, 16); verify_test_tc(&pus_tc, true, 16);
let mut test_vec = Vec::new(); let mut test_vec = Vec::new();
let size = pus_tc.append_to_vec(&mut test_vec); let size = pus_tc
.append_to_vec(&mut test_vec)
.expect("Error writing TC to vector");
assert_eq!(test_vec[11], 1); assert_eq!(test_vec[11], 1);
assert_eq!(test_vec[12], 2); assert_eq!(test_vec[12], 2);
assert_eq!(test_vec[13], 3); assert_eq!(test_vec[13], 3);

View File

@ -19,6 +19,7 @@ use alloc::vec::Vec;
use delegate::delegate; use delegate::delegate;
use crate::time::{TimeWriter, TimestampError}; use crate::time::{TimeWriter, TimestampError};
pub use legacy_tm::*;
use self::zc::PusTmSecHeaderWithoutTimestamp; use self::zc::PusTmSecHeaderWithoutTimestamp;
@ -115,7 +116,6 @@ 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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PusTmSecondaryHeader<'stamp> { pub struct PusTmSecondaryHeader<'stamp> {
pus_version: PusVersion, pus_version: PusVersion,
pub sc_time_ref_status: u8, pub sc_time_ref_status: u8,
@ -123,17 +123,17 @@ pub struct PusTmSecondaryHeader<'stamp> {
pub subservice: u8, pub subservice: u8,
pub msg_counter: u16, pub msg_counter: u16,
pub dest_id: u16, pub dest_id: u16,
pub time_stamp: &'stamp [u8], pub timestamp: &'stamp [u8],
} }
impl<'stamp> PusTmSecondaryHeader<'stamp> { impl<'stamp> PusTmSecondaryHeader<'stamp> {
pub fn new_simple(service: u8, subservice: u8, time_stamp: &'stamp [u8]) -> Self { pub fn new_simple(service: u8, subservice: u8, timestamp: &'stamp [u8]) -> Self {
Self::new(service, subservice, 0, 0, time_stamp) Self::new(service, subservice, 0, 0, Some(timestamp))
} }
/// Like [Self::new_simple] but without a timestamp. /// Like [Self::new_simple] but without a timestamp.
pub fn new_simple_no_timestamp(service: u8, subservice: u8) -> Self { pub fn new_simple_no_timestamp(service: u8, subservice: u8) -> Self {
Self::new(service, subservice, 0, 0, &[]) Self::new(service, subservice, 0, 0, None)
} }
pub fn new( pub fn new(
@ -141,7 +141,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> {
subservice: u8, subservice: u8,
msg_counter: u16, msg_counter: u16,
dest_id: u16, dest_id: u16,
time_stamp: &'stamp [u8], timestamp: Option<&'stamp [u8]>,
) -> Self { ) -> Self {
PusTmSecondaryHeader { PusTmSecondaryHeader {
pus_version: PusVersion::PusC, pus_version: PusVersion::PusC,
@ -150,7 +150,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> {
subservice, subservice,
msg_counter, msg_counter,
dest_id, dest_id,
time_stamp, timestamp: timestamp.unwrap_or(&[]),
} }
} }
} }
@ -192,11 +192,340 @@ 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(),
time_stamp: sec_header.timestamp, timestamp: sec_header.timestamp,
}) })
} }
} }
pub mod legacy_tm {
use crate::ecss::tm::{
zc, GenericPusTmSecondaryHeader, IsPusTelemetry, PusTmSecondaryHeader,
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA, PUS_TM_MIN_SEC_HEADER_LEN,
};
use crate::ecss::PusVersion;
use crate::ecss::{
ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls, user_data_from_raw,
verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, WritablePusPacket,
CCSDS_HEADER_LEN,
};
use crate::SequenceFlags;
use crate::{ByteConversionError, CcsdsPacket, PacketType, SpHeader, CRC_CCITT_FALSE};
use core::mem::size_of;
use zerocopy::AsBytes;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use delegate::delegate;
/// 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
/// provider like [postcard](https://docs.rs/postcard/latest/postcard/).
///
/// There is no spare bytes support yet.
///
/// # Lifetimes
///
/// * `'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<'raw_data> {
pub sp_header: SpHeader,
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<&'raw_data [u8]>,
source_data: &'raw_data [u8],
crc16: Option<u16>,
}
impl<'raw_data> PusTm<'raw_data> {
/// Generates a new struct instance.
///
/// # Arguments
///
/// * `sp_header` - Space packet header information. The correct packet type will be set
/// automatically
/// * `sec_header` - Information contained in the secondary header, including the service
/// and subservice type
/// * `app_data` - Custom application data
/// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length
/// field. If this is not set to true, [PusTm::update_ccsds_data_len] can be called to set
/// the correct value to this field manually
#[deprecated(
since = "0.7.0",
note = "Use specialized PusTmCreator or PusTmReader classes instead"
)]
pub fn new(
sp_header: &mut SpHeader,
sec_header: PusTmSecondaryHeader<'raw_data>,
source_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool,
) -> Self {
sp_header.set_packet_type(PacketType::Tm);
sp_header.set_sec_header_flag();
let mut pus_tm = PusTm {
sp_header: *sp_header,
raw_data: None,
source_data: source_data.unwrap_or(&[]),
sec_header,
calc_crc_on_serialization: true,
crc16: None,
};
if set_ccsds_len {
pus_tm.update_ccsds_data_len();
}
pus_tm
}
pub fn timestamp(&self) -> &[u8] {
self.sec_header.timestamp
}
pub fn source_data(&self) -> &[u8] {
self.source_data
}
pub fn set_dest_id(&mut self, dest_id: u16) {
self.sec_header.dest_id = dest_id;
}
pub fn set_msg_counter(&mut self, msg_counter: u16) {
self.sec_header.msg_counter = msg_counter
}
pub fn set_sc_time_ref_status(&mut self, sc_time_ref_status: u8) {
self.sec_header.sc_time_ref_status = sc_time_ref_status & 0b1111;
}
sp_header_impls!();
/// This is called automatically if the `set_ccsds_len` argument in the [PusTm::new] call was
/// used.
/// If this was not done or the time stamp or source data is set or changed after construction,
/// this function needs to be called to ensure that the data length field of the CCSDS header
/// is set correctly
pub fn update_ccsds_data_len(&mut self) {
self.sp_header.data_len =
self.len_written() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
}
/// This function should be called before the TM packet is serialized if
/// [PusTm.calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16.
pub fn calc_own_crc16(&mut self) {
let mut digest = CRC_CCITT_FALSE.digest();
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
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.timestamp);
digest.update(self.source_data);
self.crc16 = Some(digest.finalize())
}
/// This helper function calls both [PusTm.update_ccsds_data_len] and [PusTm.calc_own_crc16]
pub fn update_packet_fields(&mut self) {
self.update_ccsds_data_len();
self.calc_own_crc16();
}
/// Append the raw PUS byte representation to a provided [alloc::vec::Vec]
#[cfg(feature = "alloc")]
#[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;
appended_len += self.sec_header.timestamp.len();
appended_len += self.source_data.len();
let start_idx = vec.len();
let mut ser_len = 0;
vec.extend_from_slice(sph_zc.as_bytes());
ser_len += sph_zc.as_bytes().len();
// The PUS version is hardcoded to PUS C
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();
ser_len += self.sec_header.timestamp.len();
vec.extend_from_slice(self.sec_header.timestamp);
vec.extend_from_slice(self.source_data);
ser_len += self.source_data.len();
let crc16 = crc_procedure(
self.calc_crc_on_serialization,
&self.crc16,
start_idx,
ser_len,
&vec[start_idx..start_idx + ser_len],
)?;
vec.extend_from_slice(crc16.to_be_bytes().as_slice());
Ok(appended_len)
}
/// Create a [PusTm] instance from a raw slice. On success, it returns a tuple containing
/// the instance and the found byte length of the packet. The timestamp length needs to be
/// known beforehand.
#[deprecated(
since = "0.7.0",
note = "Use specialized PusTmCreator or PusTmReader classes instead"
)]
pub fn from_bytes(
slice: &'raw_data [u8],
timestamp_len: usize,
) -> Result<(Self, usize), PusError> {
let raw_data_len = slice.len();
if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
return Err(ByteConversionError::FromSliceTooSmall {
found: raw_data_len,
expected: PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA,
}
.into());
}
let mut current_idx = 0;
let (sp_header, _) = SpHeader::from_be_bytes(&slice[0..CCSDS_HEADER_LEN])?;
current_idx += 6;
let total_len = sp_header.total_len();
if raw_data_len < total_len {
return Err(ByteConversionError::FromSliceTooSmall {
found: raw_data_len,
expected: PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA,
}
.into());
}
if total_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
return Err(ByteConversionError::FromSliceTooSmall {
found: total_len,
expected: PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA,
}
.into());
}
let sec_header_zc = zc::PusTmSecHeaderWithoutTimestamp::from_bytes(
&slice[current_idx..current_idx + PUS_TM_MIN_SEC_HEADER_LEN],
)
.ok_or(ByteConversionError::ZeroCopyFromError)?;
current_idx += PUS_TM_MIN_SEC_HEADER_LEN;
let zc_sec_header_wrapper = zc::PusTmSecHeader {
zc_header: sec_header_zc,
timestamp: &slice[current_idx..current_idx + timestamp_len],
};
current_idx += timestamp_len;
let raw_data = &slice[0..total_len];
let pus_tm = PusTm {
sp_header,
sec_header: PusTmSecondaryHeader::try_from(zc_sec_header_wrapper).unwrap(),
raw_data: Some(&slice[0..total_len]),
source_data: user_data_from_raw(current_idx, total_len, slice)?,
calc_crc_on_serialization: false,
crc16: Some(crc_from_raw_data(raw_data)?),
};
verify_crc16_ccitt_false_from_raw_to_pus_error(
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 WritablePusPacket for PusTm<'_> {
fn len_written(&self) -> usize {
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA
+ self.sec_header.timestamp.len()
+ self.source_data.len()
}
/// Write the raw PUS byte representation to a provided buffer.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = 0;
let total_size = self.len_written();
if total_size > slice.len() {
return Err(ByteConversionError::ToSliceTooSmall {
found: slice.len(),
expected: total_size,
}
.into());
}
self.sp_header
.write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?;
curr_idx += CCSDS_HEADER_LEN;
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
sec_header
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
.ok_or(ByteConversionError::ZeroCopyToError)?;
curr_idx += sec_header_len;
slice[curr_idx..curr_idx + self.sec_header.timestamp.len()]
.copy_from_slice(self.sec_header.timestamp);
curr_idx += self.sec_header.timestamp.len();
slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data);
curr_idx += self.source_data.len();
let crc16 = crc_procedure(
self.calc_crc_on_serialization,
&self.crc16,
0,
curr_idx,
slice,
)?;
slice[curr_idx..curr_idx + 2].copy_from_slice(crc16.to_be_bytes().as_slice());
curr_idx += 2;
Ok(curr_idx)
}
}
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
}
}
impl CcsdsPacket for PusTm<'_> {
ccsds_impl!();
}
impl PusPacket for PusTm<'_> {
delegate!(to self.sec_header {
fn pus_version(&self) -> PusVersion;
fn service(&self) -> u8;
fn subservice(&self) -> u8;
});
fn user_data(&self) -> &[u8] {
self.source_data
}
fn crc16(&self) -> Option<u16> {
self.crc16
}
}
impl GenericPusTmSecondaryHeader for PusTm<'_> {
delegate!(to self.sec_header {
fn pus_version(&self) -> PusVersion;
fn service(&self) -> u8;
fn subservice(&self) -> u8;
fn dest_id(&self) -> u16;
fn msg_counter(&self) -> u16;
fn sc_time_ref_status(&self) -> u8;
});
}
impl IsPusTelemetry for PusTm<'_> {}
}
/// This class models the PUS C telemetry packet. It is the primary data structure to generate the /// This class models the PUS C telemetry packet. It is the primary data structure to generate the
/// raw byte representation of PUS telemetry. /// raw byte representation of PUS telemetry.
/// ///
@ -211,18 +540,16 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
/// * `'raw_data` - This is the lifetime of the user provided time stamp and source data. /// * `'raw_data` - This is the lifetime of the user provided time stamp and source data.
#[derive(Eq, Debug, Copy, Clone)] #[derive(Eq, Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTmCreator<'raw_data> {
pub struct PusTmCreator<'time, 'raw_data> {
pub sp_header: SpHeader, pub sp_header: SpHeader,
#[cfg_attr(feature = "serde", serde(borrow))] pub sec_header: PusTmSecondaryHeader<'raw_data>,
pub sec_header: PusTmSecondaryHeader<'time>,
source_data: &'raw_data [u8], source_data: &'raw_data [u8],
/// If this is set to false, a manual call to [Self::calc_own_crc16] or /// If this is set to false, a manual call to [PusTm::calc_own_crc16] or
/// [Self::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,
} }
impl<'time, 'raw_data> PusTmCreator<'time, 'raw_data> { impl<'raw_data> PusTmCreator<'raw_data> {
/// Generates a new struct instance. /// Generates a new struct instance.
/// ///
/// # Arguments /// # Arguments
@ -233,11 +560,11 @@ impl<'time, 'raw_data> PusTmCreator<'time, 'raw_data> {
/// and subservice type /// and subservice type
/// * `source_data` - Custom application data /// * `source_data` - Custom application data
/// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length /// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length
/// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set /// field. If this is not set to true, [PusTm::update_ccsds_data_len] can be called to set
/// 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<'time>, sec_header: PusTmSecondaryHeader<'raw_data>,
source_data: &'raw_data [u8], source_data: &'raw_data [u8],
set_ccsds_len: bool, set_ccsds_len: bool,
) -> Self { ) -> Self {
@ -260,7 +587,7 @@ impl<'time, 'raw_data> PusTmCreator<'time, 'raw_data> {
service: u8, service: u8,
subservice: u8, subservice: u8,
time_provider: &impl TimeWriter, time_provider: &impl TimeWriter,
stamp_buf: &'time mut [u8], stamp_buf: &'raw_data mut [u8],
source_data: Option<&'raw_data [u8]>, source_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool, set_ccsds_len: bool,
) -> Result<Self, TimestampError> { ) -> Result<Self, TimestampError> {
@ -277,14 +604,14 @@ impl<'time, 'raw_data> PusTmCreator<'time, 'raw_data> {
pub fn new_no_source_data( pub fn new_no_source_data(
sp_header: &mut SpHeader, sp_header: &mut SpHeader,
sec_header: PusTmSecondaryHeader<'time>, sec_header: PusTmSecondaryHeader<'raw_data>,
set_ccsds_len: bool, set_ccsds_len: bool,
) -> Self { ) -> Self {
Self::new(sp_header, sec_header, &[], set_ccsds_len) Self::new(sp_header, sec_header, &[], set_ccsds_len)
} }
pub fn timestamp(&self) -> &[u8] { pub fn timestamp(&self) -> &[u8] {
self.sec_header.time_stamp self.sec_header.timestamp
} }
pub fn source_data(&self) -> &[u8] { pub fn source_data(&self) -> &[u8] {
@ -305,7 +632,7 @@ impl<'time, 'raw_data> PusTmCreator<'time, 'raw_data> {
sp_header_impls!(); sp_header_impls!();
/// This is called automatically if the `set_ccsds_len` argument in the [Self::new] call was /// This is called automatically if the `set_ccsds_len` argument in the [PusTm::new] call was
/// used. /// used.
/// If this was not done or the time stamp or source data is set or changed after construction, /// If this was not done or the time stamp or source data is set or changed after construction,
/// this function needs to be called to ensure that the data length field of the CCSDS header /// this function needs to be called to ensure that the data length field of the CCSDS header
@ -316,32 +643,60 @@ impl<'time, 'raw_data> PusTmCreator<'time, 'raw_data> {
} }
/// This function should be called before the TM packet is serialized if /// This function should be called before the TM packet is serialized if
/// [Self::calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16. /// [PusTm.calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16.
pub fn calc_own_crc16(&self) -> u16 { pub fn calc_own_crc16(&self) -> u16 {
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
let sph_zc = crate::zc::SpHeader::from(self.sp_header); let sph_zc = crate::zc::SpHeader::from(self.sp_header);
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());
digest.update(self.sec_header.time_stamp); digest.update(self.sec_header.timestamp);
digest.update(self.source_data); digest.update(self.source_data);
digest.finalize() digest.finalize()
} }
/// This helper function calls both [Self::update_ccsds_data_len] and [Self::calc_own_crc16] /// This helper function calls both [PusTm.update_ccsds_data_len] and [PusTm.calc_own_crc16]
pub fn update_packet_fields(&mut self) { pub fn update_packet_fields(&mut self) {
self.update_ccsds_data_len(); self.update_ccsds_data_len();
} }
/// Append the raw PUS byte representation to a provided [alloc::vec::Vec]
#[cfg(feature = "alloc")]
#[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.timestamp.len();
appended_len += self.source_data.len();
let start_idx = vec.len();
vec.extend_from_slice(sph_zc.as_bytes());
// The PUS version is hardcoded to PUS C
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
vec.extend_from_slice(sec_header.as_bytes());
vec.extend_from_slice(self.sec_header.timestamp);
vec.extend_from_slice(self.source_data);
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&vec[start_idx..start_idx + appended_len - 2]);
vec.extend_from_slice(&digest.finalize().to_be_bytes());
Ok(appended_len)
}
}
impl WritablePusPacket for PusTmCreator<'_> {
fn len_written(&self) -> usize {
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA
+ self.sec_header.timestamp.len()
+ self.source_data.len()
}
/// Write the raw PUS byte representation to a provided buffer. /// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = 0; let mut curr_idx = 0;
let total_size = self.len_written(); let total_size = self.len_written();
if total_size > slice.len() { if total_size > slice.len() {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
found: slice.len(), found: slice.len(),
expected: total_size, expected: total_size,
}); }
.into());
} }
self.sp_header self.sp_header
.write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?; .write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?;
@ -352,9 +707,9 @@ impl<'time, 'raw_data> PusTmCreator<'time, 'raw_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;
slice[curr_idx..curr_idx + self.sec_header.time_stamp.len()] slice[curr_idx..curr_idx + self.sec_header.timestamp.len()]
.copy_from_slice(self.sec_header.time_stamp); .copy_from_slice(self.sec_header.timestamp);
curr_idx += self.sec_header.time_stamp.len(); curr_idx += self.sec_header.timestamp.len();
slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data); slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data);
curr_idx += self.source_data.len(); curr_idx += self.source_data.len();
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
@ -363,41 +718,9 @@ impl<'time, 'raw_data> PusTmCreator<'time, 'raw_data> {
curr_idx += 2; curr_idx += 2;
Ok(curr_idx) Ok(curr_idx)
} }
/// Append the raw PUS byte representation to a provided [alloc::vec::Vec]
#[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();
appended_len += self.source_data.len();
let start_idx = vec.len();
vec.extend_from_slice(sph_zc.as_bytes());
// The PUS version is hardcoded to PUS C
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
vec.extend_from_slice(sec_header.as_bytes());
vec.extend_from_slice(self.sec_header.time_stamp);
vec.extend_from_slice(self.source_data);
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&vec[start_idx..start_idx + appended_len - 2]);
vec.extend_from_slice(&digest.finalize().to_be_bytes());
Ok(appended_len)
}
} }
impl WritablePusPacket for PusTmCreator<'_, '_> { impl PartialEq for PusTmCreator<'_> {
fn len_written(&self) -> usize {
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA
+ self.sec_header.time_stamp.len()
+ self.source_data.len()
}
/// Write the raw PUS byte representation to a provided buffer.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes(self, slice)?)
}
}
impl PartialEq for PusTmCreator<'_, '_> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.sp_header == other.sp_header self.sp_header == other.sp_header
&& self.sec_header == other.sec_header && self.sec_header == other.sec_header
@ -405,11 +728,11 @@ impl PartialEq for PusTmCreator<'_, '_> {
} }
} }
impl CcsdsPacket for PusTmCreator<'_, '_> { impl CcsdsPacket for PusTmCreator<'_> {
ccsds_impl!(); ccsds_impl!();
} }
impl PusPacket for PusTmCreator<'_, '_> { impl PusPacket for PusTmCreator<'_> {
delegate!(to self.sec_header { delegate!(to self.sec_header {
fn pus_version(&self) -> PusVersion; fn pus_version(&self) -> PusVersion;
fn service(&self) -> u8; fn service(&self) -> u8;
@ -425,7 +748,7 @@ impl PusPacket for PusTmCreator<'_, '_> {
} }
} }
impl GenericPusTmSecondaryHeader for PusTmCreator<'_, '_> { impl GenericPusTmSecondaryHeader for PusTmCreator<'_> {
delegate!(to self.sec_header { delegate!(to self.sec_header {
fn pus_version(&self) -> PusVersion; fn pus_version(&self) -> PusVersion;
fn service(&self) -> u8; fn service(&self) -> u8;
@ -436,7 +759,7 @@ impl GenericPusTmSecondaryHeader for PusTmCreator<'_, '_> {
}); });
} }
impl IsPusTelemetry for PusTmCreator<'_, '_> {} impl IsPusTelemetry for PusTmCreator<'_> {}
/// This class models the PUS C telemetry packet. It is the primary data structure to read /// This class models the PUS C telemetry packet. It is the primary data structure to read
/// a telemetry packet from raw bytes. /// a telemetry packet from raw bytes.
@ -452,7 +775,6 @@ impl IsPusTelemetry for PusTmCreator<'_, '_> {}
/// * `'raw_data` - Lifetime of the raw slice this class is constructed from. /// * `'raw_data` - Lifetime of the raw slice this class is constructed from.
#[derive(Eq, Debug, Copy, Clone)] #[derive(Eq, Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PusTmReader<'raw_data> { pub struct PusTmReader<'raw_data> {
pub sp_header: SpHeader, pub sp_header: SpHeader,
pub sec_header: PusTmSecondaryHeader<'raw_data>, pub sec_header: PusTmSecondaryHeader<'raw_data>,
@ -466,9 +788,6 @@ impl<'raw_data> PusTmReader<'raw_data> {
/// Create a [PusTmReader] instance from a raw slice. On success, it returns a tuple containing /// Create a [PusTmReader] instance from a raw slice. On success, it returns a tuple containing
/// 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.
///
/// This function will check the CRC-16 of the PUS packet and will return an appropriate
/// [PusError] if the check fails.
pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<(Self, usize), PusError> { pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<(Self, usize), PusError> {
let raw_data_len = slice.len(); let raw_data_len = slice.len();
if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA { if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
@ -527,7 +846,7 @@ impl<'raw_data> PusTmReader<'raw_data> {
} }
pub fn timestamp(&self) -> &[u8] { pub fn timestamp(&self) -> &[u8] {
self.sec_header.time_stamp self.sec_header.timestamp
} }
/// This function will return the slice [Self] was constructed from. /// This function will return the slice [Self] was constructed from.
@ -578,15 +897,15 @@ impl GenericPusTmSecondaryHeader for PusTmReader<'_> {
impl IsPusTelemetry for PusTmReader<'_> {} impl IsPusTelemetry for PusTmReader<'_> {}
impl PartialEq<PusTmCreator<'_, '_>> for PusTmReader<'_> { impl PartialEq<PusTmCreator<'_>> for PusTmReader<'_> {
fn eq(&self, other: &PusTmCreator<'_, '_>) -> bool { fn eq(&self, other: &PusTmCreator<'_>) -> bool {
self.sp_header == other.sp_header self.sp_header == other.sp_header
&& self.sec_header == other.sec_header && self.sec_header == other.sec_header
&& self.source_data == other.source_data && self.source_data == other.source_data
} }
} }
impl PartialEq<PusTmReader<'_>> for PusTmCreator<'_, '_> { impl PartialEq<PusTmReader<'_>> for PusTmCreator<'_> {
fn eq(&self, other: &PusTmReader<'_>) -> bool { fn eq(&self, other: &PusTmReader<'_>) -> bool {
self.sp_header == other.sp_header self.sp_header == other.sp_header
&& self.sec_header == other.sec_header && self.sec_header == other.sec_header
@ -798,7 +1117,7 @@ mod tests {
PusTmCreator::new(&mut sph, tm_header, DUMMY_DATA, true) PusTmCreator::new(&mut sph, tm_header, DUMMY_DATA, true)
} }
fn base_hk_reply<'a, 'b>(timestamp: &'a [u8], src_data: &'b [u8]) -> PusTmCreator<'a, 'b> { fn base_hk_reply<'a>(timestamp: &'a [u8], src_data: &'a [u8]) -> PusTmCreator<'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, timestamp); let tc_header = PusTmSecondaryHeader::new_simple(3, 5, timestamp);
PusTmCreator::new(&mut sph, tc_header, src_data, true) PusTmCreator::new(&mut sph, tc_header, src_data, true)
@ -941,11 +1260,18 @@ mod tests {
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());
let error = res.unwrap_err(); let error = res.unwrap_err();
if let ByteConversionError::ToSliceTooSmall { found, expected } = error { assert!(matches!(error, PusError::ByteConversion { .. }));
assert_eq!(expected, 22); match error {
assert_eq!(found, 16); PusError::ByteConversion(err) => match err {
} else { ByteConversionError::ToSliceTooSmall { found, expected } => {
panic!("Invalid error {:?}", error); assert_eq!(expected, 22);
assert_eq!(found, 16);
}
_ => panic!("Invalid PUS error {:?}", err),
},
_ => {
panic!("Invalid error {:?}", error);
}
} }
} }
@ -1147,7 +1473,7 @@ mod tests {
#[test] #[test]
fn test_sec_header_without_stamp() { fn test_sec_header_without_stamp() {
let sec_header = PusTmSecondaryHeader::new_simple_no_timestamp(17, 1); let sec_header = PusTmSecondaryHeader::new_simple_no_timestamp(17, 1);
assert_eq!(sec_header.time_stamp, &[]); assert_eq!(sec_header.timestamp, &[]);
} }
#[test] #[test]

View File

@ -36,11 +36,7 @@
//! ### Optional features //! ### 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` `derives. //! `Deserialize` `derive`s
//! - [`chrono`](https://crates.io/crates/chrono): Add basic support for the `chrono` time library.
//! - [`timelib`](https://crates.io/crates/time): Add basic support for the `time` time library.
//! - [`defmt`](https://defmt.ferrous-systems.com/): Add support for the `defmt` by adding the
//! [`defmt::Format`](https://defmt.ferrous-systems.com/format) derive on many types.
//! //!
//! ## Module //! ## Module
//! //!
@ -59,7 +55,7 @@
//! println!("{:x?}", &ccsds_buf[0..6]); //! println!("{:x?}", &ccsds_buf[0..6]);
//! ``` //! ```
#![no_std] #![no_std]
#![cfg_attr(docs_rs, feature(doc_auto_cfg))] #![cfg_attr(doc_cfg, feature(doc_cfg))]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
extern crate alloc; extern crate alloc;
#[cfg(any(feature = "std", test))] #[cfg(any(feature = "std", test))]
@ -97,7 +93,6 @@ pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1;
/// Generic error type when converting to and from raw byte slices. /// Generic error type when converting to and from raw byte slices.
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ByteConversionError { pub enum ByteConversionError {
/// The passed slice is too small. Returns the passed slice length and expected minimum size /// The passed slice is too small. Returns the passed slice length and expected minimum size
ToSliceTooSmall { ToSliceTooSmall {
@ -147,7 +142,6 @@ impl Error for ByteConversionError {}
/// CCSDS packet type enumeration. /// 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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PacketType { pub enum PacketType {
Tm = 0, Tm = 0,
Tc = 1, Tc = 1,
@ -171,7 +165,6 @@ pub fn packet_type_in_raw_packet_id(packet_id: u16) -> PacketType {
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SequenceFlags { pub enum SequenceFlags {
ContinuationSegment = 0b00, ContinuationSegment = 0b00,
FirstSegment = 0b01, FirstSegment = 0b01,
@ -199,7 +192,6 @@ impl TryFrom<u8> for SequenceFlags {
/// of the first two bytes in the CCSDS primary header. /// of the first two bytes in the CCSDS primary header.
#[derive(Debug, Eq, Copy, Clone)] #[derive(Debug, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PacketId { pub struct PacketId {
pub ptype: PacketType, pub ptype: PacketType,
pub sec_header_flag: bool, pub sec_header_flag: bool,
@ -311,7 +303,6 @@ impl From<u16> for PacketId {
/// third and the fourth byte in the CCSDS primary header. /// 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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PacketSequenceCtrl { pub struct PacketSequenceCtrl {
pub seq_flags: SequenceFlags, pub seq_flags: SequenceFlags,
seq_count: u16, seq_count: u16,
@ -472,7 +463,6 @@ pub trait CcsdsPrimaryHeader {
/// * `data_len` - Data length field occupies the fifth and the sixth byte of the raw header /// * `data_len` - Data length field occupies the fifth and the sixth byte of the raw 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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SpHeader { pub struct SpHeader {
pub version: u8, pub version: u8,
pub packet_id: PacketId, pub packet_id: PacketId,
@ -808,7 +798,7 @@ pub(crate) mod tests {
let id_default = PacketId::default(); let id_default = PacketId::default();
assert_eq!(id_default.ptype, PacketType::Tm); assert_eq!(id_default.ptype, PacketType::Tm);
assert_eq!(id_default.apid, 0x000); assert_eq!(id_default.apid, 0x000);
assert!(!id_default.sec_header_flag); assert_eq!(id_default.sec_header_flag, false);
} }
#[test] #[test]
@ -818,7 +808,7 @@ pub(crate) mod tests {
let packet_id = packet_id.unwrap(); let packet_id = packet_id.unwrap();
assert_eq!(packet_id.apid(), 0x1ff); assert_eq!(packet_id.apid(), 0x1ff);
assert_eq!(packet_id.ptype, PacketType::Tc); assert_eq!(packet_id.ptype, PacketType::Tc);
assert!(packet_id.sec_header_flag); assert_eq!(packet_id.sec_header_flag, true);
let packet_id_tc = PacketId::tc(true, 0x1ff); let packet_id_tc = PacketId::tc(true, 0x1ff);
assert!(packet_id_tc.is_some()); assert!(packet_id_tc.is_some());
let packet_id_tc = packet_id_tc.unwrap(); let packet_id_tc = packet_id_tc.unwrap();

View File

@ -40,11 +40,13 @@ pub mod alloc_mod_chrono {
}; };
/// Generates a time code formatter using the [FMT_STR_CODE_A_WITH_SIZE] format. /// Generates a time code formatter using the [FMT_STR_CODE_A_WITH_SIZE] format.
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
pub fn generate_time_code_a(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> { pub fn generate_time_code_a(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_A_WITH_SIZE.0) date.format(FMT_STR_CODE_A_WITH_SIZE.0)
} }
/// Generates a time code formatter using the [FMT_STR_CODE_A_TERMINATED_WITH_SIZE] format. /// Generates a time code formatter using the [FMT_STR_CODE_A_TERMINATED_WITH_SIZE] format.
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
pub fn generate_time_code_a_terminated( pub fn generate_time_code_a_terminated(
date: &DateTime<Utc>, date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> { ) -> DelayedFormat<StrftimeItems<'static>> {
@ -52,11 +54,13 @@ pub mod alloc_mod_chrono {
} }
/// Generates a time code formatter using the [FMT_STR_CODE_B_WITH_SIZE] format. /// Generates a time code formatter using the [FMT_STR_CODE_B_WITH_SIZE] format.
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
pub fn generate_time_code_b(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> { pub fn generate_time_code_b(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_B_WITH_SIZE.0) date.format(FMT_STR_CODE_B_WITH_SIZE.0)
} }
/// Generates a time code formatter using the [FMT_STR_CODE_B_TERMINATED_WITH_SIZE] format. /// Generates a time code formatter using the [FMT_STR_CODE_B_TERMINATED_WITH_SIZE] format.
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
pub fn generate_time_code_b_terminated( pub fn generate_time_code_b_terminated(
date: &DateTime<Utc>, date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> { ) -> DelayedFormat<StrftimeItems<'static>> {

View File

@ -1,7 +1,7 @@
//! Module to generate or read CCSDS Day Segmented (CDS) timestamps as specified in //! Module to generate or read CCSDS Day Segmented (CDS) timestamps as specified in
//! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.3 . //! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.3 .
//! //!
//! The core data structure to do this is the [CdsTime] struct and the //! The core data structure to do this is the [TimeProvider] struct and the
//! [get_dyn_time_provider_from_bytes] function to retrieve correct instances of the //! [get_dyn_time_provider_from_bytes] function to retrieve correct instances of the
//! struct from a bytestream. //! struct from a bytestream.
use crate::private::Sealed; use crate::private::Sealed;
@ -34,9 +34,8 @@ use core::any::Any;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{ use super::{
ccsds_to_unix_days, unix_to_ccsds_days, CcsdsTimeCode, CcsdsTimeProvider, ccsds_to_unix_days, unix_to_ccsds_days, CcsdsTimeCode, CcsdsTimeProvider, TimeReader,
DateBeforeCcsdsEpochError, TimeReader, TimeWriter, TimestampError, UnixTime, MS_PER_DAY, TimeWriter, TimestampError, UnixTime, MS_PER_DAY, SECONDS_PER_DAY,
SECONDS_PER_DAY,
}; };
/// Base value for the preamble field for a time field parser to determine the time field type. /// Base value for the preamble field for a time field parser to determine the time field type.
@ -59,7 +58,7 @@ pub trait ProvidesDaysLength: Sealed + Clone {
+ Into<i64>; + Into<i64>;
} }
/// Type level token to be used as a generic parameter to [CdsTime]. /// Type level token to be used as a generic parameter to [TimeProvider].
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct DaysLen16Bits {} pub struct DaysLen16Bits {}
@ -68,7 +67,7 @@ impl ProvidesDaysLength for DaysLen16Bits {
type FieldType = u16; type FieldType = u16;
} }
/// Type level token to be used as a generic parameter to [CdsTime]. /// Type level token to be used as a generic parameter to [TimeProvider].
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct DaysLen24Bits {} pub struct DaysLen24Bits {}
impl Sealed for DaysLen24Bits {} impl Sealed for DaysLen24Bits {}
@ -78,7 +77,6 @@ impl ProvidesDaysLength for DaysLen24Bits {
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LengthOfDaySegment { pub enum LengthOfDaySegment {
Short16Bits = 0, Short16Bits = 0,
Long24Bits = 1, Long24Bits = 1,
@ -95,14 +93,12 @@ pub enum SubmillisPrecision {
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CdsError { pub enum CdsError {
/// CCSDS days value exceeds maximum allowed size or is negative /// CCSDS days value exceeds maximum allowed size or is negative
InvalidCcsdsDays(i64), InvalidCcsdsDays(i64),
/// There are distinct constructors depending on the days field width detected in the preamble /// There are distinct constructors depending on the days field width detected in the preamble
/// field. This error will be returned if there is a missmatch. /// field. This error will be returned if there is a missmatch.
InvalidCtorForDaysOfLenInPreamble(LengthOfDaySegment), InvalidCtorForDaysOfLenInPreamble(LengthOfDaySegment),
DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError),
} }
impl Display for CdsError { impl Display for CdsError {
@ -117,27 +113,12 @@ impl Display for CdsError {
"wrong constructor for length of day {length_of_day:?} detected in preamble", "wrong constructor for length of day {length_of_day:?} detected in preamble",
) )
} }
CdsError::DateBeforeCcsdsEpoch(e) => write!(f, "date before CCSDS epoch: {e}"),
} }
} }
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl Error for CdsError { impl Error for CdsError {}
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
CdsError::DateBeforeCcsdsEpoch(e) => Some(e),
_ => None,
}
}
}
impl From<DateBeforeCcsdsEpochError> for CdsError {
fn from(value: DateBeforeCcsdsEpochError) -> Self {
Self::DateBeforeCcsdsEpoch(value)
}
}
pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment { pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment {
if (pfield >> 2) & 0b1 == 1 { if (pfield >> 2) & 0b1 == 1 {
return LengthOfDaySegment::Long24Bits; return LengthOfDaySegment::Long24Bits;
@ -175,19 +156,19 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision {
/// ///
/// ``` /// ```
/// use core::time::Duration; /// use core::time::Duration;
/// use spacepackets::time::cds::{CdsTime, length_of_day_segment_from_pfield, LengthOfDaySegment}; /// use spacepackets::time::cds::{TimeProvider, length_of_day_segment_from_pfield, LengthOfDaySegment};
/// use spacepackets::time::{TimeWriter, CcsdsTimeCode, CcsdsTimeProvider}; /// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, CcsdsTimeProvider};
/// ///
/// let timestamp_now = CdsTime::now_with_u16_days().unwrap(); /// let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap();
/// let mut raw_stamp = [0; 7]; /// let mut raw_stamp = [0; 7];
/// { /// {
/// let written = timestamp_now.write_to_bytes(&mut raw_stamp).unwrap(); /// let written = timestamp_now.write_to_bytes(&mut raw_stamp).unwrap();
/// assert_eq!((raw_stamp[0] >> 4) & 0b111, CcsdsTimeCode::Cds as u8); /// assert_eq!((raw_stamp[0] >> 4) & 0b111, CcsdsTimeCodes::Cds as u8);
/// assert_eq!(written, 7); /// assert_eq!(written, 7);
/// } /// }
/// { /// {
/// assert_eq!(length_of_day_segment_from_pfield(raw_stamp[0]), LengthOfDaySegment::Short16Bits); /// assert_eq!(length_of_day_segment_from_pfield(raw_stamp[0]), LengthOfDaySegment::Short16Bits);
/// let read_result = CdsTime::from_bytes_with_u16_days(&raw_stamp); /// let read_result = TimeProvider::from_bytes_with_u16_days(&raw_stamp);
/// assert!(read_result.is_ok()); /// assert!(read_result.is_ok());
/// let stamp_deserialized = read_result.unwrap(); /// let stamp_deserialized = read_result.unwrap();
/// assert_eq!(stamp_deserialized.len_as_bytes(), 7); /// assert_eq!(stamp_deserialized.len_as_bytes(), 7);
@ -207,7 +188,7 @@ pub struct CdsTime<DaysLen: ProvidesDaysLength = DaysLen16Bits> {
submillis: u32, submillis: u32,
/// This is not strictly necessary but still cached because it significantly simplifies the /// This is not strictly necessary but still cached because it significantly simplifies the
/// calculation of [`DateTime<Utc>`]. /// calculation of [`DateTime<Utc>`].
unix_time: UnixTime, unix_stamp: UnixTime,
} }
/// Common properties for all CDS time providers. /// Common properties for all CDS time providers.
@ -245,11 +226,11 @@ impl ConversionFromUnix {
unix_seconds: i64, unix_seconds: i64,
subsec_nanos: u32, subsec_nanos: u32,
precision: SubmillisPrecision, precision: SubmillisPrecision,
) -> Result<Self, DateBeforeCcsdsEpochError> { ) -> Result<Self, TimestampError> {
let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(unix_seconds); let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(unix_seconds);
let ccsds_days = unix_to_ccsds_days(unix_days); let ccsds_days = unix_to_ccsds_days(unix_days);
if ccsds_days == 0 && (secs_of_day > 0 || subsec_nanos > 0) || ccsds_days < 0 { if ccsds_days == 0 && (secs_of_day > 0 || subsec_nanos > 0) || ccsds_days < 0 {
return Err(DateBeforeCcsdsEpochError( return Err(TimestampError::DateBeforeCcsdsEpoch(
UnixTime::new_checked(unix_seconds, subsec_nanos) UnixTime::new_checked(unix_seconds, subsec_nanos)
.expect("unix timestamp creation failed"), .expect("unix timestamp creation failed"),
)); ));
@ -337,19 +318,19 @@ fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) {
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
impl ConversionFromChronoDatetime { impl ConversionFromChronoDatetime {
fn new(dt: &chrono::DateTime<chrono::Utc>) -> Result<Self, DateBeforeCcsdsEpochError> { fn new(dt: &chrono::DateTime<chrono::Utc>) -> Result<Self, TimestampError> {
Self::new_generic(dt, SubmillisPrecision::Absent) Self::new_generic(dt, SubmillisPrecision::Absent)
} }
fn new_with_submillis_us_prec( fn new_with_submillis_us_prec(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, DateBeforeCcsdsEpochError> { ) -> Result<Self, TimestampError> {
Self::new_generic(dt, SubmillisPrecision::Microseconds) Self::new_generic(dt, SubmillisPrecision::Microseconds)
} }
fn new_with_submillis_ps_prec( fn new_with_submillis_ps_prec(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, DateBeforeCcsdsEpochError> { ) -> Result<Self, TimestampError> {
Self::new_generic(dt, SubmillisPrecision::Picoseconds) Self::new_generic(dt, SubmillisPrecision::Picoseconds)
} }
@ -357,10 +338,10 @@ impl ConversionFromChronoDatetime {
fn new_generic( fn new_generic(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
prec: SubmillisPrecision, prec: SubmillisPrecision,
) -> Result<Self, DateBeforeCcsdsEpochError> { ) -> Result<Self, TimestampError> {
// The CDS timestamp does not support timestamps before the CCSDS epoch. // The CDS timestamp does not support timestamps before the CCSDS epoch.
if dt.year() < 1958 { if dt.year() < 1958 {
return Err(DateBeforeCcsdsEpochError(UnixTime::from(*dt))); return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTime::from(*dt)));
} }
// The contained values in the conversion should be all positive now // The contained values in the conversion should be all positive now
let unix_conversion = let unix_conversion =
@ -453,28 +434,31 @@ impl CdsConverter for ConversionFromNow {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub trait DynCdsTimeProvider: CcsdsTimeProvider + CdsTimestamp + TimeWriter + Any {} pub trait DynCdsTimeProvider: CcsdsTimeProvider + CdsTimestamp + TimeWriter + Any {}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
impl DynCdsTimeProvider for CdsTime<DaysLen16Bits> {} impl DynCdsTimeProvider for CdsTime<DaysLen16Bits> {}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
impl DynCdsTimeProvider for CdsTime<DaysLen24Bits> {} impl DynCdsTimeProvider for CdsTime<DaysLen24Bits> {}
/// This function returns the correct [CdsTime] instance from a raw byte array /// This function returns the correct [TimeProvider] instance from a raw byte array
/// by checking the length of days field. It also checks the CCSDS time code for correctness. /// by checking the length of days field. It also checks the CCSDS time code for correctness.
/// ///
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use spacepackets::time::cds::{ /// use spacepackets::time::cds::{
/// CdsTime, LengthOfDaySegment, get_dyn_time_provider_from_bytes, SubmillisPrecision, /// TimeProvider, LengthOfDaySegment, get_dyn_time_provider_from_bytes, SubmillisPrecision,
/// }; /// };
/// use spacepackets::time::{TimeWriter, CcsdsTimeCode, CcsdsTimeProvider}; /// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, CcsdsTimeProvider};
/// ///
/// let timestamp_now = CdsTime::new_with_u16_days(24, 24); /// let timestamp_now = TimeProvider::new_with_u16_days(24, 24);
/// let mut raw_stamp = [0; 7]; /// let mut raw_stamp = [0; 7];
/// { /// {
/// let written = timestamp_now.write_to_bytes(&mut raw_stamp).unwrap(); /// let written = timestamp_now.write_to_bytes(&mut raw_stamp).unwrap();
/// assert_eq!((raw_stamp[0] >> 4) & 0b111, CcsdsTimeCode::Cds as u8); /// assert_eq!((raw_stamp[0] >> 4) & 0b111, CcsdsTimeCodes::Cds as u8);
/// assert_eq!(written, 7); /// assert_eq!(written, 7);
/// } /// }
/// { /// {
@ -486,6 +470,7 @@ impl DynCdsTimeProvider for CdsTime<DaysLen24Bits> {}
/// } /// }
/// ``` /// ```
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn get_dyn_time_provider_from_bytes( pub fn get_dyn_time_provider_from_bytes(
buf: &[u8], buf: &[u8],
) -> Result<Box<dyn DynCdsTimeProvider>, TimestampError> { ) -> Result<Box<dyn DynCdsTimeProvider>, TimestampError> {
@ -656,7 +641,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
if let Some(precision) = self.precision_as_ns() { if let Some(precision) = self.precision_as_ns() {
subsec_nanos += precision; subsec_nanos += precision;
} }
self.unix_time = UnixTime::new(unix_days_seconds, subsec_nanos); self.unix_stamp = UnixTime::new(unix_days_seconds, subsec_nanos);
} }
fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> { fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> {
@ -683,7 +668,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
pfield: Self::generate_p_field(days_len, SubmillisPrecision::Absent), pfield: Self::generate_p_field(days_len, SubmillisPrecision::Absent),
ccsds_days, ccsds_days,
ms_of_day, ms_of_day,
unix_time: Default::default(), unix_stamp: Default::default(),
submillis: 0, submillis: 0,
}; };
let unix_days_seconds = ccsds_to_unix_days(i64::from(ccsds_days)) * SECONDS_PER_DAY as i64; let unix_days_seconds = ccsds_to_unix_days(i64::from(ccsds_days)) * SECONDS_PER_DAY as i64;
@ -695,7 +680,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
fn from_dt_generic( fn from_dt_generic(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
days_len: LengthOfDaySegment, days_len: LengthOfDaySegment,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
let conv_from_dt = ConversionFromChronoDatetime::new(dt)?; let conv_from_dt = ConversionFromChronoDatetime::new(dt)?;
Self::generic_from_conversion(days_len, conv_from_dt) Self::generic_from_conversion(days_len, conv_from_dt)
} }
@ -704,7 +689,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
fn from_dt_generic_us_prec( fn from_dt_generic_us_prec(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
days_len: LengthOfDaySegment, days_len: LengthOfDaySegment,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_us_prec(dt)?; let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_us_prec(dt)?;
Self::generic_from_conversion(days_len, conv_from_dt) Self::generic_from_conversion(days_len, conv_from_dt)
} }
@ -713,7 +698,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
fn from_dt_generic_ps_prec( fn from_dt_generic_ps_prec(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
days_len: LengthOfDaySegment, days_len: LengthOfDaySegment,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_ps_prec(dt)?; let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_ps_prec(dt)?;
Self::generic_from_conversion(days_len, conv_from_dt) Self::generic_from_conversion(days_len, conv_from_dt)
} }
@ -722,46 +707,48 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
unix_stamp: &UnixTime, unix_stamp: &UnixTime,
days_len: LengthOfDaySegment, days_len: LengthOfDaySegment,
submillis_prec: SubmillisPrecision, submillis_prec: SubmillisPrecision,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
let conv_from_dt = let conv_from_dt =
ConversionFromUnix::new(unix_stamp.secs, unix_stamp.subsec_nanos, submillis_prec)?; ConversionFromUnix::new(unix_stamp.secs, unix_stamp.subsec_nanos, submillis_prec)?;
Self::generic_from_conversion(days_len, conv_from_dt) Self::generic_from_conversion(days_len, conv_from_dt)
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
fn now_generic(days_len: LengthOfDaySegment) -> Result<Self, StdTimestampError> { fn from_now_generic(days_len: LengthOfDaySegment) -> Result<Self, StdTimestampError> {
let conversion_from_now = ConversionFromNow::new()?; let conversion_from_now = ConversionFromNow::new()?;
Self::generic_from_conversion(days_len, conversion_from_now) Self::generic_from_conversion(days_len, conversion_from_now)
.map_err(|e| StdTimestampError::Timestamp(TimestampError::from(e))) .map_err(StdTimestampError::Timestamp)
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
fn now_generic_with_us_prec(days_len: LengthOfDaySegment) -> Result<Self, StdTimestampError> { fn from_now_generic_us_prec(days_len: LengthOfDaySegment) -> Result<Self, StdTimestampError> {
let conversion_from_now = ConversionFromNow::new_with_submillis_us_prec()?; let conversion_from_now = ConversionFromNow::new_with_submillis_us_prec()?;
Self::generic_from_conversion(days_len, conversion_from_now) Self::generic_from_conversion(days_len, conversion_from_now)
.map_err(|e| StdTimestampError::Timestamp(TimestampError::from(e))) .map_err(StdTimestampError::Timestamp)
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
fn from_now_generic_ps_prec(days_len: LengthOfDaySegment) -> Result<Self, StdTimestampError> { fn from_now_generic_ps_prec(days_len: LengthOfDaySegment) -> Result<Self, StdTimestampError> {
let conversion_from_now = ConversionFromNow::new_with_submillis_ps_prec()?; let conversion_from_now = ConversionFromNow::new_with_submillis_ps_prec()?;
Self::generic_from_conversion(days_len, conversion_from_now) Self::generic_from_conversion(days_len, conversion_from_now)
.map_err(|e| StdTimestampError::Timestamp(TimestampError::from(e))) .map_err(StdTimestampError::Timestamp)
} }
fn generic_from_conversion<C: CdsConverter>( fn generic_from_conversion<C: CdsConverter>(
days_len: LengthOfDaySegment, days_len: LengthOfDaySegment,
converter: C, converter: C,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
let ccsds_days: ProvidesDaysLen::FieldType = converter let ccsds_days: ProvidesDaysLen::FieldType =
.ccsds_days_as_u32() converter.ccsds_days_as_u32().try_into().map_err(|_| {
.try_into() TimestampError::Cds(CdsError::InvalidCcsdsDays(
.map_err(|_| CdsError::InvalidCcsdsDays(converter.ccsds_days_as_u32().into()))?; converter.ccsds_days_as_u32().into(),
))
})?;
let mut provider = Self { let mut provider = Self {
pfield: Self::generate_p_field(days_len, converter.submillis_precision()), pfield: Self::generate_p_field(days_len, converter.submillis_precision()),
ccsds_days, ccsds_days,
ms_of_day: converter.ms_of_day(), ms_of_day: converter.ms_of_day(),
unix_time: Default::default(), unix_stamp: Default::default(),
submillis: converter.submillis(), submillis: converter.submillis(),
}; };
provider.setup(converter.unix_days_seconds(), converter.ms_of_day()); provider.setup(converter.unix_days_seconds(), converter.ms_of_day());
@ -789,6 +776,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
} }
#[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 conversion_from_now = self.generic_conversion_from_now()?; let conversion_from_now = self.generic_conversion_from_now()?;
let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now let ccsds_days: ProvidesDaysLen::FieldType = conversion_from_now
@ -824,19 +812,22 @@ impl CdsTime<DaysLen24Bits> {
/// Generate a time stamp from the current time using the system clock. /// Generate a time stamp from the current time using the system clock.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn now_with_u24_days() -> Result<Self, StdTimestampError> { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
Self::now_generic(LengthOfDaySegment::Long24Bits) pub fn from_now_with_u24_days() -> Result<Self, StdTimestampError> {
Self::from_now_generic(LengthOfDaySegment::Long24Bits)
} }
/// Create a provider from a [`chrono::DateTime<chrono::Utc>`] struct. /// Create a provider from a [`DateTime<Utc>`] struct.
/// ///
/// ## Errors /// ## Errors
/// ///
/// This function will return [CdsError::DateBeforeCcsdsEpoch] if the time is before the CCSDS /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// epoch (1958-01-01T00:00:00+00:00) or the CCSDS days value exceeds the allowed bit width /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
/// (24 bits). /// or the CCSDS days value exceeds the allowed bit width (24 bits).
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn from_dt_with_u24_days(dt: &chrono::DateTime<chrono::Utc>) -> Result<Self, CdsError> { pub fn from_dt_with_u24_days(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits) Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits)
} }
@ -844,13 +835,13 @@ impl CdsTime<DaysLen24Bits> {
/// ///
/// ## Errors /// ## Errors
/// ///
/// This function will return [CdsError::DateBeforeCcsdsEpoch] if the time is before the CCSDS /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// epoch (1958-01-01T00:00:00+00:00) or the CCSDS days value exceeds the allowed bit width /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
/// (24 bits). /// or the CCSDS days value exceeds the allowed bit width (24 bits).
pub fn from_unix_time_with_u24_day( pub fn from_unix_stamp_with_u24_days(
unix_stamp: &UnixTime, unix_stamp: &UnixTime,
submillis_prec: SubmillisPrecision, submillis_prec: SubmillisPrecision,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits, submillis_prec) Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits, submillis_prec)
} }
@ -858,7 +849,7 @@ impl CdsTime<DaysLen24Bits> {
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn from_dt_with_u24_days_us_precision( pub fn from_dt_with_u24_days_us_precision(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits) Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits)
} }
@ -866,20 +857,22 @@ impl CdsTime<DaysLen24Bits> {
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn from_dt_with_u24_days_ps_precision( pub fn from_dt_with_u24_days_ps_precision(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits) Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits)
} }
/// Like [Self::now_with_u24_days] but with microsecond sub-millisecond precision. /// Like [Self::from_now_with_u24_days] but with microsecond sub-millisecond precision.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn now_with_u24_days_us_precision() -> Result<Self, StdTimestampError> { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
Self::now_generic_with_us_prec(LengthOfDaySegment::Long24Bits) pub fn from_now_with_u24_days_us_precision() -> Result<Self, StdTimestampError> {
Self::from_now_generic_us_prec(LengthOfDaySegment::Long24Bits)
} }
/// Like [Self::now_with_u24_days] but with picoseconds sub-millisecond precision. /// Like [Self::from_now_with_u24_days] but with picoseconds sub-millisecond precision.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn now_with_u24_days_ps_precision() -> Result<Self, StdTimestampError> { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
Self::now_generic_with_us_prec(LengthOfDaySegment::Long24Bits) pub fn from_now_with_u24_days_ps_precision() -> Result<Self, StdTimestampError> {
Self::from_now_generic_us_prec(LengthOfDaySegment::Long24Bits)
} }
pub fn from_bytes_with_u24_days(buf: &[u8]) -> Result<Self, TimestampError> { pub fn from_bytes_with_u24_days(buf: &[u8]) -> Result<Self, TimestampError> {
@ -916,33 +909,36 @@ impl CdsTime<DaysLen16Bits> {
Self::generic_new(LengthOfDaySegment::Short16Bits, ccsds_days, ms_of_day).unwrap() Self::generic_new(LengthOfDaySegment::Short16Bits, ccsds_days, ms_of_day).unwrap()
} }
/// Create a provider from a [`chrono::DateTime<Utc>`] struct. /// Create a provider from a [`DateTime<Utc>`] struct.
/// ///
/// This function will return a [CdsError::DateBeforeCcsdsEpoch] if the time is before the /// This function will return a [TimestampError::DateBeforeCcsdsEpoch] or a
/// CCSDS epoch (01-01-1958 00:00:00) or the CCSDS days value exceeds the allowed bit width /// [TimestampError::Cds] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or
/// (16 bits). /// the CCSDS days value exceeds the allowed bit width (16 bits).
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn from_dt_with_u16_days(dt: &chrono::DateTime<chrono::Utc>) -> Result<Self, CdsError> { pub fn from_dt_with_u16_days(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits) Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits)
} }
/// Generate a time stamp from the current time using the system clock. /// Generate a time stamp from the current time using the system clock.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn now_with_u16_days() -> Result<Self, StdTimestampError> { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
Self::now_generic(LengthOfDaySegment::Short16Bits) pub fn from_now_with_u16_days() -> Result<Self, StdTimestampError> {
Self::from_now_generic(LengthOfDaySegment::Short16Bits)
} }
/// Create a provider from a generic UNIX timestamp (seconds since 1970-01-01T00:00:00+00:00). /// Create a provider from a generic UNIX timestamp (seconds since 1970-01-01T00:00:00+00:00).
/// ///
/// ## Errors /// ## Errors
/// ///
/// This function will return [CdsError::DateBeforeCcsdsEpoch] if the time is before the CCSDS /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// epoch (1958-01-01T00:00:00+00:00) or the CCSDS days value exceeds the allowed bit width /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
/// (24 bits). /// or the CCSDS days value exceeds the allowed bit width (24 bits).
pub fn from_unix_time_with_u16_days( pub fn from_unix_stamp_with_u16_days(
unix_stamp: &UnixTime, unix_stamp: &UnixTime,
submillis_prec: SubmillisPrecision, submillis_prec: SubmillisPrecision,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits, submillis_prec) Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits, submillis_prec)
} }
@ -950,7 +946,7 @@ impl CdsTime<DaysLen16Bits> {
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn from_dt_with_u16_days_us_precision( pub fn from_dt_with_u16_days_us_precision(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits) Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits)
} }
@ -958,18 +954,20 @@ impl CdsTime<DaysLen16Bits> {
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn from_dt_with_u16_days_ps_precision( pub fn from_dt_with_u16_days_ps_precision(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, CdsError> { ) -> Result<Self, TimestampError> {
Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits) Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits)
} }
/// Like [Self::now_with_u16_days] but with microsecond sub-millisecond precision. /// Like [Self::from_now_with_u16_days] but with microsecond sub-millisecond precision.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn now_with_u16_days_us_precision() -> Result<Self, StdTimestampError> { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
Self::now_generic_with_us_prec(LengthOfDaySegment::Short16Bits) pub fn from_now_with_u16_days_us_precision() -> Result<Self, StdTimestampError> {
Self::from_now_generic_us_prec(LengthOfDaySegment::Short16Bits)
} }
/// Like [Self::now_with_u16_days] but with picosecond sub-millisecond precision. /// Like [Self::from_now_with_u16_days] but with picosecond sub-millisecond precision.
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn from_now_with_u16_days_ps_precision() -> Result<Self, StdTimestampError> { pub fn from_now_with_u16_days_ps_precision() -> Result<Self, StdTimestampError> {
Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits) Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits)
} }
@ -1170,7 +1168,7 @@ impl AddAssign<Duration> for CdsTime<DaysLen24Bits> {
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
impl TryFrom<chrono::DateTime<chrono::Utc>> for CdsTime<DaysLen16Bits> { impl TryFrom<chrono::DateTime<chrono::Utc>> for CdsTime<DaysLen16Bits> {
type Error = CdsError; type Error = TimestampError;
fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> { fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
let conversion = ConversionFromChronoDatetime::new(&dt)?; let conversion = ConversionFromChronoDatetime::new(&dt)?;
@ -1180,7 +1178,8 @@ impl TryFrom<chrono::DateTime<chrono::Utc>> for CdsTime<DaysLen16Bits> {
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
impl TryFrom<chrono::DateTime<chrono::Utc>> for CdsTime<DaysLen24Bits> { impl TryFrom<chrono::DateTime<chrono::Utc>> for CdsTime<DaysLen24Bits> {
type Error = CdsError; type Error = TimestampError;
fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> { fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
let conversion = ConversionFromChronoDatetime::new(&dt)?; let conversion = ConversionFromChronoDatetime::new(&dt)?;
Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion) Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion)
@ -1202,16 +1201,16 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CcsdsTimeProvider for CdsTime<Provides
#[inline] #[inline]
fn unix_secs(&self) -> i64 { fn unix_secs(&self) -> i64 {
self.unix_time.secs self.unix_stamp.secs
} }
#[inline] #[inline]
fn subsec_nanos(&self) -> u32 { fn subsec_nanos(&self) -> u32 {
self.unix_time.subsec_nanos self.unix_stamp.subsec_nanos
} }
#[inline] #[inline]
fn unix_time(&self) -> UnixTime { fn unix_stamp(&self) -> UnixTime {
self.unix_time self.unix_stamp
} }
} }
@ -1358,7 +1357,7 @@ mod tests {
#[test] #[test]
fn test_time_stamp_zero_args() { fn test_time_stamp_zero_args() {
let time_stamper = CdsTime::new_with_u16_days(0, 0); let time_stamper = CdsTime::new_with_u16_days(0, 0);
let unix_stamp = time_stamper.unix_time(); let unix_stamp = time_stamper.unix_stamp();
assert_eq!( assert_eq!(
unix_stamp.secs, unix_stamp.secs,
(DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64 (DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64
@ -1387,7 +1386,7 @@ mod tests {
#[test] #[test]
fn test_time_stamp_unix_epoch() { fn test_time_stamp_unix_epoch() {
let time_stamper = CdsTime::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 0); let time_stamper = CdsTime::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 0);
assert_eq!(time_stamper.unix_time().secs, 0); assert_eq!(time_stamper.unix_stamp().secs, 0);
assert_eq!( assert_eq!(
time_stamper.submillis_precision(), time_stamper.submillis_precision(),
SubmillisPrecision::Absent SubmillisPrecision::Absent
@ -1464,7 +1463,7 @@ mod tests {
fn test_write() { fn test_write() {
let mut buf = [0; 16]; let mut buf = [0; 16];
let time_stamper_0 = CdsTime::new_with_u16_days(0, 0); let time_stamper_0 = CdsTime::new_with_u16_days(0, 0);
let unix_stamp = time_stamper_0.unix_time(); let unix_stamp = time_stamper_0.unix_stamp();
assert_eq!( assert_eq!(
unix_stamp.secs, unix_stamp.secs,
(DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32).into() (DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32).into()
@ -1610,14 +1609,14 @@ mod tests {
#[test] #[test]
fn test_time_now() { fn test_time_now() {
let timestamp_now = CdsTime::now_with_u16_days().unwrap(); let timestamp_now = CdsTime::from_now_with_u16_days().unwrap();
let compare_stamp = chrono::Utc::now(); let compare_stamp = chrono::Utc::now();
generic_now_test(timestamp_now, compare_stamp); generic_now_test(timestamp_now, compare_stamp);
} }
#[test] #[test]
fn test_time_now_us_prec() { fn test_time_now_us_prec() {
let timestamp_now = CdsTime::now_with_u16_days_us_precision().unwrap(); let timestamp_now = CdsTime::from_now_with_u16_days_us_precision().unwrap();
let compare_stamp = chrono::Utc::now(); let compare_stamp = chrono::Utc::now();
generic_now_test(timestamp_now, compare_stamp); generic_now_test(timestamp_now, compare_stamp);
} }
@ -1638,7 +1637,7 @@ mod tests {
#[test] #[test]
fn test_time_now_ps_prec_u24_days() { fn test_time_now_ps_prec_u24_days() {
let timestamp_now = CdsTime::now_with_u24_days_ps_precision().unwrap(); let timestamp_now = CdsTime::from_now_with_u24_days_ps_precision().unwrap();
let compare_stamp = chrono::Utc::now(); let compare_stamp = chrono::Utc::now();
generic_now_test(timestamp_now, compare_stamp); generic_now_test(timestamp_now, compare_stamp);
} }
@ -1923,7 +1922,7 @@ mod tests {
fn test_creation_from_unix_stamp_0_u16_days() { fn test_creation_from_unix_stamp_0_u16_days() {
let unix_secs = 0; let unix_secs = 0;
let subsec_millis = 0; let subsec_millis = 0;
let time_provider = CdsTime::from_unix_time_with_u16_days( let time_provider = CdsTime::from_unix_stamp_with_u16_days(
&UnixTime::new(unix_secs, subsec_millis), &UnixTime::new(unix_secs, subsec_millis),
SubmillisPrecision::Absent, SubmillisPrecision::Absent,
) )
@ -1935,7 +1934,7 @@ mod tests {
fn test_creation_from_unix_stamp_0_u24_days() { fn test_creation_from_unix_stamp_0_u24_days() {
let unix_secs = 0; let unix_secs = 0;
let subsec_millis = 0; let subsec_millis = 0;
let time_provider = CdsTime::from_unix_time_with_u24_day( let time_provider = CdsTime::from_unix_stamp_with_u24_days(
&UnixTime::new(unix_secs, subsec_millis), &UnixTime::new(unix_secs, subsec_millis),
SubmillisPrecision::Absent, SubmillisPrecision::Absent,
) )
@ -1952,9 +1951,11 @@ mod tests {
.unwrap() .unwrap()
.and_local_timezone(chrono::Utc) .and_local_timezone(chrono::Utc)
.unwrap(); .unwrap();
let time_provider = let time_provider = CdsTime::from_unix_stamp_with_u16_days(
CdsTime::from_unix_time_with_u16_days(&datetime_utc.into(), SubmillisPrecision::Absent) &datetime_utc.into(),
.expect("creating provider from unix stamp failed"); SubmillisPrecision::Absent,
)
.expect("creating provider from unix stamp failed");
// https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023
// Leap years need to be accounted for as well. // Leap years need to be accounted for as well.
assert_eq!(time_provider.ccsds_days, 23754); assert_eq!(time_provider.ccsds_days, 23754);
@ -1970,7 +1971,7 @@ mod tests {
fn test_creation_0_ccsds_days() { fn test_creation_0_ccsds_days() {
let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64; let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64;
let subsec_millis = 0; let subsec_millis = 0;
let time_provider = CdsTime::from_unix_time_with_u16_days( let time_provider = CdsTime::from_unix_stamp_with_u16_days(
&UnixTime::new(unix_secs, subsec_millis), &UnixTime::new(unix_secs, subsec_millis),
SubmillisPrecision::Absent, SubmillisPrecision::Absent,
) )
@ -1982,7 +1983,7 @@ mod tests {
fn test_invalid_creation_from_unix_stamp_days_too_large() { fn test_invalid_creation_from_unix_stamp_days_too_large() {
let invalid_unix_secs: i64 = (u16::MAX as i64 + 1) * SECONDS_PER_DAY as i64; let invalid_unix_secs: i64 = (u16::MAX as i64 + 1) * SECONDS_PER_DAY as i64;
let subsec_millis = 0; let subsec_millis = 0;
match CdsTime::from_unix_time_with_u16_days( match CdsTime::from_unix_stamp_with_u16_days(
&UnixTime::new(invalid_unix_secs, subsec_millis), &UnixTime::new(invalid_unix_secs, subsec_millis),
SubmillisPrecision::Absent, SubmillisPrecision::Absent,
) { ) {
@ -1990,12 +1991,12 @@ mod tests {
panic!("creation should not succeed") panic!("creation should not succeed")
} }
Err(e) => { Err(e) => {
if let CdsError::InvalidCcsdsDays(days) = e { if let TimestampError::Cds(CdsError::InvalidCcsdsDays(days)) = e {
assert_eq!( assert_eq!(
days, days,
unix_to_ccsds_days(invalid_unix_secs / SECONDS_PER_DAY as i64) unix_to_ccsds_days(invalid_unix_secs / SECONDS_PER_DAY as i64)
); );
assert_eq!(e.to_string(), "invalid ccsds days 69919"); assert_eq!(e.to_string(), "cds error: invalid ccsds days 69919");
} else { } else {
panic!("unexpected error {}", e) panic!("unexpected error {}", e)
} }
@ -2009,7 +2010,7 @@ mod tests {
// precisely 31-12-1957 23:59:55 // precisely 31-12-1957 23:59:55
let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5; let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5;
let subsec_millis = 0; let subsec_millis = 0;
match CdsTime::from_unix_time_with_u16_days( match CdsTime::from_unix_stamp_with_u16_days(
&UnixTime::new(unix_secs as i64, subsec_millis), &UnixTime::new(unix_secs as i64, subsec_millis),
SubmillisPrecision::Absent, SubmillisPrecision::Absent,
) { ) {
@ -2017,8 +2018,8 @@ mod tests {
panic!("creation should not succeed") panic!("creation should not succeed")
} }
Err(e) => { Err(e) => {
if let CdsError::DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError(unix_dt)) = e { if let TimestampError::DateBeforeCcsdsEpoch(dt) = e {
let dt = unix_dt.chrono_date_time(); let dt = dt.chrono_date_time();
if let chrono::LocalResult::Single(dt) = dt { if let chrono::LocalResult::Single(dt) = dt {
assert_eq!(dt.year(), 1957); assert_eq!(dt.year(), 1957);
assert_eq!(dt.month(), 12); assert_eq!(dt.month(), 12);
@ -2248,9 +2249,7 @@ mod tests {
.unwrap(); .unwrap();
let time_provider = CdsTime::from_dt_with_u24_days(&datetime_utc); let time_provider = CdsTime::from_dt_with_u24_days(&datetime_utc);
assert!(time_provider.is_err()); assert!(time_provider.is_err());
if let CdsError::DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError(dt)) = if let TimestampError::DateBeforeCcsdsEpoch(dt) = time_provider.unwrap_err() {
time_provider.unwrap_err()
{
assert_eq!(dt, datetime_utc.into()); assert_eq!(dt, datetime_utc.into());
} }
} }
@ -2296,7 +2295,7 @@ mod tests {
fn test_update_from_now() { fn test_update_from_now() {
let mut stamp = CdsTime::new_with_u16_days(0, 0); let mut stamp = CdsTime::new_with_u16_days(0, 0);
let _ = stamp.update_from_now(); let _ = stamp.update_from_now();
let dt = stamp.unix_time().chrono_date_time().unwrap(); let dt = stamp.unix_stamp().chrono_date_time().unwrap();
assert!(dt.year() > 2020); assert!(dt.year() > 2020);
} }
@ -2309,7 +2308,7 @@ mod tests {
#[test] #[test]
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
fn test_serialization() { fn test_serialization() {
let stamp_now = CdsTime::now_with_u16_days().expect("Error retrieving time"); let stamp_now = CdsTime::from_now_with_u16_days().expect("Error retrieving time");
let val = to_allocvec(&stamp_now).expect("Serializing timestamp failed"); let val = to_allocvec(&stamp_now).expect("Serializing timestamp failed");
assert!(val.len() > 0); assert!(val.len() > 0);
let stamp_deser: CdsTime = from_bytes(&val).expect("Stamp deserialization failed"); let stamp_deser: CdsTime = from_bytes(&val).expect("Stamp deserialization failed");

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +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; use crate::ByteConversionError;
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
use chrono::{TimeZone, Utc}; use chrono::{DateTime, LocalResult, TimeZone, Utc};
use core::cmp::Ordering; use core::cmp::Ordering;
use core::fmt::{Display, Formatter}; use core::fmt::{Display, Formatter};
use core::ops::{Add, AddAssign, Sub}; use core::ops::{Add, AddAssign, Sub};
@ -33,7 +33,6 @@ pub const NANOS_PER_SECOND: u32 = 1_000_000_000;
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CcsdsTimeCode { pub enum CcsdsTimeCode {
CucCcsdsEpoch = 0b001, CucCcsdsEpoch = 0b001,
CucAgencyEpoch = 0b010, CucAgencyEpoch = 0b010,
@ -66,27 +65,13 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCode, 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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DateBeforeCcsdsEpochError(UnixTime);
impl Display for DateBeforeCcsdsEpochError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "date before ccsds epoch: {:?}", self.0)
}
}
#[cfg(feature = "std")]
impl Error for DateBeforeCcsdsEpochError {}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum TimestampError { pub enum TimestampError {
InvalidTimeCode { expected: CcsdsTimeCode, found: u8 }, InvalidTimeCode { expected: CcsdsTimeCode, found: u8 },
ByteConversion(ByteConversionError), ByteConversion(ByteConversionError),
Cds(cds::CdsError), Cds(cds::CdsError),
Cuc(cuc::CucError), Cuc(cuc::CucError),
DateBeforeCcsdsEpoch(UnixTime),
CustomEpochNotSupported, CustomEpochNotSupported,
} }
@ -108,6 +93,9 @@ impl Display for TimestampError {
TimestampError::ByteConversion(e) => { TimestampError::ByteConversion(e) => {
write!(f, "time stamp: {e}") write!(f, "time stamp: {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")
} }
@ -126,7 +114,6 @@ impl Error for TimestampError {
} }
} }
} }
impl From<cds::CdsError> for TimestampError { impl From<cds::CdsError> for TimestampError {
fn from(e: cds::CdsError) -> Self { fn from(e: cds::CdsError) -> Self {
TimestampError::Cds(e) TimestampError::Cds(e)
@ -140,6 +127,7 @@ impl From<cuc::CucError> for TimestampError {
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std_mod { pub mod std_mod {
use crate::time::TimestampError; use crate::time::TimestampError;
use std::time::SystemTimeError; use std::time::SystemTimeError;
@ -147,7 +135,7 @@ pub mod std_mod {
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum StdTimestampError { pub enum StdTimestampError {
#[error("system time error: {0:?}")] #[error("system time error: {0}")]
SystemTime(#[from] SystemTimeError), SystemTime(#[from] SystemTimeError),
#[error("timestamp error: {0}")] #[error("timestamp error: {0}")]
Timestamp(#[from] TimestampError), Timestamp(#[from] TimestampError),
@ -155,6 +143,7 @@ pub mod std_mod {
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn seconds_since_epoch() -> f64 { pub fn seconds_since_epoch() -> f64 {
SystemTime::now() SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
@ -189,6 +178,7 @@ pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: i64) -> i64 {
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn ms_of_day_using_sysclock() -> u32 { pub fn ms_of_day_using_sysclock() -> u32 {
ms_of_day(seconds_since_epoch()) ms_of_day(seconds_since_epoch())
} }
@ -209,6 +199,7 @@ pub trait TimeWriter {
fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError>; fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError>;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
fn to_vec(&self) -> Result<alloc::vec::Vec<u8>, TimestampError> { fn to_vec(&self) -> Result<alloc::vec::Vec<u8>, TimestampError> {
let mut vec = alloc::vec![0; self.len_written()]; let mut vec = alloc::vec![0; self.len_written()];
self.write_to_bytes(&mut vec)?; self.write_to_bytes(&mut vec)?;
@ -242,16 +233,18 @@ pub trait CcsdsTimeProvider {
(self.subsec_nanos() / 1_000_000) as u16 (self.subsec_nanos() / 1_000_000) as u16
} }
fn unix_time(&self) -> UnixTime { fn unix_stamp(&self) -> UnixTime {
UnixTime::new(self.unix_secs(), self.subsec_nanos()) UnixTime::new(self.unix_secs(), self.subsec_nanos())
} }
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> { fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos()) chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos())
} }
#[cfg(feature = "timelib")] #[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> { fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())? Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())?
+ time::Duration::nanoseconds(self.subsec_nanos().into())) + time::Duration::nanoseconds(self.subsec_nanos().into()))
@ -265,7 +258,6 @@ pub trait CcsdsTimeProvider {
/// similarly to other common time formats and libraries. /// similarly to other common time formats and libraries.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct UnixTime { pub struct UnixTime {
secs: i64, secs: i64,
subsec_nanos: u32, subsec_nanos: u32,
@ -309,7 +301,7 @@ impl UnixTime {
} }
/// This function will panic if the subsecond value is larger than the fraction of a second. /// This function will panic if the subsecond value is larger than the fraction of a second.
/// Use [Self::new_checked] if you want to handle this case without a panic. /// Use [new_checked] if you want to handle this case without a panic.
pub const fn new(unix_seconds: i64, subsecond_nanos: u32) -> Self { pub const fn new(unix_seconds: i64, subsecond_nanos: u32) -> Self {
if subsecond_nanos >= NANOS_PER_SECOND { if subsecond_nanos >= NANOS_PER_SECOND {
panic!("invalid subsecond nanos value"); panic!("invalid subsecond nanos value");
@ -321,7 +313,7 @@ impl UnixTime {
} }
/// This function will panic if the subsecond value is larger than the fraction of a second. /// This function will panic if the subsecond value is larger than the fraction of a second.
/// Use [Self::new_subsec_millis_checked] if you want to handle this case without a panic. /// Use [new_subsecond_millis_checked] if you want to handle this case without a panic.
pub const fn new_subsec_millis(unix_seconds: i64, subsecond_millis: u16) -> Self { pub const fn new_subsec_millis(unix_seconds: i64, subsecond_millis: u16) -> Self {
if subsecond_millis >= 1000 { if subsecond_millis >= 1000 {
panic!("invalid subsecond millisecond value"); panic!("invalid subsecond millisecond value");
@ -349,7 +341,8 @@ impl UnixTime {
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn now() -> Result<Self, SystemTimeError> { #[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 now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let epoch = now.as_secs(); let epoch = now.as_secs();
Ok(Self::new(epoch as i64, now.subsec_nanos())) Ok(Self::new(epoch as i64, now.subsec_nanos()))
@ -360,49 +353,43 @@ impl UnixTime {
self.secs as f64 + (self.subsec_nanos as f64 / 1_000_000_000.0) self.secs as f64 + (self.subsec_nanos as f64 / 1_000_000_000.0)
} }
pub fn as_secs(&self) -> i64 { pub fn secs(&self) -> i64 {
self.secs self.secs
} }
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> { #[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
pub fn chrono_date_time(&self) -> LocalResult<DateTime<Utc>> {
Utc.timestamp_opt(self.secs, self.subsec_nanos) Utc.timestamp_opt(self.secs, self.subsec_nanos)
} }
#[cfg(feature = "timelib")] #[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
pub fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> { pub fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.as_secs())? Ok(time::OffsetDateTime::from_unix_timestamp(self.secs())?
+ time::Duration::nanoseconds(self.subsec_nanos().into())) + time::Duration::nanoseconds(self.subsec_nanos().into()))
} }
// Calculate the difference in milliseconds between two UnixTimestamps // Calculate the difference in milliseconds between two UnixTimestamps
pub fn diff_in_millis(&self, other: &UnixTime) -> Option<i64> { pub fn diff_in_millis(&self, other: &UnixTime) -> i64 {
let seconds_difference = self.secs.checked_sub(other.secs)?; let seconds_difference = self.secs - other.secs;
// Convert seconds difference to milliseconds // Convert seconds difference to milliseconds
let milliseconds_difference = seconds_difference.checked_mul(1000)?; let milliseconds_difference = seconds_difference * 1000;
// Calculate the difference in subsecond milliseconds directly // Calculate the difference in subsecond milliseconds directly
let subsecond_difference_nanos = self.subsec_nanos as i64 - other.subsec_nanos as i64; let subsecond_difference_nanos = self.subsec_nanos as i64 - other.subsec_nanos as i64;
// Combine the differences // Combine the differences
Some(milliseconds_difference + (subsecond_difference_nanos / 1_000_000)) milliseconds_difference + (subsecond_difference_nanos / 1_000_000)
} }
} }
#[cfg(feature = "chrono")] impl From<DateTime<Utc>> for UnixTime {
impl From<chrono::DateTime<chrono::Utc>> for UnixTime { fn from(value: DateTime<Utc>) -> Self {
fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
Self::new(value.timestamp(), value.timestamp_subsec_nanos()) Self::new(value.timestamp(), value.timestamp_subsec_nanos())
} }
} }
#[cfg(feature = "timelib")]
impl From<time::OffsetDateTime> for UnixTime {
fn from(value: time::OffsetDateTime) -> Self {
Self::new(value.unix_timestamp(), value.nanosecond())
}
}
impl PartialOrd for UnixTime { impl PartialOrd for UnixTime {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
@ -450,11 +437,11 @@ pub struct StampDiff {
} }
impl Sub for UnixTime { impl Sub for UnixTime {
type Output = Option<StampDiff>; type Output = StampDiff;
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
let difference = self.diff_in_millis(&rhs)?; let difference = self.diff_in_millis(&rhs);
Some(if difference < 0 { if difference < 0 {
StampDiff { StampDiff {
positive_duration: false, positive_duration: false,
duration_absolute: Duration::from_millis(-difference as u64), duration_absolute: Duration::from_millis(-difference as u64),
@ -464,7 +451,7 @@ impl Sub for UnixTime {
positive_duration: true, positive_duration: true,
duration_absolute: Duration::from_millis(difference as u64), duration_absolute: Duration::from_millis(difference as u64),
} }
}) }
} }
} }
@ -653,7 +640,7 @@ mod tests {
fn test_addition() { fn test_addition() {
let mut stamp0 = UnixTime::new_only_secs(1); let mut stamp0 = UnixTime::new_only_secs(1);
stamp0 += Duration::from_secs(5); stamp0 += Duration::from_secs(5);
assert_eq!(stamp0.as_secs(), 6); assert_eq!(stamp0.secs(), 6);
assert_eq!(stamp0.subsec_millis(), 0); assert_eq!(stamp0.subsec_millis(), 0);
let stamp1 = stamp0 + Duration::from_millis(500); let stamp1 = stamp0 + Duration::from_millis(500);
assert_eq!(stamp1.secs, 6); assert_eq!(stamp1.secs, 6);
@ -682,7 +669,7 @@ mod tests {
#[test] #[test]
fn test_from_now() { fn test_from_now() {
let stamp_now = UnixTime::now().unwrap(); let stamp_now = UnixTime::from_now().unwrap();
let dt_now = stamp_now.chrono_date_time().unwrap(); let dt_now = stamp_now.chrono_date_time().unwrap();
assert!(dt_now.year() >= 2020); assert!(dt_now.year() >= 2020);
} }
@ -693,7 +680,7 @@ mod tests {
let StampDiff { let StampDiff {
positive_duration, positive_duration,
duration_absolute, duration_absolute,
} = (stamp_later - UnixTime::new(1, 0)).expect("stamp diff error"); } = stamp_later - UnixTime::new(1, 0);
assert!(positive_duration); assert!(positive_duration);
assert_eq!(duration_absolute, Duration::from_secs(1)); assert_eq!(duration_absolute, Duration::from_secs(1));
} }
@ -705,7 +692,7 @@ mod tests {
let StampDiff { let StampDiff {
positive_duration, positive_duration,
duration_absolute, duration_absolute,
} = (stamp_later - stamp_earlier).expect("stamp diff error"); } = stamp_later - stamp_earlier;
assert!(positive_duration); assert!(positive_duration);
assert_eq!(duration_absolute, Duration::from_millis(1900)); assert_eq!(duration_absolute, Duration::from_millis(1900));
} }
@ -717,7 +704,7 @@ mod tests {
let StampDiff { let StampDiff {
positive_duration, positive_duration,
duration_absolute, duration_absolute,
} = (stamp_earlier - stamp_later).expect("stamp diff error"); } = stamp_earlier - stamp_later;
assert!(!positive_duration); assert!(!positive_duration);
assert_eq!(duration_absolute, Duration::from_millis(1900)); assert_eq!(duration_absolute, Duration::from_millis(1900));
} }
@ -752,13 +739,4 @@ mod tests {
assert_eq!(timelib_dt.minute(), 0); assert_eq!(timelib_dt.minute(), 0);
assert_eq!(timelib_dt.second(), 0); assert_eq!(timelib_dt.second(), 0);
} }
#[test]
#[cfg(feature = "timelib")]
fn test_unix_stamp_from_timelib_datetime() {
let timelib_dt = time::OffsetDateTime::UNIX_EPOCH;
let unix_time = UnixTime::from(timelib_dt);
let timelib_converted_back = unix_time.timelib_date_time().unwrap();
assert_eq!(timelib_dt, timelib_converted_back);
}
} }

View File

@ -77,6 +77,7 @@ pub trait UnsignedEnum {
fn value(&self) -> u64; fn value(&self) -> u64;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
fn to_vec(&self) -> alloc::vec::Vec<u8> { fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut buf = alloc::vec![0; self.size()]; let mut buf = alloc::vec![0; self.size()];
self.write_to_be_bytes(&mut buf).unwrap(); self.write_to_be_bytes(&mut buf).unwrap();
@ -131,7 +132,6 @@ impl Error for UnsignedByteFieldError {}
/// Type erased variant. /// Type erased variant.
#[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))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct UnsignedByteField { pub struct UnsignedByteField {
width: usize, width: usize,
value: u64, value: u64,
@ -221,7 +221,6 @@ impl UnsignedEnum for UnsignedByteField {
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct GenericUnsignedByteField<TYPE: Copy + Into<u64>> { pub struct GenericUnsignedByteField<TYPE: Copy + Into<u64>> {
value: TYPE, value: TYPE,
} }