41 Commits

Author SHA1 Message Date
3a71b00198 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 20:00:49 +01:00
13be7ca1e7 do not push version just yet
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-04 19:58:53 +01:00
098a534199 bump changelog
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 19:57:51 +01:00
322a56335e add tests for new functionality
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 19:56:21 +01:00
f7c688d8db line break
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 18:26:38 +01:00
54bb4bdaaa new helper functions for CCSDS SP construction
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 18:25:30 +01:00
1969a26f14 bump changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-04 17:22:51 +01:00
d28ea7d4da add docs for new feature
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-04 17:18:10 +01:00
aeb2e806b8 move improvements
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 17:11:44 +01:00
938c4ba770 make serde dependency optional
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 12:17:36 +01:00
97c70bf03b bump changelog and cargo.toml
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-03 15:47:18 +01:00
dc6d726e61 added missing doc_cfg feature for doc_cfg attr
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-03 15:40:36 +01:00
85bfcad111 bump changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-01 01:22:19 +01:00
03d112cbef update spacepackets deps
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-11-30 01:05:37 +01:00
1ec21c1bff use const instead of struct field
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-11-20 18:42:35 +01:00
c750f94fba use non-deprecated API
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-11-19 02:39:25 +01:00
1d6cf3a75d update changelog
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-11-02 11:10:41 +01:00
f8199ca87a better docs 2022-11-02 00:38:59 +01:00
4c1101f65f better naming 2022-11-02 00:36:18 +01:00
38b789ca6d cargo fmt 2022-10-31 00:23:13 +01:00
d391891991 add EcssEnumerationExt trait extension 2022-10-31 00:22:28 +01:00
65e85f20e0 doc cfg support
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-10-26 00:22:56 +02:00
a2673c9870 make ToBeBytes trait public
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-10-23 18:35:56 +02:00
603f688ac3 small clippy fix
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-10-15 19:56:17 +02:00
638e4cda62 bump version
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-13 10:34:31 +02:00
1cc4771a53 cross-ref docs for examples
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-13 10:31:47 +02:00
427b368057 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-13 10:29:09 +02:00
795abc57fa better names
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-13 10:28:20 +02:00
7da7e5329c typos
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-13 10:21:52 +02:00
5631372e58 update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-13 10:18:21 +02:00
28ba4f887d added some auto-conversion
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-13 09:52:59 +02:00
d559646d80 better naming/docs. new const for MAX_SEQ_COUNT 2022-09-13 09:41:21 +02:00
fe1a30327b work on uniform API
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-11 20:50:46 +02:00
94489da003 return usize instead of u8 for byte width
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-06 10:14:23 +02:00
c72c5ad4aa extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
- Add source_data getter for PusTm
- Add std time info updater for CDS short time stamp provider
2022-09-03 20:54:37 +02:00
bb83e67e54 bump changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-03 18:50:21 +02:00
a4e297f0c0 Add new features
- Basic ECSS enumeration support for u8, u16, u32 and u64
- Better names for generic error enums
2022-09-03 18:47:59 +02:00
96d389a651 fix test
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-09-03 16:30:21 +02:00
cc680dba46 timestamp writer should return timestamp error too 2022-09-03 16:28:11 +02:00
42d3487c19 raw accessor function
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-08-28 00:25:22 +02:00
3970061ca1 only allow tests for std envs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-08-20 23:19:38 +02:00
8 changed files with 760 additions and 251 deletions

View File

@ -6,9 +6,54 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased] # [unreleased]
## [v0.1.0] 16.08.2022 ## 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
- Small fix for faulty docs.rs build
# [v0.3.0] 01.12.2022
## Added
- `EcssEnumerationExt` trait which implements `Debug`, `Copy`, `Clone`,
`PartialEq` and `Eq` in addition to `EcssEnumeration`
## Changed
- `EcssEnumeration` trait: Rename `write_to_bytes`
to `write_to_be_bytes`
# [v0.2.0] 13.09.2022
## Added
- Basic support for ECSS enumeration types for u8, u16, u32 and u64
## Changed
- Better names for generic error enumerations: `PacketError` renamed to `ByteConversionError`
- CCSDS module: `ssc` abbreviations fully replaced by better name `seq_count`
- Time module: `CcsdsTimeProvider::date_time` now has `Option<DateTime<Utc>>` as
a returnvalue instead of `DateTime<Utc>`
- `PusTc` and `PusTm`: `new_from_raw_slice` renamed to simpler `from_bytes`
# [v0.1.0] 16.08.2022
Initial release with CCSDS Space Packet Primary Header implementation and basic PUS TC and TM Initial release with CCSDS Space Packet Primary Header implementation and basic PUS TC and TM
implementations. implementations.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "spacepackets" name = "spacepackets"
version = "0.1.0" version = "0.3.1"
edition = "2021" edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "Generic implementations for various CCSDS and ECSS packet standards" description = "Generic implementations for various CCSDS and ECSS packet standards"
@ -12,17 +12,18 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
zerocopy = "0.6.1" zerocopy = "0.6"
crc = "3.0.0" crc = "3.0"
delegate = "0.7.0" delegate = "0.8"
[dependencies.serde] [dependencies.serde]
version = "1.0.142" version = "1.0"
optional = true
default-features = false default-features = false
features = ["derive"] features = ["derive"]
[dependencies.chrono] [dependencies.chrono]
version = "0.4.20" version = "0.4"
default-features = false default-features = false
[dependencies.num-traits] [dependencies.num-traits]
@ -30,9 +31,14 @@ version = "0.2"
default-features = false default-features = false
[dev-dependencies.postcard] [dev-dependencies.postcard]
version = "1.0.1" 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]
all-features = true
rustdoc-args = ["--cfg", "doc_cfg"]

View File

