63 Commits

Author SHA1 Message Date
de99bb926a Merge pull request 'small changelog tweak' () from small-changelog-tweak into main
Reviewed-on: 
2025-05-10 15:08:18 +02:00
167f53cac7 small changelog tweak 2025-05-10 15:07:58 +02:00
172227b843 Merge pull request 'update MSRV check' () from update-msrv-check into main
Reviewed-on: 
2025-05-10 15:04:21 +02:00
1bbca6866b update MSRV check 2025-05-10 15:03:05 +02:00
b569208d45 Merge pull request 'prepare v0.14.0' () from prepare-release into main
Reviewed-on: 
2025-05-10 14:58:45 +02:00
d9709ffd6c prepare v0.14.0 2025-05-10 14:54:27 +02:00
243dc64a78 Merge pull request 'remove badge' () from remove-badge into main
Reviewed-on: 
2025-05-10 14:38:23 +02:00
a6dc173f7f Merge branch 'main' into remove-badge 2025-05-10 14:38:19 +02:00
86dddbeef5 remove badge 2025-05-10 14:36:00 +02:00
17d112e838 Merge pull request 'one more test fix' () from one-more-test-fix into main
Reviewed-on: 
2025-05-10 14:31:53 +02:00
9c8467ccfe one more test fix 2025-05-10 14:30:00 +02:00
217a8c2cc7 Merge pull request 'formatting' () from formatting into main
Reviewed-on: 
2025-05-10 14:26:27 +02:00
349e34bed6 formatting 2025-05-10 14:25:44 +02:00
d6a76ca360 Merge pull request 'CRC handling and dependency update' () from msp430-tweak into main
Reviewed-on: 
2025-05-10 14:24:54 +02:00
8f4351771b API variants which use table-less CRC 2025-05-10 13:58:10 +02:00
b08c3329f4 Merge pull request 'bump patch release' () from prep-v0.13.1 into main
Reviewed-on: 
2025-03-21 14:53:01 +01:00
08e0d39154 bump patch release 2025-03-21 14:50:10 +01:00
ab97607024 Merge pull request 'clippy fixes' () from clippy-fixes into main
Reviewed-on: 
2025-03-21 14:47:06 +01:00
60d1f77844 bugfix due to operator precendence and clippy fixes 2025-03-21 14:46:13 +01:00
5a112b7f39 Merge pull request 'add funding file' () from add-funding-file into main
Reviewed-on: 
2025-03-17 16:33:46 +01:00
e774dd69d4 add funding file 2025-03-17 16:32:43 +01:00
a03d26a49c Merge pull request 'prep v0.13.0' () from prep-v0.13.0 into main
Reviewed-on: 
2024-11-08 16:55:52 +01:00
026173514f prep v0.13.0 2024-11-08 16:54:53 +01:00
2d7ccc0909 Merge pull request 'Add back API which was deleted accidently' () from add-back-api into main
Reviewed-on: 
2024-11-08 15:50:46 +01:00
05d3bac927 Add back API which was deleted accidently 2024-11-08 15:46:42 +01:00
d58df5fee2 Merge pull request 'Switch to thiserror' () from switch-to-thiserror into main
Reviewed-on: 
2024-11-08 15:42:37 +01:00
9d23ac5b9b switch to thiserror completely 2024-11-08 15:26:40 +01:00
c0b4653c01 Merge pull request 'bump CI msrv check' () from bump-msrv-check into main
Reviewed-on: 
2024-11-08 11:27:52 +01:00
f156833985 bump CI msrv check 2024-11-08 11:26:51 +01:00
9aea3dba00 Merge pull request 'bump dependencies' () from bump-dependencies into main
Reviewed-on: 
2024-11-08 11:14:04 +01:00
48247a0a87 bump thiserror and zerocopy 2024-11-08 11:13:41 +01:00
f70b957d9a Merge pull request 'docs fixes' () from smaller-doc-fixes into main
Reviewed-on: 
2024-11-07 23:28:41 +01:00
fbf953df0e docs fixes 2024-11-04 11:42:51 +01:00
f135d54364 Merge pull request 'prepare v0.12.0' () from prepare-v0.12.0 into main
Reviewed-on: 
2024-09-10 17:58:03 +02:00
d8b2a3dfea prepare v0.12.0 2024-09-10 17:51:31 +02:00
448b76be91 Merge pull request 'condition code bugfix' () from cfdp-cond-code-bugfix into main
Reviewed-on: 
2024-08-29 09:47:27 +02:00
027b01f00f condition code bugfix 2024-08-29 09:46:40 +02:00
bf15b22889 Merge pull request 'added max file segment length calculator' () from file-segment-calculator into main
Reviewed-on: 
2024-08-21 14:29:16 +02:00
16f91b562d added max file segment length calculator 2024-08-21 14:26:11 +02:00
cd77b806fe Merge pull request 'Added additional converter method' () from msgs-to-user-converter-method into main
Reviewed-on: 
2024-08-21 11:20:33 +02:00
43c88da3f2 Added additional converter method 2024-08-20 17:24:53 +02:00
b19a61b859 Merge pull request 'update msg to user module' () from cfdp-msg-to-user-update into main
Reviewed-on: 
2024-08-20 17:17:11 +02:00
8aa957b8bb update msg to user module 2024-08-20 16:56:25 +02:00
190fa1befc Merge pull request 'Added generic sequence counter module' () from seq-count-module into main
Reviewed-on: 
2024-08-20 11:20:07 +02:00
175b61deca Added generic sequence counter module 2024-08-20 10:57:53 +02:00
51c28b5cc6 Merge pull request 'Github MSRV version update' () from github-msrv into main
Reviewed-on: 
2024-08-19 10:58:31 +02:00
45cc74daa7 Github MSRV version update 2024-08-19 10:44:33 +02:00
191c6f8146 Merge pull request 'Bump MSRV and delegate version' () from bump-msrv-delegate-version into main
Reviewed-on: 
2024-08-19 10:42:29 +02:00
5449884b2e Bump MSRV and delegate version 2024-08-19 02:23:34 -06:00
9c93c76193 Merge pull request 'Update EOF PDU API' () from eof-pdu-update into main
Reviewed-on: 
2024-08-19 10:18:19 +02:00
043927c7ef Update EOF PDU API 2024-07-21 10:14:41 -07:00
f4dc5a0302 Merge pull request 'added new API for file data PDU' () from file-data-pdu-update into main
Reviewed-on: 
2024-07-21 18:25:08 +02:00
9166faa4ae optimization 2024-07-19 11:29:37 -07:00
ed808e69d4 added new API for file data PDU 2024-07-19 10:41:31 -07:00
d146b6cf57 Merge pull request 'Metadata PDU creator update' () from metadata-pdu-creator-update into main
Reviewed-on: 
2024-07-14 17:08:46 +02:00
ff0c9d8c70 Update and simplify Metadata PDU creator API 2024-07-09 16:30:48 +02:00
c40bc855a2 Merge pull request 'add owned TLV type' () from cfdp-tlv-owned-type into main
Reviewed-on: 
2024-07-09 16:08:53 +02:00
81423fc6e8 add owned TLV type 2024-07-09 16:04:08 +02:00
a399b11a8e Merge pull request 'update documentation build' () from update-docs-build into main
Reviewed-on: 
2024-07-03 16:14:46 +02:00
9d4c7446a3 Merge branch 'main' into update-docs-build 2024-06-25 16:20:08 +02:00
b87f7d73b1 Merge pull request 'clippy fix' () from clippy-fix into main
Reviewed-on: 
2024-06-25 16:20:01 +02:00
80744eea16 clippy fix 2024-06-25 16:19:30 +02:00
a5918bfd4a update documentation build 2024-06-25 16:07:07 +02:00
27 changed files with 1673 additions and 699 deletions

@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.68.2 - uses: dtolnay/rust-toolchain@1.70.0
- run: cargo check --release - run: cargo check --release
cross-check: cross-check:
@ -61,7 +61,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@nightly
- run: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]' - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
clippy: clippy:
name: Clippy name: Clippy

