Merge pull request 'Various improvements' (#1) from various_improvements into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good

Reviewed-on: #1
This commit is contained in:
Robin Müller 2022-12-05 00:16:07 +01:00
commit 283f9ff495
8 changed files with 352 additions and 123 deletions

View File

@ -8,6 +8,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
## Changed
- `serde` support is now optional and behind the `serde` feature
- `PusTcSecondaryHeaderT` trait renamed to `GenericPusTcSecondaryHeader`
- `PusTmSecondaryHeaderT` trait renamed to `GenericPusTmSecondaryHeader`
- `SpHeader`: Former `tc` and `tm` methods now named `tc_unseg` and `tm_unseg`.
Former `new` method now called `new_from_single_fields`
## Added
- `serde` `Serialize` and `Deserialize` added to all types
- Added `const` constructors for `PacketId`, `PacketSeqCtrl` and
`SpHeader`
- Added `PartialEq` and `Eq` `derive`s to `CdsShortTimeProvider`
# [v0.3.1] 03.12.2022 # [v0.3.1] 03.12.2022
- Small fix for faulty docs.rs build - Small fix for faulty docs.rs build

View File

@ -18,6 +18,7 @@ delegate = "0.8"
[dependencies.serde] [dependencies.serde]
version = "1.0" version = "1.0"
optional = true
default-features = false default-features = false
features = ["derive"] features = ["derive"]
@ -33,8 +34,9 @@ default-features = false
version = "1.0" version = "1.0"
[features] [features]
default = ["std"] default = ["std", "dep:serde"]
std = ["chrono/std", "chrono/clock", "alloc"] std = ["chrono/std", "chrono/clock", "alloc"]
serde = ["chrono/serde"]
alloc = ["postcard/alloc"] alloc = ["postcard/alloc"]
[package.metadata.docs.rs] [package.metadata.docs.rs]

View File

@ -33,6 +33,7 @@ Default features:
- [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers
like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html). like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html).
Enabled by the `std` feature. Enabled by the `std` feature.
- [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s
# Examples # Examples

View File

@ -4,6 +4,7 @@ use crate::{ByteConversionError, CcsdsPacket, SizeMissmatch};
use core::fmt::Debug; use core::fmt::Debug;
use core::mem::size_of; use core::mem::size_of;
use crc::{Crc, CRC_16_IBM_3740}; use crc::{Crc, CRC_16_IBM_3740};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub type CrcType = u16; pub type CrcType = u16;
@ -13,7 +14,8 @@ pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
pub const CCSDS_HEADER_LEN: usize = size_of::<crate::zc::SpHeader>(); pub const CCSDS_HEADER_LEN: usize = size_of::<crate::zc::SpHeader>();
/// 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, Serialize, Deserialize, Debug)] #[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PusVersion { pub enum PusVersion {
EsaPus = 0, EsaPus = 0,
PusA = 1, PusA = 1,
@ -35,6 +37,7 @@ impl TryFrom<u8> for PusVersion {
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PacketTypeCodes { pub enum PacketTypeCodes {
Boolean = 1, Boolean = 1,
Enumerated = 2, Enumerated = 2,
@ -51,6 +54,7 @@ pub enum PacketTypeCodes {
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PusError { pub enum PusError {
VersionNotSupported(PusVersion), VersionNotSupported(PusVersion),
IncorrectCrc(u16), IncorrectCrc(u16),
@ -212,6 +216,7 @@ impl ToBeBytes for u64 {
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GenericEcssEnumWrapper<TYPE> { pub struct GenericEcssEnumWrapper<TYPE> {
val: TYPE, val: TYPE,
} }

View File

@ -27,18 +27,20 @@
//! - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers //! - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers
//! like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html). //! like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html).
//! Enabled by the `std` feature. //! Enabled by the `std` feature.
//! - [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and
//! `Deserialize` `derive`s
//! //!
//! ## Module //! ## Module
//! //!
//! This module contains helpers and data structures to generate Space Packets according to the //! This module contains helpers and data structures to generate Space Packets according to the
//! [CCSDS 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf). This includes the //! [CCSDS 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf). This includes the
//! [SpHeader] class to generate the Space Packet Header component common to all space packets //! [SpHeader] class to generate the Space Packet Header component common to all space packets.
//! //!
//! ## Example //! ## Example
//! //!
//! ```rust //! ```rust
//! use spacepackets::SpHeader; //! use spacepackets::SpHeader;
//! let sp_header = SpHeader::tc(0x42, 12, 0).expect("Error creating SP header"); //! let sp_header = SpHeader::tc_unseg(0x42, 12, 0).expect("Error creating SP header");
//! println!("{:?}", sp_header); //! println!("{:?}", sp_header);
//! ``` //! ```
#![no_std] #![no_std]
@ -51,6 +53,7 @@ extern crate std;
use crate::ecss::CCSDS_HEADER_LEN; use crate::ecss::CCSDS_HEADER_LEN;
use delegate::delegate; use delegate::delegate;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod ecss; pub mod ecss;
@ -62,12 +65,14 @@ pub const MAX_APID: u16 = 2u16.pow(11) - 1;
pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1; pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1;
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SizeMissmatch { pub struct SizeMissmatch {
pub found: usize, pub found: usize,
pub expected: usize, pub expected: usize,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
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(SizeMissmatch), ToSliceTooSmall(SizeMissmatch),
@ -78,7 +83,8 @@ pub enum ByteConversionError {
ZeroCopyFromError, ZeroCopyFromError,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PacketType { pub enum PacketType {
Tm = 0, Tm = 0,
Tc = 1, Tc = 1,
@ -100,7 +106,8 @@ pub fn packet_type_in_raw_packet_id(packet_id: u16) -> PacketType {
PacketType::try_from((packet_id >> 12) as u8 & 0b1).unwrap() PacketType::try_from((packet_id >> 12) as u8 & 0b1).unwrap()
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SequenceFlags { pub enum SequenceFlags {
ContinuationSegment = 0b00, ContinuationSegment = 0b00,
FirstSegment = 0b01, FirstSegment = 0b01,
@ -124,21 +131,57 @@ impl TryFrom<u8> for SequenceFlags {
} }
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PacketId { pub struct PacketId {
pub ptype: PacketType, pub ptype: PacketType,
pub sec_header_flag: bool, pub sec_header_flag: bool,
apid: u16, apid: u16,
} }
impl PacketId { impl Default for PacketId {
pub fn new(ptype: PacketType, sec_header_flag: bool, apid: u16) -> Option<PacketId> { fn default() -> Self {
let mut pid = PacketId { PacketId {
ptype, ptype: PacketType::Tm,
sec_header_flag, sec_header_flag: false,
apid: 0, apid: 0,
}; }
pid.set_apid(apid).then_some(pid) }
}
impl PacketId {
pub const fn const_tc(sec_header: bool, apid: u16) -> Self {
Self::const_new(PacketType::Tc, sec_header, apid)
}
pub const fn const_tm(sec_header: bool, apid: u16) -> Self {
Self::const_new(PacketType::Tm, sec_header, apid)
}
pub fn tc(sec_header: bool, apid: u16) -> Option<Self> {
Self::new(PacketType::Tc, sec_header, apid)
}
pub fn tm(sec_header: bool, apid: u16) -> Option<Self> {
Self::new(PacketType::Tm, sec_header, apid)
}
pub const fn const_new(ptype: PacketType, sec_header: bool, apid: u16) -> Self {
if apid > MAX_APID {
panic!("APID too large");
}
PacketId {
ptype,
sec_header_flag: sec_header,
apid,
}
}
pub fn new(ptype: PacketType, sec_header_flag: bool, apid: u16) -> Option<PacketId> {
if apid > MAX_APID {
return None;
}
Some(PacketId::const_new(ptype, sec_header_flag, apid))
} }
/// Set a new Application Process ID (APID). If the passed number is invalid, the APID will /// Set a new Application Process ID (APID). If the passed number is invalid, the APID will
@ -171,27 +214,39 @@ impl From<u16> for PacketId {
} }
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PacketSequenceCtrl { pub struct PacketSequenceCtrl {
pub seq_flags: SequenceFlags, pub seq_flags: SequenceFlags,
seq_count: u16, seq_count: u16,
} }
impl PacketSequenceCtrl { impl PacketSequenceCtrl {
/// Returns [None] if the passed sequence count exceeds [MAX_SEQ_COUNT] /// const variant of [PacketSequenceCtrl::new], but panics if the sequence count exceeds
pub fn new(seq_flags: SequenceFlags, seq_count: u16) -> Option<PacketSequenceCtrl> { /// [MAX_SEQ_COUNT].
let mut psc = PacketSequenceCtrl { const fn const_new(seq_flags: SequenceFlags, seq_count: u16) -> PacketSequenceCtrl {
if seq_count > MAX_SEQ_COUNT {
panic!("Sequence count too large");
}
PacketSequenceCtrl {
seq_flags, seq_flags,
seq_count: 0, seq_count,
}; }
psc.set_seq_count(seq_count).then_some(psc) }
/// Returns [None] if the passed sequence count exceeds [MAX_SEQ_COUNT].
pub fn new(seq_flags: SequenceFlags, seq_count: u16) -> Option<PacketSequenceCtrl> {
if seq_count > MAX_SEQ_COUNT {
return None;
}
Some(PacketSequenceCtrl::const_new(seq_flags, seq_count))
} }
pub fn raw(&self) -> u16 { pub fn raw(&self) -> u16 {
((self.seq_flags as u16) << 14) | self.seq_count ((self.seq_flags as u16) << 14) | self.seq_count
} }
/// Set a new sequence count. If the passed number is invalid, the sequence count will not be /// Set a new sequence count. If the passed number is invalid, the sequence count will not be
/// set and false will be returned. The maximum allowed value for the 14-bit field is 16383 /// set and false will be returned. The maximum allowed value for the 14-bit field is 16383.
pub fn set_seq_count(&mut self, ssc: u16) -> bool { pub fn set_seq_count(&mut self, ssc: u16) -> bool {
if ssc > MAX_SEQ_COUNT { if ssc > MAX_SEQ_COUNT {
return false; return false;
@ -232,7 +287,7 @@ macro_rules! sph_from_other {
const SSC_MASK: u16 = 0x3FFF; const SSC_MASK: u16 = 0x3FFF;
const VERSION_MASK: u16 = 0xE000; const VERSION_MASK: u16 = 0xE000;
/// Generic trait to access fields of a CCSDS space packet header according to CCSDS 133.0-B-2 /// Generic trait to access fields of a CCSDS space packet header according to CCSDS 133.0-B-2.
pub trait CcsdsPacket { pub trait CcsdsPacket {
fn ccsds_version(&self) -> u8; fn ccsds_version(&self) -> u8;
fn packet_id(&self) -> PacketId; fn packet_id(&self) -> PacketId;
@ -246,7 +301,7 @@ pub trait CcsdsPacket {
} }
/// Retrieve 13 bit Packet Identification field. Can usually be retrieved with a bitwise AND /// Retrieve 13 bit Packet Identification field. Can usually be retrieved with a bitwise AND
/// of the first 2 bytes with 0x1FFF /// of the first 2 bytes with 0x1FFF.
#[inline] #[inline]
fn packet_id_raw(&self) -> u16 { fn packet_id_raw(&self) -> u16 {
self.packet_id().raw() self.packet_id().raw()
@ -257,8 +312,8 @@ pub trait CcsdsPacket {
self.psc().raw() self.psc().raw()
} }
/// Retrieve Packet Type (TM: 0, TC: 1).
#[inline] #[inline]
/// Retrieve Packet Type (TM: 0, TC: 1)
fn ptype(&self) -> PacketType { fn ptype(&self) -> PacketType {
// This call should never fail because only 0 and 1 can be passed to the try_from call // This call should never fail because only 0 and 1 can be passed to the try_from call
self.packet_id().ptype self.packet_id().ptype
@ -275,13 +330,13 @@ pub trait CcsdsPacket {
} }
/// Retrieve the secondary header flag. Returns true if a secondary header is present /// Retrieve the secondary header flag. Returns true if a secondary header is present
/// and false if it is not /// and false if it is not.
#[inline] #[inline]
fn sec_header_flag(&self) -> bool { fn sec_header_flag(&self) -> bool {
self.packet_id().sec_header_flag self.packet_id().sec_header_flag
} }
/// Retrieve Application Process ID /// Retrieve Application Process ID.
#[inline] #[inline]
fn apid(&self) -> u16 { fn apid(&self) -> u16 {
self.packet_id().apid self.packet_id().apid
@ -309,16 +364,18 @@ pub trait CcsdsPrimaryHeader {
) -> Self; ) -> Self;
} }
/// Space Packet Primary Header according to CCSDS 133.0-B-2 /// Space Packet Primary Header according to CCSDS 133.0-B-2.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `version` - CCSDS version field, occupies the first 3 bits of the raw header /// * `version` - CCSDS version field, occupies the first 3 bits of the raw header. Will generally
/// be set to 0b000 in all constructors provided by this crate.
/// * `packet_id` - Packet Identifier, which can also be used as a start marker. Occupies the last /// * `packet_id` - Packet Identifier, which can also be used as a start marker. Occupies the last
/// 13 bits of the first two bytes of the raw header /// 13 bits of the first two bytes of the raw header
/// * `psc` - Packet Sequence Control, occupies the third and fourth byte of the raw header /// * `psc` - Packet Sequence Control, occupies the third and fourth byte of the raw header
/// * `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(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SpHeader { pub struct SpHeader {
pub version: u8, pub version: u8,
pub packet_id: PacketId, pub packet_id: PacketId,
@ -327,55 +384,94 @@ pub struct SpHeader {
} }
impl Default for SpHeader { impl Default for SpHeader {
/// The default function sets the sequence flag field to [SequenceFlags::Unsegmented]. The data
/// length field is set to 1, which denotes an empty space packets.
fn default() -> Self { fn default() -> Self {
SpHeader { SpHeader {
version: 0, version: 0,
packet_id: PacketId { packet_id: PacketId::default(),
ptype: PacketType::Tm,
apid: 0,
sec_header_flag: false,
},
psc: PacketSequenceCtrl { psc: PacketSequenceCtrl {
seq_flags: SequenceFlags::Unsegmented, seq_flags: SequenceFlags::Unsegmented,
seq_count: 0, seq_count: 0,
}, },
data_len: 0, data_len: 1,
} }
} }
} }
impl SpHeader { impl SpHeader {
/// Create a new Space Packet Header instance which can be used to create generic pub const fn new(packet_id: PacketId, psc: PacketSequenceCtrl, data_len: u16) -> Self {
/// Space Packets. This will return [None] if the APID or sequence count argument Self {
/// exceed [MAX_APID] or [MAX_SEQ_COUNT] respectively. version: 0,
pub fn new( packet_id,
psc,
data_len,
}
}
/// const variant of the [SpHeader::new_fron_single_fields] function. Panics if the passed
/// APID exceeds [MAX_APID] or the passed packet sequence count exceeds [MAX_SEQ_COUNT].
const fn const_new_from_single_fields(
ptype: PacketType, ptype: PacketType,
sec_header: bool, sec_header: bool,
apid: u16, apid: u16,
seq_flags: SequenceFlags,
seq_count: u16,
data_len: u16,
) -> Self {
if seq_count > MAX_SEQ_COUNT {
panic!("Sequence count is too large");
}
if apid > MAX_APID {
panic!("APID is too large");
}
Self {
psc: PacketSequenceCtrl::const_new(seq_flags, seq_count),
packet_id: PacketId::const_new(ptype, sec_header, apid),
data_len,
version: 0,
}
}
/// Create a new Space Packet Header instance which can be used to create generic
/// Space Packets. This will return [None] if the APID or sequence count argument
/// exceed [MAX_APID] or [MAX_SEQ_COUNT] respectively. The version field is set to 0b000.
pub fn new_from_single_fields(
ptype: PacketType,
sec_header: bool,
apid: u16,
seq_flags: SequenceFlags,
seq_count: u16, seq_count: u16,
data_len: u16, data_len: u16,
) -> Option<Self> { ) -> Option<Self> {
if seq_count > MAX_SEQ_COUNT || apid > MAX_APID { if seq_count > MAX_SEQ_COUNT || apid > MAX_APID {
return None; return None;
} }
let mut header = SpHeader::default(); Some(SpHeader::const_new_from_single_fields(
header.packet_id.sec_header_flag = sec_header; ptype, sec_header, apid, seq_flags, seq_count, data_len,
header.packet_id.apid = apid; ))
header.packet_id.ptype = ptype;
header.psc.seq_count = seq_count;
header.data_len = data_len;
Some(header)
} }
/// Helper function for telemetry space packet headers. The packet type field will be /// Helper function for telemetry space packet headers. The packet type field will be
/// set accordingly. /// set accordingly. The secondary header flag field is set to false.
pub fn tm(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> { pub fn tm(apid: u16, seq_flags: SequenceFlags, seq_count: u16, data_len: u16) -> Option<Self> {
Self::new(PacketType::Tm, false, apid, seq_count, data_len) Self::new_from_single_fields(PacketType::Tm, false, apid, seq_flags, seq_count, data_len)
} }
/// Helper function for telecommand space packet headers. The packet type field will be /// Helper function for telemetry space packet headers. The packet type field will be
/// set accordingly. /// set accordingly. The secondary header flag field is set to false.
pub fn tc(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> { pub fn tc(apid: u16, seq_flags: SequenceFlags, seq_count: u16, data_len: u16) -> Option<Self> {
Self::new(PacketType::Tc, false, apid, seq_count, data_len) Self::new_from_single_fields(PacketType::Tc, false, apid, seq_flags, seq_count, data_len)
}
/// Variant of [SpHeader::tm] which sets the sequence flag field to [SequenceFlags::Unsegmented]
pub fn tm_unseg(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> {
Self::tm(apid, SequenceFlags::Unsegmented, seq_count, data_len)
}
/// Variant of [SpHeader::tc] which sets the sequence flag field to [SequenceFlags::Unsegmented]
pub fn tc_unseg(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> {
Self::tc(apid, SequenceFlags::Unsegmented, seq_count, data_len)
} }
//noinspection RsTraitImplementation //noinspection RsTraitImplementation
@ -541,23 +637,75 @@ pub mod zc {
sph_from_other!(SpHeader, crate::SpHeader); sph_from_other!(SpHeader, crate::SpHeader);
} }
#[cfg(test)] #[cfg(all(test, feature = "std"))]
mod tests { mod tests {
#[cfg(feature = "std")] #[cfg(feature = "serde")]
use crate::CcsdsPrimaryHeader; use crate::CcsdsPrimaryHeader;
use crate::SpHeader;
use crate::{ use crate::{
packet_type_in_raw_packet_id, zc, CcsdsPacket, PacketId, PacketSequenceCtrl, PacketType, packet_type_in_raw_packet_id, zc, CcsdsPacket, PacketId, PacketSequenceCtrl, PacketType,
SequenceFlags,
}; };
use crate::{SequenceFlags, SpHeader};
use alloc::vec; use alloc::vec;
#[cfg(not(feature = "std"))]
use num::pow;
#[cfg(feature = "std")]
use num_traits::pow; use num_traits::pow;
use postcard::from_bytes; #[cfg(feature = "serde")]
#[cfg(feature = "alloc")] use postcard::{from_bytes, to_allocvec};
use postcard::to_allocvec;
const CONST_SP: SpHeader = SpHeader::new(
PacketId::const_tc(true, 0x36),
PacketSequenceCtrl::const_new(SequenceFlags::ContinuationSegment, 0x88),
0x90,
);
const PACKET_ID_TM: PacketId = PacketId::const_tm(true, 0x22);
#[test]
fn verify_const_packet_id() {
assert_eq!(PACKET_ID_TM.apid(), 0x22);
assert_eq!(PACKET_ID_TM.sec_header_flag, true);
assert_eq!(PACKET_ID_TM.ptype, PacketType::Tm);
let const_tc_id = PacketId::const_tc(true, 0x23);
assert_eq!(const_tc_id.ptype, PacketType::Tc);
}
#[test]
fn test_default_packet_id() {
let id_default = PacketId::default();
assert_eq!(id_default.ptype, PacketType::Tm);
assert_eq!(id_default.apid, 0x000);
assert_eq!(id_default.sec_header_flag, false);
}
#[test]
fn test_packet_id_ctors() {
let packet_id = PacketId::new(PacketType::Tc, true, 0x1ff);
assert!(packet_id.is_some());
let packet_id = packet_id.unwrap();
assert_eq!(packet_id.apid(), 0x1ff);
assert_eq!(packet_id.ptype, PacketType::Tc);
assert_eq!(packet_id.sec_header_flag, true);
let packet_id_tc = PacketId::tc(true, 0x1ff);
assert!(packet_id_tc.is_some());
let packet_id_tc = packet_id_tc.unwrap();
assert_eq!(packet_id_tc, packet_id);
let packet_id_tm = PacketId::tm(true, 0x2ff);
assert!(packet_id_tm.is_some());
let packet_id_tm = packet_id_tm.unwrap();
assert_eq!(packet_id_tm.sec_header_flag, true);
assert_eq!(packet_id_tm.ptype, PacketType::Tm);
assert_eq!(packet_id_tm.apid, 0x2ff);
}
#[test]
fn verify_const_sp_header() {
assert_eq!(CONST_SP.sec_header_flag(), true);
assert_eq!(CONST_SP.apid(), 0x36);
assert_eq!(
CONST_SP.sequence_flags(),
SequenceFlags::ContinuationSegment
);
assert_eq!(CONST_SP.seq_count(), 0x88);
assert_eq!(CONST_SP.data_len, 0x90);
}
#[test] #[test]
fn test_seq_flag_helpers() { fn test_seq_flag_helpers() {
@ -640,9 +788,9 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "std")] #[cfg(feature = "serde")]
fn test_serde_sph() { fn test_serde_sph() {
let sp_header = SpHeader::tc(0x42, 12, 0).expect("Error creating SP header"); let sp_header = SpHeader::tc_unseg(0x42, 12, 0).expect("Error creating SP header");
assert_eq!(sp_header.ccsds_version(), 0b000); assert_eq!(sp_header.ccsds_version(), 0b000);
assert!(sp_header.is_tc()); assert!(sp_header.is_tc());
assert!(!sp_header.sec_header_flag()); assert!(!sp_header.sec_header_flag());
@ -664,7 +812,7 @@ mod tests {
assert_eq!(sp_header.ccsds_version(), 0b000); assert_eq!(sp_header.ccsds_version(), 0b000);
assert_eq!(sp_header.data_len, 0); assert_eq!(sp_header.data_len, 0);
let sp_header = SpHeader::tm(0x7, 22, 36).expect("Error creating SP header"); let sp_header = SpHeader::tm_unseg(0x7, 22, 36).expect("Error creating SP header");
assert_eq!(sp_header.ccsds_version(), 0b000); assert_eq!(sp_header.ccsds_version(), 0b000);
assert!(sp_header.is_tm()); assert!(sp_header.is_tm());
assert!(!sp_header.sec_header_flag()); assert!(!sp_header.sec_header_flag());
@ -695,28 +843,69 @@ mod tests {
} }
#[test] #[test]
fn test_sp_header_setters() { fn test_setters() {
let mut sp_header = SpHeader::tc(0x42, 12, 0).expect("Error creating SP header"); let sp_header = SpHeader::tc(0x42, SequenceFlags::Unsegmented, 25, 0);
assert_eq!(sp_header.apid(), 0x42); assert!(sp_header.is_some());
let mut sp_header = sp_header.unwrap();
sp_header.set_apid(0x12); sp_header.set_apid(0x12);
assert_eq!(sp_header.apid(), 0x12); assert_eq!(sp_header.apid(), 0x12);
sp_header.set_sec_header_flag(); sp_header.set_sec_header_flag();
assert!(sp_header.sec_header_flag()); assert!(sp_header.sec_header_flag());
sp_header.clear_sec_header_flag(); sp_header.clear_sec_header_flag();
assert!(!sp_header.sec_header_flag()); assert!(!sp_header.sec_header_flag());
sp_header.set_seq_count(0x45);
assert_eq!(sp_header.seq_count(), 0x45);
assert_eq!(sp_header.ptype(), PacketType::Tc); assert_eq!(sp_header.ptype(), PacketType::Tc);
sp_header.set_packet_type(PacketType::Tm); sp_header.set_packet_type(PacketType::Tm);
assert_eq!(sp_header.ptype(), PacketType::Tm); assert_eq!(sp_header.ptype(), PacketType::Tm);
sp_header.set_seq_count(0x45);
assert_eq!(sp_header.seq_count(), 0x45);
}
#[test]
fn test_tc_ctor() {
let sp_header = SpHeader::tc(0x42, SequenceFlags::Unsegmented, 25, 0);
assert!(sp_header.is_some());
let sp_header = sp_header.unwrap();
verify_sp_fields(PacketType::Tc, &sp_header);
}
#[test]
fn test_tc_ctor_unseg() {
let sp_header = SpHeader::tc_unseg(0x42, 25, 0);
assert!(sp_header.is_some());
let sp_header = sp_header.unwrap();
verify_sp_fields(PacketType::Tc, &sp_header);
}
#[test]
fn test_tm_ctor() {
let sp_header = SpHeader::tm(0x42, SequenceFlags::Unsegmented, 25, 0);
assert!(sp_header.is_some());
let sp_header = sp_header.unwrap();
verify_sp_fields(PacketType::Tm, &sp_header);
}
#[test]
fn test_tm_ctor_unseg() {
let sp_header = SpHeader::tm_unseg(0x42, 25, 0);
assert!(sp_header.is_some());
let sp_header = sp_header.unwrap();
verify_sp_fields(PacketType::Tm, &sp_header);
}
fn verify_sp_fields(ptype: PacketType, sp_header: &SpHeader) {
assert_eq!(sp_header.ptype(), ptype);
assert_eq!(sp_header.sequence_flags(), SequenceFlags::Unsegmented);
assert_eq!(sp_header.apid(), 0x42);
assert_eq!(sp_header.seq_count(), 25);
assert_eq!(sp_header.data_len(), 0);
} }
#[test] #[test]
fn test_zc_sph() { fn test_zc_sph() {
use zerocopy::AsBytes; use zerocopy::AsBytes;
let sp_header = SpHeader::tc(0x7FF, pow(2, 14) - 1, 0).expect("Error creating SP header"); let sp_header =
SpHeader::tc_unseg(0x7FF, pow(2, 14) - 1, 0).expect("Error creating SP header");
assert_eq!(sp_header.ptype(), PacketType::Tc); assert_eq!(sp_header.ptype(), PacketType::Tc);
assert_eq!(sp_header.apid(), 0x7FF); assert_eq!(sp_header.apid(), 0x7FF);
assert_eq!(sp_header.data_len(), 0); assert_eq!(sp_header.data_len(), 0);

View File

@ -9,7 +9,7 @@
//! use spacepackets::ecss::PusPacket; //! use spacepackets::ecss::PusPacket;
//! //!
//! // Create a ping telecommand with no user application data //! // Create a ping telecommand with no user application data
//! let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap(); //! let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
//! let tc_header = PusTcSecondaryHeader::new_simple(17, 1); //! let tc_header = PusTcSecondaryHeader::new_simple(17, 1);
//! let pus_tc = PusTc::new(&mut sph, tc_header, None, true); //! let pus_tc = PusTc::new(&mut sph, tc_header, None, true);
//! println!("{:?}", pus_tc); //! println!("{:?}", pus_tc);
@ -41,6 +41,7 @@ use crate::{
}; };
use core::mem::size_of; use core::mem::size_of;
use delegate::delegate; use delegate::delegate;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zerocopy::AsBytes; use zerocopy::AsBytes;
@ -66,7 +67,7 @@ pub const ACK_ALL: u8 = AckOpts::Acceptance as u8
| AckOpts::Progress as u8 | AckOpts::Progress as u8
| AckOpts::Completion as u8; | AckOpts::Completion as u8;
pub trait PusTcSecondaryHeaderT { pub trait GenericPusTcSecondaryHeader {
fn pus_version(&self) -> PusVersion; fn pus_version(&self) -> PusVersion;
fn ack_flags(&self) -> u8; fn ack_flags(&self) -> u8;
fn service(&self) -> u8; fn service(&self) -> u8;
@ -76,7 +77,7 @@ pub trait PusTcSecondaryHeaderT {
pub mod zc { pub mod zc {
use crate::ecss::{PusError, PusVersion}; use crate::ecss::{PusError, PusVersion};
use crate::tc::PusTcSecondaryHeaderT; use crate::tc::GenericPusTcSecondaryHeader;
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16}; use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
#[derive(FromBytes, AsBytes, Unaligned)] #[derive(FromBytes, AsBytes, Unaligned)]
@ -103,7 +104,7 @@ pub mod zc {
} }
} }
impl PusTcSecondaryHeaderT for PusTcSecondaryHeader { impl GenericPusTcSecondaryHeader for PusTcSecondaryHeader {
fn pus_version(&self) -> PusVersion { fn pus_version(&self) -> PusVersion {
PusVersion::try_from(self.version_ack >> 4 & 0b1111).unwrap_or(PusVersion::Invalid) PusVersion::try_from(self.version_ack >> 4 & 0b1111).unwrap_or(PusVersion::Invalid)
} }
@ -136,7 +137,8 @@ pub mod zc {
} }
} }
#[derive(PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Debug)] #[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTcSecondaryHeader { pub struct PusTcSecondaryHeader {
pub service: u8, pub service: u8,
pub subservice: u8, pub subservice: u8,
@ -145,7 +147,7 @@ pub struct PusTcSecondaryHeader {
pub version: PusVersion, pub version: PusVersion,
} }
impl PusTcSecondaryHeaderT for PusTcSecondaryHeader { impl GenericPusTcSecondaryHeader for PusTcSecondaryHeader {
fn pus_version(&self) -> PusVersion { fn pus_version(&self) -> PusVersion {
self.version self.version
} }
@ -211,14 +213,15 @@ impl PusTcSecondaryHeader {
/// [postcard](https://docs.rs/postcard/latest/postcard/). /// [postcard](https://docs.rs/postcard/latest/postcard/).
/// ///
/// There is no spare bytes support yet. /// There is no spare bytes support yet.
#[derive(PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Debug)] #[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTc<'slice> { pub struct PusTc<'slice> {
sp_header: SpHeader, sp_header: SpHeader,
pub sec_header: PusTcSecondaryHeader, pub sec_header: PusTcSecondaryHeader,
/// If this is set to false, a manual call to [PusTc::calc_own_crc16] or /// If this is set to false, a manual call to [PusTc::calc_own_crc16] or
/// [PusTc::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid. /// [PusTc::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
pub calc_crc_on_serialization: bool, pub calc_crc_on_serialization: bool,
#[serde(skip)] #[cfg_attr(feature = "serde", serde(skip))]
raw_data: Option<&'slice [u8]>, raw_data: Option<&'slice [u8]>,
app_data: Option<&'slice [u8]>, app_data: Option<&'slice [u8]>,
crc16: Option<u16>, crc16: Option<u16>,
@ -260,7 +263,7 @@ impl<'slice> PusTc<'slice> {
} }
/// Simplified version of the [PusTc::new] function which allows to only specify service and /// Simplified version of the [PusTc::new] function which allows to only specify service and
/// subservice instead of the full PUS TC secondary header /// 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,
@ -303,7 +306,7 @@ impl<'slice> PusTc<'slice> {
/// 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
/// is set correctly /// is set correctly.
pub fn update_ccsds_data_len(&mut self) { pub fn update_ccsds_data_len(&mut self) {
self.sp_header.data_len = self.sp_header.data_len =
self.len_packed() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1; self.len_packed() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
@ -323,7 +326,7 @@ impl<'slice> PusTc<'slice> {
self.crc16 = Some(digest.finalize()) self.crc16 = Some(digest.finalize())
} }
/// This helper function calls both [PusTc.update_ccsds_data_len] and [PusTc.calc_own_crc16] /// This helper function calls both [PusTc.update_ccsds_data_len] and [PusTc.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();
self.calc_own_crc16(); self.calc_own_crc16();
@ -401,7 +404,7 @@ impl<'slice> PusTc<'slice> {
} }
/// Create a [PusTc] instance from a raw slice. On success, it returns a tuple containing /// Create a [PusTc] instance from a raw slice. On success, it returns a tuple containing
/// the instance and the found byte length of the packet /// the instance and the found byte length of the packet.
pub fn from_bytes(slice: &'slice [u8]) -> Result<(Self, usize), PusError> { pub fn from_bytes(slice: &'slice [u8]) -> Result<(Self, usize), PusError> {
let raw_data_len = slice.len(); let raw_data_len = slice.len();
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA { if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
@ -462,7 +465,7 @@ impl PusPacket for PusTc<'_> {
} }
//noinspection RsTraitImplementation //noinspection RsTraitImplementation
impl PusTcSecondaryHeaderT for PusTc<'_> { impl GenericPusTcSecondaryHeader for PusTc<'_> {
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;
@ -472,29 +475,29 @@ impl PusTcSecondaryHeaderT for PusTc<'_> {
}); });
} }
#[cfg(test)] #[cfg(all(test, feature = "std"))]
mod tests { mod tests {
use crate::ecss::PusVersion::PusC; use crate::ecss::PusVersion::PusC;
use crate::ecss::{PusError, PusPacket}; use crate::ecss::{PusError, PusPacket};
use crate::tc::ACK_ALL; use crate::tc::ACK_ALL;
use crate::tc::{PusTc, PusTcSecondaryHeader, PusTcSecondaryHeaderT}; use crate::tc::{GenericPusTcSecondaryHeader, PusTc, PusTcSecondaryHeader};
use crate::{ByteConversionError, SpHeader}; use crate::{ByteConversionError, SpHeader};
use crate::{CcsdsPacket, SequenceFlags}; use crate::{CcsdsPacket, SequenceFlags};
use alloc::vec::Vec; use alloc::vec::Vec;
fn base_ping_tc_full_ctor() -> PusTc<'static> { fn base_ping_tc_full_ctor() -> PusTc<'static> {
let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap(); let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
let tc_header = PusTcSecondaryHeader::new_simple(17, 1); let tc_header = PusTcSecondaryHeader::new_simple(17, 1);
PusTc::new(&mut sph, tc_header, None, true) PusTc::new(&mut sph, tc_header, None, true)
} }
fn base_ping_tc_simple_ctor() -> PusTc<'static> { fn base_ping_tc_simple_ctor() -> PusTc<'static> {
let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap(); let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
PusTc::new_simple(&mut sph, 17, 1, None, true) PusTc::new_simple(&mut sph, 17, 1, None, true)
} }
fn base_ping_tc_simple_ctor_with_app_data(app_data: &'static [u8]) -> PusTc<'static> { fn base_ping_tc_simple_ctor_with_app_data(app_data: &'static [u8]) -> PusTc<'static> {
let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap(); let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
PusTc::new_simple(&mut sph, 17, 1, Some(app_data), true) PusTc::new_simple(&mut sph, 17, 1, Some(app_data), true)
} }
@ -534,7 +537,7 @@ mod tests {
#[test] #[test]
fn test_update_func() { fn test_update_func() {
let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap(); let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
let mut tc = PusTc::new_simple(&mut sph, 17, 1, None, false); let mut tc = PusTc::new_simple(&mut sph, 17, 1, None, false);
tc.calc_crc_on_serialization = false; tc.calc_crc_on_serialization = false;
assert_eq!(tc.data_len(), 0); assert_eq!(tc.data_len(), 0);
@ -560,7 +563,6 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "alloc")]
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();
@ -625,7 +627,7 @@ mod tests {
} }
#[test] #[test]
fn test_write_buf_too_msall() { fn test_write_buf_too_small() {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf = [0; 12]; let mut test_buf = [0; 12];
let res = pus_tc.write_to_bytes(test_buf.as_mut_slice()); let res = pus_tc.write_to_bytes(test_buf.as_mut_slice());
@ -698,7 +700,7 @@ mod tests {
assert_eq!(tc.apid(), 0x02); assert_eq!(tc.apid(), 0x02);
assert_eq!(tc.ack_flags(), ACK_ALL); assert_eq!(tc.ack_flags(), ACK_ALL);
assert_eq!(tc.len_packed(), exp_full_len); assert_eq!(tc.len_packed(), exp_full_len);
let mut comp_header = SpHeader::tc(0x02, 0x34, exp_full_len as u16 - 7).unwrap(); let mut comp_header = SpHeader::tc_unseg(0x02, 0x34, exp_full_len as u16 - 7).unwrap();
comp_header.set_sec_header_flag(); comp_header.set_sec_header_flag();
assert_eq!(tc.sp_header, comp_header); assert_eq!(tc.sp_header, comp_header);
} }

View File

@ -10,11 +10,15 @@ use crate::time::CcsdsTimeCodes::Cds;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::time::{SystemTime, SystemTimeError}; use std::time::{SystemTime, SystemTimeError};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub const CDS_SHORT_LEN: usize = 7; pub const CDS_SHORT_LEN: usize = 7;
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383; pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
pub const SECONDS_PER_DAY: u32 = 86400; pub const SECONDS_PER_DAY: u32 = 86400;
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CcsdsTimeCodes { pub enum CcsdsTimeCodes {
None = 0, None = 0,
CucCcsdsEpoch = 0b001, CucCcsdsEpoch = 0b001,
@ -23,7 +27,7 @@ pub enum CcsdsTimeCodes {
Ccs = 0b101, Ccs = 0b101,
} }
const CDS_SHORT_P_FIELD: u8 = (CcsdsTimeCodes::Cds as u8) << 4; const CDS_SHORT_P_FIELD: u8 = (Cds as u8) << 4;
impl TryFrom<u8> for CcsdsTimeCodes { impl TryFrom<u8> for CcsdsTimeCodes {
type Error = (); type Error = ();
@ -41,6 +45,7 @@ impl TryFrom<u8> for CcsdsTimeCodes {
} }
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TimestampError { pub enum TimestampError {
/// Contains tuple where first value is the expected time code and the second /// Contains tuple where first value is the expected time code and the second
/// value is the found raw value /// value is the found raw value
@ -83,7 +88,7 @@ pub trait TimeReader {
Self: Sized; Self: Sized;
} }
/// Trait for generic CCSDS time providers /// Trait for generic CCSDS time providers.
pub trait CcsdsTimeProvider { pub trait CcsdsTimeProvider {
fn len_as_bytes(&self) -> usize; fn len_as_bytes(&self) -> usize;
@ -112,12 +117,12 @@ pub trait CcsdsTimeProvider {
/// timestamp_now.write_to_bytes(&mut raw_stamp).unwrap(); /// timestamp_now.write_to_bytes(&mut raw_stamp).unwrap();
/// assert_eq!((raw_stamp[0] >> 4) & 0b111, Cds as u8); /// assert_eq!((raw_stamp[0] >> 4) & 0b111, Cds as u8);
/// ``` /// ```
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CdsShortTimeProvider { pub struct CdsShortTimeProvider {
ccsds_days: u16, ccsds_days: u16,
ms_of_day: u32, ms_of_day: u32,
unix_seconds: i64, unix_seconds: i64,
date_time: Option<DateTime<Utc>>,
} }
impl CdsShortTimeProvider { impl CdsShortTimeProvider {
@ -126,7 +131,6 @@ impl CdsShortTimeProvider {
ccsds_days, ccsds_days,
ms_of_day, ms_of_day,
unix_seconds: 0, unix_seconds: 0,
date_time: None,
}; };
let unix_days_seconds = let unix_days_seconds =
ccsds_to_unix_days(ccsds_days as i32) as i64 * SECONDS_PER_DAY as i64; ccsds_to_unix_days(ccsds_days as i32) as i64 * SECONDS_PER_DAY as i64;
@ -146,7 +150,6 @@ impl CdsShortTimeProvider {
as u16, as u16,
ms_of_day: ms_of_day as u32, ms_of_day: ms_of_day as u32,
unix_seconds: 0, unix_seconds: 0,
date_time: None,
}; };
Ok(provider.setup(unix_days_seconds as i64, ms_of_day)) Ok(provider.setup(unix_days_seconds as i64, ms_of_day))
} }
@ -165,7 +168,6 @@ impl CdsShortTimeProvider {
fn setup(mut self, unix_days_seconds: i64, ms_of_day: u64) -> Self { fn setup(mut self, unix_days_seconds: i64, ms_of_day: u64) -> Self {
self.calc_unix_seconds(unix_days_seconds, ms_of_day); self.calc_unix_seconds(unix_days_seconds, ms_of_day);
self.calc_date_time((ms_of_day % 1000) as u32);
self self
} }
@ -193,14 +195,13 @@ impl CdsShortTimeProvider {
} }
} }
fn calc_date_time(&mut self, ms_since_last_second: u32) { fn calc_date_time(&self, ms_since_last_second: u32) -> Option<DateTime<Utc>> {
assert!(ms_since_last_second < 1000, "Invalid MS since last second"); assert!(ms_since_last_second < 1000, "Invalid MS since last second");
let ns_since_last_sec = ms_since_last_second * 1e6 as u32; let ns_since_last_sec = ms_since_last_second * 1e6 as u32;
if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_sec) { if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_sec) {
self.date_time = Some(val); return Some(val);
} else {
self.date_time = None;
} }
None
} }
} }
@ -222,7 +223,7 @@ impl CcsdsTimeProvider for CdsShortTimeProvider {
} }
fn date_time(&self) -> Option<DateTime<Utc>> { fn date_time(&self) -> Option<DateTime<Utc>> {
self.date_time self.calc_date_time((self.ms_of_day % 1000) as u32)
} }
} }
@ -267,13 +268,15 @@ impl TimeReader for CdsShortTimeProvider {
} }
} }
#[cfg(test)] #[cfg(all(test, feature = "std"))]
mod tests { mod tests {
use super::*; use super::*;
use crate::time::TimestampError::{InvalidTimeCode, OtherPacketError}; use crate::time::TimestampError::{InvalidTimeCode, OtherPacketError};
use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall}; use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall};
use alloc::format; use alloc::format;
use chrono::{Datelike, Timelike}; use chrono::{Datelike, Timelike};
#[cfg(feature = "serde")]
use postcard::{from_bytes, to_allocvec};
#[test] #[test]
fn test_creation() { fn test_creation() {
@ -281,7 +284,6 @@ mod tests {
assert_eq!(ccsds_to_unix_days(0), DAYS_CCSDS_TO_UNIX); assert_eq!(ccsds_to_unix_days(0), DAYS_CCSDS_TO_UNIX);
} }
#[cfg(feature = "std")]
#[test] #[test]
fn test_get_current_time() { fn test_get_current_time() {
let sec_floats = seconds_since_epoch(); let sec_floats = seconds_since_epoch();
@ -429,7 +431,6 @@ mod tests {
assert_eq!(read_stamp.ms_of_day, u32::MAX - 1); assert_eq!(read_stamp.ms_of_day, u32::MAX - 1);
} }
#[cfg(feature = "std")]
#[test] #[test]
fn test_time_now() { fn test_time_now() {
let timestamp_now = CdsShortTimeProvider::from_now().unwrap(); let timestamp_now = CdsShortTimeProvider::from_now().unwrap();
@ -455,7 +456,17 @@ mod tests {
generic_dt_property_equality_check(dt.minute(), compare_stamp.minute(), 0, 59); generic_dt_property_equality_check(dt.minute(), compare_stamp.minute(), 0, 59);
} }
#[cfg(feature = "std")] #[test]
#[cfg(feature = "serde")]
fn test_serialization() {
let stamp_now = CdsShortTimeProvider::from_now().expect("Error retrieving time");
let val = to_allocvec(&stamp_now).expect("Serializing timestamp failed");
assert!(val.len() > 0);
let stamp_deser: CdsShortTimeProvider =
from_bytes(&val).expect("Stamp deserialization failed");
assert_eq!(stamp_deser, stamp_now);
}
fn generic_dt_property_equality_check(first: u32, second: u32, start: u32, end: u32) { fn generic_dt_property_equality_check(first: u32, second: u32, start: u32, end: u32) {
if second < first { if second < first {
assert_eq!(second, start); assert_eq!(second, start);

View File

@ -9,6 +9,7 @@ use crate::{
CCSDS_HEADER_LEN, CCSDS_HEADER_LEN,
}; };
use core::mem::size_of; use core::mem::size_of;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zerocopy::AsBytes; use zerocopy::AsBytes;
@ -21,7 +22,7 @@ pub const PUC_TM_MIN_SEC_HEADER_LEN: usize = 7;
pub const PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA: usize = pub const PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA: usize =
CCSDS_HEADER_LEN + PUC_TM_MIN_SEC_HEADER_LEN + size_of::<CrcType>(); CCSDS_HEADER_LEN + PUC_TM_MIN_SEC_HEADER_LEN + size_of::<CrcType>();
pub trait PusTmSecondaryHeaderT { pub trait GenericPusTmSecondaryHeader {
fn pus_version(&self) -> PusVersion; fn pus_version(&self) -> PusVersion;
fn sc_time_ref_status(&self) -> u8; fn sc_time_ref_status(&self) -> u8;
fn service(&self) -> u8; fn service(&self) -> u8;
@ -31,6 +32,7 @@ pub trait PusTmSecondaryHeaderT {
} }
pub mod zc { pub mod zc {
use super::GenericPusTmSecondaryHeader;
use crate::ecss::{PusError, PusVersion}; use crate::ecss::{PusError, PusVersion};
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16}; use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
@ -76,7 +78,7 @@ pub mod zc {
} }
} }
impl super::PusTmSecondaryHeaderT for PusTmSecHeaderWithoutTimestamp { impl GenericPusTmSecondaryHeader for PusTmSecHeaderWithoutTimestamp {
fn pus_version(&self) -> PusVersion { fn pus_version(&self) -> PusVersion {
PusVersion::try_from(self.pus_version_and_sc_time_ref_status >> 4 & 0b1111) PusVersion::try_from(self.pus_version_and_sc_time_ref_status >> 4 & 0b1111)
.unwrap_or(PusVersion::Invalid) .unwrap_or(PusVersion::Invalid)
@ -104,7 +106,8 @@ pub mod zc {
} }
} }
#[derive(PartialEq, Eq, Serialize, Deserialize, Copy, Clone, Debug)] #[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTmSecondaryHeader<'slice> { pub struct PusTmSecondaryHeader<'slice> {
pus_version: PusVersion, pus_version: PusVersion,
pub sc_time_ref_status: u8, pub sc_time_ref_status: u8,
@ -147,7 +150,7 @@ impl<'slice> PusTmSecondaryHeader<'slice> {
} }
} }
impl PusTmSecondaryHeaderT for PusTmSecondaryHeader<'_> { impl GenericPusTmSecondaryHeader for PusTmSecondaryHeader<'_> {
fn pus_version(&self) -> PusVersion { fn pus_version(&self) -> PusVersion {
self.pus_version self.pus_version
} }
@ -198,14 +201,15 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
/// [postcard](https://docs.rs/postcard/latest/postcard/). /// [postcard](https://docs.rs/postcard/latest/postcard/).
/// ///
/// There is no spare bytes support yet. /// There is no spare bytes support yet.
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Copy, Clone)] #[derive(PartialEq, Eq, Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTm<'slice> { pub struct PusTm<'slice> {
pub sp_header: SpHeader, pub sp_header: SpHeader,
pub sec_header: PusTmSecondaryHeader<'slice>, pub sec_header: PusTmSecondaryHeader<'slice>,
/// If this is set to false, a manual call to [PusTm::calc_own_crc16] or /// If this is set to false, a manual call to [PusTm::calc_own_crc16] or
/// [PusTm::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid. /// [PusTm::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
pub calc_crc_on_serialization: bool, pub calc_crc_on_serialization: bool,
#[serde(skip)] #[cfg_attr(feature = "serde", serde(skip))]
raw_data: Option<&'slice [u8]>, raw_data: Option<&'slice [u8]>,
source_data: Option<&'slice [u8]>, source_data: Option<&'slice [u8]>,
crc16: Option<u16>, crc16: Option<u16>,
@ -452,7 +456,7 @@ impl PusPacket for PusTm<'_> {
} }
//noinspection RsTraitImplementation //noinspection RsTraitImplementation
impl PusTmSecondaryHeaderT for PusTm<'_> { impl GenericPusTmSecondaryHeader for PusTm<'_> {
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;
@ -470,13 +474,13 @@ mod tests {
use crate::SpHeader; use crate::SpHeader;
fn base_ping_reply_full_ctor(time_stamp: &[u8]) -> PusTm { fn base_ping_reply_full_ctor(time_stamp: &[u8]) -> PusTm {
let mut sph = SpHeader::tm(0x123, 0x234, 0).unwrap(); let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &time_stamp); let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &time_stamp);
PusTm::new(&mut sph, tc_header, None, true) PusTm::new(&mut sph, tc_header, None, true)
} }
fn base_hk_reply<'a>(time_stamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> { fn base_hk_reply<'a>(time_stamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> {
let mut sph = SpHeader::tm(0x123, 0x234, 0).unwrap(); let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, &time_stamp); let tc_header = PusTmSecondaryHeader::new_simple(3, 5, &time_stamp);
PusTm::new(&mut sph, tc_header, Some(src_data), true) PusTm::new(&mut sph, tc_header, Some(src_data), true)
} }
@ -548,7 +552,7 @@ mod tests {
#[test] #[test]
fn test_manual_field_update() { fn test_manual_field_update() {
let mut sph = SpHeader::tm(0x123, 0x234, 0).unwrap(); let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, dummy_time_stamp()); let tc_header = PusTmSecondaryHeader::new_simple(17, 2, dummy_time_stamp());
let mut tm = PusTm::new(&mut sph, tc_header, None, false); let mut tm = PusTm::new(&mut sph, tc_header, None, false);
tm.calc_crc_on_serialization = false; tm.calc_crc_on_serialization = false;