@ -20,11 +20,9 @@ Currently, this includes the following components:
# Features # Features
`spacepackets` supports various runtime environments and is also suitable `spacepackets` supports various runtime environments and is also suitable for `no_std` environments.
for suitable for `no_std` environments. It has several features which may be enabled
for disabled.
It also offers support for [`serde`](https://serde.rs/). The Space Paccket, PUS TM and TC It offers support for [`serde`](https://serde.rs/). The Space Packet, PUS TM and TC
implementations derive the `serde` `Serialize` and `Deserialize` trait. This allows serializing and implementations derive the `serde` `Serialize` and `Deserialize` trait. This allows serializing and
deserializing them with an appropriate `serde` provider like deserializing them with an appropriate `serde` provider like
[`postcard`](https://github.com/jamesmunns/postcard). [`postcard`](https://github.com/jamesmunns/postcard).
@ -35,3 +33,9 @@ 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
You can check the [documentation](https://docs.rs/spacepackets) of individual modules for various
usage examples.

View File

@ -1,8 +1,10 @@
//! Common definitions and helpers required to create PUS TMTC packets according to //! Common definitions and helpers required to create PUS TMTC packets according to
//! [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/) //! [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/)
use crate::{CcsdsPacket, PacketError}; use crate::{ByteConversionError, CcsdsPacket, SizeMissmatch};
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;
@ -12,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,
@ -34,6 +37,24 @@ 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 {
Boolean = 1,
Enumerated = 2,
UnsignedInt = 3,
SignedInt = 4,
Real = 5,
BitString = 6,
OctetString = 7,
CharString = 8,
AbsoluteTime = 9,
RelativeTime = 10,
Deduced = 11,
Packet = 12,
}
#[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),
@ -41,7 +62,13 @@ pub enum PusError {
NoRawData, NoRawData,
/// CRC16 needs to be calculated first /// CRC16 needs to be calculated first
CrcCalculationMissing, CrcCalculationMissing,
PacketError(PacketError), ByteConversionError(ByteConversionError),
}
impl From<ByteConversionError> for PusError {
fn from(e: ByteConversionError) -> Self {
PusError::ByteConversionError(e)
}
} }
pub trait PusPacket: CcsdsPacket { pub trait PusPacket: CcsdsPacket {
@ -135,3 +162,174 @@ macro_rules! sp_header_impls {
pub(crate) use ccsds_impl; pub(crate) use ccsds_impl;
pub(crate) use sp_header_impls; pub(crate) use sp_header_impls;
/// Generic trait for ECSS enumeration which consist of a PFC field denoting their bit length
/// and an unsigned value. The trait makes no assumptions about the actual type of the unsigned
/// value and only requires implementors to implement a function which writes the enumeration into
/// a raw byte format.
pub trait EcssEnumeration {
/// Packet Format Code, which denotes the number of bits of the enumeration
fn pfc(&self) -> u8;
fn byte_width(&self) -> usize {
(self.pfc() / 8) as usize
}
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError>;
}
pub trait EcssEnumerationExt: EcssEnumeration + Debug + Copy + Clone + PartialEq + Eq {}
pub trait ToBeBytes {
type ByteArray: AsRef<[u8]>;
fn to_be_bytes(&self) -> Self::ByteArray;
}
impl ToBeBytes for u8 {
type ByteArray = [u8; 1];
fn to_be_bytes(&self) -> Self::ByteArray {
u8::to_be_bytes(*self)
}
}
impl ToBeBytes for u16 {
type ByteArray = [u8; 2];
fn to_be_bytes(&self) -> Self::ByteArray {
u16::to_be_bytes(*self)
}
}
impl ToBeBytes for u32 {
type ByteArray = [u8; 4];
fn to_be_bytes(&self) -> Self::ByteArray {
u32::to_be_bytes(*self)
}
}
impl ToBeBytes for u64 {
type ByteArray = [u8; 8];
fn to_be_bytes(&self) -> Self::ByteArray {
u64::to_be_bytes(*self)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GenericEcssEnumWrapper<TYPE> {
val: TYPE,
}
impl<TYPE> GenericEcssEnumWrapper<TYPE> {
pub const fn ptc() -> PacketTypeCodes {
PacketTypeCodes::Enumerated
}
pub fn new(val: TYPE) -> Self {
Self { val }
}
}
impl<TYPE: ToBeBytes> EcssEnumeration for GenericEcssEnumWrapper<TYPE> {
fn pfc(&self) -> u8 {
size_of::<TYPE>() as u8 * 8_u8
}
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> {
if buf.len() < self.byte_width() as usize {
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
found: buf.len(),
expected: self.byte_width() as usize,
}));
}
buf[0..self.byte_width() as usize].copy_from_slice(self.val.to_be_bytes().as_ref());
Ok(())
}
}
impl<TYPE: Debug + Copy + Clone + PartialEq + Eq + ToBeBytes> EcssEnumerationExt
for GenericEcssEnumWrapper<TYPE>
{
}
pub type EcssEnumU8 = GenericEcssEnumWrapper<u8>;
pub type EcssEnumU16 = GenericEcssEnumWrapper<u16>;
pub type EcssEnumU32 = GenericEcssEnumWrapper<u32>;
pub type EcssEnumU64 = GenericEcssEnumWrapper<u64>;
#[cfg(test)]
mod tests {
use crate::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU8, EcssEnumeration};
use crate::ByteConversionError;
#[test]
fn test_enum_u8() {
let mut buf = [0, 0, 0];
let my_enum = EcssEnumU8::new(1);
my_enum
.write_to_be_bytes(&mut buf[1..2])
.expect("To byte conversion of u8 failed");
assert_eq!(buf[1], 1);
}
#[test]
fn test_enum_u16() {
let mut buf = [0, 0, 0];
let my_enum = EcssEnumU16::new(0x1f2f);
my_enum
.write_to_be_bytes(&mut buf[1..3])
.expect("To byte conversion of u8 failed");
assert_eq!(buf[1], 0x1f);
assert_eq!(buf[2], 0x2f);
}
#[test]
fn test_slice_u16_too_small() {
let mut buf = [0];
let my_enum = EcssEnumU16::new(0x1f2f);
let res = my_enum.write_to_be_bytes(&mut buf[0..1]);
assert!(res.is_err());
let error = res.unwrap_err();
match error {
ByteConversionError::ToSliceTooSmall(missmatch) => {
assert_eq!(missmatch.expected, 2);
assert_eq!(missmatch.found, 1);
}
_ => {
panic!("Unexpected error {:?}", error);
}
}
}
#[test]
fn test_enum_u32() {
let mut buf = [0, 0, 0, 0, 0];
let my_enum = EcssEnumU32::new(0x1f2f3f4f);
my_enum
.write_to_be_bytes(&mut buf[1..5])
.expect("To byte conversion of u8 failed");
assert_eq!(buf[1], 0x1f);
assert_eq!(buf[2], 0x2f);
assert_eq!(buf[3], 0x3f);
assert_eq!(buf[4], 0x4f);
}
#[test]
fn test_slice_u32_too_small() {
let mut buf = [0, 0, 0, 0, 0];
let my_enum = EcssEnumU32::new(0x1f2f3f4f);
let res = my_enum.write_to_be_bytes(&mut buf[0..3]);
assert!(res.is_err());
let error = res.unwrap_err();
match error {
ByteConversionError::ToSliceTooSmall(missmatch) => {
assert_eq!(missmatch.expected, 4);
assert_eq!(missmatch.found, 3);
}
_ => {
panic!("Unexpected error {:?}", error);
}
}
}
}

View File

@ -14,11 +14,9 @@
//! //!
//! ## Features //! ## Features
//! //!
//! `spacepackets` supports various runtime environments and is also suitable //! `spacepackets` supports various runtime environments and is also suitable for `no_std` environments.
//! for suitable for `no_std` environments. It has several features which may be enabled
//! for disabled.
//! //!
//! It also offers support for [`serde`](https://serde.rs/). The Space Paccket, PUS TM and TC //! It also offers support for [`serde`](https://serde.rs/). The Space Packet, PUS TM and TC
//! implementations derive the `serde` `Serialize` and `Deserialize` trait. This allows serializing and //! implementations derive the `serde` `Serialize` and `Deserialize` trait. This allows serializing and
//! deserializing them with an appropriate `serde` provider like //! deserializing them with an appropriate `serde` provider like
//! [`postcard`](https://github.com/jamesmunns/postcard). //! [`postcard`](https://github.com/jamesmunns/postcard).
@ -29,29 +27,33 @@
//! - [`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]
#![cfg_attr(doc_cfg, feature(doc_cfg))]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
extern crate alloc; extern crate alloc;
#[cfg(feature = "std")] #[cfg(any(feature = "std", test))]
extern crate std; 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;
@ -59,23 +61,30 @@ pub mod tc;
pub mod time; pub mod time;
pub mod tm; pub mod tm;
pub const MAX_APID: u16 = 2u16.pow(11) - 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)]
pub enum PacketError { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
/// The passed slice is too small. Returns the found and expected minimum size pub enum ByteConversionError {
ToBytesSliceTooSmall(SizeMissmatch), /// The passed slice is too small. Returns the passed slice length and expected minimum size
/// The provider buffer it soo small. Returns the found and expected minimum size ToSliceTooSmall(SizeMissmatch),
FromBytesSliceTooSmall(SizeMissmatch), /// The provider buffer is too small. Returns the passed slice length and expected minimum size
FromSliceTooSmall(SizeMissmatch),
/// The [zerocopy] library failed to write to bytes /// The [zerocopy] library failed to write to bytes
ToBytesZeroCopyError, ZeroCopyToError,
FromBytesZeroCopyError, 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,
@ -97,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,
@ -121,28 +131,64 @@ 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(|| 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
/// not be set and false will be returned. The maximum allowed value for the 11-bit field is /// not be set and false will be returned. The maximum allowed value for the 11-bit field is
/// 2047 /// 2047
pub fn set_apid(&mut self, apid: u16) -> bool { pub fn set_apid(&mut self, apid: u16) -> bool {
if apid > 2u16.pow(11) - 1 { if apid > MAX_APID {
return false; return false;
} }
self.apid = apid; self.apid = apid;
@ -168,35 +214,48 @@ 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 {
pub fn new(seq_flags: SequenceFlags, ssc: u16) -> Option<PacketSequenceCtrl> { /// const variant of [PacketSequenceCtrl::new], but panics if the sequence count exceeds
let mut psc = PacketSequenceCtrl { /// [MAX_SEQ_COUNT].
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(ssc).then(|| 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 > 2u16.pow(14) - 1 { if ssc > MAX_SEQ_COUNT {
return false; return false;
} }
self.seq_count = ssc; self.seq_count = ssc;
true true
} }
pub fn ssc(&self) -> u16 { pub fn seq_count(&self) -> u16 {
self.seq_count self.seq_count
} }
} }
@ -228,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;
@ -242,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()
@ -253,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
@ -271,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
@ -305,65 +364,114 @@ 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,
pub psc: PacketSequenceCtrl, pub psc: PacketSequenceCtrl,
pub data_len: u16, pub data_len: u16,
} }
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 {
pub fn new( pub const fn new(packet_id: PacketId, psc: PacketSequenceCtrl, data_len: u16) -> Self {
Self {
version: 0,
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,
ssc: 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,
data_len: u16, data_len: u16,
) -> Option<Self> { ) -> Option<Self> {
if ssc > 2u16.pow(14) - 1 || apid > 2u16.pow(11) - 1 { 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 = ssc;
header.data_len = data_len;
Some(header)
} }
pub fn tm(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> { /// Helper function for telemetry space packet headers. The packet type field will be
Self::new(PacketType::Tm, false, apid, seq_count, data_len) /// set accordingly. The secondary header flag field is set to false.
pub fn tm(apid: u16, seq_flags: SequenceFlags, seq_count: u16, data_len: u16) -> Option<Self> {
Self::new_from_single_fields(PacketType::Tm, false, apid, seq_flags, seq_count, data_len)
} }
pub fn tc(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> { /// Helper function for telemetry space packet headers. The packet type field will be
Self::new(PacketType::Tc, false, apid, seq_count, data_len) /// set accordingly. The secondary header flag field is set to false.
pub fn tc(apid: u16, seq_flags: SequenceFlags, seq_count: u16, data_len: u16) -> Option<Self> {
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
@ -391,15 +499,15 @@ impl SpHeader {
self.packet_id.ptype = packet_type; self.packet_id.ptype = packet_type;
} }
pub fn from_raw_slice(buf: &[u8]) -> Result<Self, PacketError> { pub fn from_raw_slice(buf: &[u8]) -> Result<Self, ByteConversionError> {
if buf.len() < CCSDS_HEADER_LEN + 1 { if buf.len() < CCSDS_HEADER_LEN + 1 {
return Err(PacketError::FromBytesSliceTooSmall(SizeMissmatch { return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
found: buf.len(), found: buf.len(),
expected: CCSDS_HEADER_LEN + 1, expected: CCSDS_HEADER_LEN + 1,
})); }));
} }
let zc_header = zc::SpHeader::from_bytes(&buf[0..CCSDS_HEADER_LEN]) let zc_header = zc::SpHeader::from_bytes(&buf[0..CCSDS_HEADER_LEN])
.ok_or(PacketError::FromBytesZeroCopyError)?; .ok_or(ByteConversionError::ZeroCopyFromError)?;
Ok(Self::from(zc_header)) Ok(Self::from(zc_header))
} }
} }
@ -529,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() {
@ -628,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());
@ -652,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());
@ -683,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);

118
src/tc.rs
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);
@ -20,13 +20,13 @@
//! // Serialize TC into a raw buffer //! // Serialize TC into a raw buffer
//! let mut test_buf: [u8; 32] = [0; 32]; //! let mut test_buf: [u8; 32] = [0; 32];
//! let size = pus_tc //! let size = pus_tc
//! .write_to(test_buf.as_mut_slice()) //! .write_to_bytes(test_buf.as_mut_slice())
//! .expect("Error writing TC to buffer"); //! .expect("Error writing TC to buffer");
//! assert_eq!(size, 13); //! assert_eq!(size, 13);
//! println!("{:?}", &test_buf[0..size]); //! println!("{:?}", &test_buf[0..size]);
//! //!
//! // Deserialize from the raw byte representation //! // Deserialize from the raw byte representation
//! let pus_tc_deserialized = PusTc::new_from_raw_slice(&test_buf).expect("Deserialization failed"); //! let pus_tc_deserialized = PusTc::from_bytes(&test_buf).expect("Deserialization failed");
//! assert_eq!(pus_tc.service(), 17); //! assert_eq!(pus_tc.service(), 17);
//! assert_eq!(pus_tc.subservice(), 1); //! assert_eq!(pus_tc.subservice(), 1);
//! assert_eq!(pus_tc.apid(), 0x02); //! assert_eq!(pus_tc.apid(), 0x02);
@ -36,9 +36,12 @@ use crate::ecss::{
verify_crc16_from_raw, CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE, verify_crc16_from_raw, CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE,
}; };
use crate::SpHeader; use crate::SpHeader;
use crate::{CcsdsPacket, PacketError, PacketType, SequenceFlags, SizeMissmatch, CCSDS_HEADER_LEN}; use crate::{
ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SizeMissmatch, CCSDS_HEADER_LEN,
};
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;
@ -64,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;
@ -74,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)]
@ -101,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)
} }
@ -124,7 +127,7 @@ pub mod zc {
} }
impl PusTcSecondaryHeader { impl PusTcSecondaryHeader {
pub fn to_bytes(&self, slice: &mut [u8]) -> Option<()> { pub fn write_to_bytes(&self, slice: &mut [u8]) -> Option<()> {
self.write_to(slice) self.write_to(slice)
} }
@ -134,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,
@ -143,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
} }
@ -209,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>,
@ -258,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,
@ -301,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;
@ -321,35 +326,34 @@ 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();
} }
/// Write the raw PUS byte representation to a provided buffer. /// Write the raw PUS byte representation to a provided buffer.
pub fn write_to(&self, slice: &mut [u8]) -> Result<usize, PusError> { pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = 0; let mut curr_idx = 0;
let sph_zc = crate::zc::SpHeader::from(self.sp_header); let sph_zc = crate::zc::SpHeader::from(self.sp_header);
let tc_header_len = size_of::<zc::PusTcSecondaryHeader>(); let tc_header_len = size_of::<zc::PusTcSecondaryHeader>();
let total_size = self.len_packed(); let total_size = self.len_packed();
if total_size > slice.len() { if total_size > slice.len() {
return Err(PusError::PacketError(PacketError::ToBytesSliceTooSmall( return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
SizeMissmatch {
found: slice.len(), found: slice.len(),
expected: total_size, expected: total_size,
}, })
))); .into());
} }
sph_zc sph_zc
.to_bytes(&mut slice[curr_idx..curr_idx + CCSDS_HEADER_LEN]) .to_bytes(&mut slice[curr_idx..curr_idx + CCSDS_HEADER_LEN])
.ok_or(PusError::PacketError(PacketError::ToBytesZeroCopyError))?; .ok_or(ByteConversionError::ZeroCopyToError)?;
curr_idx += CCSDS_HEADER_LEN; curr_idx += CCSDS_HEADER_LEN;
let sec_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap(); let sec_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
sec_header sec_header
.to_bytes(&mut slice[curr_idx..curr_idx + tc_header_len]) .write_to_bytes(&mut slice[curr_idx..curr_idx + tc_header_len])
.ok_or(PusError::PacketError(PacketError::ToBytesZeroCopyError))?; .ok_or(ByteConversionError::ZeroCopyToError)?;
curr_idx += tc_header_len; curr_idx += tc_header_len;
if let Some(app_data) = self.app_data { if let Some(app_data) = self.app_data {
@ -369,6 +373,7 @@ impl<'slice> PusTc<'slice> {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> { pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
let sph_zc = crate::zc::SpHeader::from(self.sp_header); let sph_zc = crate::zc::SpHeader::from(self.sp_header);
let mut appended_len = PUS_TC_MIN_LEN_WITHOUT_APP_DATA; let mut appended_len = PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
@ -399,8 +404,8 @@ 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 new_from_raw_slice(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 {
return Err(PusError::RawDataTooShort(raw_data_len)); return Err(PusError::RawDataTooShort(raw_data_len));
@ -408,16 +413,16 @@ impl<'slice> PusTc<'slice> {
let mut current_idx = 0; let mut current_idx = 0;
let sph = let sph =
crate::zc::SpHeader::from_bytes(&slice[current_idx..current_idx + CCSDS_HEADER_LEN]) crate::zc::SpHeader::from_bytes(&slice[current_idx..current_idx + CCSDS_HEADER_LEN])
.ok_or(PusError::PacketError(PacketError::FromBytesZeroCopyError))?; .ok_or(ByteConversionError::ZeroCopyFromError)?;
current_idx += CCSDS_HEADER_LEN; current_idx += CCSDS_HEADER_LEN;
let total_len = sph.total_len(); let total_len = sph.total_len();
if raw_data_len < total_len || total_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA { if raw_data_len < total_len || total_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
return Err(PusError::RawDataTooShort(raw_data_len)); return Err(PusError::RawDataTooShort(raw_data_len));
} }
let sec_header = crate::tc::zc::PusTcSecondaryHeader::from_bytes( let sec_header = zc::PusTcSecondaryHeader::from_bytes(
&slice[current_idx..current_idx + PUC_TC_SECONDARY_HEADER_LEN], &slice[current_idx..current_idx + PUC_TC_SECONDARY_HEADER_LEN],
) )
.ok_or(PusError::PacketError(PacketError::FromBytesZeroCopyError))?; .ok_or(ByteConversionError::ZeroCopyFromError)?;
current_idx += PUC_TC_SECONDARY_HEADER_LEN; current_idx += PUC_TC_SECONDARY_HEADER_LEN;
let raw_data = &slice[0..total_len]; let raw_data = &slice[0..total_len];
let pus_tc = PusTc { let pus_tc = PusTc {
@ -431,6 +436,10 @@ impl<'slice> PusTc<'slice> {
verify_crc16_from_raw(raw_data, pus_tc.crc16.expect("CRC16 invalid"))?; verify_crc16_from_raw(raw_data, pus_tc.crc16.expect("CRC16 invalid"))?;
Ok((pus_tc, total_len)) Ok((pus_tc, total_len))
} }
pub fn raw(&self) -> Option<&'slice [u8]> {
self.raw_data
}
} }
//noinspection RsTraitImplementation //noinspection RsTraitImplementation
@ -456,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;
@ -466,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::{CcsdsPacket, SequenceFlags}; use crate::{CcsdsPacket, SequenceFlags};
use crate::{PacketError, SpHeader};
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)
} }
@ -504,7 +513,7 @@ mod tests {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc let size = pus_tc
.write_to(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
assert_eq!(size, 13); assert_eq!(size, 13);
} }
@ -514,11 +523,11 @@ mod tests {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc let size = pus_tc
.write_to(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
assert_eq!(size, 13); assert_eq!(size, 13);
let (tc_from_raw, size) = PusTc::new_from_raw_slice(&test_buf) let (tc_from_raw, size) =
.expect("Creating PUS TC struct from raw buffer failed"); PusTc::from_bytes(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(size, 13); assert_eq!(size, 13);
verify_test_tc(&tc_from_raw, false, 13); verify_test_tc(&tc_from_raw, false, 13);
assert!(tc_from_raw.user_data().is_none()); assert!(tc_from_raw.user_data().is_none());
@ -528,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);
@ -540,11 +549,11 @@ 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]);
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc let size = pus_tc
.write_to(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
assert_eq!(size, 16); assert_eq!(size, 16);
let (tc_from_raw, size) = PusTc::new_from_raw_slice(&test_buf) let (tc_from_raw, size) =
.expect("Creating PUS TC struct from raw buffer failed"); PusTc::from_bytes(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(size, 16); assert_eq!(size, 16);
verify_test_tc(&tc_from_raw, true, 16); verify_test_tc(&tc_from_raw, true, 16);
let user_data = tc_from_raw.user_data().unwrap(); let user_data = tc_from_raw.user_data().unwrap();
@ -554,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();
@ -571,10 +579,10 @@ mod tests {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
pus_tc pus_tc
.write_to(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
test_buf[12] = 0; test_buf[12] = 0;
let res = PusTc::new_from_raw_slice(&test_buf); let res = PusTc::from_bytes(&test_buf);
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();
assert!(matches!(err, PusError::IncorrectCrc { .. })); assert!(matches!(err, PusError::IncorrectCrc { .. }));
@ -587,7 +595,7 @@ mod tests {
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
pus_tc.calc_own_crc16(); pus_tc.calc_own_crc16();
pus_tc pus_tc
.write_to(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
verify_test_tc_raw(&test_buf); verify_test_tc_raw(&test_buf);
verify_crc_no_app_data(&test_buf); verify_crc_no_app_data(&test_buf);
@ -598,7 +606,7 @@ mod tests {
let mut pus_tc = base_ping_tc_simple_ctor(); let mut pus_tc = base_ping_tc_simple_ctor();
pus_tc.calc_crc_on_serialization = false; pus_tc.calc_crc_on_serialization = false;
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
let res = pus_tc.write_to(test_buf.as_mut_slice()); let res = pus_tc.write_to_bytes(test_buf.as_mut_slice());
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();
assert!(matches!(err, PusError::CrcCalculationMissing { .. })); assert!(matches!(err, PusError::CrcCalculationMissing { .. }));
@ -619,15 +627,15 @@ 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(test_buf.as_mut_slice()); let res = pus_tc.write_to_bytes(test_buf.as_mut_slice());
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();
match err { match err {
PusError::PacketError(err) => match err { PusError::ByteConversionError(err) => match err {
PacketError::ToBytesSliceTooSmall(missmatch) => { ByteConversionError::ToSliceTooSmall(missmatch) => {
assert_eq!(missmatch.expected, pus_tc.len_packed()); assert_eq!(missmatch.expected, pus_tc.len_packed());
assert_eq!(missmatch.found, 12); assert_eq!(missmatch.found, 12);
} }
@ -643,7 +651,7 @@ mod tests {
verify_test_tc(&pus_tc, true, 16); verify_test_tc(&pus_tc, true, 16);
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc let size = pus_tc
.write_to(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
assert_eq!(test_buf[11], 1); assert_eq!(test_buf[11], 1);
assert_eq!(test_buf[12], 2); assert_eq!(test_buf[12], 2);
@ -667,7 +675,7 @@ mod tests {
assert_eq!(pus_tc.sequence_flags(), SequenceFlags::Unsegmented); assert_eq!(pus_tc.sequence_flags(), SequenceFlags::Unsegmented);
pus_tc.calc_own_crc16(); pus_tc.calc_own_crc16();
pus_tc pus_tc
.write_to(test_buf.as_mut_slice()) .write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
assert_eq!(test_buf[0], 0x1f); assert_eq!(test_buf[0], 0x1f);
assert_eq!(test_buf[1], 0xff); assert_eq!(test_buf[1], 0xff);
@ -692,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

@ -1,6 +1,6 @@
//! 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::{PacketError, SizeMissmatch}; use crate::{ByteConversionError, SizeMissmatch};
use chrono::{DateTime, TimeZone, Utc}; use chrono::{DateTime, LocalResult, TimeZone, Utc};
#[allow(unused_imports)] #[allow(unused_imports)]
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
@ -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,6 +27,8 @@ pub enum CcsdsTimeCodes {
Ccs = 0b101, Ccs = 0b101,
} }
const CDS_SHORT_P_FIELD: u8 = (Cds as u8) << 4;
impl TryFrom<u8> for CcsdsTimeCodes { impl TryFrom<u8> for CcsdsTimeCodes {
type Error = (); type Error = ();
@ -38,15 +44,17 @@ impl TryFrom<u8> for CcsdsTimeCodes {
} }
} }
#[derive(Debug, PartialEq, Eq)] #[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
InvalidTimeCode(CcsdsTimeCodes, u8), InvalidTimeCode(CcsdsTimeCodes, u8),
OtherPacketError(PacketError), OtherPacketError(ByteConversionError),
} }
#[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)
@ -71,7 +79,7 @@ pub const fn ccsds_to_unix_days(ccsds_days: i32) -> i32 {
} }
pub trait TimeWriter { pub trait TimeWriter {
fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<(), PacketError>; fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<(), TimestampError>;
} }
pub trait TimeReader { pub trait TimeReader {
@ -80,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;
@ -91,26 +99,38 @@ pub trait CcsdsTimeProvider {
fn p_field(&self) -> (usize, [u8; 2]); fn p_field(&self) -> (usize, [u8; 2]);
fn ccdsd_time_code(&self) -> CcsdsTimeCodes; fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
fn unix_seconds(&self) -> i64; fn unix_seconds(&self) -> i64;
fn date_time(&self) -> DateTime<Utc>; fn date_time(&self) -> Option<DateTime<Utc>>;
} }
#[derive(Debug, Copy, Clone)] /// This object is the abstraction for the CCSDS Day Segmented Time Code (CDS).
///
/// It has the capability to generate and read timestamps as specified in the CCSDS 301.0-B-4
/// section 3.3
///
/// # Example
///
/// ```
/// use spacepackets::time::{CdsShortTimeProvider, TimeWriter};
/// use spacepackets::time::CcsdsTimeCodes::Cds;
/// let timestamp_now = CdsShortTimeProvider::from_now().unwrap();
/// let mut raw_stamp = [0; 7];
/// timestamp_now.write_to_bytes(&mut raw_stamp).unwrap();
/// assert_eq!((raw_stamp[0] >> 4) & 0b111, Cds as u8);
/// ```
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CdsShortTimeProvider { pub struct CdsShortTimeProvider {
pfield: u8,
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 {
pub fn new(ccsds_days: u16, ms_of_day: u32) -> Self { pub fn new(ccsds_days: u16, ms_of_day: u32) -> Self {
let provider = Self { let provider = Self {
pfield: (CcsdsTimeCodes::Cds as u8) << 4,
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;
@ -118,6 +138,7 @@ impl CdsShortTimeProvider {
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn from_now() -> Result<Self, SystemTimeError> { 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();
@ -125,23 +146,33 @@ impl CdsShortTimeProvider {
let unix_days_seconds = epoch - secs_of_day; let unix_days_seconds = epoch - secs_of_day;
let ms_of_day = secs_of_day * 1000 + now.subsec_millis() as u64; let ms_of_day = secs_of_day * 1000 + now.subsec_millis() as u64;
let provider = Self { let provider = Self {
pfield: (CcsdsTimeCodes::Cds as u8) << 4,
ccsds_days: unix_to_ccsds_days((unix_days_seconds / SECONDS_PER_DAY as u64) as i32) ccsds_days: unix_to_ccsds_days((unix_days_seconds / SECONDS_PER_DAY as u64) as i32)
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))
} }
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn update_from_now(&mut self) -> Result<(), SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let epoch = now.as_secs();
let secs_of_day = epoch % SECONDS_PER_DAY as u64;
let unix_days_seconds = epoch - secs_of_day;
let ms_of_day = secs_of_day * 1000 + now.subsec_millis() as u64;
self.setup(unix_days_seconds as i64, ms_of_day);
Ok(())
}
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
} }
#[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 {
Self::ms_of_day(seconds_since_epoch()) Self::ms_of_day(seconds_since_epoch())
} }
@ -164,10 +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;
self.date_time = Some(Utc.timestamp(self.unix_seconds, ns_since_last_sec)); if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_sec) {
return Some(val);
}
None
} }
} }
@ -177,31 +211,33 @@ impl CcsdsTimeProvider for CdsShortTimeProvider {
} }
fn p_field(&self) -> (usize, [u8; 2]) { fn p_field(&self) -> (usize, [u8; 2]) {
(1, [self.pfield, 0]) (1, [CDS_SHORT_P_FIELD, 0])
} }
fn ccdsd_time_code(&self) -> CcsdsTimeCodes { fn ccdsd_time_code(&self) -> CcsdsTimeCodes {
CcsdsTimeCodes::Cds Cds
} }
fn unix_seconds(&self) -> i64 { fn unix_seconds(&self) -> i64 {
self.unix_seconds self.unix_seconds
} }
fn date_time(&self) -> DateTime<Utc> { fn date_time(&self) -> Option<DateTime<Utc>> {
self.date_time.expect("Invalid date time") self.calc_date_time((self.ms_of_day % 1000) as u32)
} }
} }
impl TimeWriter for CdsShortTimeProvider { impl TimeWriter for CdsShortTimeProvider {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<(), PacketError> { fn write_to_bytes(&self, buf: &mut [u8]) -> Result<(), TimestampError> {
if buf.len() < self.len_as_bytes() { if buf.len() < self.len_as_bytes() {
return Err(PacketError::ToBytesSliceTooSmall(SizeMissmatch { return Err(TimestampError::OtherPacketError(
ByteConversionError::ToSliceTooSmall(SizeMissmatch {
expected: self.len_as_bytes(), expected: self.len_as_bytes(),
found: buf.len(), found: buf.len(),
})); }),
));
} }
buf[0] = self.pfield; buf[0] = CDS_SHORT_P_FIELD;
buf[1..3].copy_from_slice(self.ccsds_days.to_be_bytes().as_slice()); buf[1..3].copy_from_slice(self.ccsds_days.to_be_bytes().as_slice());
buf[3..7].copy_from_slice(self.ms_of_day.to_be_bytes().as_slice()); buf[3..7].copy_from_slice(self.ms_of_day.to_be_bytes().as_slice());
Ok(()) Ok(())
@ -212,7 +248,7 @@ impl TimeReader for CdsShortTimeProvider {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> { fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> {
if buf.len() < CDS_SHORT_LEN { if buf.len() < CDS_SHORT_LEN {
return Err(TimestampError::OtherPacketError( return Err(TimestampError::OtherPacketError(
PacketError::FromBytesSliceTooSmall(SizeMissmatch { ByteConversionError::FromSliceTooSmall(SizeMissmatch {
expected: CDS_SHORT_LEN, expected: CDS_SHORT_LEN,
found: buf.len(), found: buf.len(),
}), }),
@ -222,19 +258,9 @@ impl TimeReader for CdsShortTimeProvider {
match CcsdsTimeCodes::try_from(pfield >> 4 & 0b111) { match CcsdsTimeCodes::try_from(pfield >> 4 & 0b111) {
Ok(cds_type) => match cds_type { Ok(cds_type) => match cds_type {
Cds => (), Cds => (),
_ => { _ => return Err(TimestampError::InvalidTimeCode(Cds, cds_type as u8)),
return Err(TimestampError::InvalidTimeCode(
CcsdsTimeCodes::Cds,
cds_type as u8,
))
}
}, },
_ => { _ => return Err(TimestampError::InvalidTimeCode(Cds, pfield >> 4 & 0b111)),
return Err(TimestampError::InvalidTimeCode(
CcsdsTimeCodes::Cds,
pfield >> 4 & 0b111,
))
}
}; };
let ccsds_days: u16 = u16::from_be_bytes(buf[1..3].try_into().unwrap()); let ccsds_days: u16 = u16::from_be_bytes(buf[1..3].try_into().unwrap());
let ms_of_day: u32 = u32::from_be_bytes(buf[3..7].try_into().unwrap()); let ms_of_day: u32 = u32::from_be_bytes(buf[3..7].try_into().unwrap());
@ -242,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::PacketError::{FromBytesSliceTooSmall, ToBytesSliceTooSmall}; 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() {
@ -256,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();
@ -270,12 +297,9 @@ mod tests {
time_stamper.unix_seconds(), time_stamper.unix_seconds(),
(DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64 (DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64
); );
assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds); assert_eq!(time_stamper.ccdsd_time_code(), Cds);
assert_eq!( assert_eq!(time_stamper.p_field(), (1, [(Cds as u8) << 4, 0]));
time_stamper.p_field(), let date_time = time_stamper.date_time().unwrap();
(1, [(CcsdsTimeCodes::Cds as u8) << 4, 0])
);
let date_time = time_stamper.date_time();
assert_eq!(date_time.year(), 1958); assert_eq!(date_time.year(), 1958);
assert_eq!(date_time.month(), 1); assert_eq!(date_time.month(), 1);
assert_eq!(date_time.day(), 1); assert_eq!(date_time.day(), 1);
@ -288,7 +312,7 @@ mod tests {
fn test_time_stamp_unix_epoch() { fn test_time_stamp_unix_epoch() {
let time_stamper = CdsShortTimeProvider::new((-DAYS_CCSDS_TO_UNIX) as u16, 0); let time_stamper = CdsShortTimeProvider::new((-DAYS_CCSDS_TO_UNIX) as u16, 0);
assert_eq!(time_stamper.unix_seconds(), 0); assert_eq!(time_stamper.unix_seconds(), 0);
let date_time = time_stamper.date_time(); let date_time = time_stamper.date_time().unwrap();
assert_eq!(date_time.year(), 1970); assert_eq!(date_time.year(), 1970);
assert_eq!(date_time.month(), 1); assert_eq!(date_time.month(), 1);
assert_eq!(date_time.day(), 1); assert_eq!(date_time.day(), 1);
@ -334,7 +358,7 @@ mod tests {
let res = time_stamper.write_to_bytes(&mut buf[0..i]); let res = time_stamper.write_to_bytes(&mut buf[0..i]);
assert!(res.is_err()); assert!(res.is_err());
match res.unwrap_err() { match res.unwrap_err() {
ToBytesSliceTooSmall(missmatch) => { OtherPacketError(ToSliceTooSmall(missmatch)) => {
assert_eq!(missmatch.found, i); assert_eq!(missmatch.found, i);
assert_eq!(missmatch.expected, 7); assert_eq!(missmatch.expected, 7);
} }
@ -357,7 +381,7 @@ mod tests {
panic!("Unexpected error"); panic!("Unexpected error");
} }
OtherPacketError(e) => match e { OtherPacketError(e) => match e {
FromBytesSliceTooSmall(missmatch) => { FromSliceTooSmall(missmatch) => {
assert_eq!(missmatch.found, i); assert_eq!(missmatch.found, i);
assert_eq!(missmatch.expected, 7); assert_eq!(missmatch.expected, 7);
} }
@ -379,7 +403,7 @@ mod tests {
let err = res.unwrap_err(); let err = res.unwrap_err();
match err { match err {
InvalidTimeCode(code, raw) => { InvalidTimeCode(code, raw) => {
assert_eq!(code, CcsdsTimeCodes::Cds); assert_eq!(code, Cds);
assert_eq!(raw, 0); assert_eq!(raw, 0);
} }
OtherPacketError(_) => {} OtherPacketError(_) => {}
@ -407,12 +431,11 @@ 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();
let compare_stamp = Utc::now(); let compare_stamp = Utc::now();
let dt = timestamp_now.date_time(); let dt = timestamp_now.date_time().unwrap();
if compare_stamp.year() > dt.year() { if compare_stamp.year() > dt.year() {
assert_eq!(compare_stamp.year() - dt.year(), 1); assert_eq!(compare_stamp.year() - dt.year(), 1);
} else { } else {
@ -433,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

@ -5,9 +5,11 @@ use crate::ecss::{
verify_crc16_from_raw, CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE, verify_crc16_from_raw, CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE,
}; };
use crate::{ use crate::{
CcsdsPacket, PacketError, PacketType, SequenceFlags, SizeMissmatch, SpHeader, CCSDS_HEADER_LEN, ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SizeMissmatch, SpHeader,
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;
@ -20,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;
@ -30,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};
@ -66,7 +69,7 @@ pub mod zc {
} }
impl PusTmSecHeaderWithoutTimestamp { impl PusTmSecHeaderWithoutTimestamp {
pub fn to_bytes(&self, slice: &mut [u8]) -> Option<()> { pub fn write_to_bytes(&self, slice: &mut [u8]) -> Option<()> {
self.write_to(slice) self.write_to(slice)
} }
@ -75,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)
@ -103,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,
@ -146,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
} }
@ -197,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>,
@ -258,6 +263,10 @@ impl<'slice> PusTm<'slice> {
self.sec_header.time_stamp self.sec_header.time_stamp
} }
pub fn source_data(&self) -> Option<&'slice [u8]> {
self.source_data
}
pub fn set_dest_id(&mut self, dest_id: u16) { pub fn set_dest_id(&mut self, dest_id: u16) {
self.sec_header.dest_id = dest_id; self.sec_header.dest_id = dest_id;
} }
@ -304,28 +313,27 @@ impl<'slice> PusTm<'slice> {
} }
/// Write the raw PUS byte representation to a provided buffer. /// Write the raw PUS byte representation to a provided buffer.
pub fn write_to(&self, slice: &mut [u8]) -> Result<usize, PusError> { pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = 0; let mut curr_idx = 0;
let sph_zc = crate::zc::SpHeader::from(self.sp_header); let sph_zc = crate::zc::SpHeader::from(self.sp_header);
let total_size = self.len_packed(); let total_size = self.len_packed();
if total_size > slice.len() { if total_size > slice.len() {
return Err(PusError::PacketError(PacketError::ToBytesSliceTooSmall( return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
SizeMissmatch {
found: slice.len(), found: slice.len(),
expected: total_size, expected: total_size,
}, })
))); .into());
} }
sph_zc sph_zc
.to_bytes(&mut slice[curr_idx..curr_idx + CCSDS_HEADER_LEN]) .to_bytes(&mut slice[curr_idx..curr_idx + CCSDS_HEADER_LEN])
.ok_or(PusError::PacketError(PacketError::ToBytesZeroCopyError))?; .ok_or(ByteConversionError::ZeroCopyToError)?;
curr_idx += CCSDS_HEADER_LEN; curr_idx += CCSDS_HEADER_LEN;
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>(); let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap(); let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
sec_header sec_header
.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(PusError::PacketError(PacketError::ToBytesZeroCopyError))?; .ok_or(ByteConversionError::ZeroCopyToError)?;
curr_idx += sec_header_len; curr_idx += sec_header_len;
let timestamp_len = self.sec_header.time_stamp.len(); let timestamp_len = self.sec_header.time_stamp.len();
slice[curr_idx..curr_idx + timestamp_len].copy_from_slice(self.sec_header.time_stamp); slice[curr_idx..curr_idx + timestamp_len].copy_from_slice(self.sec_header.time_stamp);
@ -348,6 +356,7 @@ impl<'slice> PusTm<'slice> {
/// Append the raw PUS byte representation to a provided [alloc::vec::Vec] /// Append the raw PUS byte representation to a provided [alloc::vec::Vec]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> { pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
let sph_zc = crate::zc::SpHeader::from(self.sp_header); let sph_zc = crate::zc::SpHeader::from(self.sp_header);
let mut appended_len = let mut appended_len =
@ -383,7 +392,7 @@ impl<'slice> PusTm<'slice> {
/// Create a [PusTm] instance from a raw slice. On success, it returns a tuple containing /// 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 /// the instance and the found byte length of the packet. The timestamp length needs to be
/// known beforehand. /// known beforehand.
pub fn new_from_raw_slice( pub fn from_bytes(
slice: &'slice [u8], slice: &'slice [u8],
timestamp_len: usize, timestamp_len: usize,
) -> Result<(Self, usize), PusError> { ) -> Result<(Self, usize), PusError> {
@ -394,7 +403,7 @@ impl<'slice> PusTm<'slice> {
let mut current_idx = 0; let mut current_idx = 0;
let sph = let sph =
crate::zc::SpHeader::from_bytes(&slice[current_idx..current_idx + CCSDS_HEADER_LEN]) crate::zc::SpHeader::from_bytes(&slice[current_idx..current_idx + CCSDS_HEADER_LEN])
.ok_or(PusError::PacketError(PacketError::FromBytesZeroCopyError))?; .ok_or(ByteConversionError::ZeroCopyFromError)?;
current_idx += 6; current_idx += 6;
let total_len = sph.total_len(); let total_len = sph.total_len();
if raw_data_len < total_len || total_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA { if raw_data_len < total_len || total_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
@ -403,7 +412,7 @@ impl<'slice> PusTm<'slice> {
let sec_header_zc = zc::PusTmSecHeaderWithoutTimestamp::from_bytes( let sec_header_zc = zc::PusTmSecHeaderWithoutTimestamp::from_bytes(
&slice[current_idx..current_idx + PUC_TM_MIN_SEC_HEADER_LEN], &slice[current_idx..current_idx + PUC_TM_MIN_SEC_HEADER_LEN],
) )
.ok_or(PusError::PacketError(PacketError::FromBytesZeroCopyError))?; .ok_or(ByteConversionError::ZeroCopyFromError)?;
current_idx += PUC_TM_MIN_SEC_HEADER_LEN; current_idx += PUC_TM_MIN_SEC_HEADER_LEN;
let zc_sec_header_wrapper = zc::PusTmSecHeader { let zc_sec_header_wrapper = zc::PusTmSecHeader {
zc_header: sec_header_zc, zc_header: sec_header_zc,
@ -447,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;
@ -465,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)
} }
@ -492,7 +501,9 @@ mod tests {
let time_stamp = dummy_time_stamp(); let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp); let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let mut buf: [u8; 32] = [0; 32]; let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm.write_to(&mut buf).expect("Serialization failed"); let ser_len = pus_tm
.write_to_bytes(&mut buf)
.expect("Serialization failed");
assert_eq!(ser_len, 22); assert_eq!(ser_len, 22);
verify_raw_ping_reply(&buf); verify_raw_ping_reply(&buf);
} }
@ -502,7 +513,9 @@ mod tests {
let src_data = [1, 2, 3]; let src_data = [1, 2, 3];
let hk_reply = base_hk_reply(dummy_time_stamp(), &src_data); let hk_reply = base_hk_reply(dummy_time_stamp(), &src_data);
let mut buf: [u8; 32] = [0; 32]; let mut buf: [u8; 32] = [0; 32];
let ser_len = hk_reply.write_to(&mut buf).expect("Serialization failed"); let ser_len = hk_reply
.write_to_bytes(&mut buf)
.expect("Serialization failed");
assert_eq!(ser_len, 25); assert_eq!(ser_len, 25);
assert_eq!(buf[20], 1); assert_eq!(buf[20], 1);
assert_eq!(buf[21], 2); assert_eq!(buf[21], 2);
@ -528,29 +541,30 @@ mod tests {
let time_stamp = dummy_time_stamp(); let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp); let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let mut buf: [u8; 32] = [0; 32]; let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm.write_to(&mut buf).expect("Serialization failed"); let ser_len = pus_tm
.write_to_bytes(&mut buf)
.expect("Serialization failed");
assert_eq!(ser_len, 22); assert_eq!(ser_len, 22);
let (tm_deserialized, size) = let (tm_deserialized, size) = PusTm::from_bytes(&buf, 7).expect("Deserialization failed");
PusTm::new_from_raw_slice(&buf, 7).expect("Deserialization failed");
assert_eq!(ser_len, size); assert_eq!(ser_len, size);
verify_ping_reply(&tm_deserialized, false, 22, dummy_time_stamp()); verify_ping_reply(&tm_deserialized, false, 22, dummy_time_stamp());
} }
#[test] #[test]
fn test_manual_field_update() { fn test_manual_field_update() {
let mut sph = SpHeader::tm(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;
assert_eq!(tm.data_len(), 0x00); assert_eq!(tm.data_len(), 0x00);
let mut buf: [u8; 32] = [0; 32]; let mut buf: [u8; 32] = [0; 32];
let res = tm.write_to(&mut buf); let res = tm.write_to_bytes(&mut buf);
assert!(res.is_err()); assert!(res.is_err());
assert!(matches!(res.unwrap_err(), PusError::CrcCalculationMissing)); assert!(matches!(res.unwrap_err(), PusError::CrcCalculationMissing));
tm.update_ccsds_data_len(); tm.update_ccsds_data_len();
assert_eq!(tm.data_len(), 15); assert_eq!(tm.data_len(), 15);
tm.calc_own_crc16(); tm.calc_own_crc16();
let res = tm.write_to(&mut buf); let res = tm.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
tm.sp_header.data_len = 0; tm.sp_header.data_len = 0;
tm.update_packet_fields(); tm.update_packet_fields();
@ -562,13 +576,13 @@ mod tests {
let time_stamp = dummy_time_stamp(); let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp); let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
let res = pus_tm.write_to(&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();
assert!(matches!(error, PusError::PacketError { .. })); assert!(matches!(error, PusError::ByteConversionError { .. }));
match error { match error {
PusError::PacketError(err) => match err { PusError::ByteConversionError(err) => match err {
PacketError::ToBytesSliceTooSmall(size_missmatch) => { ByteConversionError::ToSliceTooSmall(size_missmatch) => {
assert_eq!(size_missmatch.expected, 22); assert_eq!(size_missmatch.expected, 22);
assert_eq!(size_missmatch.found, 16); assert_eq!(size_missmatch.found, 16);
} }