@ -8,6 +8,76 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.14.0] 2025-05-10
## Changed
- Moved CRC constants/implementations to dedicated `crc` module.
- `crc::CRC_CCITT_FALSE_NO_TABLE` and `crc::CRC_CCITT_FALSE_BIG_TABLE` variants.
- Renamed `PusPacket::crc16` to `PusPacket::opt_crc16`.
## Added
- `WritablePusPacket::write_to_bytes_crc_no_table` and `WritablePusPacket::write_to_bytes_no_crc`
variants.
- `PusTmReader::new_crc_no_table` and `PusTcReader::new_crc_no_table` variants.
- `crc16` methods for PUS TM and PUS TC reader.
- PUS TM and PUS TC reader now return the reader instance directly instead of a tuple of the reader
and the read size. The instance `total_len` method can be used to retrieve the read lenght.
# [v0.13.1] 2025-03-21
- Bugfix due to operator precendence for `PusTcSecondaryHeader::pus_version`,
`PusTcSecondaryHeaderWithoutTimestamp::pus_version`, `CdsTime::from_bytes_with_u16_days` and
`CdsTime::from_bytes_with_u24_days`
# [v0.13.0] 2024-11-08
- Bumped MSRV to 1.81.0
- Bump `zerocopy` to v0.8.0
- Bump `thiserror` to v2.0.0
## Changed
- Migrated all Error implementations to thiserror, improved some naming and error handling in
general
# [v0.12.0] 2024-09-10
- Bumped MSRV to 1.70.0
## Added
- Added new `cfdp::tlv::TlvOwned` type which erases the lifetime and is clonable.
- Dedicated `cfdp::tlv::TlvLvDataTooLarge` error struct for APIs where this is the only possible
API error.
- Added File Data PDU API which expects the expected file data size and then exposes the unwritten
file data field as a mutable slice. This allows to read data from the virtual file system
API to the file data buffer without an intermediate buffer.
- Generic `EofPdu::new` constructor.
- Added generic sequence counter module.
- Added `MsgToUserTlv::to_tlv` converter which reduced the type and converts
it to a generic `Tlv`.
- Implemented `From<MsgToUserTlv> for Tlv` converter trait.
- Added CFDP maximum file segment length calculator method `calculate_max_file_seg_len_for_max_packet_len_and_pdu_header`
## Added and Changed
- Added new `ReadableTlv` to avoid some boilerplate code and have a common abstraction implemented
for both `Tlv` and `TlvOwned` to read the raw TLV data field and its length.
- Replaced `cfdp::tlv::TlvLvError` by `cfdp::tlv::TlvLvDataTooLarge` where applicable.
## Fixed
- Fixed an error in the EOF writer which wrote the fault location to the wrong buffer position.
- cfdp `ConditionCode::CheckLimitReached` previous had the wrong numerical value of `0b1001` (9)
and now has the correct value of `0b1010` (10).
## Changed
- Minor documentation build updates.
- Increased delegate version range to v0.13
# [v0.11.2] 2024-05-19 # [v0.11.2] 2024-05-19
- Bumped MSRV to 1.68.2 - Bumped MSRV to 1.68.2
@ -514,3 +584,9 @@ The timestamp of `PusTm` is now optional. See Added and Changed section for deta
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.
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.14.0...HEAD
[v0.14.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.13.1...v0.14.0
[v0.13.1]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.13.0...v0.13.1
[v0.13.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.12.0...v0.13.0
[v0.12.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.11.2...v0.12.0

@ -1,8 +1,8 @@
[package] [package]
name = "spacepackets" name = "spacepackets"
version = "0.11.2" version = "0.14.0"
edition = "2021" edition = "2021"
rust-version = "1.68.2" rust-version = "1.70.0"
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"
homepage = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" homepage = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
@ -13,58 +13,30 @@ 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]
crc = "3" crc = "3.3"
delegate = ">=0.8, <0.11" delegate = ">=0.8, <=0.13"
paste = "1"
zerocopy = { version = "0.8", features = ["derive"] }
thiserror = { version = "2", default-features = false }
num_enum = { version = ">0.5, <=0.7", default-features = false }
num-traits = { version = "0.2", default-features = false }
serde = { version = "1", optional = true, default-features = false, features = ["derive"] }
[dependencies.zerocopy] time = { version = "0.3", default-features = false, optional = true }
version = "0.7" chrono = { version = "0.4", default-features = false, optional = true }
features = ["derive"] defmt = { version = "1", default-features = false, optional = true }
[dependencies.thiserror]
version = "1"
optional = true
[dependencies.num_enum]
version = ">0.5, <=0.7"
default-features = false
[dependencies.serde]
version = "1"
optional = true
default-features = false
features = ["derive"]
[dependencies.time]
version = "0.3"
default-features = false
optional = true
[dependencies.chrono]
version = "0.4"
default-features = false
optional = true
[dependencies.num-traits]
version = "0.2"
default-features = false
[dependencies.defmt]
version = "0.3"
optional = true
[dev-dependencies]
postcard = "1"
chrono = "0.4"
[features] [features]
default = ["std"] default = ["std"]
std = ["chrono/std", "chrono/clock", "alloc", "thiserror"] std = ["alloc", "chrono/std", "chrono/clock", "thiserror/std"]
serde = ["dep:serde", "chrono/serde"] serde = ["dep:serde", "chrono?/serde"]
alloc = ["postcard/alloc", "chrono/alloc"] alloc = ["chrono?/alloc", "defmt?/alloc", "serde?/alloc"]
chrono = ["dep:chrono"]
timelib = ["dep:time"] timelib = ["dep:time"]
defmt = ["dep:defmt"]
[dev-dependencies]
postcard = { version = "1", features = ["alloc"] }
chrono = "0.4"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"] rustdoc-args = ["--generate-link-to-definition"]

1
FUNDING.yml Normal file

@ -0,0 +1 @@
github: robamu

@ -1,7 +1,6 @@
[![Crates.io](https://img.shields.io/crates/v/spacepackets)](https://crates.io/crates/spacepackets) [![Crates.io](https://img.shields.io/crates/v/spacepackets)](https://crates.io/crates/spacepackets)
[![docs.rs](https://img.shields.io/docsrs/spacepackets)](https://docs.rs/spacepackets) [![docs.rs](https://img.shields.io/docsrs/spacepackets)](https://docs.rs/spacepackets)
[![ci](https://github.com/us-irs/spacepackets-rs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/us-irs/spacepackets-rs/actions/workflows/ci.yml) [![ci](https://github.com/us-irs/spacepackets-rs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/us-irs/spacepackets-rs/actions/workflows/ci.yml)
[![coverage](https://shields.io/endpoint?url=https://absatsw.irs.uni-stuttgart.de/projects/spacepackets/coverage-rs/latest/coverage.json)](https://absatsw.irs.uni-stuttgart.de/projects/spacepackets/coverage-rs/latest/index.html)
ECSS and CCSDS Spacepackets ECSS and CCSDS Spacepackets
====== ======

@ -21,7 +21,9 @@ pipeline {
} }
stage('Docs') { stage('Docs') {
steps { steps {
sh 'cargo +nightly doc --all-features' sh """
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
"""
} }
} }
stage('Rustfmt') { stage('Rustfmt') {

3
docs.sh Executable file

@ -0,0 +1,3 @@
#!/bin/sh
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --all-features --open

@ -4,7 +4,9 @@ Checklist for new releases
# Pre-Release # Pre-Release
1. Make sure any new modules are documented sufficiently enough and check docs with 1. Make sure any new modules are documented sufficiently enough and check docs with
`cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]' --open`. `RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features --open`
or `cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docsrs" --generate-link-to-definition"]' --open`
(was problematic on more recent nightly versions).
2. Bump version specifier in `Cargo.toml`. 2. Bump version specifier in `Cargo.toml`.
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new 3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
`unreleased` section. `unreleased` section.

@ -1,5 +1,4 @@
//! Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. //! Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8.
use crate::cfdp::TlvLvError;
use crate::ByteConversionError; use crate::ByteConversionError;
use core::str::Utf8Error; use core::str::Utf8Error;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@ -7,6 +6,8 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::string::String; use std::string::String;
use super::TlvLvDataTooLargeError;
pub const MIN_LV_LEN: usize = 1; pub const MIN_LV_LEN: usize = 1;
/// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. /// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8.
@ -63,9 +64,9 @@ pub(crate) fn generic_len_check_deserialization(
impl<'data> Lv<'data> { impl<'data> Lv<'data> {
#[inline] #[inline]
pub fn new(data: &[u8]) -> Result<Lv, TlvLvError> { pub fn new(data: &[u8]) -> Result<Lv, TlvLvDataTooLargeError> {
if data.len() > u8::MAX as usize { if data.len() > u8::MAX as usize {
return Err(TlvLvError::DataTooLarge(data.len())); return Err(TlvLvDataTooLargeError(data.len()));
} }
Ok(Lv { Ok(Lv {
data, data,
@ -85,7 +86,7 @@ impl<'data> Lv<'data> {
/// Helper function to build a string LV. This is especially useful for the file or directory /// Helper function to build a string LV. This is especially useful for the file or directory
/// path LVs /// path LVs
#[inline] #[inline]
pub fn new_from_str(str_slice: &str) -> Result<Lv, TlvLvError> { pub fn new_from_str(str_slice: &str) -> Result<Lv, TlvLvDataTooLargeError> {
Self::new(str_slice.as_bytes()) Self::new(str_slice.as_bytes())
} }
@ -93,7 +94,7 @@ impl<'data> Lv<'data> {
/// path LVs /// path LVs
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[inline] #[inline]
pub fn new_from_string(string: &'data String) -> Result<Lv<'data>, TlvLvError> { pub fn new_from_string(string: &'data String) -> Result<Lv<'data>, TlvLvDataTooLargeError> {
Self::new(string.as_bytes()) Self::new(string.as_bytes())
} }
@ -177,10 +178,10 @@ impl<'data> Lv<'data> {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*;
use alloc::string::ToString; use alloc::string::ToString;
use crate::cfdp::TlvLvError; use super::*;
use crate::ByteConversionError; use crate::ByteConversionError;
use std::string::String; use std::string::String;
@ -271,15 +272,11 @@ pub mod tests {
let lv = Lv::new(&data_big); let lv = Lv::new(&data_big);
assert!(lv.is_err()); assert!(lv.is_err());
let error = lv.unwrap_err(); let error = lv.unwrap_err();
if let TlvLvError::DataTooLarge(size) = error { assert_eq!(error.0, u8::MAX as usize + 1);
assert_eq!(size, u8::MAX as usize + 1); assert_eq!(
assert_eq!( error.to_string(),
error.to_string(), "data with size 256 larger than allowed 255 bytes"
"data with size 256 larger than allowed 255 bytes" );
);
} else {
panic!("invalid exception {:?}", error)
}
} }
#[test] #[test]

@ -1,11 +1,8 @@
//! Low-level CCSDS File Delivery Protocol (CFDP) support according to [CCSDS 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf). //! Low-level CCSDS File Delivery Protocol (CFDP) support according to [CCSDS 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf).
use crate::ByteConversionError; use crate::ByteConversionError;
use core::fmt::{Display, Formatter};
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
pub mod lv; pub mod lv;
pub mod pdu; pub mod pdu;
@ -116,7 +113,7 @@ pub enum ConditionCode {
FileSizeError = 0b0110, FileSizeError = 0b0110,
NakLimitReached = 0b0111, NakLimitReached = 0b0111,
InactivityDetected = 0b1000, InactivityDetected = 0b1000,
CheckLimitReached = 0b1001, CheckLimitReached = 0b1010,
UnsupportedChecksumType = 0b1011, UnsupportedChecksumType = 0b1011,
/// Not an actual fault condition for which fault handler overrides can be specified /// Not an actual fault condition for which fault handler overrides can be specified
SuspendRequestReceived = 0b1110, SuspendRequestReceived = 0b1110,
@ -176,76 +173,43 @@ impl Default for ChecksumType {
pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4]; pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4];
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("data with size {0} larger than allowed {max} bytes", max = u8::MAX)]
pub struct TlvLvDataTooLargeError(pub usize);
/// First value: Found value. Second value: Expected value if there is one.
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("invalid TLV type field, found {found}, expected {expected:?}")]
pub struct InvalidTlvTypeFieldError {
found: u8,
expected: Option<u8>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TlvLvError { pub enum TlvLvError {
DataTooLarge(usize), #[error("{0}")]
ByteConversion(ByteConversionError), DataTooLarge(#[from] TlvLvDataTooLargeError),
/// First value: Found value. Second value: Expected value if there is one. #[error("byte conversion error: {0}")]
InvalidTlvTypeField { ByteConversion(#[from] ByteConversionError),
found: u8, #[error("{0}")]
expected: Option<u8>, InvalidTlvTypeField(#[from] InvalidTlvTypeFieldError),
}, #[error("invalid value length {0}")]
/// Logically invalid value length detected. The value length may not exceed 255 bytes.
/// Depending on the concrete TLV type, the value length may also be logically invalid.
InvalidValueLength(usize), InvalidValueLength(usize),
/// Only applies to filestore requests and responses. Second name was missing where one is /// Only applies to filestore requests and responses. Second name was missing where one is
/// expected. /// expected.
#[error("second name missing for filestore request or response")]
SecondNameMissing, SecondNameMissing,
/// Invalid action code for filestore requests or responses. /// Invalid action code for filestore requests or responses.
#[error("invalid action code {0}")]
InvalidFilestoreActionCode(u8), InvalidFilestoreActionCode(u8),
} }
impl From<ByteConversionError> for TlvLvError {
fn from(value: ByteConversionError) -> Self {
Self::ByteConversion(value)
}
}
impl Display for TlvLvError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
TlvLvError::DataTooLarge(data_len) => {
write!(
f,
"data with size {} larger than allowed {} bytes",
data_len,
u8::MAX
)
}
TlvLvError::ByteConversion(e) => {
write!(f, "tlv or lv byte conversion: {}", e)
}
TlvLvError::InvalidTlvTypeField { found, expected } => {
write!(
f,
"invalid TLV type field, found {found}, expected {expected:?}"
)
}
TlvLvError::InvalidValueLength(len) => {
write!(f, "invalid value length {len}")
}
TlvLvError::SecondNameMissing => {
write!(f, "second name missing for filestore request or response")
}
TlvLvError::InvalidFilestoreActionCode(raw) => {
write!(f, "invalid filestore action code with raw value {raw}")
}
}
}
}
#[cfg(feature = "std")]
impl Error for TlvLvError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
TlvLvError::ByteConversion(e) => Some(e),
_ => None,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

@ -25,20 +25,36 @@ pub struct EofPdu {
} }
impl EofPdu { impl EofPdu {
pub fn new_no_error(mut pdu_header: PduHeader, file_checksum: u32, file_size: u64) -> Self { pub fn new(
mut pdu_header: PduHeader,
condition_code: ConditionCode,
file_checksum: u32,
file_size: u64,
fault_location: Option<EntityIdTlv>,
) -> Self {
// Force correct direction flag. // Force correct direction flag.
pdu_header.pdu_conf.direction = Direction::TowardsReceiver; pdu_header.pdu_conf.direction = Direction::TowardsReceiver;
let mut eof_pdu = Self { let mut eof_pdu = Self {
pdu_header, pdu_header,
condition_code: ConditionCode::NoError, condition_code,
file_checksum, file_checksum,
file_size, file_size,
fault_location: None, fault_location,
}; };
eof_pdu.pdu_header.pdu_datafield_len = eof_pdu.calc_pdu_datafield_len() as u16; eof_pdu.pdu_header.pdu_datafield_len = eof_pdu.calc_pdu_datafield_len() as u16;
eof_pdu eof_pdu
} }
pub fn new_no_error(pdu_header: PduHeader, file_checksum: u32, file_size: u64) -> Self {
Self::new(
pdu_header,
ConditionCode::NoError,
file_checksum,
file_size,
None,
)
}
pub fn pdu_header(&self) -> &PduHeader { pub fn pdu_header(&self) -> &PduHeader {
&self.pdu_header &self.pdu_header
} }
@ -148,7 +164,7 @@ impl WritablePduPacket for EofPdu {
&mut buf[current_idx..], &mut buf[current_idx..],
)?; )?;
if let Some(fault_location) = self.fault_location { if let Some(fault_location) = self.fault_location {
current_idx += fault_location.write_to_bytes(buf)?; current_idx += fault_location.write_to_bytes(&mut buf[current_idx..])?;
} }
if self.crc_flag() == CrcFlag::WithCrc { if self.crc_flag() == CrcFlag::WithCrc {
current_idx = add_pdu_crc(buf, current_idx); current_idx = add_pdu_crc(buf, current_idx);
@ -171,13 +187,23 @@ mod tests {
use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag, PduType, TransmissionMode}; use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag, PduType, TransmissionMode};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::tests::generic_serde_test; use crate::tests::generic_serde_test;
use crate::util::{UnsignedByteFieldU16, UnsignedEnum};
fn verify_state(&eof_pdu: &EofPdu, file_flag: LargeFileFlag) { fn verify_state_no_error_no_crc(eof_pdu: &EofPdu, file_flag: LargeFileFlag) {
verify_state(eof_pdu, CrcFlag::NoCrc, file_flag, ConditionCode::NoError);
}
fn verify_state(
eof_pdu: &EofPdu,
crc_flag: CrcFlag,
file_flag: LargeFileFlag,
cond_code: ConditionCode,
) {
assert_eq!(eof_pdu.file_checksum(), 0x01020304); assert_eq!(eof_pdu.file_checksum(), 0x01020304);
assert_eq!(eof_pdu.file_size(), 12); assert_eq!(eof_pdu.file_size(), 12);
assert_eq!(eof_pdu.condition_code(), ConditionCode::NoError); assert_eq!(eof_pdu.condition_code(), cond_code);
assert_eq!(eof_pdu.crc_flag(), CrcFlag::NoCrc); assert_eq!(eof_pdu.crc_flag(), crc_flag);
assert_eq!(eof_pdu.file_flag(), file_flag); assert_eq!(eof_pdu.file_flag(), file_flag);
assert_eq!(eof_pdu.pdu_type(), PduType::FileDirective); assert_eq!(eof_pdu.pdu_type(), PduType::FileDirective);
assert_eq!( assert_eq!(
@ -197,7 +223,7 @@ mod tests {
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 4 + 4); assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 4 + 4);
verify_state(&eof_pdu, LargeFileFlag::Normal); verify_state_no_error_no_crc(&eof_pdu, LargeFileFlag::Normal);
} }
#[test] #[test]
@ -271,7 +297,7 @@ mod tests {
buf[written - 1] -= 1; buf[written - 1] -= 1;
let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16; let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16;
let error = EofPdu::from_bytes(&buf).unwrap_err(); let error = EofPdu::from_bytes(&buf).unwrap_err();
if let PduError::ChecksumError(e) = error { if let PduError::Checksum(e) = error {
assert_eq!(e, crc); assert_eq!(e, crc);
} else { } else {
panic!("expected crc error"); panic!("expected crc error");
@ -283,7 +309,7 @@ mod tests {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
verify_state(&eof_pdu, LargeFileFlag::Large); verify_state_no_error_no_crc(&eof_pdu, LargeFileFlag::Large);
assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 8 + 4); assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 8 + 4);
} }
@ -295,4 +321,48 @@ mod tests {
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
generic_serde_test(eof_pdu); generic_serde_test(eof_pdu);
} }
fn generic_test_with_fault_location_and_error(crc: CrcFlag) {
let pdu_conf = common_pdu_conf(crc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
let eof_pdu = EofPdu::new(
pdu_header,
ConditionCode::FileChecksumFailure,
0x01020304,
12,
Some(EntityIdTlv::new(UnsignedByteFieldU16::new(5).into())),
);
let mut expected_len = pdu_header.header_len() + 2 + 4 + 4 + 4;
if crc == CrcFlag::WithCrc {
expected_len += 2;
}
// Entity ID TLV increaes length by 4.
assert_eq!(eof_pdu.len_written(), expected_len);
verify_state(
&eof_pdu,
crc,
LargeFileFlag::Normal,
ConditionCode::FileChecksumFailure,
);
let eof_vec = eof_pdu.to_vec().unwrap();
let eof_read_back = EofPdu::from_bytes(&eof_vec);
if let Err(e) = eof_read_back {
panic!("deserialization failed with: {e}")
}
let eof_read_back = eof_read_back.unwrap();
assert_eq!(eof_read_back, eof_pdu);
assert!(eof_read_back.fault_location.is_some());
assert_eq!(eof_read_back.fault_location.unwrap().entity_id().value(), 5);
assert_eq!(eof_read_back.fault_location.unwrap().entity_id().size(), 2);
}
#[test]
fn test_with_fault_location_and_error() {
generic_test_with_fault_location_and_error(CrcFlag::NoCrc);
}
#[test]
fn test_with_fault_location_and_error_and_crc() {
generic_test_with_fault_location_and_error(CrcFlag::WithCrc);
}
} }

@ -92,16 +92,67 @@ impl<'seg_meta> SegmentMetadata<'seg_meta> {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
struct FdPduBase<'seg_meta> {
pdu_header: PduHeader,
#[cfg_attr(feature = "serde", serde(borrow))]
segment_metadata: Option<SegmentMetadata<'seg_meta>>,
offset: u64,
}
impl CfdpPdu for FdPduBase<'_> {
fn pdu_header(&self) -> &PduHeader {
&self.pdu_header
}
fn file_directive_type(&self) -> Option<FileDirectiveType> {
None
}
}
impl FdPduBase<'_> {
fn calc_pdu_datafield_len(&self, file_data_len: u64) -> usize {
let mut len = core::mem::size_of::<u32>();
if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large {
len += 4;
}
if self.segment_metadata.is_some() {
len += self.segment_metadata.as_ref().unwrap().written_len()
}
len += file_data_len as usize;
if self.crc_flag() == CrcFlag::WithCrc {
len += 2;
}
len
}
fn write_common_fields_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
let mut current_idx = self.pdu_header.write_to_bytes(buf)?;
if self.segment_metadata.is_some() {
current_idx += self
.segment_metadata
.as_ref()
.unwrap()
.write_to_bytes(&mut buf[current_idx..])?;
}
current_idx += write_fss_field(
self.pdu_header.common_pdu_conf().file_flag,
self.offset,
&mut buf[current_idx..],
)?;
Ok(current_idx)
}
}
/// File Data PDU abstraction. /// File Data PDU abstraction.
/// ///
/// For more information, refer to CFDP chapter 5.3. /// For more information, refer to CFDP chapter 5.3.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FileDataPdu<'seg_meta, 'file_data> { pub struct FileDataPdu<'seg_meta, 'file_data> {
pdu_header: PduHeader,
#[cfg_attr(feature = "serde", serde(borrow))] #[cfg_attr(feature = "serde", serde(borrow))]
segment_metadata: Option<SegmentMetadata<'seg_meta>>, common: FdPduBase<'seg_meta>,
offset: u64,
file_data: &'file_data [u8], file_data: &'file_data [u8],
} }
@ -134,42 +185,34 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> {
pdu_header.seg_metadata_flag = SegmentMetadataFlag::Present; pdu_header.seg_metadata_flag = SegmentMetadataFlag::Present;
} }
let mut pdu = Self { let mut pdu = Self {
pdu_header, common: FdPduBase {
segment_metadata, pdu_header,
offset, segment_metadata,
offset,
},
file_data, file_data,
}; };
pdu.pdu_header.pdu_datafield_len = pdu.calc_pdu_datafield_len() as u16; pdu.common.pdu_header.pdu_datafield_len = pdu.calc_pdu_datafield_len() as u16;
pdu pdu
} }
fn calc_pdu_datafield_len(&self) -> usize { fn calc_pdu_datafield_len(&self) -> usize {
let mut len = core::mem::size_of::<u32>(); self.common
if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { .calc_pdu_datafield_len(self.file_data.len() as u64)
len += 4; }
}
if self.segment_metadata.is_some() { pub fn segment_metadata(&self) -> Option<&SegmentMetadata> {
len += self.segment_metadata.as_ref().unwrap().written_len() self.common.segment_metadata.as_ref()
}
len += self.file_data.len();
if self.crc_flag() == CrcFlag::WithCrc {
len += 2;
}
len
} }
pub fn offset(&self) -> u64 { pub fn offset(&self) -> u64 {
self.offset self.common.offset
} }
pub fn file_data(&self) -> &'file_data [u8] { pub fn file_data(&self) -> &'file_data [u8] {
self.file_data self.file_data
} }
pub fn segment_metadata(&self) -> Option<&SegmentMetadata> {
self.segment_metadata.as_ref()
}
pub fn from_bytes<'buf: 'seg_meta + 'file_data>(buf: &'buf [u8]) -> Result<Self, PduError> { pub fn from_bytes<'buf: 'seg_meta + 'file_data>(buf: &'buf [u8]) -> Result<Self, PduError> {
let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?;
let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?;
@ -190,16 +233,18 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> {
.into()); .into());
} }
Ok(Self { Ok(Self {
pdu_header, common: FdPduBase {
segment_metadata, pdu_header,
offset, segment_metadata,
offset,
},
file_data: &buf[current_idx..full_len_without_crc], file_data: &buf[current_idx..full_len_without_crc],
}) })
} }
} }
impl CfdpPdu for FileDataPdu<'_, '_> { impl CfdpPdu for FileDataPdu<'_, '_> {
fn pdu_header(&self) -> &PduHeader { fn pdu_header(&self) -> &PduHeader {
&self.pdu_header &self.common.pdu_header
} }
fn file_directive_type(&self) -> Option<FileDirectiveType> { fn file_directive_type(&self) -> Option<FileDirectiveType> {
@ -216,19 +261,8 @@ impl WritablePduPacket for FileDataPdu<'_, '_> {
} }
.into()); .into());
} }
let mut current_idx = self.pdu_header.write_to_bytes(buf)?;
if self.segment_metadata.is_some() { let mut current_idx = self.common.write_common_fields_to_bytes(buf)?;
current_idx += self
.segment_metadata
.as_ref()
.unwrap()
.write_to_bytes(&mut buf[current_idx..])?;
}
current_idx += write_fss_field(
self.pdu_header.common_pdu_conf().file_flag,
self.offset,
&mut buf[current_idx..],
)?;
buf[current_idx..current_idx + self.file_data.len()].copy_from_slice(self.file_data); buf[current_idx..current_idx + self.file_data.len()].copy_from_slice(self.file_data);
current_idx += self.file_data.len(); current_idx += self.file_data.len();
if self.crc_flag() == CrcFlag::WithCrc { if self.crc_flag() == CrcFlag::WithCrc {
@ -238,10 +272,167 @@ impl WritablePduPacket for FileDataPdu<'_, '_> {
} }
fn len_written(&self) -> usize { fn len_written(&self) -> usize {
self.pdu_header.header_len() + self.calc_pdu_datafield_len() self.common.pdu_header.header_len() + self.calc_pdu_datafield_len()
} }
} }
/// File Data PDU creator abstraction.
///
/// This special creator object allows to read into the file data buffer directly. This avoids
/// the need of an additional buffer to create a file data PDU. This structure therefore
/// does not implement the regular [WritablePduPacket] trait.
///
/// For more information, refer to CFDP chapter 5.3.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FileDataPduCreatorWithReservedDatafield<'seg_meta> {
#[cfg_attr(feature = "serde", serde(borrow))]
common: FdPduBase<'seg_meta>,
file_data_len: u64,
}
impl<'seg_meta> FileDataPduCreatorWithReservedDatafield<'seg_meta> {
pub fn new_with_seg_metadata(
pdu_header: PduHeader,
segment_metadata: SegmentMetadata<'seg_meta>,
offset: u64,
file_data_len: u64,
) -> Self {
Self::new_generic(pdu_header, Some(segment_metadata), offset, file_data_len)
}
pub fn new_no_seg_metadata(pdu_header: PduHeader, offset: u64, file_data_len: u64) -> Self {
Self::new_generic(pdu_header, None, offset, file_data_len)
}
pub fn new_generic(
mut pdu_header: PduHeader,
segment_metadata: Option<SegmentMetadata<'seg_meta>>,
offset: u64,
file_data_len: u64,
) -> Self {
pdu_header.pdu_type = PduType::FileData;
if segment_metadata.is_some() {
pdu_header.seg_metadata_flag = SegmentMetadataFlag::Present;
}
let mut pdu = Self {
common: FdPduBase {
pdu_header,
segment_metadata,
offset,
},
file_data_len,
};
pdu.common.pdu_header.pdu_datafield_len = pdu.calc_pdu_datafield_len() as u16;
pdu
}
fn calc_pdu_datafield_len(&self) -> usize {
self.common.calc_pdu_datafield_len(self.file_data_len)
}
pub fn len_written(&self) -> usize {
self.common.pdu_header.header_len() + self.calc_pdu_datafield_len()
}
/// This function performs a partial write by writing all data except the file data
/// and the CRC.
///
/// It returns a [FileDataPduCreatorWithUnwrittenData] which provides a mutable slice to
/// the reserved file data field. The user can read file data into this field directly and
/// then finish the PDU creation using the [FileDataPduCreatorWithUnwrittenData::finish] call.
pub fn write_to_bytes_partially<'buf>(
&self,
buf: &'buf mut [u8],
) -> Result<FileDataPduCreatorWithUnwrittenData<'buf>, PduError> {
if buf.len() < self.len_written() {
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
expected: self.len_written(),
}
.into());
}
let mut current_idx = self.common.write_common_fields_to_bytes(buf)?;
let file_data_offset = current_idx as u64;
current_idx += self.file_data_len as usize;
if self.crc_flag() == CrcFlag::WithCrc {
current_idx += 2;
}
Ok(FileDataPduCreatorWithUnwrittenData {
write_buf: &mut buf[0..current_idx],
file_data_offset,
file_data_len: self.file_data_len,
needs_crc: self.crc_flag() == CrcFlag::WithCrc,
})
}
}
impl CfdpPdu for FileDataPduCreatorWithReservedDatafield<'_> {
fn pdu_header(&self) -> &PduHeader {
&self.common.pdu_header
}
fn file_directive_type(&self) -> Option<FileDirectiveType> {
None
}
}
/// This structure is created with [FileDataPduCreatorWithReservedDatafield::write_to_bytes_partially]
/// and provides an API to read file data from the virtual filesystem into the file data PDU buffer
/// directly.
///
/// This structure provides a mutable slice to the reserved file data field. The user can read
/// file data into this field directly and then finish the PDU creation using the
/// [FileDataPduCreatorWithUnwrittenData::finish] call.
pub struct FileDataPduCreatorWithUnwrittenData<'buf> {
write_buf: &'buf mut [u8],
file_data_offset: u64,
file_data_len: u64,
needs_crc: bool,
}
impl FileDataPduCreatorWithUnwrittenData<'_> {
pub fn file_data_field_mut(&mut self) -> &mut [u8] {
&mut self.write_buf[self.file_data_offset as usize
..self.file_data_offset as usize + self.file_data_len as usize]
}
/// This functio needs to be called to add a CRC to the file data PDU where applicable.
///
/// It returns the full written size of the PDU.
pub fn finish(self) -> usize {
if self.needs_crc {
add_pdu_crc(
self.write_buf,
self.file_data_offset as usize + self.file_data_len as usize,
);
}
self.write_buf.len()
}
}
/// This function can be used to calculate the maximum allowed file segment size for
/// a given maximum packet length and the segment metadata if there is any.
pub fn calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(
pdu_header: &PduHeader,
max_packet_len: usize,
segment_metadata: Option<&SegmentMetadata>,
) -> usize {
let mut subtract = pdu_header.header_len();
if segment_metadata.is_some() {
subtract += 1 + segment_metadata.as_ref().unwrap().metadata().unwrap().len();
}
if pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large {
subtract += 8;
} else {
subtract += 4;
}
if pdu_header.common_pdu_conf().crc_flag == CrcFlag::WithCrc {
subtract += 2;
}
max_packet_len.saturating_sub(subtract)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -263,7 +454,7 @@ mod tests {
assert!(fd_pdu.segment_metadata().is_none()); assert!(fd_pdu.segment_metadata().is_none());
assert_eq!( assert_eq!(
fd_pdu.len_written(), fd_pdu.len_written(),
fd_pdu.pdu_header.header_len() + core::mem::size_of::<u32>() + 4 fd_pdu.pdu_header().header_len() + core::mem::size_of::<u32>() + 4
); );
assert_eq!(fd_pdu.crc_flag(), CrcFlag::NoCrc); assert_eq!(fd_pdu.crc_flag(), CrcFlag::NoCrc);
@ -290,11 +481,11 @@ mod tests {
let written = res.unwrap(); let written = res.unwrap();
assert_eq!( assert_eq!(
written, written,
fd_pdu.pdu_header.header_len() + core::mem::size_of::<u32>() + 4 fd_pdu.pdu_header().header_len() + core::mem::size_of::<u32>() + 4
); );
let mut current_idx = fd_pdu.pdu_header.header_len(); let mut current_idx = fd_pdu.pdu_header().header_len();
let file_size = u32::from_be_bytes( let file_size = u32::from_be_bytes(
buf[fd_pdu.pdu_header.header_len()..fd_pdu.pdu_header.header_len() + 4] buf[fd_pdu.pdu_header().header_len()..fd_pdu.pdu_header().header_len() + 4]
.try_into() .try_into()
.unwrap(), .unwrap(),
); );
@ -353,7 +544,7 @@ mod tests {
buf[written - 1] -= 1; buf[written - 1] -= 1;
let crc: u16 = ((buf[written - 2] as u16) << 8) | buf[written - 1] as u16; let crc: u16 = ((buf[written - 2] as u16) << 8) | buf[written - 1] as u16;
let error = FileDataPdu::from_bytes(&buf).unwrap_err(); let error = FileDataPdu::from_bytes(&buf).unwrap_err();
if let PduError::ChecksumError(e) = error { if let PduError::Checksum(e) = error {
assert_eq!(e, crc); assert_eq!(e, crc);
} else { } else {
panic!("expected crc error"); panic!("expected crc error");
@ -380,7 +571,7 @@ mod tests {
assert_eq!(*fd_pdu.segment_metadata().unwrap(), segment_meta); assert_eq!(*fd_pdu.segment_metadata().unwrap(), segment_meta);
assert_eq!( assert_eq!(
fd_pdu.len_written(), fd_pdu.len_written(),
fd_pdu.pdu_header.header_len() fd_pdu.pdu_header().header_len()
+ 1 + 1
+ seg_metadata.len() + seg_metadata.len()
+ core::mem::size_of::<u32>() + core::mem::size_of::<u32>()
@ -390,7 +581,7 @@ mod tests {
fd_pdu fd_pdu
.write_to_bytes(&mut buf) .write_to_bytes(&mut buf)
.expect("writing FD PDU failed"); .expect("writing FD PDU failed");
let mut current_idx = fd_pdu.pdu_header.header_len(); let mut current_idx = fd_pdu.pdu_header().header_len();
assert_eq!( assert_eq!(
RecordContinuationState::try_from((buf[current_idx] >> 6) & 0b11).unwrap(), RecordContinuationState::try_from((buf[current_idx] >> 6) & 0b11).unwrap(),
RecordContinuationState::StartAndEnd RecordContinuationState::StartAndEnd
@ -482,4 +673,142 @@ mod tests {
let output_converted_back: FileDataPdu = from_bytes(&output).unwrap(); let output_converted_back: FileDataPdu = from_bytes(&output).unwrap();
assert_eq!(output_converted_back, fd_pdu); assert_eq!(output_converted_back, fd_pdu);
} }
#[test]
fn test_fd_pdu_creator_with_reserved_field_no_crc() {
let common_conf =
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
let test_str = "hello world!";
let fd_pdu = FileDataPduCreatorWithReservedDatafield::new_no_seg_metadata(
pdu_header,
10,
test_str.len() as u64,
);
let mut write_buf: [u8; 64] = [0; 64];
let mut pdu_unwritten = fd_pdu
.write_to_bytes_partially(&mut write_buf)
.expect("partial write failed");
pdu_unwritten
.file_data_field_mut()
.copy_from_slice(test_str.as_bytes());
pdu_unwritten.finish();
let pdu_reader = FileDataPdu::from_bytes(&write_buf).expect("reading file data PDU failed");
assert_eq!(
core::str::from_utf8(pdu_reader.file_data()).expect("reading utf8 string failed"),
"hello world!"
);
}
#[test]
fn test_fd_pdu_creator_with_reserved_field_with_crc() {
let mut common_conf =
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
common_conf.crc_flag = true.into();
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
let test_str = "hello world!";
let fd_pdu = FileDataPduCreatorWithReservedDatafield::new_no_seg_metadata(
pdu_header,
10,
test_str.len() as u64,
);
let mut write_buf: [u8; 64] = [0; 64];
let mut pdu_unwritten = fd_pdu
.write_to_bytes_partially(&mut write_buf)
.expect("partial write failed");
pdu_unwritten
.file_data_field_mut()
.copy_from_slice(test_str.as_bytes());
pdu_unwritten.finish();
let pdu_reader = FileDataPdu::from_bytes(&write_buf).expect("reading file data PDU failed");
assert_eq!(
core::str::from_utf8(pdu_reader.file_data()).expect("reading utf8 string failed"),
"hello world!"
);
}
#[test]
fn test_fd_pdu_creator_with_reserved_field_with_crc_without_finish_fails() {
let mut common_conf =
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
common_conf.crc_flag = true.into();
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
let test_str = "hello world!";
let fd_pdu = FileDataPduCreatorWithReservedDatafield::new_no_seg_metadata(
pdu_header,
10,
test_str.len() as u64,
);
let mut write_buf: [u8; 64] = [0; 64];
let mut pdu_unwritten = fd_pdu
.write_to_bytes_partially(&mut write_buf)
.expect("partial write failed");
pdu_unwritten
.file_data_field_mut()
.copy_from_slice(test_str.as_bytes());
let pdu_reader_error = FileDataPdu::from_bytes(&write_buf);
assert!(pdu_reader_error.is_err());
let error = pdu_reader_error.unwrap_err();
match error {
PduError::Checksum(_) => (),
_ => {
panic!("unexpected PDU error {}", error)
}
}
}
#[test]
fn test_max_file_seg_calculator_0() {
let pdu_header = PduHeader::new_for_file_data_default(CommonPduConfig::default(), 0);
assert_eq!(
calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(&pdu_header, 64, None),
53
);
}
#[test]
fn test_max_file_seg_calculator_1() {
let common_conf = CommonPduConfig {
crc_flag: CrcFlag::WithCrc,
..Default::default()
};
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
assert_eq!(
calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(&pdu_header, 64, None),
51
);
}
#[test]
fn test_max_file_seg_calculator_2() {
let common_conf = CommonPduConfig {
file_flag: LargeFileFlag::Large,
..Default::default()
};
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
assert_eq!(
calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(&pdu_header, 64, None),
49
);
}
#[test]
fn test_max_file_seg_calculator_saturating_sub() {
let common_conf = CommonPduConfig {
file_flag: LargeFileFlag::Large,
..Default::default()
};
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
assert_eq!(
calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(&pdu_header, 15, None),
0
);
assert_eq!(
calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(&pdu_header, 14, None),
0
);
}
} }

@ -4,13 +4,14 @@ use crate::cfdp::pdu::{
use crate::cfdp::tlv::{ use crate::cfdp::tlv::{
EntityIdTlv, FilestoreResponseTlv, GenericTlv, Tlv, TlvType, TlvTypeField, WritableTlv, EntityIdTlv, FilestoreResponseTlv, GenericTlv, Tlv, TlvType, TlvTypeField, WritableTlv,
}; };
use crate::cfdp::{ConditionCode, CrcFlag, Direction, PduType, TlvLvError}; use crate::cfdp::{ConditionCode, CrcFlag, Direction, PduType};
use crate::ByteConversionError; use crate::ByteConversionError;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{CfdpPdu, WritablePduPacket}; use super::tlv::ReadableTlv;
use super::{CfdpPdu, InvalidTlvTypeFieldError, WritablePduPacket};
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -331,22 +332,26 @@ impl<'buf> FinishedPduReader<'buf> {
// last TLV, everything else would break the whole handling of the packet // last TLV, everything else would break the whole handling of the packet
// TLVs. // TLVs.
if current_idx != full_len_without_crc { if current_idx != full_len_without_crc {
return Err(PduError::FormatError); return Err(PduError::Format);
} }
} else { } else {
return Err(TlvLvError::InvalidTlvTypeField { return Err(PduError::TlvLv(
found: tlv_type.into(), InvalidTlvTypeFieldError {
expected: Some(TlvType::FilestoreResponse.into()), found: tlv_type.into(),
} expected: Some(TlvType::FilestoreResponse.into()),
.into()); }
.into(),
));
} }
} }
TlvTypeField::Custom(raw) => { TlvTypeField::Custom(raw) => {
return Err(TlvLvError::InvalidTlvTypeField { return Err(PduError::TlvLv(
found: raw, InvalidTlvTypeFieldError {
expected: None, found: raw,
} expected: None,
.into()); }
.into(),
));
} }
} }
} }
@ -563,7 +568,7 @@ mod tests {
buf[written - 1] -= 1; buf[written - 1] -= 1;
let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16; let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16;
let error = FinishedPduReader::new(&buf).unwrap_err(); let error = FinishedPduReader::new(&buf).unwrap_err();
if let PduError::ChecksumError(e) = error { if let PduError::Checksum(e) = error {
assert_eq!(e, crc); assert_eq!(e, crc);
} else { } else {
panic!("expected crc error"); panic!("expected crc error");

@ -1,3 +1,5 @@
#[cfg(feature = "alloc")]
use super::tlv::TlvOwned;
use crate::cfdp::lv::Lv; use crate::cfdp::lv::Lv;
use crate::cfdp::pdu::{ use crate::cfdp::pdu::{
add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field,
@ -11,6 +13,7 @@ use alloc::vec::Vec;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::tlv::ReadableTlv;
use super::{CfdpPdu, WritablePduPacket}; use super::{CfdpPdu, WritablePduPacket};
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
@ -51,6 +54,15 @@ pub fn build_metadata_opts_from_vec(
build_metadata_opts_from_slice(buf, tlvs.as_slice()) build_metadata_opts_from_slice(buf, tlvs.as_slice())
} }
#[cfg(feature = "alloc")]
pub fn build_metadata_opts_from_owned_slice(tlvs: &[TlvOwned]) -> Vec<u8> {
let mut sum_vec = Vec::new();
for tlv in tlvs {
sum_vec.extend(tlv.to_vec());
}
sum_vec
}
/// Metadata PDU creator abstraction. /// Metadata PDU creator abstraction.
/// ///
/// This abstraction exposes a specialized API for creating metadata PDUs as specified in /// This abstraction exposes a specialized API for creating metadata PDUs as specified in
@ -61,7 +73,7 @@ pub struct MetadataPduCreator<'src_name, 'dest_name, 'opts> {
metadata_params: MetadataGenericParams, metadata_params: MetadataGenericParams,
src_file_name: Lv<'src_name>, src_file_name: Lv<'src_name>,
dest_file_name: Lv<'dest_name>, dest_file_name: Lv<'dest_name>,
options: &'opts [Tlv<'opts>], options: &'opts [u8],
} }
impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'opts> { impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'opts> {
@ -85,7 +97,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
metadata_params: MetadataGenericParams, metadata_params: MetadataGenericParams,
src_file_name: Lv<'src_name>, src_file_name: Lv<'src_name>,
dest_file_name: Lv<'dest_name>, dest_file_name: Lv<'dest_name>,
options: &'opts [Tlv<'opts>], options: &'opts [u8],
) -> Self { ) -> Self {
Self::new( Self::new(
pdu_header, pdu_header,
@ -101,7 +113,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
metadata_params: MetadataGenericParams, metadata_params: MetadataGenericParams,
src_file_name: Lv<'src_name>, src_file_name: Lv<'src_name>,
dest_file_name: Lv<'dest_name>, dest_file_name: Lv<'dest_name>,
options: &'opts [Tlv<'opts>], options: &'opts [u8],
) -> Self { ) -> Self {
pdu_header.pdu_type = PduType::FileDirective; pdu_header.pdu_type = PduType::FileDirective;
pdu_header.pdu_conf.direction = Direction::TowardsReceiver; pdu_header.pdu_conf.direction = Direction::TowardsReceiver;
@ -128,10 +140,19 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
self.dest_file_name self.dest_file_name
} }
pub fn options(&self) -> &'opts [Tlv<'opts>] { pub fn options(&self) -> &'opts [u8] {
self.options self.options
} }
/// Yield an iterator which can be used to loop through all options. Returns [None] if the
/// options field is empty.
pub fn options_iter(&self) -> OptionsIter<'_> {
OptionsIter {
opt_buf: self.options,
current_idx: 0,
}
}
fn calc_pdu_datafield_len(&self) -> usize { fn calc_pdu_datafield_len(&self) -> usize {
// One directve type octet and one byte of the directive parameter field. // One directve type octet and one byte of the directive parameter field.
let mut len = 2; let mut len = 2;
@ -142,9 +163,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
} }
len += self.src_file_name.len_full(); len += self.src_file_name.len_full();
len += self.dest_file_name.len_full(); len += self.dest_file_name.len_full();
for tlv in self.options() { len += self.options().len();
len += tlv.len_full()
}
if self.crc_flag() == CrcFlag::WithCrc { if self.crc_flag() == CrcFlag::WithCrc {
len += 2; len += 2;
} }
@ -190,10 +209,8 @@ impl WritablePduPacket for MetadataPduCreator<'_, '_, '_> {
current_idx += self current_idx += self
.dest_file_name .dest_file_name
.write_to_be_bytes(&mut buf[current_idx..])?; .write_to_be_bytes(&mut buf[current_idx..])?;
for opt in self.options() { buf[current_idx..current_idx + self.options.len()].copy_from_slice(self.options);
opt.write_to_bytes(&mut buf[current_idx..current_idx + opt.len_full()])?; current_idx += self.options.len();
current_idx += opt.len_full();
}
if self.crc_flag() == CrcFlag::WithCrc { if self.crc_flag() == CrcFlag::WithCrc {
current_idx = add_pdu_crc(buf, current_idx); current_idx = add_pdu_crc(buf, current_idx);
} }
@ -354,7 +371,7 @@ pub mod tests {
}; };
use crate::cfdp::pdu::{CfdpPdu, PduError, WritablePduPacket}; use crate::cfdp::pdu::{CfdpPdu, PduError, WritablePduPacket};
use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; use crate::cfdp::pdu::{FileDirectiveType, PduHeader};
use crate::cfdp::tlv::{Tlv, TlvType}; use crate::cfdp::tlv::{ReadableTlv, Tlv, TlvOwned, TlvType, WritableTlv};
use crate::cfdp::{ use crate::cfdp::{
ChecksumType, CrcFlag, Direction, LargeFileFlag, PduType, SegmentMetadataFlag, ChecksumType, CrcFlag, Direction, LargeFileFlag, PduType, SegmentMetadataFlag,
SegmentationControl, TransmissionMode, SegmentationControl, TransmissionMode,
@ -364,16 +381,16 @@ pub mod tests {
const SRC_FILENAME: &str = "hello-world.txt"; const SRC_FILENAME: &str = "hello-world.txt";
const DEST_FILENAME: &str = "hello-world2.txt"; const DEST_FILENAME: &str = "hello-world2.txt";
fn generic_metadata_pdu<'opts>( fn generic_metadata_pdu(
crc_flag: CrcFlag, crc_flag: CrcFlag,
checksum_type: ChecksumType, checksum_type: ChecksumType,
closure_requested: bool, closure_requested: bool,
fss: LargeFileFlag, fss: LargeFileFlag,
opts: &'opts [Tlv], opts: &[u8],
) -> ( ) -> (
Lv<'static>, Lv<'static>,
Lv<'static>, Lv<'static>,
MetadataPduCreator<'static, 'static, 'opts>, MetadataPduCreator<'static, 'static, '_>,
) { ) {
let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0); let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0);
let metadata_params = MetadataGenericParams::new(closure_requested, checksum_type, 0x1010); let metadata_params = MetadataGenericParams::new(closure_requested, checksum_type, 0x1010);
@ -543,9 +560,9 @@ pub mod tests {
assert_eq!(written.metadata_params(), read.metadata_params()); assert_eq!(written.metadata_params(), read.metadata_params());
assert_eq!(written.src_file_name(), read.src_file_name()); assert_eq!(written.src_file_name(), read.src_file_name());
assert_eq!(written.dest_file_name(), read.dest_file_name()); assert_eq!(written.dest_file_name(), read.dest_file_name());
let opts = written.options(); let opts = written.options_iter();
for (tlv_written, tlv_read) in opts.iter().zip(read.options_iter().unwrap()) { for (tlv_written, tlv_read) in opts.zip(read.options_iter().unwrap()) {
assert_eq!(tlv_written, &tlv_read); assert_eq!(&tlv_written, &tlv_read);
} }
} }
@ -660,14 +677,14 @@ pub mod tests {
let tlv1 = Tlv::new_empty(TlvType::FlowLabel); let tlv1 = Tlv::new_empty(TlvType::FlowLabel);
let msg_to_user: [u8; 4] = [1, 2, 3, 4]; let msg_to_user: [u8; 4] = [1, 2, 3, 4];
let tlv2 = Tlv::new(TlvType::MsgToUser, &msg_to_user).unwrap(); let tlv2 = Tlv::new(TlvType::MsgToUser, &msg_to_user).unwrap();
let tlv_vec = vec![tlv1, tlv2]; let mut tlv_buf: [u8; 64] = [0; 64];
let opts_len = tlv1.len_full() + tlv2.len_full(); let opts_len = build_metadata_opts_from_slice(&mut tlv_buf, &[tlv1, tlv2]).unwrap();
let (src_filename, dest_filename, metadata_pdu) = generic_metadata_pdu( let (src_filename, dest_filename, metadata_pdu) = generic_metadata_pdu(
CrcFlag::NoCrc, CrcFlag::NoCrc,
ChecksumType::Crc32, ChecksumType::Crc32,
false, false,
LargeFileFlag::Normal, LargeFileFlag::Normal,
&tlv_vec, &tlv_buf[0..opts_len],
); );
let mut buf: [u8; 128] = [0; 128]; let mut buf: [u8; 128] = [0; 128];
let write_res = metadata_pdu.write_to_bytes(&mut buf); let write_res = metadata_pdu.write_to_bytes(&mut buf);
@ -690,7 +707,55 @@ pub mod tests {
let opts_iter = opts_iter.unwrap(); let opts_iter = opts_iter.unwrap();
let mut accumulated_len = 0; let mut accumulated_len = 0;
for (idx, opt) in opts_iter.enumerate() { for (idx, opt) in opts_iter.enumerate() {
assert_eq!(tlv_vec[idx], opt); if idx == 0 {
assert_eq!(tlv1, opt);
} else if idx == 1 {
assert_eq!(tlv2, opt);
}
accumulated_len += opt.len_full();
}
assert_eq!(accumulated_len, pdu_read_back.options().len());
}
#[test]
fn test_with_owned_opts() {
let tlv1 = TlvOwned::new_empty(TlvType::FlowLabel);
let msg_to_user: [u8; 4] = [1, 2, 3, 4];
let tlv2 = TlvOwned::new(TlvType::MsgToUser, &msg_to_user);
let mut all_tlvs = tlv1.to_vec();
all_tlvs.extend(tlv2.to_vec());
let (src_filename, dest_filename, metadata_pdu) = generic_metadata_pdu(
CrcFlag::NoCrc,
ChecksumType::Crc32,
false,
LargeFileFlag::Normal,
&all_tlvs,
);
let mut buf: [u8; 128] = [0; 128];
let write_res = metadata_pdu.write_to_bytes(&mut buf);
assert!(write_res.is_ok());
let written = write_res.unwrap();
assert_eq!(
written,
metadata_pdu.pdu_header.header_len()
+ 1
+ 1
+ 4
+ src_filename.len_full()
+ dest_filename.len_full()
+ all_tlvs.len()
);
let pdu_read_back = MetadataPduReader::from_bytes(&buf).unwrap();
compare_read_pdu_to_written_pdu(&metadata_pdu, &pdu_read_back);
let opts_iter = pdu_read_back.options_iter();
assert!(opts_iter.is_some());
let opts_iter = opts_iter.unwrap();
let mut accumulated_len = 0;
for (idx, opt) in opts_iter.enumerate() {
if idx == 0 {
assert_eq!(tlv1, opt);
} else if idx == 1 {
assert_eq!(tlv2, opt);
}
accumulated_len += opt.len_full(); accumulated_len += opt.len_full();
} }
assert_eq!(accumulated_len, pdu_read_back.options().len()); assert_eq!(accumulated_len, pdu_read_back.options().len());
@ -715,7 +780,7 @@ pub mod tests {
assert_eq!(expected, Some(FileDirectiveType::MetadataPdu)); assert_eq!(expected, Some(FileDirectiveType::MetadataPdu));
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"invalid directive type value 255, expected Some(MetadataPdu)" "invalid directive type, found 255, expected Some(MetadataPdu)"
); );
} else { } else {
panic!("Expected InvalidDirectiveType error, got {:?}", error); panic!("Expected InvalidDirectiveType error, got {:?}", error);
@ -741,7 +806,7 @@ pub mod tests {
assert_eq!(expected, FileDirectiveType::MetadataPdu); assert_eq!(expected, FileDirectiveType::MetadataPdu);
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"found directive type EofPdu, expected MetadataPdu" "wrong directive type, found EofPdu, expected MetadataPdu"
); );
} else { } else {
panic!("Expected InvalidDirectiveType error, got {:?}", error); panic!("Expected InvalidDirectiveType error, got {:?}", error);

@ -1,13 +1,10 @@
//! CFDP Packet Data Unit (PDU) support. //! CFDP Packet Data Unit (PDU) support.
use crate::cfdp::*; use crate::cfdp::*;
use crate::crc::CRC_CCITT_FALSE;
use crate::util::{UnsignedByteField, UnsignedByteFieldU8, UnsignedEnum}; use crate::util::{UnsignedByteField, UnsignedByteFieldU8, UnsignedEnum};
use crate::ByteConversionError; use crate::ByteConversionError;
use crate::CRC_CCITT_FALSE;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt::{Display, Formatter};
#[cfg(feature = "std")]
use std::error::Error;
pub mod ack; pub mod ack;
pub mod eof; pub mod eof;
@ -30,137 +27,62 @@ pub enum FileDirectiveType {
KeepAlivePdu = 0x0c, KeepAlivePdu = 0x0c,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PduError { pub enum PduError {
ByteConversion(ByteConversionError), #[error("byte conversion error: {0}")]
/// Found version ID invalid, not equal to [CFDP_VERSION_2]. ByteConversion(#[from] ByteConversionError),
/// Found version ID invalid, not equal to [super::CFDP_VERSION_2].
#[error("CFDP version missmatch, found {0}, expected {ver}", ver = super::CFDP_VERSION_2)]
CfdpVersionMissmatch(u8), CfdpVersionMissmatch(u8),
/// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported. /// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported.
#[error("invalid PDU entity ID length {0}, only [1, 2, 4, 8] are allowed")]
InvalidEntityLen(u8), InvalidEntityLen(u8),
/// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported. /// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported.
#[error("invalid transaction ID length {0}")]
InvalidTransactionSeqNumLen(u8), InvalidTransactionSeqNumLen(u8),
#[error(
"missmatch of PDU source ID length {src_id_len} and destination ID length {dest_id_len}"
)]
SourceDestIdLenMissmatch { SourceDestIdLenMissmatch {
src_id_len: usize, src_id_len: usize,
dest_id_len: usize, dest_id_len: usize,
}, },
/// Wrong directive type, for example when parsing the directive field for a file directive /// Wrong directive type, for example when parsing the directive field for a file directive
/// PDU. /// PDU.
#[error("wrong directive type, found {found:?}, expected {expected:?}")]
WrongDirectiveType { WrongDirectiveType {
found: FileDirectiveType, found: FileDirectiveType,
expected: FileDirectiveType, expected: FileDirectiveType,
}, },
/// The directive type field contained a value not in the range of permitted values. This can /// The directive type field contained a value not in the range of permitted values. This can
/// also happen if an invalid value is passed to the ACK PDU constructor. /// also happen if an invalid value is passed to the ACK PDU constructor.
#[error("invalid directive type, found {found:?}, expected {expected:?}")]
InvalidDirectiveType { InvalidDirectiveType {
found: u8, found: u8,
expected: Option<FileDirectiveType>, expected: Option<FileDirectiveType>,
}, },
#[error("invalid start or end of scope value for NAK PDU")]
InvalidStartOrEndOfScopeValue, InvalidStartOrEndOfScopeValue,
/// Invalid condition code. Contains the raw detected value. /// Invalid condition code. Contains the raw detected value.
#[error("invalid condition code {0}")]
InvalidConditionCode(u8), InvalidConditionCode(u8),
/// Invalid checksum type which is not part of the checksums listed in the /// Invalid checksum type which is not part of the checksums listed in the
/// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/). /// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/).
#[error("invalid checksum type {0}")]
InvalidChecksumType(u8), InvalidChecksumType(u8),
#[error("file size {0} too large")]
FileSizeTooLarge(u64), FileSizeTooLarge(u64),
/// If the CRC flag for a PDU is enabled and the checksum check fails. Contains raw 16-bit CRC. /// If the CRC flag for a PDU is enabled and the checksum check fails. Contains raw 16-bit CRC.
ChecksumError(u16), #[error("checksum error for checksum {0}")]
Checksum(u16),
/// Generic error for invalid PDU formats. /// Generic error for invalid PDU formats.
FormatError, #[error("generic PDU format error")]
Format,
/// Error handling a TLV field. /// Error handling a TLV field.
TlvLvError(TlvLvError), #[error("PDU error: {0}")]
} TlvLv(#[from] TlvLvError),
impl Display for PduError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
PduError::InvalidEntityLen(raw_id) => {
write!(
f,
"invalid PDU entity ID length {raw_id}, only [1, 2, 4, 8] are allowed"
)
}
PduError::InvalidStartOrEndOfScopeValue => {
write!(f, "invalid start or end of scope for NAK PDU")
}
PduError::InvalidTransactionSeqNumLen(raw_id) => {
write!(
f,
"invalid PDUtransaction seq num length {raw_id}, only [1, 2, 4, 8] are allowed"
)
}
PduError::CfdpVersionMissmatch(raw) => {
write!(
f,
"cfdp version missmatch, found {raw}, expected {CFDP_VERSION_2}"
)
}
PduError::SourceDestIdLenMissmatch {
src_id_len,
dest_id_len,
} => {
write!(
f,
"missmatch of PDU source length {src_id_len} and destination length {dest_id_len}"
)
}
PduError::ByteConversion(e) => {
write!(f, "{}", e)
}
PduError::FileSizeTooLarge(value) => {
write!(f, "file size value {value} exceeds allowed 32 bit width")
}
PduError::WrongDirectiveType { found, expected } => {
write!(f, "found directive type {found:?}, expected {expected:?}")
}
PduError::InvalidConditionCode(raw_code) => {
write!(f, "found invalid condition code with raw value {raw_code}")
}
PduError::InvalidDirectiveType { found, expected } => {
write!(
f,
"invalid directive type value {found}, expected {expected:?}"
)
}
PduError::InvalidChecksumType(checksum_type) => {
write!(f, "invalid checksum type {checksum_type}")
}
PduError::ChecksumError(checksum) => {
write!(f, "checksum error for CRC {checksum:#04x}")
}
PduError::TlvLvError(error) => {
write!(f, "pdu tlv error: {error}")
}
PduError::FormatError => {
write!(f, "generic PDU format error")
}
}
}
}
#[cfg(feature = "std")]
impl Error for PduError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
PduError::ByteConversion(e) => Some(e),
PduError::TlvLvError(e) => Some(e),
_ => None,
}
}
}
impl From<ByteConversionError> for PduError {
#[inline]
fn from(value: ByteConversionError) -> Self {
Self::ByteConversion(value)
}
}
impl From<TlvLvError> for PduError {
#[inline]
fn from(e: TlvLvError) -> Self {
Self::TlvLvError(e)
}
} }
pub trait WritablePduPacket { pub trait WritablePduPacket {
@ -532,7 +454,7 @@ impl PduHeader {
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&buf[..self.pdu_len()]); digest.update(&buf[..self.pdu_len()]);
if digest.finalize() != 0 { if digest.finalize() != 0 {
return Err(PduError::ChecksumError(u16::from_be_bytes( return Err(PduError::Checksum(u16::from_be_bytes(
buf[self.pdu_len() - 2..self.pdu_len()].try_into().unwrap(), buf[self.pdu_len() - 2..self.pdu_len()].try_into().unwrap(),
))); )));
} }
@ -981,7 +903,7 @@ mod tests {
assert_eq!(raw_version, CFDP_VERSION_2 + 1); assert_eq!(raw_version, CFDP_VERSION_2 + 1);
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"cfdp version missmatch, found 2, expected 1" "CFDP version missmatch, found 2, expected 1"
); );
} else { } else {
panic!("invalid exception: {}", error); panic!("invalid exception: {}", error);
@ -1029,7 +951,7 @@ mod tests {
assert_eq!(expected, 7); assert_eq!(expected, 7);
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"source slice with size 6 too small, expected at least 7 bytes" "byte conversion error: source slice with size 6 too small, expected at least 7 bytes"
); );
} }
} }
@ -1084,7 +1006,7 @@ mod tests {
assert_eq!(dest_id_len, 2); assert_eq!(dest_id_len, 2);
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"missmatch of PDU source length 1 and destination length 2" "missmatch of PDU source ID length 1 and destination ID length 2"
); );
} }
} }

@ -61,7 +61,7 @@ impl<'seg_reqs> NakPduCreator<'seg_reqs> {
start_of_scope: u32, start_of_scope: u32,
end_of_scope: u32, end_of_scope: u32,
segment_requests: &'seg_reqs [(u32, u32)], segment_requests: &'seg_reqs [(u32, u32)],
) -> Result<NakPduCreator, PduError> { ) -> Result<NakPduCreator<'seg_reqs>, PduError> {
let mut passed_segment_requests = None; let mut passed_segment_requests = None;
if !segment_requests.is_empty() { if !segment_requests.is_empty() {
passed_segment_requests = Some(SegmentRequests::U32Pairs(segment_requests)); passed_segment_requests = Some(SegmentRequests::U32Pairs(segment_requests));
@ -79,7 +79,7 @@ impl<'seg_reqs> NakPduCreator<'seg_reqs> {
start_of_scope: u64, start_of_scope: u64,
end_of_scope: u64, end_of_scope: u64,
segment_requests: &'seg_reqs [(u64, u64)], segment_requests: &'seg_reqs [(u64, u64)],
) -> Result<NakPduCreator, PduError> { ) -> Result<NakPduCreator<'seg_reqs>, PduError> {
let mut passed_segment_requests = None; let mut passed_segment_requests = None;
if !segment_requests.is_empty() { if !segment_requests.is_empty() {
passed_segment_requests = Some(SegmentRequests::U64Pairs(segment_requests)); passed_segment_requests = Some(SegmentRequests::U64Pairs(segment_requests));
@ -97,7 +97,7 @@ impl<'seg_reqs> NakPduCreator<'seg_reqs> {
start_of_scope: u64, start_of_scope: u64,
end_of_scope: u64, end_of_scope: u64,
segment_requests: Option<SegmentRequests<'seg_reqs>>, segment_requests: Option<SegmentRequests<'seg_reqs>>,
) -> Result<NakPduCreator, PduError> { ) -> Result<NakPduCreator<'seg_reqs>, PduError> {
// Force correct direction flag. // Force correct direction flag.
pdu_header.pdu_conf.direction = Direction::TowardsSender; pdu_header.pdu_conf.direction = Direction::TowardsSender;
if let Some(ref segment_requests) = segment_requests { if let Some(ref segment_requests) = segment_requests {
@ -269,7 +269,7 @@ impl SegReqFromBytes for u64 {
} }
} }
impl<'a, T> Iterator for SegmentRequestIter<'a, T> impl<T> Iterator for SegmentRequestIter<'_, T>
where where
T: SegReqFromBytes, T: SegReqFromBytes,
{ {
@ -282,8 +282,8 @@ where
} }
} }
impl<'a, 'b> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'b, u32> { impl<'a> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'_, u32> {
fn eq(&self, other: &SegmentRequests) -> bool { fn eq(&self, other: &SegmentRequests<'a>) -> bool {
match other { match other {
SegmentRequests::U32Pairs(pairs) => self.compare_pairs(pairs), SegmentRequests::U32Pairs(pairs) => self.compare_pairs(pairs),
SegmentRequests::U64Pairs(pairs) => { SegmentRequests::U64Pairs(pairs) => {
@ -296,8 +296,8 @@ impl<'a, 'b> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'b, u32> {
} }
} }
impl<'a, 'b> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'b, u64> { impl<'a> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'_, u64> {
fn eq(&self, other: &SegmentRequests) -> bool { fn eq(&self, other: &SegmentRequests<'a>) -> bool {
match other { match other {
SegmentRequests::U32Pairs(pairs) => { SegmentRequests::U32Pairs(pairs) => {
if pairs.is_empty() && self.seq_req_raw.is_empty() { if pairs.is_empty() && self.seq_req_raw.is_empty() {
@ -310,7 +310,7 @@ impl<'a, 'b> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'b, u64> {
} }
} }
impl<'a, T> SegmentRequestIter<'a, T> impl<T> SegmentRequestIter<'_, T>
where where
T: SegReqFromBytes + PartialEq, T: SegReqFromBytes + PartialEq,
{ {
@ -374,11 +374,11 @@ impl CfdpPdu for NakPduReader<'_> {
} }
impl<'seg_reqs> NakPduReader<'seg_reqs> { impl<'seg_reqs> NakPduReader<'seg_reqs> {
pub fn new(buf: &'seg_reqs [u8]) -> Result<NakPduReader, PduError> { pub fn new(buf: &'seg_reqs [u8]) -> Result<NakPduReader<'seg_reqs>, PduError> {
Self::from_bytes(buf) Self::from_bytes(buf)
} }
pub fn from_bytes(buf: &'seg_reqs [u8]) -> Result<NakPduReader, PduError> { pub fn from_bytes(buf: &'seg_reqs [u8]) -> Result<NakPduReader<'seg_reqs>, PduError> {
let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?;
let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?;
// Minimum length of 9: 1 byte directive field and start and end of scope for normal file // Minimum length of 9: 1 byte directive field and start and end of scope for normal file
@ -474,7 +474,7 @@ impl<'seg_reqs> NakPduReader<'seg_reqs> {
} }
} }
impl<'a, 'b> PartialEq<NakPduCreator<'a>> for NakPduReader<'b> { impl<'a> PartialEq<NakPduCreator<'a>> for NakPduReader<'_> {
fn eq(&self, other: &NakPduCreator<'a>) -> bool { fn eq(&self, other: &NakPduCreator<'a>) -> bool {
if self.pdu_header() != other.pdu_header() if self.pdu_header() != other.pdu_header()
|| self.end_of_scope() != other.end_of_scope() || self.end_of_scope() != other.end_of_scope()
@ -751,7 +751,7 @@ mod tests {
if let PduError::InvalidStartOrEndOfScopeValue = error { if let PduError::InvalidStartOrEndOfScopeValue = error {
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"invalid start or end of scope for NAK PDU" "invalid start or end of scope value for NAK PDU"
); );
} else { } else {
panic!("unexpected error {error}"); panic!("unexpected error {error}");
@ -796,7 +796,7 @@ mod tests {
nak_vec[nak_pdu.len_written() - 1] -= 1; nak_vec[nak_pdu.len_written() - 1] -= 1;
let nak_pdu_deser = NakPduReader::new(&nak_vec); let nak_pdu_deser = NakPduReader::new(&nak_vec);
assert!(nak_pdu_deser.is_err()); assert!(nak_pdu_deser.is_err());
if let Err(PduError::ChecksumError(raw)) = nak_pdu_deser { if let Err(PduError::Checksum(raw)) = nak_pdu_deser {
assert_eq!( assert_eq!(
raw, raw,
u16::from_be_bytes(nak_vec[nak_pdu.len_written() - 2..].try_into().unwrap()) u16::from_be_bytes(nak_vec[nak_pdu.len_written() - 2..].try_into().unwrap())

@ -9,10 +9,14 @@ use crate::ByteConversionError;
use alloc::vec; use alloc::vec;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{InvalidTlvTypeFieldError, TlvLvDataTooLargeError};
pub mod msg_to_user; pub mod msg_to_user;
pub const MIN_TLV_LEN: usize = 2; pub const MIN_TLV_LEN: usize = 2;
@ -39,6 +43,26 @@ pub trait GenericTlv {
} }
} }
pub trait ReadableTlv {
fn value(&self) -> &[u8];
/// Checks whether the value field is empty.
fn is_empty(&self) -> bool {
self.value().is_empty()
}
/// Helper method to retrieve the length of the value. Simply calls the [slice::len] method of
/// [Self::value]
fn len_value(&self) -> usize {
self.value().len()
}
/// Returns the full raw length, including the length byte.
fn len_full(&self) -> usize {
self.len_value() + 2
}
}
pub trait WritableTlv { pub trait WritableTlv {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>; fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
fn len_written(&self) -> usize; fn len_written(&self) -> usize;
@ -129,14 +153,14 @@ pub struct Tlv<'data> {
} }
impl<'data> Tlv<'data> { impl<'data> Tlv<'data> {
pub fn new(tlv_type: TlvType, data: &[u8]) -> Result<Tlv, TlvLvError> { pub fn new(tlv_type: TlvType, data: &[u8]) -> Result<Tlv, TlvLvDataTooLargeError> {
Ok(Tlv { Ok(Tlv {
tlv_type_field: TlvTypeField::Standard(tlv_type), tlv_type_field: TlvTypeField::Standard(tlv_type),
lv: Lv::new(data)?, lv: Lv::new(data)?,
}) })
} }
pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Result<Tlv, TlvLvError> { pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Result<Tlv, TlvLvDataTooLargeError> {
Ok(Tlv { Ok(Tlv {
tlv_type_field: TlvTypeField::Custom(tlv_type), tlv_type_field: TlvTypeField::Custom(tlv_type),
lv: Lv::new(data)?, lv: Lv::new(data)?,
@ -151,31 +175,11 @@ impl<'data> Tlv<'data> {
} }
} }
pub fn value(&self) -> &[u8] {
self.lv.value()
}
/// Checks whether the value field is empty.
pub fn is_empty(&self) -> bool {
self.value().is_empty()
}
/// Helper method to retrieve the length of the value. Simply calls the [slice::len] method of
/// [Self::value]
pub fn len_value(&self) -> usize {
self.value().len()
}
/// Returns the full raw length, including the length byte.
pub fn len_full(&self) -> usize {
self.len_value() + 2
}
/// Creates a TLV give a raw bytestream. Please note that is is not necessary to pass the /// Creates a TLV give a raw bytestream. Please note that is is not necessary to pass the
/// bytestream with the exact size of the expected TLV. This function will take care /// bytestream with the exact size of the expected TLV. This function will take care
/// of parsing the length byte, and the length of the parsed TLV can be retrieved using /// of parsing the length byte, and the length of the parsed TLV can be retrieved using
/// [Self::len_full]. /// [Self::len_full].
pub fn from_bytes(buf: &'data [u8]) -> Result<Tlv<'data>, TlvLvError> { pub fn from_bytes(buf: &'data [u8]) -> Result<Tlv<'data>, ByteConversionError> {
generic_len_check_deserialization(buf, MIN_TLV_LEN)?; generic_len_check_deserialization(buf, MIN_TLV_LEN)?;
let mut tlv = Self { let mut tlv = Self {
tlv_type_field: TlvTypeField::from(buf[0]), tlv_type_field: TlvTypeField::from(buf[0]),
@ -192,6 +196,27 @@ impl<'data> Tlv<'data> {
pub fn raw_data(&self) -> Option<&[u8]> { pub fn raw_data(&self) -> Option<&[u8]> {
self.lv.raw_data() self.lv.raw_data()
} }
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
TlvOwned {
tlv_type_field: self.tlv_type_field,
data: self.value().to_vec(),
}
}
}
#[cfg(feature = "alloc")]
impl PartialEq<TlvOwned> for Tlv<'_> {
fn eq(&self, other: &TlvOwned) -> bool {
self.tlv_type_field == other.tlv_type_field && self.value() == other.value()
}
}
impl ReadableTlv for Tlv<'_> {
fn value(&self) -> &[u8] {
self.lv.value()
}
} }
impl WritableTlv for Tlv<'_> { impl WritableTlv for Tlv<'_> {
@ -212,18 +237,90 @@ impl GenericTlv for Tlv<'_> {
} }
} }
pub(crate) fn verify_tlv_type(raw_type: u8, expected_tlv_type: TlvType) -> Result<(), TlvLvError> { #[cfg(feature = "alloc")]
let tlv_type = TlvType::try_from(raw_type).map_err(|_| TlvLvError::InvalidTlvTypeField { pub mod alloc_mod {
found: raw_type, use super::*;
expected: Some(expected_tlv_type.into()),
})?; /// Owned variant of [Tlv] which is consequently [Clone]able and does not have a lifetime
if tlv_type != expected_tlv_type { /// associated to a data slice.
return Err(TlvLvError::InvalidTlvTypeField { #[derive(Debug, Clone, PartialEq, Eq)]
found: tlv_type as u8, #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
expected: Some(expected_tlv_type as u8), #[cfg_attr(feature = "defmt", derive(defmt::Format))]
}); pub struct TlvOwned {
pub(crate) tlv_type_field: TlvTypeField,
pub(crate) data: Vec<u8>,
}
impl TlvOwned {
pub fn new(tlv_type: TlvType, data: &[u8]) -> Self {
Self {
tlv_type_field: TlvTypeField::Standard(tlv_type),
data: data.to_vec(),
}
}
pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Self {
Self {
tlv_type_field: TlvTypeField::Custom(tlv_type),
data: data.to_vec(),
}
}
/// Creates a TLV with an empty value field.
pub fn new_empty(tlv_type: TlvType) -> Self {
Self {
tlv_type_field: TlvTypeField::Standard(tlv_type),
data: Vec::new(),
}
}
pub fn as_tlv(&self) -> Tlv<'_> {
Tlv {
tlv_type_field: self.tlv_type_field,
// The API should ensure that the data length is never to large, so the unwrap for the
// LV creation should never be an issue.
lv: Lv::new(&self.data).expect("lv creation failed unexpectedly"),
}
}
}
impl ReadableTlv for TlvOwned {
fn value(&self) -> &[u8] {
&self.data
}
}
impl WritableTlv for TlvOwned {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
generic_len_check_data_serialization(buf, self.data.len(), MIN_TLV_LEN)?;
buf[0] = self.tlv_type_field.into();
buf[1] = self.data.len() as u8;
buf[2..2 + self.data.len()].copy_from_slice(&self.data);
Ok(self.len_written())
}
fn len_written(&self) -> usize {
self.data.len() + 2
}
}
impl GenericTlv for TlvOwned {
fn tlv_type_field(&self) -> TlvTypeField {
self.tlv_type_field
}
}
impl From<Tlv<'_>> for TlvOwned {
fn from(value: Tlv<'_>) -> Self {
value.to_owned()
}
}
impl PartialEq<Tlv<'_>> for TlvOwned {
fn eq(&self, other: &Tlv) -> bool {
self.tlv_type_field == other.tlv_type_field && self.data == other.value()
}
} }
Ok(())
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -277,11 +374,8 @@ impl EntityIdTlv {
Self::len_check(buf)?; Self::len_check(buf)?;
self.entity_id self.entity_id
.write_to_be_bytes(&mut buf[2..2 + self.entity_id.size()])?; .write_to_be_bytes(&mut buf[2..2 + self.entity_id.size()])?;
Tlv::new(TlvType::EntityId, &buf[2..2 + self.entity_id.size()]).map_err(|e| match e { // Can't fail.
TlvLvError::ByteConversion(e) => e, Ok(Tlv::new(TlvType::EntityId, &buf[2..2 + self.entity_id.size()]).unwrap())
// All other errors are impossible.
_ => panic!("unexpected TLV error"),
})
} }
} }
@ -304,24 +398,26 @@ impl GenericTlv for EntityIdTlv {
} }
} }
impl<'data> TryFrom<Tlv<'data>> for EntityIdTlv { impl TryFrom<Tlv<'_>> for EntityIdTlv {
type Error = TlvLvError; type Error = TlvLvError;
fn try_from(value: Tlv) -> Result<Self, Self::Error> { fn try_from(value: Tlv) -> Result<Self, TlvLvError> {
match value.tlv_type_field { match value.tlv_type_field {
TlvTypeField::Standard(tlv_type) => { TlvTypeField::Standard(tlv_type) => {
if tlv_type != TlvType::EntityId { if tlv_type != TlvType::EntityId {
return Err(TlvLvError::InvalidTlvTypeField { return Err(InvalidTlvTypeFieldError {
found: tlv_type as u8, found: tlv_type as u8,
expected: Some(TlvType::EntityId as u8), expected: Some(TlvType::EntityId as u8),
}); }
.into());
} }
} }
TlvTypeField::Custom(val) => { TlvTypeField::Custom(val) => {
return Err(TlvLvError::InvalidTlvTypeField { return Err(InvalidTlvTypeFieldError {
found: val, found: val,
expected: Some(TlvType::EntityId as u8), expected: Some(TlvType::EntityId as u8),
}); }
.into());
} }
} }
let len_value = value.value().len(); let len_value = value.value().len();
@ -752,6 +848,23 @@ impl GenericTlv for FilestoreResponseTlv<'_, '_, '_> {
} }
} }
pub(crate) fn verify_tlv_type(
raw_type: u8,
expected_tlv_type: TlvType,
) -> Result<(), InvalidTlvTypeFieldError> {
let tlv_type = TlvType::try_from(raw_type).map_err(|_| InvalidTlvTypeFieldError {
found: raw_type,
expected: Some(expected_tlv_type.into()),
})?;
if tlv_type != expected_tlv_type {
return Err(InvalidTlvTypeFieldError {
found: tlv_type as u8,
expected: Some(expected_tlv_type as u8),
});
}
Ok(())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -939,14 +1052,14 @@ mod tests {
let tlv_res = Tlv::new(TlvType::MsgToUser, &buf_too_large); let tlv_res = Tlv::new(TlvType::MsgToUser, &buf_too_large);
assert!(tlv_res.is_err()); assert!(tlv_res.is_err());
let error = tlv_res.unwrap_err(); let error = tlv_res.unwrap_err();
if let TlvLvError::DataTooLarge(size) = error { match error {
assert_eq!(size, u8::MAX as usize + 1); TlvLvDataTooLargeError(size) => {
assert_eq!( assert_eq!(size, u8::MAX as usize + 1);
error.to_string(), assert_eq!(
"data with size 256 larger than allowed 255 bytes" error.to_string(),
); "data with size 256 larger than allowed 255 bytes"
} else { );
panic!("unexpected error {:?}", error); }
} }
} }
@ -1256,7 +1369,8 @@ mod tests {
let error = EntityIdTlv::try_from(msg_to_user_tlv); let error = EntityIdTlv::try_from(msg_to_user_tlv);
assert!(error.is_err()); assert!(error.is_err());
let error = error.unwrap_err(); let error = error.unwrap_err();
if let TlvLvError::InvalidTlvTypeField { found, expected } = error { if let TlvLvError::InvalidTlvTypeField(InvalidTlvTypeFieldError { found, expected }) = error
{
assert_eq!(found, TlvType::MsgToUser as u8); assert_eq!(found, TlvType::MsgToUser as u8);
assert_eq!(expected, Some(TlvType::EntityId as u8)); assert_eq!(expected, Some(TlvType::EntityId as u8));
assert_eq!( assert_eq!(
@ -1300,4 +1414,71 @@ mod tests {
assert_eq!(tlv_as_vec[0], 20); assert_eq!(tlv_as_vec[0], 20);
assert_eq!(tlv_as_vec[1], 0); assert_eq!(tlv_as_vec[1], 0);
} }
#[test]
fn test_tlv_to_owned() {
let entity_id = UbfU8::new(5);
let mut buf: [u8; 4] = [0; 4];
assert!(entity_id.write_to_be_bytes(&mut buf).is_ok());
let tlv_res = Tlv::new(TlvType::EntityId, &buf[0..1]);
assert!(tlv_res.is_ok());
let tlv_res = tlv_res.unwrap();
let tlv_owned = tlv_res.to_owned();
assert_eq!(tlv_res, tlv_owned);
let tlv_owned_from_conversion: TlvOwned = tlv_res.into();
assert_eq!(tlv_owned_from_conversion, tlv_owned);
assert_eq!(tlv_owned_from_conversion, tlv_res);
}
#[test]
fn test_owned_tlv() {
let entity_id = UbfU8::new(5);
let mut buf: [u8; 4] = [0; 4];
assert!(entity_id.write_to_be_bytes(&mut buf).is_ok());
let tlv_res = TlvOwned::new(TlvType::EntityId, &buf[0..1]);
assert_eq!(
tlv_res.tlv_type_field(),
TlvTypeField::Standard(TlvType::EntityId)
);
assert_eq!(tlv_res.len_full(), 3);
assert_eq!(tlv_res.value().len(), 1);
assert_eq!(tlv_res.len_value(), 1);
assert!(!tlv_res.is_empty());
assert_eq!(tlv_res.value()[0], 5);
}
#[test]
fn test_owned_tlv_empty() {
let tlv_res = TlvOwned::new_empty(TlvType::FlowLabel);
assert_eq!(
tlv_res.tlv_type_field(),
TlvTypeField::Standard(TlvType::FlowLabel)
);
assert_eq!(tlv_res.len_full(), 2);
assert_eq!(tlv_res.value().len(), 0);
assert_eq!(tlv_res.len_value(), 0);
assert!(tlv_res.is_empty());
}
#[test]
fn test_owned_tlv_custom_type() {
let tlv_res = TlvOwned::new_with_custom_type(32, &[]);
assert_eq!(tlv_res.tlv_type_field(), TlvTypeField::Custom(32));
assert_eq!(tlv_res.len_full(), 2);
assert_eq!(tlv_res.value().len(), 0);
assert_eq!(tlv_res.len_value(), 0);
assert!(tlv_res.is_empty());
}
#[test]
fn test_owned_tlv_conversion_to_bytes() {
let entity_id = UbfU8::new(5);
let mut buf: [u8; 4] = [0; 4];
assert!(entity_id.write_to_be_bytes(&mut buf).is_ok());
let tlv_res = Tlv::new(TlvType::EntityId, &buf[0..1]);
assert!(tlv_res.is_ok());
let tlv_res = tlv_res.unwrap();
let tlv_owned_from_conversion: TlvOwned = tlv_res.into();
assert_eq!(tlv_res.to_vec(), tlv_owned_from_conversion.to_vec());
}
} }

@ -1,6 +1,11 @@
//! Abstractions for the Message to User CFDP TLV subtype. //! Abstractions for the Message to User CFDP TLV subtype.
use super::{GenericTlv, Tlv, TlvLvError, TlvType, TlvTypeField, WritableTlv}; #[cfg(feature = "alloc")]
use crate::ByteConversionError; use super::TlvOwned;
use super::{GenericTlv, ReadableTlv, Tlv, TlvLvError, TlvType, TlvTypeField, WritableTlv};
use crate::{
cfdp::{InvalidTlvTypeFieldError, TlvLvDataTooLargeError},
ByteConversionError,
};
use delegate::delegate; use delegate::delegate;
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -10,7 +15,7 @@ pub struct MsgToUserTlv<'data> {
impl<'data> MsgToUserTlv<'data> { impl<'data> MsgToUserTlv<'data> {
/// Create a new message to user TLV where the type field is set correctly. /// Create a new message to user TLV where the type field is set correctly.
pub fn new(value: &'data [u8]) -> Result<MsgToUserTlv<'data>, TlvLvError> { pub fn new(value: &'data [u8]) -> Result<MsgToUserTlv<'data>, TlvLvDataTooLargeError> {
Ok(Self { Ok(Self {
tlv: Tlv::new(TlvType::MsgToUser, value)?, tlv: Tlv::new(TlvType::MsgToUser, value)?,
}) })
@ -60,21 +65,38 @@ impl<'data> MsgToUserTlv<'data> {
match msg_to_user.tlv.tlv_type_field() { match msg_to_user.tlv.tlv_type_field() {
TlvTypeField::Standard(tlv_type) => { TlvTypeField::Standard(tlv_type) => {
if tlv_type != TlvType::MsgToUser { if tlv_type != TlvType::MsgToUser {
return Err(TlvLvError::InvalidTlvTypeField { return Err(InvalidTlvTypeFieldError {
found: tlv_type as u8, found: tlv_type as u8,
expected: Some(TlvType::MsgToUser as u8), expected: Some(TlvType::MsgToUser as u8),
}); }
.into());
} }
} }
TlvTypeField::Custom(raw) => { TlvTypeField::Custom(raw) => {
return Err(TlvLvError::InvalidTlvTypeField { return Err(InvalidTlvTypeFieldError {
found: raw, found: raw,
expected: Some(TlvType::MsgToUser as u8), expected: Some(TlvType::MsgToUser as u8),
}); }
.into());
} }
} }
Ok(msg_to_user) Ok(msg_to_user)
} }
pub fn to_tlv(&self) -> Tlv<'data> {
self.tlv
}
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
self.tlv.to_owned()
}
}
impl<'a> From<MsgToUserTlv<'a>> for Tlv<'a> {
fn from(value: MsgToUserTlv<'a>) -> Tlv<'a> {
value.to_tlv()
}
} }
impl WritableTlv for MsgToUserTlv<'_> { impl WritableTlv for MsgToUserTlv<'_> {
@ -139,6 +161,40 @@ mod tests {
); );
} }
#[test]
fn test_msg_to_user_type_reduction() {
let custom_value: [u8; 4] = [1, 2, 3, 4];
let msg_to_user = MsgToUserTlv::new(&custom_value).unwrap();
let tlv = msg_to_user.to_tlv();
assert_eq!(
tlv.tlv_type_field(),
TlvTypeField::Standard(TlvType::MsgToUser)
);
assert_eq!(tlv.value(), custom_value);
}
#[test]
fn test_msg_to_user_to_tlv() {
let custom_value: [u8; 4] = [1, 2, 3, 4];
let msg_to_user = MsgToUserTlv::new(&custom_value).unwrap();
let tlv: Tlv = msg_to_user.into();
assert_eq!(msg_to_user.to_tlv(), tlv);
}
#[test]
fn test_msg_to_user_owner_converter() {
let custom_value: [u8; 4] = [1, 2, 3, 4];
let msg_to_user = MsgToUserTlv::new(&custom_value).unwrap();
let tlv = msg_to_user.to_owned();
assert_eq!(
tlv.tlv_type_field(),
TlvTypeField::Standard(TlvType::MsgToUser)
);
assert_eq!(tlv.value(), custom_value);
}
#[test] #[test]
fn test_reserved_msg_deserialization() { fn test_reserved_msg_deserialization() {
let custom_value: [u8; 3] = [1, 2, 3]; let custom_value: [u8; 3] = [1, 2, 3];
@ -154,9 +210,9 @@ mod tests {
fn test_reserved_msg_deserialization_invalid_type() { fn test_reserved_msg_deserialization_invalid_type() {
let trash: [u8; 5] = [TlvType::FlowLabel as u8, 3, 1, 2, 3]; let trash: [u8; 5] = [TlvType::FlowLabel as u8, 3, 1, 2, 3];
let error = MsgToUserTlv::from_bytes(&trash).unwrap_err(); let error = MsgToUserTlv::from_bytes(&trash).unwrap_err();
if let TlvLvError::InvalidTlvTypeField { found, expected } = error { if let TlvLvError::InvalidTlvTypeField(inner) = error {
assert_eq!(found, TlvType::FlowLabel as u8); assert_eq!(inner.found, TlvType::FlowLabel as u8);
assert_eq!(expected, Some(TlvType::MsgToUser as u8)); assert_eq!(inner.expected, Some(TlvType::MsgToUser as u8));
} else { } else {
panic!("Wrong error type returned: {:?}", error); panic!("Wrong error type returned: {:?}", error);
} }

11
src/crc.rs Normal file

@ -0,0 +1,11 @@
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard, using
/// a [crc::NoTable] as the CRC implementation.
pub const CRC_CCITT_FALSE_NO_TABLE: crc::Crc<u16, crc::NoTable> =
crc::Crc::<u16, crc::NoTable>::new(&crc::CRC_16_IBM_3740);
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard, using
/// [crc::Table<1>] as the CRC implementation.
pub const CRC_CCITT_FALSE: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_3740);
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard, using
/// a [crc::Table<16>] large table as the CRC implementation.
pub const CRC_CCITT_FALSE_BIG_TABLE: crc::Crc<u16, crc::Table<16>> =
crc::Crc::<u16, crc::Table<16>>::new(&crc::CRC_16_IBM_3740);

@ -3,16 +3,17 @@
//! //!
//! You can find the PUS telecommand types in the [tc] module and the the PUS telemetry //! You can find the PUS telecommand types in the [tc] module and the the PUS telemetry
//! types inside the [tm] module. //! types inside the [tm] module.
use crate::{ByteConversionError, CcsdsPacket, CRC_CCITT_FALSE}; use crate::{
crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE},
ByteConversionError, CcsdsPacket,
};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt::{Debug, Display, Formatter}; use core::fmt::Debug;
use core::mem::size_of; use core::mem::size_of;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
pub mod event; pub mod event;
pub mod hk; pub mod hk;
@ -148,50 +149,19 @@ pub enum PfcReal {
DoubleMilStd = 4, DoubleMilStd = 4,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PusError { pub enum PusError {
#[error("PUS version {0:?} not supported")]
VersionNotSupported(PusVersion), VersionNotSupported(PusVersion),
#[error("checksum verification for crc16 {0:#06x} failed")]
ChecksumFailure(u16), ChecksumFailure(u16),
/// CRC16 needs to be calculated first /// CRC16 needs to be calculated first
CrcCalculationMissing, //#[error("crc16 was not calculated")]
ByteConversion(ByteConversionError), //CrcCalculationMissing,
} #[error("pus error: {0}")]
ByteConversion(#[from] ByteConversionError),
impl Display for PusError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
PusError::VersionNotSupported(v) => {
write!(f, "PUS version {v:?} not supported")
}
PusError::ChecksumFailure(crc) => {
write!(f, "checksum verification for crc16 {crc:#06x} failed")
}
PusError::CrcCalculationMissing => {
write!(f, "crc16 was not calculated")
}
PusError::ByteConversion(e) => {
write!(f, "pus error: {e}")
}
}
}
}
#[cfg(feature = "std")]
impl Error for PusError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let PusError::ByteConversion(e) = self {
return Some(e);
}
None
}
}
impl From<ByteConversionError> for PusError {
fn from(e: ByteConversionError) -> Self {
PusError::ByteConversion(e)
}
} }
/// Generic trait to describe common attributes for both PUS Telecommands (TC) and PUS Telemetry /// Generic trait to describe common attributes for both PUS Telecommands (TC) and PUS Telemetry
@ -203,7 +173,7 @@ pub trait PusPacket: CcsdsPacket {
fn service(&self) -> u8; fn service(&self) -> u8;
fn subservice(&self) -> u8; fn subservice(&self) -> u8;
fn user_data(&self) -> &[u8]; fn user_data(&self) -> &[u8];
fn crc16(&self) -> Option<u16>; fn opt_crc16(&self) -> Option<u16>;
} }
pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> { pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> {
@ -240,7 +210,7 @@ pub(crate) fn user_data_from_raw(
} }
} }
pub(crate) fn verify_crc16_ccitt_false_from_raw_to_pus_error( pub fn verify_crc16_ccitt_false_from_raw_to_pus_error(
raw_data: &[u8], raw_data: &[u8],
crc16: u16, crc16: u16,
) -> Result<(), PusError> { ) -> Result<(), PusError> {
@ -249,6 +219,15 @@ pub(crate) fn verify_crc16_ccitt_false_from_raw_to_pus_error(
.ok_or(PusError::ChecksumFailure(crc16)) .ok_or(PusError::ChecksumFailure(crc16))
} }
pub fn verify_crc16_ccitt_false_from_raw_to_pus_error_no_table(
raw_data: &[u8],
crc16: u16,
) -> Result<(), PusError> {
verify_crc16_ccitt_false_from_raw_no_table(raw_data)
.then_some(())
.ok_or(PusError::ChecksumFailure(crc16))
}
pub(crate) fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool { pub(crate) fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool {
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
digest.update(raw_data); digest.update(raw_data);
@ -258,6 +237,15 @@ pub(crate) fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool {
false false
} }
pub(crate) fn verify_crc16_ccitt_false_from_raw_no_table(raw_data: &[u8]) -> bool {
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
digest.update(raw_data);
if digest.finalize() == 0 {
return true;
}
false
}
macro_rules! ccsds_impl { macro_rules! ccsds_impl {
() => { () => {
delegate!(to self.sp_header { delegate!(to self.sp_header {
@ -382,7 +370,34 @@ generic_ecss_enum_typedefs_and_from_impls! {
/// on the serialization of those packets. /// on the serialization of those packets.
pub trait WritablePusPacket { pub trait WritablePusPacket {
fn len_written(&self) -> usize; fn len_written(&self) -> usize;
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError>;
/// Writes the packet to the given slice without writing the CRC.
///
/// The returned size is the written size WITHOUT the CRC.
fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError>;
/// First uses [Self::write_to_bytes_no_crc] to write the packet to the given slice and then
/// uses the [CRC_CCITT_FALS] to calculate the CRC and write it to the slice.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?;
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx)
}
/// First uses [Self::write_to_bytes_no_crc] to write the packet to the given slice and then
/// uses the [CRC_CCITT_FALSE_NO_TABLE] to calculate the CRC and write it to the slice.
fn write_to_bytes_crc_no_table(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?;
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx)
}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> Result<Vec<u8>, PusError> { fn to_vec(&self) -> Result<Vec<u8>, PusError> {
// This is the correct way to do this. See // This is the correct way to do this. See

@ -33,23 +33,26 @@
//! assert_eq!(pus_tc.subservice(), 1); //! assert_eq!(pus_tc.subservice(), 1);
//! assert_eq!(pus_tc.apid(), 0x02); //! assert_eq!(pus_tc.apid(), 0x02);
//! ``` //! ```
use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE};
use crate::ecss::{ use crate::ecss::{
ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw, ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw,
verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion, verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion,
WritablePusPacket, WritablePusPacket,
}; };
use crate::SpHeader;
use crate::{ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, CCSDS_HEADER_LEN}; use crate::{ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, CCSDS_HEADER_LEN};
use crate::{SpHeader, CRC_CCITT_FALSE};
use core::mem::size_of; use core::mem::size_of;
use delegate::delegate; use delegate::delegate;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zerocopy::AsBytes; use zerocopy::{FromBytes, IntoBytes};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
use super::verify_crc16_ccitt_false_from_raw_to_pus_error_no_table;
/// PUS C secondary header length is fixed /// PUS C secondary header length is fixed
pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::<zc::PusTcSecondaryHeader>(); pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::<zc::PusTcSecondaryHeader>();
pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize = pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize =
@ -86,9 +89,9 @@ pub trait GenericPusTcSecondaryHeader {
pub mod zc { pub mod zc {
use crate::ecss::tc::GenericPusTcSecondaryHeader; use crate::ecss::tc::GenericPusTcSecondaryHeader;
use crate::ecss::{PusError, PusVersion}; use crate::ecss::{PusError, PusVersion};
use zerocopy::{AsBytes, FromBytes, FromZeroes, NetworkEndian, Unaligned, U16}; use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16};
#[derive(FromZeroes, FromBytes, AsBytes, Unaligned)] #[derive(FromBytes, IntoBytes, Immutable, Unaligned)]
#[repr(C)] #[repr(C)]
pub struct PusTcSecondaryHeader { pub struct PusTcSecondaryHeader {
version_ack: u8, version_ack: u8,
@ -115,7 +118,7 @@ pub mod zc {
impl GenericPusTcSecondaryHeader for PusTcSecondaryHeader { impl GenericPusTcSecondaryHeader for PusTcSecondaryHeader {
#[inline] #[inline]
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)
} }
#[inline] #[inline]
@ -138,16 +141,6 @@ pub mod zc {
self.source_id.get() self.source_id.get()
} }
} }
impl PusTcSecondaryHeader {
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Option<()> {
self.write_to(slice)
}
pub fn from_bytes(slice: &[u8]) -> Option<Self> {
Self::read_from(slice)
}
}
} }
#[derive(PartialEq, Eq, Copy, Clone, Debug)] #[derive(PartialEq, Eq, Copy, Clone, Debug)]
@ -249,13 +242,13 @@ impl<'app_data> PusTcCreator<'app_data> {
/// # Arguments /// # Arguments
/// ///
/// * `sp_header` - Space packet header information. The correct packet type and the secondary /// * `sp_header` - Space packet header information. The correct packet type and the secondary
/// header flag are set correctly by the constructor. /// header flag are set correctly by the constructor.
/// * `sec_header` - Information contained in the data field header, including the service /// * `sec_header` - Information contained in the data field header, including the service
/// and subservice type /// and subservice type
/// * `app_data` - Custom application data /// * `app_data` - Custom application data
/// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length /// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length
/// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set /// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set
/// the correct value to this field manually /// the correct value to this field manually
#[inline] #[inline]
pub fn new( pub fn new(
mut sp_header: SpHeader, mut sp_header: SpHeader,
@ -352,6 +345,17 @@ impl<'app_data> PusTcCreator<'app_data> {
digest.finalize() digest.finalize()
} }
/// This function calculates and returns the CRC16 for the current packet.
pub fn calc_own_crc16_no_table(&self) -> u16 {
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
digest.update(sph_zc.as_bytes());
let pus_tc_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
digest.update(pus_tc_header.as_bytes());
digest.update(self.app_data);
digest.finalize()
}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> usize { pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> usize {
let sph_zc = crate::zc::SpHeader::from(self.sp_header); let sph_zc = crate::zc::SpHeader::from(self.sp_header);
@ -376,8 +380,10 @@ impl WritablePusPacket for PusTcCreator<'_> {
PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len() PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len()
} }
/// Write the raw PUS byte representation to a provided buffer. /// Writes the packet to the given slice without writing the CRC.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> { ///
/// The returned size is the written size WITHOUT the CRC.
fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = 0; let mut curr_idx = 0;
let tc_header_len = size_of::<zc::PusTcSecondaryHeader>(); let tc_header_len = size_of::<zc::PusTcSecondaryHeader>();
let total_size = self.len_written(); let total_size = self.len_written();
@ -392,16 +398,12 @@ impl WritablePusPacket for PusTcCreator<'_> {
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
.write_to_bytes(&mut slice[curr_idx..curr_idx + tc_header_len]) .write_to(&mut slice[curr_idx..curr_idx + tc_header_len])
.ok_or(ByteConversionError::ZeroCopyToError)?; .map_err(|_| ByteConversionError::ZeroCopyToError)?;
curr_idx += tc_header_len; curr_idx += tc_header_len;
slice[curr_idx..curr_idx + self.app_data.len()].copy_from_slice(self.app_data); slice[curr_idx..curr_idx + self.app_data.len()].copy_from_slice(self.app_data);
curr_idx += self.app_data.len(); curr_idx += self.app_data.len();
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx) Ok(curr_idx)
} }
} }
@ -426,7 +428,7 @@ impl PusPacket for PusTcCreator<'_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(self.calc_own_crc16()) Some(self.calc_own_crc16())
} }
} }
@ -475,7 +477,22 @@ impl<'raw_data> PusTcReader<'raw_data> {
/// Create a [PusTcReader] instance from a raw slice. On success, it returns a tuple containing /// Create a [PusTcReader] instance from a raw slice. On success, it returns a tuple containing
/// the instance and the found byte length of the packet. This function also performs a CRC /// the instance and the found byte length of the packet. This function also performs a CRC
/// check and will return an appropriate [PusError] if the check fails. /// check and will return an appropriate [PusError] if the check fails.
pub fn new(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> { pub fn new(slice: &'raw_data [u8]) -> Result<Self, PusError> {
let pus_tc = Self::new_no_crc_check(slice)?;
verify_crc16_ccitt_false_from_raw_to_pus_error(pus_tc.raw_data(), pus_tc.crc16())?;
Ok(pus_tc)
}
/// Similar to [PusTcReader::new], but uses a table-less CRC16 algorithm which can reduce
/// binary size and memory usage.
pub fn new_crc_no_table(slice: &'raw_data [u8]) -> Result<Self, PusError> {
let pus_tc = Self::new_no_crc_check(slice)?;
verify_crc16_ccitt_false_from_raw_to_pus_error_no_table(pus_tc.raw_data(), pus_tc.crc16())?;
Ok(pus_tc)
}
/// Creates a new instance without performing a CRC check.
pub fn new_no_crc_check(slice: &'raw_data [u8]) -> Result<Self, 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(ByteConversionError::FromSliceTooSmall { return Err(ByteConversionError::FromSliceTooSmall {
@ -502,21 +519,19 @@ impl<'raw_data> PusTcReader<'raw_data> {
} }
.into()); .into());
} }
let sec_header = zc::PusTcSecondaryHeader::from_bytes( let sec_header = zc::PusTcSecondaryHeader::read_from_bytes(
&slice[current_idx..current_idx + PUC_TC_SECONDARY_HEADER_LEN], &slice[current_idx..current_idx + PUC_TC_SECONDARY_HEADER_LEN],
) )
.ok_or(ByteConversionError::ZeroCopyFromError)?; .map_err(|_| 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 = Self { Ok(Self {
sp_header, sp_header,
sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(), sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(),
raw_data, raw_data,
app_data: user_data_from_raw(current_idx, total_len, slice)?, app_data: user_data_from_raw(current_idx, total_len, slice)?,
crc16: crc_from_raw_data(raw_data)?, crc16: crc_from_raw_data(raw_data)?,
}; })
verify_crc16_ccitt_false_from_raw_to_pus_error(raw_data, pus_tc.crc16)?;
Ok((pus_tc, total_len))
} }
#[inline] #[inline]
@ -538,6 +553,11 @@ impl<'raw_data> PusTcReader<'raw_data> {
pub fn sp_header(&self) -> &SpHeader { pub fn sp_header(&self) -> &SpHeader {
&self.sp_header &self.sp_header
} }
#[inline]
pub fn crc16(&self) -> u16 {
self.crc16
}
} }
impl PartialEq for PusTcReader<'_> { impl PartialEq for PusTcReader<'_> {
@ -567,7 +587,7 @@ impl PusPacket for PusTcReader<'_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(self.crc16) Some(self.crc16)
} }
} }
@ -650,11 +670,37 @@ mod tests {
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
assert_eq!(size, 13); assert_eq!(size, 13);
assert_eq!( assert_eq!(
pus_tc.crc16().unwrap(), pus_tc.opt_crc16().unwrap(),
u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap()) u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap())
); );
} }
#[test]
fn test_serialization_crc_no_table() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc
.write_to_bytes_crc_no_table(test_buf.as_mut_slice())
.expect("error writing tc to buffer");
assert_eq!(size, 13);
assert_eq!(
pus_tc.opt_crc16().unwrap(),
u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap())
);
}
#[test]
fn test_serialization_no_crc() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc
.write_to_bytes_no_crc(test_buf.as_mut_slice())
.expect("error writing tc to buffer");
assert_eq!(size, 11);
assert_eq!(test_buf[11], 0);
assert_eq!(test_buf[12], 0);
}
#[test] #[test]
fn test_deserialization() { fn test_deserialization() {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
@ -663,9 +709,26 @@ mod tests {
.write_to_bytes(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) = let tc_from_raw =
PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed"); PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(tc_from_raw.total_len(), 13);
verify_test_tc_with_reader(&tc_from_raw, false, 13);
assert!(tc_from_raw.user_data().is_empty());
verify_test_tc_raw(&test_buf);
verify_crc_no_app_data(&test_buf);
}
#[test]
fn test_deserialization_no_table() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc
.write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer");
assert_eq!(size, 13); assert_eq!(size, 13);
let tc_from_raw = PusTcReader::new_crc_no_table(&test_buf)
.expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(tc_from_raw.total_len(), 13);
verify_test_tc_with_reader(&tc_from_raw, false, 13); verify_test_tc_with_reader(&tc_from_raw, false, 13);
assert!(tc_from_raw.user_data().is_empty()); assert!(tc_from_raw.user_data().is_empty());
verify_test_tc_raw(&test_buf); verify_test_tc_raw(&test_buf);
@ -677,9 +740,9 @@ mod tests {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
let tc_vec = pus_tc.to_vec().expect("Error writing TC to buffer"); let tc_vec = pus_tc.to_vec().expect("Error writing TC to buffer");
assert_eq!(tc_vec.len(), 13); assert_eq!(tc_vec.len(), 13);
let (tc_from_raw, size) = PusTcReader::new(tc_vec.as_slice()) let tc_from_raw = PusTcReader::new(tc_vec.as_slice())
.expect("Creating PUS TC struct from raw buffer failed"); .expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(size, 13); assert_eq!(tc_from_raw.total_len(), 13);
verify_test_tc_with_reader(&tc_from_raw, false, 13); verify_test_tc_with_reader(&tc_from_raw, false, 13);
assert!(tc_from_raw.user_data().is_empty()); assert!(tc_from_raw.user_data().is_empty());
verify_test_tc_raw(&tc_vec); verify_test_tc_raw(&tc_vec);
@ -702,15 +765,15 @@ mod tests {
.write_to_bytes(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) = let tc_from_raw =
PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed"); PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(size, 16); assert_eq!(tc_from_raw.total_len(), 16);
verify_test_tc_with_reader(&tc_from_raw, true, 16); verify_test_tc_with_reader(&tc_from_raw, true, 16);
let user_data = tc_from_raw.user_data(); let user_data = tc_from_raw.user_data();
assert_eq!(tc_from_raw.user_data(), tc_from_raw.app_data()); assert_eq!(tc_from_raw.user_data(), tc_from_raw.app_data());
assert_eq!(tc_from_raw.raw_data(), &test_buf[..size]); assert_eq!(tc_from_raw.raw_data(), &test_buf[..size]);
assert_eq!( assert_eq!(
tc_from_raw.crc16().unwrap(), tc_from_raw.opt_crc16().unwrap(),
u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap()) u16::from_be_bytes(test_buf[size - 2..size].try_into().unwrap())
); );
assert_eq!(user_data[0], 1); assert_eq!(user_data[0], 1);
@ -725,9 +788,9 @@ mod tests {
pus_tc pus_tc
.write_to_bytes(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");
let (tc_from_raw_0, _) = let tc_from_raw_0 =
PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed"); PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
let (tc_from_raw_1, _) = let tc_from_raw_1 =
PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed"); PusTcReader::new(&test_buf).expect("Creating PUS TC struct from raw buffer failed");
assert_eq!(tc_from_raw_0, tc_from_raw_1); assert_eq!(tc_from_raw_0, tc_from_raw_1);
} }
@ -879,7 +942,7 @@ mod tests {
assert_eq!(*tc.sp_header(), comp_header); assert_eq!(*tc.sp_header(), comp_header);
} }
fn verify_test_tc_generic(tc: &(impl CcsdsPacket + PusPacket + GenericPusTcSecondaryHeader)) { fn verify_test_tc_generic(tc: &(impl PusPacket + GenericPusTcSecondaryHeader)) {
assert_eq!(PusPacket::service(tc), 17); assert_eq!(PusPacket::service(tc), 17);
assert_eq!(GenericPusTcSecondaryHeader::service(tc), 17); assert_eq!(GenericPusTcSecondaryHeader::service(tc), 17);
assert_eq!(PusPacket::subservice(tc), 1); assert_eq!(PusPacket::subservice(tc), 1);
@ -943,8 +1006,8 @@ mod tests {
let pus_tc = base_ping_tc_simple_ctor(); let pus_tc = base_ping_tc_simple_ctor();
let mut buf = [0; 32]; let mut buf = [0; 32];
pus_tc.write_to_bytes(&mut buf).unwrap(); pus_tc.write_to_bytes(&mut buf).unwrap();
assert_eq!(pus_tc, PusTcReader::new(&buf).unwrap().0); assert_eq!(pus_tc, PusTcReader::new(&buf).unwrap());
assert_eq!(PusTcReader::new(&buf).unwrap().0, pus_tc); assert_eq!(PusTcReader::new(&buf).unwrap(), pus_tc);
} }
#[test] #[test]

@ -35,13 +35,14 @@
//! println!("{:?}", &test_buf[0..written_size]); //! println!("{:?}", &test_buf[0..written_size]);
//! //!
//! // Deserialize from the raw byte representation //! // Deserialize from the raw byte representation
//! let (ping_tm_reader, read_size) = PusTmReader::new(&test_buf, 7).expect("Deserialization failed"); //! let ping_tm_reader = PusTmReader::new(&test_buf, 7).expect("Deserialization failed");
//! assert_eq!(written_size, read_size); //! assert_eq!(written_size, ping_tm_reader.total_len());
//! assert_eq!(ping_tm_reader.service(), 17); //! assert_eq!(ping_tm_reader.service(), 17);
//! assert_eq!(ping_tm_reader.subservice(), 2); //! assert_eq!(ping_tm_reader.subservice(), 2);
//! assert_eq!(ping_tm_reader.apid(), 0x02); //! assert_eq!(ping_tm_reader.apid(), 0x02);
//! assert_eq!(ping_tm_reader.timestamp(), &time_buf); //! assert_eq!(ping_tm_reader.timestamp(), &time_buf);
//! ``` //! ```
use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE};
use crate::ecss::{ use crate::ecss::{
calc_pus_crc16, ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw, calc_pus_crc16, ccsds_impl, crc_from_raw_data, sp_header_impls, user_data_from_raw,
verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion, verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError, PusPacket, PusVersion,
@ -49,12 +50,12 @@ use crate::ecss::{
}; };
use crate::{ use crate::{
ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SpHeader, CCSDS_HEADER_LEN, ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SpHeader, CCSDS_HEADER_LEN,
CRC_CCITT_FALSE, MAX_APID, MAX_SEQ_COUNT, MAX_APID, MAX_SEQ_COUNT,
}; };
use core::mem::size_of; use core::mem::size_of;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zerocopy::AsBytes; use zerocopy::{FromBytes, IntoBytes};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
@ -64,6 +65,8 @@ use crate::time::{TimeWriter, TimestampError};
use self::zc::PusTmSecHeaderWithoutTimestamp; use self::zc::PusTmSecHeaderWithoutTimestamp;
use super::verify_crc16_ccitt_false_from_raw_to_pus_error_no_table;
pub trait IsPusTelemetry {} pub trait IsPusTelemetry {}
/// Length without timestamp /// Length without timestamp
@ -83,9 +86,9 @@ pub trait GenericPusTmSecondaryHeader {
pub mod zc { pub mod zc {
use super::GenericPusTmSecondaryHeader; use super::GenericPusTmSecondaryHeader;
use crate::ecss::{PusError, PusVersion}; use crate::ecss::{PusError, PusVersion};
use zerocopy::{AsBytes, FromBytes, FromZeroes, NetworkEndian, Unaligned, U16}; use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16};
#[derive(FromBytes, FromZeroes, AsBytes, Unaligned)] #[derive(FromBytes, IntoBytes, Immutable, Unaligned)]
#[repr(C)] #[repr(C)]
pub struct PusTmSecHeaderWithoutTimestamp { pub struct PusTmSecHeaderWithoutTimestamp {
pus_version_and_sc_time_ref_status: u8, pus_version_and_sc_time_ref_status: u8,
@ -117,20 +120,10 @@ pub mod zc {
} }
} }
impl PusTmSecHeaderWithoutTimestamp {
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Option<()> {
self.write_to(slice)
}
pub fn from_bytes(slice: &[u8]) -> Option<Self> {
Self::read_from(slice)
}
}
impl GenericPusTmSecondaryHeader for PusTmSecHeaderWithoutTimestamp { impl GenericPusTmSecondaryHeader for PusTmSecHeaderWithoutTimestamp {
#[inline] #[inline]
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)
} }
@ -287,13 +280,13 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> {
/// # Arguments /// # Arguments
/// ///
/// * `sp_header` - Space packet header information. The correct packet type and the secondary /// * `sp_header` - Space packet header information. The correct packet type and the secondary
/// header flag are set correctly by the constructor. /// header flag are set correctly by the constructor.
/// * `sec_header` - Information contained in the secondary header, including the service /// * `sec_header` - Information contained in the secondary header, including the service
/// and subservice type /// and subservice type
/// * `source_data` - Custom application data /// * `source_data` - Custom application data
/// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length /// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length
/// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set /// field. If this is not set to true, [Self::update_ccsds_data_len] can be called to set
/// the correct value to this field manually /// the correct value to this field manually
#[inline] #[inline]
pub fn new( pub fn new(
mut sp_header: SpHeader, mut sp_header: SpHeader,
@ -399,6 +392,29 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> {
/// Write the raw PUS byte representation to a provided buffer. /// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> { pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?;
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx)
}
/// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes_crc_no_table(
&self,
slice: &mut [u8],
) -> Result<usize, ByteConversionError> {
let mut curr_idx = self.write_to_bytes_no_crc(slice)?;
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx)
}
/// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> {
let mut curr_idx = 0; let mut curr_idx = 0;
let total_size = self.len_written(); let total_size = self.len_written();
if total_size > slice.len() { if total_size > slice.len() {
@ -413,18 +429,14 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> {
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
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len]) .write_to(&mut slice[curr_idx..curr_idx + sec_header_len])
.ok_or(ByteConversionError::ZeroCopyToError)?; .map_err(|_| ByteConversionError::ZeroCopyToError)?;
curr_idx += sec_header_len; curr_idx += sec_header_len;
slice[curr_idx..curr_idx + self.sec_header.timestamp.len()] slice[curr_idx..curr_idx + self.sec_header.timestamp.len()]
.copy_from_slice(self.sec_header.timestamp); .copy_from_slice(self.sec_header.timestamp);
curr_idx += self.sec_header.timestamp.len(); curr_idx += self.sec_header.timestamp.len();
slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data); slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data);
curr_idx += self.source_data.len(); curr_idx += self.source_data.len();
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
Ok(curr_idx) Ok(curr_idx)
} }
@ -456,8 +468,8 @@ impl WritablePusPacket for PusTmCreator<'_, '_> {
+ self.source_data.len() + self.source_data.len()
} }
/// Write the raw PUS byte representation to a provided buffer. /// Write the raw PUS byte representation to a provided buffer.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> { fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes(self, slice)?) Ok(Self::write_to_bytes_no_crc(self, slice)?)
} }
} }
@ -489,7 +501,7 @@ impl PusPacket for PusTmCreator<'_, '_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(self.calc_own_crc16()) Some(self.calc_own_crc16())
} }
} }
@ -544,7 +556,26 @@ impl<'raw_data> PusTmReader<'raw_data> {
/// ///
/// This function will check the CRC-16 of the PUS packet and will return an appropriate /// This function will check the CRC-16 of the PUS packet and will return an appropriate
/// [PusError] if the check fails. /// [PusError] if the check fails.
pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<(Self, usize), PusError> { pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<Self, PusError> {
let tc = Self::new_no_crc_check(slice, timestamp_len)?;
verify_crc16_ccitt_false_from_raw_to_pus_error(tc.raw_data(), tc.crc16)?;
Ok(tc)
}
/// Like [PusTmReader::new] but uses a table-less CRC implementation.
pub fn new_crc_no_table(
slice: &'raw_data [u8],
timestamp_len: usize,
) -> Result<Self, PusError> {
let tc = Self::new_no_crc_check(slice, timestamp_len)?;
verify_crc16_ccitt_false_from_raw_to_pus_error_no_table(tc.raw_data(), tc.crc16)?;
Ok(tc)
}
pub fn new_no_crc_check(
slice: &'raw_data [u8],
timestamp_len: usize,
) -> Result<Self, PusError> {
let raw_data_len = slice.len(); let raw_data_len = slice.len();
if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA { if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
return Err(ByteConversionError::FromSliceTooSmall { return Err(ByteConversionError::FromSliceTooSmall {
@ -571,10 +602,10 @@ impl<'raw_data> PusTmReader<'raw_data> {
} }
.into()); .into());
} }
let sec_header_zc = zc::PusTmSecHeaderWithoutTimestamp::from_bytes( let sec_header_zc = zc::PusTmSecHeaderWithoutTimestamp::read_from_bytes(
&slice[current_idx..current_idx + PUS_TM_MIN_SEC_HEADER_LEN], &slice[current_idx..current_idx + PUS_TM_MIN_SEC_HEADER_LEN],
) )
.ok_or(ByteConversionError::ZeroCopyFromError)?; .map_err(|_| ByteConversionError::ZeroCopyFromError)?;
current_idx += PUS_TM_MIN_SEC_HEADER_LEN; current_idx += PUS_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,
@ -582,15 +613,13 @@ impl<'raw_data> PusTmReader<'raw_data> {
}; };
current_idx += timestamp_len; current_idx += timestamp_len;
let raw_data = &slice[0..total_len]; let raw_data = &slice[0..total_len];
let pus_tm = Self { Ok(Self {
sp_header, sp_header,
sec_header: PusTmSecondaryHeader::try_from(zc_sec_header_wrapper).unwrap(), sec_header: PusTmSecondaryHeader::try_from(zc_sec_header_wrapper).unwrap(),
raw_data: &slice[0..total_len], raw_data: &slice[0..total_len],
source_data: user_data_from_raw(current_idx, total_len, slice)?, source_data: user_data_from_raw(current_idx, total_len, slice)?,
crc16: crc_from_raw_data(raw_data)?, crc16: crc_from_raw_data(raw_data)?,
}; })
verify_crc16_ccitt_false_from_raw_to_pus_error(raw_data, pus_tm.crc16)?;
Ok((pus_tm, total_len))
} }
#[inline] #[inline]
@ -608,6 +637,11 @@ impl<'raw_data> PusTmReader<'raw_data> {
self.sec_header.timestamp self.sec_header.timestamp
} }
#[inline]
pub fn crc16(&self) -> u16 {
self.crc16
}
/// This function will return the slice [Self] was constructed from. /// This function will return the slice [Self] was constructed from.
#[inline] #[inline]
pub fn raw_data(&self) -> &[u8] { pub fn raw_data(&self) -> &[u8] {
@ -644,8 +678,8 @@ impl PusPacket for PusTmReader<'_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(self.crc16) Some(self.crc16())
} }
} }
@ -710,7 +744,7 @@ impl<'raw> PusTmZeroCopyWriter<'raw> {
if raw_tm_len < CCSDS_HEADER_LEN + PUS_TM_MIN_SEC_HEADER_LEN + timestamp_len { if raw_tm_len < CCSDS_HEADER_LEN + PUS_TM_MIN_SEC_HEADER_LEN + timestamp_len {
return None; return None;
} }
let sp_header = crate::zc::SpHeader::from_bytes(&raw_tm[0..CCSDS_HEADER_LEN]).unwrap(); let sp_header = crate::zc::SpHeader::read_from_bytes(&raw_tm[0..CCSDS_HEADER_LEN]).unwrap();
if raw_tm_len < sp_header.total_len() { if raw_tm_len < sp_header.total_len() {
return None; return None;
} }
@ -751,7 +785,7 @@ impl<'raw> PusTmZeroCopyWriter<'raw> {
#[inline] #[inline]
pub fn sp_header(&self) -> crate::zc::SpHeader { pub fn sp_header(&self) -> crate::zc::SpHeader {
// Valid minimum length of packet was checked before. // Valid minimum length of packet was checked before.
crate::zc::SpHeader::from_bytes(&self.raw_tm[0..CCSDS_HEADER_LEN]).unwrap() crate::zc::SpHeader::read_from_bytes(&self.raw_tm[0..CCSDS_HEADER_LEN]).unwrap()
} }
/// Helper API to generate the portion of the secondary header without a timestamp from the /// Helper API to generate the portion of the secondary header without a timestamp from the
@ -759,7 +793,7 @@ impl<'raw> PusTmZeroCopyWriter<'raw> {
#[inline] #[inline]
pub fn sec_header_without_timestamp(&self) -> PusTmSecHeaderWithoutTimestamp { pub fn sec_header_without_timestamp(&self) -> PusTmSecHeaderWithoutTimestamp {
// Valid minimum length of packet was checked before. // Valid minimum length of packet was checked before.
PusTmSecHeaderWithoutTimestamp::from_bytes( PusTmSecHeaderWithoutTimestamp::read_from_bytes(
&self.raw_tm[CCSDS_HEADER_LEN..CCSDS_HEADER_LEN + PUS_TM_MIN_SEC_HEADER_LEN], &self.raw_tm[CCSDS_HEADER_LEN..CCSDS_HEADER_LEN + PUS_TM_MIN_SEC_HEADER_LEN],
) )
.unwrap() .unwrap()
@ -832,7 +866,7 @@ impl PusPacket for PusTmZeroCopyWriter<'_> {
} }
#[inline] #[inline]
fn crc16(&self) -> Option<u16> { fn opt_crc16(&self) -> Option<u16> {
Some(u16::from_be_bytes( Some(u16::from_be_bytes(
self.raw_tm[self.sp_header().total_len() - 2..self.sp_header().total_len()] self.raw_tm[self.sp_header().total_len() - 2..self.sp_header().total_len()]
.try_into() .try_into()
@ -928,7 +962,32 @@ mod tests {
.write_to_bytes(&mut buf) .write_to_bytes(&mut buf)
.expect("Serialization failed"); .expect("Serialization failed");
assert_eq!(ser_len, 22); assert_eq!(ser_len, 22);
verify_raw_ping_reply(pus_tm.crc16().unwrap(), &buf); verify_raw_ping_reply(pus_tm.opt_crc16().unwrap(), &buf);
}
#[test]
fn test_serialization_no_source_data_no_table() {
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm
.write_to_bytes_crc_no_table(&mut buf)
.expect("Serialization failed");
assert_eq!(ser_len, 22);
verify_raw_ping_reply(pus_tm.opt_crc16().unwrap(), &buf);
}
#[test]
fn test_serialization_no_source_data_no_crc() {
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm
.write_to_bytes_no_crc(&mut buf)
.expect("Serialization failed");
assert_eq!(ser_len, 20);
assert_eq!(buf[20], 0);
assert_eq!(buf[21], 0);
} }
#[test] #[test]
@ -965,9 +1024,9 @@ mod tests {
let pus_tm = base_ping_reply_full_ctor(timestamp); let pus_tm = base_ping_reply_full_ctor(timestamp);
let tm_vec = pus_tm.to_vec().expect("Serialization failed"); let tm_vec = pus_tm.to_vec().expect("Serialization failed");
assert_eq!(tm_vec.len(), 22); assert_eq!(tm_vec.len(), 22);
let (tm_deserialized, size) = let tm_deserialized =
PusTmReader::new(tm_vec.as_slice(), 7).expect("Deserialization failed"); PusTmReader::new(tm_vec.as_slice(), 7).expect("Deserialization failed");
assert_eq!(tm_vec.len(), size); assert_eq!(tm_vec.len(), tm_deserialized.total_len());
verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp()); verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp());
} }
#[test] #[test]
@ -979,13 +1038,32 @@ mod tests {
.write_to_bytes(&mut buf) .write_to_bytes(&mut buf)
.expect("Serialization failed"); .expect("Serialization failed");
assert_eq!(ser_len, 22); assert_eq!(ser_len, 22);
let (tm_deserialized, size) = PusTmReader::new(&buf, 7).expect("Deserialization failed"); let tm_deserialized = PusTmReader::new(&buf, 7).expect("Deserialization failed");
assert_eq!(ser_len, size); assert_eq!(ser_len, tm_deserialized.total_len());
assert_eq!(tm_deserialized.user_data(), tm_deserialized.source_data()); assert_eq!(tm_deserialized.user_data(), tm_deserialized.source_data());
assert_eq!(tm_deserialized.raw_data(), &buf[..ser_len]); assert_eq!(tm_deserialized.raw_data(), &buf[..ser_len]);
assert_eq!(tm_deserialized.crc16().unwrap(), pus_tm.crc16().unwrap()); assert_eq!(tm_deserialized.crc16(), pus_tm.opt_crc16().unwrap());
verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp()); verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp());
} }
#[test]
fn test_deserialization_no_table() {
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm
.write_to_bytes(&mut buf)
.expect("Serialization failed");
assert_eq!(ser_len, 22);
let tm_deserialized =
PusTmReader::new_crc_no_table(&buf, 7).expect("Deserialization failed");
assert_eq!(ser_len, tm_deserialized.total_len());
assert_eq!(tm_deserialized.user_data(), tm_deserialized.source_data());
assert_eq!(tm_deserialized.raw_data(), &buf[..ser_len]);
assert_eq!(tm_deserialized.crc16(), pus_tm.opt_crc16().unwrap());
verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp());
}
#[test] #[test]
fn test_deserialization_faulty_crc() { fn test_deserialization_faulty_crc() {
let timestamp = dummy_timestamp(); let timestamp = dummy_timestamp();
@ -1052,7 +1130,7 @@ mod tests {
let res = pus_tm.append_to_vec(&mut vec); let res = pus_tm.append_to_vec(&mut vec);
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 22); assert_eq!(res.unwrap(), 22);
verify_raw_ping_reply(pus_tm.crc16().unwrap(), vec.as_slice()); verify_raw_ping_reply(pus_tm.opt_crc16().unwrap(), vec.as_slice());
} }
#[test] #[test]
@ -1121,7 +1199,7 @@ mod tests {
} }
fn verify_ping_reply_generic( fn verify_ping_reply_generic(
tm: &(impl CcsdsPacket + GenericPusTmSecondaryHeader + PusPacket), tm: &(impl GenericPusTmSecondaryHeader + PusPacket),
has_user_data: bool, has_user_data: bool,
exp_full_len: usize, exp_full_len: usize,
) { ) {
@ -1162,7 +1240,7 @@ mod tests {
let pus_tm = base_ping_reply_full_ctor(timestamp); let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf = [0; 32]; let mut buf = [0; 32];
pus_tm.write_to_bytes(&mut buf).unwrap(); pus_tm.write_to_bytes(&mut buf).unwrap();
assert_eq!(pus_tm, PusTmReader::new(&buf, timestamp.len()).unwrap().0); assert_eq!(pus_tm, PusTmReader::new(&buf, timestamp.len()).unwrap());
} }
#[test] #[test]
@ -1182,9 +1260,8 @@ mod tests {
assert!(!writer.set_apid(MAX_SEQ_COUNT + 1)); assert!(!writer.set_apid(MAX_SEQ_COUNT + 1));
writer.finish(); writer.finish();
// This performs all necessary checks, including the CRC check. // This performs all necessary checks, including the CRC check.
let (tm_read_back, tm_size_read_back) = let tm_read_back = PusTmReader::new(&buf, 7).expect("Re-creating PUS TM failed");
PusTmReader::new(&buf, 7).expect("Re-creating PUS TM failed"); assert_eq!(tm_read_back.total_len(), tm_size);
assert_eq!(tm_size_read_back, tm_size);
assert_eq!(tm_read_back.msg_counter(), 100); assert_eq!(tm_read_back.msg_counter(), 100);
assert_eq!(tm_read_back.dest_id(), 55); assert_eq!(tm_read_back.dest_id(), 55);
assert_eq!(tm_read_back.seq_count(), MAX_SEQ_COUNT); assert_eq!(tm_read_back.seq_count(), MAX_SEQ_COUNT);
@ -1232,7 +1309,7 @@ mod tests {
assert_eq!(writer.sec_header_without_timestamp().msg_counter(), 100); assert_eq!(writer.sec_header_without_timestamp().msg_counter(), 100);
assert_eq!(writer.user_data(), DUMMY_DATA); assert_eq!(writer.user_data(), DUMMY_DATA);
// Need to check crc16 before finish, because finish will update the CRC. // Need to check crc16 before finish, because finish will update the CRC.
let crc16 = writer.crc16(); let crc16 = writer.opt_crc16();
assert!(crc16.is_some()); assert!(crc16.is_some());
assert_eq!(crc16.unwrap(), crc16_raw); assert_eq!(crc16.unwrap(), crc16_raw);
writer.finish(); writer.finish();
@ -1250,8 +1327,8 @@ mod tests {
let pus_tm = base_ping_reply_full_ctor(timestamp); let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf = [0; 32]; let mut buf = [0; 32];
pus_tm.write_to_bytes(&mut buf).unwrap(); pus_tm.write_to_bytes(&mut buf).unwrap();
let (tm_0, _) = PusTmReader::new(&buf, timestamp.len()).unwrap(); let tm_0 = PusTmReader::new(&buf, timestamp.len()).unwrap();
let (tm_1, _) = PusTmReader::new(&buf, timestamp.len()).unwrap(); let tm_1 = PusTmReader::new(&buf, timestamp.len()).unwrap();
assert_eq!(tm_0, tm_1); assert_eq!(tm_0, tm_1);
} }
#[test] #[test]
@ -1323,7 +1400,7 @@ mod tests {
PusTmCreator::new_simple(sph, 17, 2, &time_provider, &mut stamp_buf, &[], true) PusTmCreator::new_simple(sph, 17, 2, &time_provider, &mut stamp_buf, &[], true)
.unwrap(); .unwrap();
let pus_tm_vec = pus_tm.to_vec().unwrap(); let pus_tm_vec = pus_tm.to_vec().unwrap();
let (tm_reader, _) = PusTmReader::new(&pus_tm_vec, time_provider.len_as_bytes()).unwrap(); let tm_reader = PusTmReader::new(&pus_tm_vec, time_provider.len_as_bytes()).unwrap();
let output = to_allocvec(&tm_reader).unwrap(); let output = to_allocvec(&tm_reader).unwrap();
let output_converted_back: PusTmReader = from_bytes(&output).unwrap(); let output_converted_back: PusTmReader = from_bytes(&output).unwrap();
assert_eq!(output_converted_back, tm_reader); assert_eq!(output_converted_back, tm_reader);

@ -26,8 +26,8 @@
//! //!
//! - [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library. //! - [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
//! - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers //! - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers
//! like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html). //! like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html).
//! Enabled by the `std` feature. //! Enabled by the `std` feature.
//! //!
//! ### Optional features //! ### Optional features
//! //!
@ -55,27 +55,23 @@
//! println!("{:x?}", &ccsds_buf[0..6]); //! println!("{:x?}", &ccsds_buf[0..6]);
//! ``` //! ```
#![no_std] #![no_std]
#![cfg_attr(docs_rs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
extern crate alloc; extern crate alloc;
#[cfg(any(feature = "std", test))] #[cfg(any(feature = "std", test))]
extern crate std; extern crate std;
use core::{ use core::{fmt::Debug, hash::Hash};
fmt::{Debug, Display, Formatter},
hash::Hash,
};
use crc::{Crc, CRC_16_IBM_3740};
use delegate::delegate; use delegate::delegate;
use zerocopy::{FromBytes, IntoBytes};
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod cfdp; pub mod cfdp;
pub mod crc;
pub mod ecss; pub mod ecss;
pub mod seq_count;
pub mod time; pub mod time;
pub mod util; pub mod util;
@ -85,62 +81,28 @@ mod private {
pub const CCSDS_HEADER_LEN: usize = core::mem::size_of::<crate::zc::SpHeader>(); pub const CCSDS_HEADER_LEN: usize = core::mem::size_of::<crate::zc::SpHeader>();
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard.
pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
pub const MAX_APID: u16 = 2u16.pow(11) - 1; pub const MAX_APID: u16 = 2u16.pow(11) - 1;
pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1; pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1;
/// Generic error type when converting to and from raw byte slices. /// Generic error type when converting to and from raw byte slices.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ByteConversionError { pub enum ByteConversionError {
/// The passed slice is too small. Returns the passed slice length and expected minimum size /// The passed slice is too small. Returns the passed slice length and expected minimum size
ToSliceTooSmall { #[error("target slice with size {found} is too small, expected size of at least {expected}")]
found: usize, ToSliceTooSmall { found: usize, expected: usize },
expected: usize,
},
/// The provider buffer is too small. Returns the passed slice length and expected minimum size /// The provider buffer is too small. Returns the passed slice length and expected minimum size
FromSliceTooSmall { #[error("source slice with size {found} too small, expected at least {expected} bytes")]
found: usize, FromSliceTooSmall { found: usize, expected: usize },
expected: usize,
},
/// The [zerocopy] library failed to write to bytes /// The [zerocopy] library failed to write to bytes
#[error("zerocopy serialization error")]
ZeroCopyToError, ZeroCopyToError,
/// The [zerocopy] library failed to read from bytes
#[error("zerocopy deserialization error")]
ZeroCopyFromError, ZeroCopyFromError,
} }
impl Display for ByteConversionError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
ByteConversionError::ToSliceTooSmall { found, expected } => {
write!(
f,
"target slice with size {} is too small, expected size of at least {}",
found, expected
)
}
ByteConversionError::FromSliceTooSmall { found, expected } => {
write!(
f,
"source slice with size {} too small, expected at least {} bytes",
found, expected
)
}
ByteConversionError::ZeroCopyToError => {
write!(f, "zerocopy serialization error")
}
ByteConversionError::ZeroCopyFromError => {
write!(f, "zerocopy deserialization error")
}
}
}
}
#[cfg(feature = "std")]
impl Error for ByteConversionError {}
/// CCSDS packet type enumeration. /// CCSDS packet type enumeration.
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -487,9 +449,9 @@ pub trait CcsdsPrimaryHeader {
/// # Arguments /// # Arguments
/// ///
/// * `version` - CCSDS version field, occupies the first 3 bits of the raw header. Will generally /// * `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. /// 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(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
@ -732,8 +694,8 @@ impl SpHeader {
expected: CCSDS_HEADER_LEN, expected: CCSDS_HEADER_LEN,
}); });
} }
let zc_header = zc::SpHeader::from_bytes(&buf[0..CCSDS_HEADER_LEN]) let zc_header = zc::SpHeader::read_from_bytes(&buf[0..CCSDS_HEADER_LEN])
.ok_or(ByteConversionError::ZeroCopyFromError)?; .map_err(|_| ByteConversionError::ZeroCopyFromError)?;
Ok((Self::from(zc_header), &buf[CCSDS_HEADER_LEN..])) Ok((Self::from(zc_header), &buf[CCSDS_HEADER_LEN..]))
} }
@ -751,8 +713,8 @@ impl SpHeader {
} }
let zc_header: zc::SpHeader = zc::SpHeader::from(*self); let zc_header: zc::SpHeader = zc::SpHeader::from(*self);
zc_header zc_header
.to_bytes(&mut buf[0..CCSDS_HEADER_LEN]) .write_to(&mut buf[0..CCSDS_HEADER_LEN])
.ok_or(ByteConversionError::ZeroCopyToError)?; .map_err(|_| ByteConversionError::ZeroCopyToError)?;
Ok(&mut buf[CCSDS_HEADER_LEN..]) Ok(&mut buf[CCSDS_HEADER_LEN..])
} }
@ -814,9 +776,9 @@ sph_from_other!(SpHeader, crate::zc::SpHeader);
pub mod zc { pub mod zc {
use crate::{CcsdsPacket, CcsdsPrimaryHeader, PacketId, PacketSequenceCtrl, VERSION_MASK}; use crate::{CcsdsPacket, CcsdsPrimaryHeader, PacketId, PacketSequenceCtrl, VERSION_MASK};
use zerocopy::byteorder::NetworkEndian; use zerocopy::byteorder::NetworkEndian;
use zerocopy::{AsBytes, FromBytes, FromZeroes, Unaligned, U16}; use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned, U16};
#[derive(FromBytes, FromZeroes, AsBytes, Unaligned, Debug)] #[derive(FromBytes, IntoBytes, Immutable, Unaligned, Debug)]
#[repr(C)] #[repr(C)]
pub struct SpHeader { pub struct SpHeader {
version_packet_id: U16<NetworkEndian>, version_packet_id: U16<NetworkEndian>,
@ -841,14 +803,6 @@ pub mod zc {
data_len: U16::from(data_len), data_len: U16::from(data_len),
} }
} }
pub fn from_bytes(slice: &[u8]) -> Option<Self> {
SpHeader::read_from(slice)
}
pub fn to_bytes(&self, slice: &mut [u8]) -> Option<()> {
self.write_to(slice)
}
} }
impl CcsdsPacket for SpHeader { impl CcsdsPacket for SpHeader {
@ -917,6 +871,7 @@ pub(crate) mod tests {
use postcard::{from_bytes, to_allocvec}; use postcard::{from_bytes, to_allocvec};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use zerocopy::FromBytes;
const CONST_SP: SpHeader = SpHeader::new( const CONST_SP: SpHeader = SpHeader::new(
PacketId::new_for_tc(true, 0x36), PacketId::new_for_tc(true, 0x36),
@ -1196,7 +1151,7 @@ pub(crate) mod tests {
#[test] #[test]
fn test_zc_sph() { fn test_zc_sph() {
use zerocopy::AsBytes; use zerocopy::IntoBytes;
let sp_header = SpHeader::new_for_unseg_tc_checked(0x7FF, pow(2, 14) - 1, 0) let sp_header = SpHeader::new_for_unseg_tc_checked(0x7FF, pow(2, 14) - 1, 0)
.expect("Error creating SP header"); .expect("Error creating SP header");
@ -1216,7 +1171,7 @@ pub(crate) mod tests {
assert_eq!(slice[5], 0x00); assert_eq!(slice[5], 0x00);
let mut slice = [0; 6]; let mut slice = [0; 6];
sp_header_zc.write_to(slice.as_mut_slice()); sp_header_zc.write_to(slice.as_mut_slice()).unwrap();
assert_eq!(slice.len(), 6); assert_eq!(slice.len(), 6);
assert_eq!(slice[0], 0x17); assert_eq!(slice[0], 0x17);
assert_eq!(slice[1], 0xFF); assert_eq!(slice[1], 0xFF);
@ -1227,7 +1182,7 @@ pub(crate) mod tests {
let mut test_vec = vec![0_u8; 6]; let mut test_vec = vec![0_u8; 6];
let slice = test_vec.as_mut_slice(); let slice = test_vec.as_mut_slice();
sp_header_zc.write_to(slice); sp_header_zc.write_to(slice).unwrap();
let slice = test_vec.as_slice(); let slice = test_vec.as_slice();
assert_eq!(slice.len(), 6); assert_eq!(slice.len(), 6);
assert_eq!(slice[0], 0x17); assert_eq!(slice[0], 0x17);
@ -1237,8 +1192,8 @@ pub(crate) mod tests {
assert_eq!(slice[4], 0x00); assert_eq!(slice[4], 0x00);
assert_eq!(slice[5], 0x00); assert_eq!(slice[5], 0x00);
let sp_header = zc::SpHeader::from_bytes(slice); let sp_header = zc::SpHeader::read_from_bytes(slice);
assert!(sp_header.is_some()); assert!(sp_header.is_ok());
let sp_header = sp_header.unwrap(); let sp_header = sp_header.unwrap();
assert_eq!(sp_header.ccsds_version(), 0b000); assert_eq!(sp_header.ccsds_version(), 0b000);
assert_eq!(sp_header.packet_id_raw(), 0x17FF); assert_eq!(sp_header.packet_id_raw(), 0x17FF);

250
src/seq_count.rs Normal file

@ -0,0 +1,250 @@
use crate::MAX_SEQ_COUNT;
use core::cell::Cell;
use paste::paste;
#[cfg(feature = "std")]
pub use stdmod::*;
/// Core trait for objects which can provide a sequence count.
///
/// The core functions are not mutable on purpose to allow easier usage with
/// static structs when using the interior mutability pattern. This can be achieved by using
/// [Cell], [core::cell::RefCell] or atomic types.
pub trait SequenceCountProvider {
type Raw: Into<u64>;
const MAX_BIT_WIDTH: usize;
fn get(&self) -> Self::Raw;
fn increment(&self);
fn get_and_increment(&self) -> Self::Raw {
let val = self.get();
self.increment();
val
}
}
#[derive(Clone)]
pub struct SeqCountProviderSimple<T: Copy> {
seq_count: Cell<T>,
max_val: T,
}
macro_rules! impl_for_primitives {
($($ty: ident,)+) => {
$(
paste! {
impl SeqCountProviderSimple<$ty> {
pub fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self {
Self {
seq_count: Cell::new(0),
max_val,
}
}
pub fn [<new_ $ty>]() -> Self {
Self {
seq_count: Cell::new(0),
max_val: $ty::MAX
}
}
}
impl Default for SeqCountProviderSimple<$ty> {
fn default() -> Self {
Self::[<new_ $ty>]()
}
}
impl SequenceCountProvider for SeqCountProviderSimple<$ty> {
type Raw = $ty;
const MAX_BIT_WIDTH: usize = core::mem::size_of::<Self::Raw>() * 8;
fn get(&self) -> Self::Raw {
self.seq_count.get()
}
fn increment(&self) {
self.get_and_increment();
}
fn get_and_increment(&self) -> Self::Raw {
let curr_count = self.seq_count.get();
if curr_count == self.max_val {
self.seq_count.set(0);
} else {
self.seq_count.set(curr_count + 1);
}
curr_count
}
}
}
)+
}
}
impl_for_primitives!(u8, u16, u32, u64,);
/// This is a sequence count provider which wraps around at [MAX_SEQ_COUNT].
#[derive(Clone)]
pub struct CcsdsSimpleSeqCountProvider {
provider: SeqCountProviderSimple<u16>,
}
impl Default for CcsdsSimpleSeqCountProvider {
fn default() -> Self {
Self {
provider: SeqCountProviderSimple::new_custom_max_val_u16(MAX_SEQ_COUNT),
}
}
}
impl SequenceCountProvider for CcsdsSimpleSeqCountProvider {
type Raw = u16;
const MAX_BIT_WIDTH: usize = core::mem::size_of::<Self::Raw>() * 8;
delegate::delegate! {
to self.provider {
fn get(&self) -> u16;
fn increment(&self);
fn get_and_increment(&self) -> u16;
}
}
}
#[cfg(feature = "std")]
pub mod stdmod {
use super::*;
use std::sync::{Arc, Mutex};
macro_rules! sync_clonable_seq_counter_impl {
($($ty: ident,)+) => {
$(paste! {
/// These sequence counters can be shared between threads and can also be
/// configured to wrap around at specified maximum values. Please note that
/// that the API provided by this class will not panic und [Mutex] lock errors,
/// but it will yield 0 for the getter functions.
#[derive(Clone, Default)]
pub struct [<SeqCountProviderSync $ty:upper>] {
seq_count: Arc<Mutex<$ty>>,
max_val: $ty
}
impl [<SeqCountProviderSync $ty:upper>] {
pub fn new() -> Self {
Self::new_with_max_val($ty::MAX)
}
pub fn new_with_max_val(max_val: $ty) -> Self {
Self {
seq_count: Arc::default(),
max_val
}
}
}
impl SequenceCountProvider for [<SeqCountProviderSync $ty:upper>] {
type Raw = $ty;
const MAX_BIT_WIDTH: usize = core::mem::size_of::<Self::Raw>() * 8;
fn get(&self) -> $ty {
match self.seq_count.lock() {
Ok(counter) => *counter,
Err(_) => 0
}
}
fn increment(&self) {
self.get_and_increment();
}
fn get_and_increment(&self) -> $ty {
match self.seq_count.lock() {
Ok(mut counter) => {
let val = *counter;
if val == self.max_val {
*counter = 0;
} else {
*counter += 1;
}
val
}
Err(_) => 0,
}
}
}
})+
}
}
sync_clonable_seq_counter_impl!(u8, u16, u32, u64,);
}
#[cfg(test)]
mod tests {
use crate::seq_count::{
CcsdsSimpleSeqCountProvider, SeqCountProviderSimple, SeqCountProviderSyncU8,
SequenceCountProvider,
};
use crate::MAX_SEQ_COUNT;
#[test]
fn test_u8_counter() {
let u8_counter = SeqCountProviderSimple::<u8>::default();
assert_eq!(u8_counter.get(), 0);
assert_eq!(u8_counter.get_and_increment(), 0);
assert_eq!(u8_counter.get_and_increment(), 1);
assert_eq!(u8_counter.get(), 2);
}
#[test]
fn test_u8_counter_overflow() {
let u8_counter = SeqCountProviderSimple::new_u8();
for _ in 0..256 {
u8_counter.increment();
}
assert_eq!(u8_counter.get(), 0);
}
#[test]
fn test_ccsds_counter() {
let ccsds_counter = CcsdsSimpleSeqCountProvider::default();
assert_eq!(ccsds_counter.get(), 0);
assert_eq!(ccsds_counter.get_and_increment(), 0);
assert_eq!(ccsds_counter.get_and_increment(), 1);
assert_eq!(ccsds_counter.get(), 2);
}
#[test]
fn test_ccsds_counter_overflow() {
let ccsds_counter = CcsdsSimpleSeqCountProvider::default();
for _ in 0..MAX_SEQ_COUNT + 1 {
ccsds_counter.increment();
}
assert_eq!(ccsds_counter.get(), 0);
}
#[test]
fn test_atomic_ref_counters() {
let sync_u8_counter = SeqCountProviderSyncU8::new();
assert_eq!(sync_u8_counter.get(), 0);
assert_eq!(sync_u8_counter.get_and_increment(), 0);
assert_eq!(sync_u8_counter.get_and_increment(), 1);
assert_eq!(sync_u8_counter.get(), 2);
}
#[test]
fn test_atomic_ref_counters_overflow() {
let sync_u8_counter = SeqCountProviderSyncU8::new();
for _ in 0..u8::MAX as u16 + 1 {
sync_u8_counter.increment();
}
assert_eq!(sync_u8_counter.get(), 0);
}
#[test]
fn test_atomic_ref_counters_overflow_custom_max_val() {
let sync_u8_counter = SeqCountProviderSyncU8::new_with_max_val(128);
for _ in 0..129 {
sync_u8_counter.increment();
}
assert_eq!(sync_u8_counter.get(), 0);
}
}

@ -7,17 +7,13 @@
use crate::private::Sealed; use crate::private::Sealed;
use crate::ByteConversionError; use crate::ByteConversionError;
use core::cmp::Ordering; use core::cmp::Ordering;
use core::fmt::{Debug, Display, Formatter}; use core::fmt::Debug;
use core::ops::{Add, AddAssign}; use core::ops::{Add, AddAssign};
use core::time::Duration; use core::time::Duration;
use delegate::delegate;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use super::StdTimestampError; use super::StdTimestampError;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::time::{SystemTime, SystemTimeError}; use std::time::{SystemTime, SystemTimeError};
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
@ -93,49 +89,19 @@ pub enum SubmillisPrecision {
Reserved = 0b11, Reserved = 0b11,
} }
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CdsError { pub enum CdsError {
/// CCSDS days value exceeds maximum allowed size or is negative /// CCSDS days value exceeds maximum allowed size or is negative
#[error("invalid ccsds days {0}")]
InvalidCcsdsDays(i64), InvalidCcsdsDays(i64),
/// There are distinct constructors depending on the days field width detected in the preamble /// There are distinct constructors depending on the days field width detected in the preamble
/// field. This error will be returned if there is a missmatch. /// field. This error will be returned if there is a missmatch.
#[error("wrong constructor for length of day {0:?} detected in preamble")]
InvalidCtorForDaysOfLenInPreamble(LengthOfDaySegment), InvalidCtorForDaysOfLenInPreamble(LengthOfDaySegment),
DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError), #[error("date before CCSDS epoch: {0}")]
} DateBeforeCcsdsEpoch(#[from] DateBeforeCcsdsEpochError),
impl Display for CdsError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
CdsError::InvalidCcsdsDays(days) => {
write!(f, "invalid ccsds days {days}")
}
CdsError::InvalidCtorForDaysOfLenInPreamble(length_of_day) => {
write!(
f,
"wrong constructor for length of day {length_of_day:?} detected in preamble",
)
}
CdsError::DateBeforeCcsdsEpoch(e) => write!(f, "date before CCSDS epoch: {e}"),
}
}
}
#[cfg(feature = "std")]
impl Error for CdsError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
CdsError::DateBeforeCcsdsEpoch(e) => Some(e),
_ => None,
}
}
}
impl From<DateBeforeCcsdsEpochError> for CdsError {
fn from(value: DateBeforeCcsdsEpochError) -> Self {
Self::DateBeforeCcsdsEpoch(value)
}
} }
pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment { pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment {
@ -300,20 +266,23 @@ impl CdsConverter for ConversionFromUnix {
self.unix_days_seconds self.unix_days_seconds
} }
} }
/// Helper struct which generates fields for the CDS time provider from a datetime. /// Helper struct which generates fields for the CDS time provider from a datetime.
#[cfg(feature = "chrono")]
struct ConversionFromChronoDatetime { struct ConversionFromChronoDatetime {
unix_conversion: ConversionFromUnix, unix_conversion: ConversionFromUnix,
submillis_prec: SubmillisPrecision, submillis_prec: SubmillisPrecision,
submillis: u32, submillis: u32,
} }
#[cfg(feature = "chrono")]
impl CdsCommon for ConversionFromChronoDatetime { impl CdsCommon for ConversionFromChronoDatetime {
#[inline] #[inline]
fn submillis_precision(&self) -> SubmillisPrecision { fn submillis_precision(&self) -> SubmillisPrecision {
self.submillis_prec self.submillis_prec
} }
delegate! { delegate::delegate! {
to self.unix_conversion { to self.unix_conversion {
#[inline] #[inline]
fn ms_of_day(&self) -> u32; fn ms_of_day(&self) -> u32;
@ -328,8 +297,9 @@ impl CdsCommon for ConversionFromChronoDatetime {
} }
} }
#[cfg(feature = "chrono")]
impl CdsConverter for ConversionFromChronoDatetime { impl CdsConverter for ConversionFromChronoDatetime {
delegate! {to self.unix_conversion { delegate::delegate! {to self.unix_conversion {
#[inline] #[inline]
fn unix_days_seconds(&self) -> i64; fn unix_days_seconds(&self) -> i64;
}} }}
@ -366,7 +336,6 @@ impl ConversionFromChronoDatetime {
Self::new_generic(dt, SubmillisPrecision::Picoseconds) Self::new_generic(dt, SubmillisPrecision::Picoseconds)
} }
#[cfg(feature = "chrono")]
fn new_generic( fn new_generic(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
prec: SubmillisPrecision, prec: SubmillisPrecision,
@ -448,7 +417,7 @@ impl CdsCommon for ConversionFromNow {
fn submillis_precision(&self) -> SubmillisPrecision { fn submillis_precision(&self) -> SubmillisPrecision {
self.submillis_prec self.submillis_prec
} }
delegate! { delegate::delegate! {
to self.unix_conversion { to self.unix_conversion {
fn ms_of_day(&self) -> u32; fn ms_of_day(&self) -> u32;
fn ccsds_days_as_u32(&self) -> u32; fn ccsds_days_as_u32(&self) -> u32;
@ -462,7 +431,7 @@ impl CdsCommon for ConversionFromNow {
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl CdsConverter for ConversionFromNow { impl CdsConverter for ConversionFromNow {
delegate! {to self.unix_conversion { fn unix_days_seconds(&self) -> i64; }} delegate::delegate! {to self.unix_conversion { fn unix_days_seconds(&self) -> i64; }}
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
@ -600,7 +569,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
)); ));
} }
let pfield = buf[0]; let pfield = buf[0];
match CcsdsTimeCode::try_from(pfield >> 4 & 0b111) { match CcsdsTimeCode::try_from((pfield >> 4) & 0b111) {
Ok(cds_type) => match cds_type { Ok(cds_type) => match cds_type {
CcsdsTimeCode::Cds => (), CcsdsTimeCode::Cds => (),
_ => { _ => {
@ -613,7 +582,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
_ => { _ => {
return Err(TimestampError::InvalidTimeCode { return Err(TimestampError::InvalidTimeCode {
expected: CcsdsTimeCode::Cds, expected: CcsdsTimeCode::Cds,
found: pfield >> 4 & 0b111, found: (pfield >> 4) & 0b111,
}); });
} }
}; };

@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize};
use core::fmt::{Debug, Display, Formatter}; use core::fmt::{Debug, Display, Formatter};
use core::ops::{Add, AddAssign}; use core::ops::{Add, AddAssign};
use core::time::Duration; use core::time::Duration;
use core::u64;
use crate::ByteConversionError; use crate::ByteConversionError;

@ -6,7 +6,6 @@ use core::cmp::Ordering;
use core::fmt::{Display, Formatter}; use core::fmt::{Display, Formatter};
use core::ops::{Add, AddAssign, Sub}; use core::ops::{Add, AddAssign, Sub};
use core::time::Duration; use core::time::Duration;
use core::u8;
#[allow(unused_imports)] #[allow(unused_imports)]
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
@ -64,20 +63,12 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCode, u8> {
CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits) CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits)
} }
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("date before ccsds epoch: {0:?}")]
pub struct DateBeforeCcsdsEpochError(UnixTime); pub struct DateBeforeCcsdsEpochError(UnixTime);
impl Display for DateBeforeCcsdsEpochError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "date before ccsds epoch: {:?}", self.0)
}
}
#[cfg(feature = "std")]
impl Error for DateBeforeCcsdsEpochError {}
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]