3 Commits

Author SHA1 Message Date
cb0ddb1338 test fixes 2024-11-08 15:26:26 +01:00
39ec3e795c switch to thiserror completely 2024-11-08 14:53:53 +01:00
3b920cbdd8 switch to thiserror completely 2024-11-08 14:17:36 +01:00
15 changed files with 199 additions and 885 deletions

View File

@ -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.70.0 - uses: dtolnay/rust-toolchain@1.81.0
- run: cargo check --release - run: cargo check --release
cross-check: cross-check:

View File

@ -8,46 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
## Added
- `PusTcCreatorWithReservedAppData` and `PusTmCreatorWithReservedSourceData` constructor variants
which allow writing source/app data into the serialization buffer directly without
requiring an extra buffer.
# [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 - Bumped MSRV to 1.81.0
- Bump `zerocopy` to v0.8.0 - Bump `zerocopy` to v0.8.0
- Bump `thiserror` to v2.0.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 # [v0.12.0] 2024-09-10
- Bumped MSRV to 1.70.0 - Bumped MSRV to 1.70.0
@ -590,9 +554,3 @@ 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

View File

@ -1,8 +1,8 @@
[package] [package]
name = "spacepackets" name = "spacepackets"
version = "0.14.0" version = "0.12.0"
edition = "2021" edition = "2021"
rust-version = "1.70.0" rust-version = "1.81.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,18 +13,45 @@ 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.3" crc = "3"
delegate = ">=0.8, <=0.13" delegate = ">=0.8, <=0.13"
paste = "1" 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"] }
time = { version = "0.3", default-features = false, optional = true } [dependencies.zerocopy]
chrono = { version = "0.4", default-features = false, optional = true } version = "0.8"
defmt = { version = "1", default-features = false, optional = true } features = ["derive"]
[dependencies.thiserror]
version = "2"
default-features = false
[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
[features] [features]
default = ["std"] default = ["std"]

View File

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

View File

@ -1,6 +1,7 @@
[![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
====== ======

View File

@ -1,8 +1,8 @@
//! 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;
@ -42,9 +42,7 @@ pub enum PduError {
/// 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}")] #[error("invalid transaction ID length {0}")]
InvalidTransactionSeqNumLen(u8), InvalidTransactionSeqNumLen(u8),
#[error( #[error("missmatch of PDU source ID length {src_id_len} and destination ID length {dest_id_len}")]
"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,

View File

@ -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<'seg_reqs>, PduError> { ) -> Result<NakPduCreator, 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<'seg_reqs>, PduError> { ) -> Result<NakPduCreator, 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<'seg_reqs>, PduError> { ) -> Result<NakPduCreator, 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<T> Iterator for SegmentRequestIter<'_, T> impl<'a, T> Iterator for SegmentRequestIter<'a, T>
where where
T: SegReqFromBytes, T: SegReqFromBytes,
{ {
@ -282,8 +282,8 @@ where
} }
} }
impl<'a> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'_, u32> { impl<'a, 'b> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'b, u32> {
fn eq(&self, other: &SegmentRequests<'a>) -> bool { fn eq(&self, other: &SegmentRequests) -> 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> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'_, u32> {
} }
} }
impl<'a> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'_, u64> { impl<'a, 'b> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'b, u64> {
fn eq(&self, other: &SegmentRequests<'a>) -> bool { fn eq(&self, other: &SegmentRequests) -> 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> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'_, u64> {
} }
} }
impl<T> SegmentRequestIter<'_, T> impl<'a, T> SegmentRequestIter<'a, 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<'seg_reqs>, PduError> { pub fn new(buf: &'seg_reqs [u8]) -> Result<NakPduReader, PduError> {
Self::from_bytes(buf) Self::from_bytes(buf)
} }
pub fn from_bytes(buf: &'seg_reqs [u8]) -> Result<NakPduReader<'seg_reqs>, PduError> { pub fn from_bytes(buf: &'seg_reqs [u8]) -> Result<NakPduReader, 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> PartialEq<NakPduCreator<'a>> for NakPduReader<'_> { impl<'a, 'b> PartialEq<NakPduCreator<'a>> for NakPduReader<'b> {
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()

View File

@ -273,15 +273,6 @@ pub mod alloc_mod {
data: Vec::new(), 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 { impl ReadableTlv for TlvOwned {
@ -398,7 +389,7 @@ impl GenericTlv for EntityIdTlv {
} }
} }
impl TryFrom<Tlv<'_>> for EntityIdTlv { impl<'data> TryFrom<Tlv<'data>> for EntityIdTlv {
type Error = TlvLvError; type Error = TlvLvError;
fn try_from(value: Tlv) -> Result<Self, TlvLvError> { fn try_from(value: Tlv) -> Result<Self, TlvLvError> {

View File

@ -2,10 +2,7 @@
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use super::TlvOwned; use super::TlvOwned;
use super::{GenericTlv, ReadableTlv, Tlv, TlvLvError, TlvType, TlvTypeField, WritableTlv}; use super::{GenericTlv, ReadableTlv, Tlv, TlvLvError, TlvType, TlvTypeField, WritableTlv};
use crate::{ use crate::{cfdp::{InvalidTlvTypeFieldError, TlvLvDataTooLargeError}, ByteConversionError};
cfdp::{InvalidTlvTypeFieldError, TlvLvDataTooLargeError},
ByteConversionError,
};
use delegate::delegate; use delegate::delegate;
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -68,16 +65,14 @@ impl<'data> MsgToUserTlv<'data> {
return Err(InvalidTlvTypeFieldError { 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());
.into());
} }
} }
TlvTypeField::Custom(raw) => { TlvTypeField::Custom(raw) => {
return Err(InvalidTlvTypeFieldError { return Err(InvalidTlvTypeFieldError {
found: raw, found: raw,
expected: Some(TlvType::MsgToUser as u8), expected: Some(TlvType::MsgToUser as u8),
} }.into());
.into());
} }
} }
Ok(msg_to_user) Ok(msg_to_user)

View File

@ -1,11 +0,0 @@
/// 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);

View File

@ -3,10 +3,7 @@
//! //!
//! 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::{ use crate::{ByteConversionError, CcsdsPacket, CRC_CCITT_FALSE};
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; use core::fmt::Debug;
@ -173,7 +170,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 opt_crc16(&self) -> Option<u16>; fn crc16(&self) -> Option<u16>;
} }
pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> { pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> {
@ -210,7 +207,7 @@ pub(crate) fn user_data_from_raw(
} }
} }
pub fn verify_crc16_ccitt_false_from_raw_to_pus_error( pub(crate) fn verify_crc16_ccitt_false_from_raw_to_pus_error(
raw_data: &[u8], raw_data: &[u8],
crc16: u16, crc16: u16,
) -> Result<(), PusError> { ) -> Result<(), PusError> {
@ -219,15 +216,6 @@ pub 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);
@ -237,15 +225,6 @@ 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 {
@ -370,34 +349,7 @@ 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_FALSE] 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

View File

@ -33,14 +33,13 @@
//! 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};
@ -51,8 +50,6 @@ 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 =
@ -118,7 +115,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]
@ -242,13 +239,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,
@ -345,17 +342,6 @@ 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);
@ -372,49 +358,6 @@ impl<'app_data> PusTcCreator<'app_data> {
vec.extend_from_slice(&digest.finalize().to_be_bytes()); vec.extend_from_slice(&digest.finalize().to_be_bytes());
appended_len appended_len
} }
/// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, ByteConversionError> {
let writer_unfinalized = self.common_write(slice)?;
Ok(writer_unfinalized.finalize())
}
/// 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 writer_unfinalized = self.common_write(slice)?;
Ok(writer_unfinalized.finalize_crc_no_table())
}
/// 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 writer_unfinalized = self.common_write(slice)?;
Ok(writer_unfinalized.finalize_no_crc())
}
fn common_write<'a>(
&self,
slice: &'a mut [u8],
) -> Result<PusTcCreatorWithReservedAppData<'a>, ByteConversionError> {
if self.len_written() > slice.len() {
return Err(ByteConversionError::ToSliceTooSmall {
found: slice.len(),
expected: self.len_written(),
});
}
let mut writer_unfinalized = PusTcCreatorWithReservedAppData::write_to_bytes_partially(
slice,
self.sp_header,
self.sec_header,
self.app_data.len(),
)?;
writer_unfinalized
.app_data_mut()
.copy_from_slice(self.app_data);
Ok(writer_unfinalized)
}
} }
impl WritablePusPacket for PusTcCreator<'_> { impl WritablePusPacket for PusTcCreator<'_> {
@ -424,16 +367,32 @@ impl WritablePusPacket for PusTcCreator<'_> {
} }
/// Write the raw PUS byte representation to a provided buffer. /// Write the raw PUS byte representation to a provided buffer.
fn write_to_bytes_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes_no_crc(self, slice)?)
}
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> { fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes(self, slice)?) let mut curr_idx = 0;
} let tc_header_len = size_of::<zc::PusTcSecondaryHeader>();
let total_size = self.len_written();
if total_size > slice.len() {
return Err(ByteConversionError::ToSliceTooSmall {
found: slice.len(),
expected: total_size,
}
.into());
}
self.sp_header.write_to_be_bytes(slice)?;
curr_idx += CCSDS_HEADER_LEN;
let sec_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
sec_header
.write_to(&mut slice[curr_idx..curr_idx + tc_header_len])
.map_err(|_| ByteConversionError::ZeroCopyToError)?;
fn write_to_bytes_crc_no_table(&self, slice: &mut [u8]) -> Result<usize, PusError> { curr_idx += tc_header_len;
Ok(Self::write_to_bytes_crc_no_table(self, slice)?) slice[curr_idx..curr_idx + self.app_data.len()].copy_from_slice(self.app_data);
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)
} }
} }
@ -457,7 +416,7 @@ impl PusPacket for PusTcCreator<'_> {
} }
#[inline] #[inline]
fn opt_crc16(&self) -> Option<u16> { fn crc16(&self) -> Option<u16> {
Some(self.calc_own_crc16()) Some(self.calc_own_crc16())
} }
} }
@ -479,133 +438,6 @@ impl GenericPusTcSecondaryHeader for PusTcCreator<'_> {
impl IsPusTelecommand for PusTcCreator<'_> {} impl IsPusTelecommand for PusTcCreator<'_> {}
/// A specialized variant of [PusTcCreator] designed for efficiency when handling large source
/// data.
///
/// Unlike [PusTcCreator], this type does not require the user to provide the application data
/// as a separate slice. Instead, it allows writing the application data directly into the provided
/// serialization buffer. This eliminates the need for an intermediate buffer and the associated
/// memory copy, improving performance, particularly when working with large payloads.
///
/// **Important:** The total length of the source data must be known and specified in advance
/// to ensure correct serialization behavior.
///
/// Note that this abstraction intentionally omits certain trait implementations that are available
/// on [PusTcCreator], as they are not applicable in this optimized usage pattern.
pub struct PusTcCreatorWithReservedAppData<'buf> {
buf: &'buf mut [u8],
app_data_offset: usize,
full_len: usize,
}
impl<'buf> PusTcCreatorWithReservedAppData<'buf> {
/// Generates a new instance with reserved space for the user application data.
///
/// # Arguments
///
/// * `sp_header` - Space packet header information. The correct packet type and the secondary
/// header flag are set correctly by the constructor.
/// * `sec_header` - Information contained in the secondary header, including the service
/// and subservice type
/// * `app_data_len` - Custom application data length
#[inline]
pub fn new(
buf: &'buf mut [u8],
mut sp_header: SpHeader,
sec_header: PusTcSecondaryHeader,
app_data_len: usize,
) -> Result<Self, ByteConversionError> {
sp_header.set_packet_type(PacketType::Tc);
sp_header.set_sec_header_flag();
let len_written = PUS_TC_MIN_LEN_WITHOUT_APP_DATA + app_data_len;
if len_written > buf.len() {
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
expected: len_written,
});
}
sp_header.data_len = len_written as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
Self::write_to_bytes_partially(buf, sp_header, sec_header, app_data_len)
}
fn write_to_bytes_partially(
buf: &'buf mut [u8],
sp_header: SpHeader,
sec_header: PusTcSecondaryHeader,
app_data_len: usize,
) -> Result<Self, ByteConversionError> {
let mut curr_idx = 0;
sp_header.write_to_be_bytes(&mut buf[0..CCSDS_HEADER_LEN])?;
curr_idx += CCSDS_HEADER_LEN;
let sec_header_len = size_of::<zc::PusTcSecondaryHeader>();
let sec_header_zc = zc::PusTcSecondaryHeader::try_from(sec_header).unwrap();
sec_header_zc
.write_to(&mut buf[curr_idx..curr_idx + sec_header_len])
.map_err(|_| ByteConversionError::ZeroCopyToError)?;
curr_idx += sec_header_len;
let app_data_offset = curr_idx;
curr_idx += app_data_len;
Ok(Self {
buf,
app_data_offset,
full_len: curr_idx + 2,
})
}
#[inline]
pub const fn len_written(&self) -> usize {
self.full_len
}
/// Mutable access to the application data buffer.
#[inline]
pub fn app_data_mut(&mut self) -> &mut [u8] {
&mut self.buf[self.app_data_offset..self.full_len - 2]
}
/// Access to the source data buffer.
#[inline]
pub fn app_data(&self) -> &[u8] {
&self.buf[self.app_data_offset..self.full_len - 2]
}
#[inline]
pub fn app_data_len(&self) -> usize {
self.full_len - 2 - self.app_data_offset
}
/// Finalize the TC packet by calculating and writing the CRC16.
///
/// Returns the full packet length.
pub fn finalize(self) -> usize {
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&self.buf[0..self.full_len - 2]);
self.buf[self.full_len - 2..self.full_len]
.copy_from_slice(&digest.finalize().to_be_bytes());
self.full_len
}
/// Finalize the TC packet by calculating and writing the CRC16 using a table-less
/// implementation.
///
/// Returns the full packet length.
pub fn finalize_crc_no_table(self) -> usize {
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
digest.update(&self.buf[0..self.full_len - 2]);
self.buf[self.full_len - 2..self.full_len]
.copy_from_slice(&digest.finalize().to_be_bytes());
self.full_len
}
/// Finalize the TC packet without writing the CRC16.
///
/// Returns the length WITHOUT the CRC16.
#[inline]
pub fn finalize_no_crc(self) -> usize {
self.full_len - 2
}
}
/// This class can be used to read a PUS TC telecommand from raw memory. /// This class can be used to read a PUS TC telecommand from raw memory.
/// ///
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the /// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the
@ -633,22 +465,7 @@ 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, PusError> { pub fn new(slice: &'raw_data [u8]) -> Result<(Self, usize), 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 {
@ -681,13 +498,15 @@ impl<'raw_data> PusTcReader<'raw_data> {
.map_err(|_| 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];
Ok(Self { let pus_tc = 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]
@ -709,11 +528,6 @@ 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<'_> {
@ -743,7 +557,7 @@ impl PusPacket for PusTcReader<'_> {
} }
#[inline] #[inline]
fn opt_crc16(&self) -> Option<u16> { fn crc16(&self) -> Option<u16> {
Some(self.crc16) Some(self.crc16)
} }
} }
@ -783,6 +597,8 @@ impl PartialEq<PusTcReader<'_>> for PusTcCreator<'_> {
#[cfg(all(test, feature = "std"))] #[cfg(all(test, feature = "std"))]
mod tests { mod tests {
use std::error::Error;
use super::*; use super::*;
use crate::ecss::PusVersion::PusC; use crate::ecss::PusVersion::PusC;
use crate::ecss::{PusError, PusPacket, WritablePusPacket}; use crate::ecss::{PusError, PusPacket, WritablePusPacket};
@ -824,74 +640,11 @@ 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.opt_crc16().unwrap(), pus_tc.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_with_trait_1() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32];
let size = WritablePusPacket::write_to_bytes(&pus_tc, 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_with_trait_2() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32];
let size = WritablePusPacket::write_to_bytes_crc_no_table(&pus_tc, 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_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]
fn test_serialization_no_crc_with_trait() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf: [u8; 32] = [0; 32];
let size = WritablePusPacket::write_to_bytes_no_crc(&pus_tc, 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();
@ -900,48 +653,9 @@ 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 = let (tc_from_raw, size) =
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_alt_ctor() {
let sph = SpHeader::new_for_unseg_tc_checked(0x02, 0x34, 0).unwrap();
let tc_header = PusTcSecondaryHeader::new_simple(17, 1);
let mut test_buf: [u8; 32] = [0; 32];
let mut pus_tc =
PusTcCreatorWithReservedAppData::new(&mut test_buf, sph, tc_header, 0).unwrap();
assert_eq!(pus_tc.len_written(), 13);
assert_eq!(pus_tc.app_data_len(), 0);
assert_eq!(pus_tc.app_data(), &[]);
assert_eq!(pus_tc.app_data_mut(), &[]);
let size = pus_tc.finalize();
assert_eq!(size, 13); assert_eq!(size, 13);
let tc_from_raw =
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);
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);
@ -953,9 +667,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 = PusTcReader::new(tc_vec.as_slice()) let (tc_from_raw, size) = 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!(tc_from_raw.total_len(), 13); assert_eq!(size, 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);
@ -970,7 +684,6 @@ mod tests {
tc.update_ccsds_data_len(); tc.update_ccsds_data_len();
assert_eq!(tc.data_len(), 6); assert_eq!(tc.data_len(), 6);
} }
#[test] #[test]
fn test_deserialization_with_app_data() { fn test_deserialization_with_app_data() {
let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]); let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]);
@ -979,15 +692,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 = let (tc_from_raw, size) =
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(), 16); assert_eq!(size, 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.opt_crc16().unwrap(), tc_from_raw.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);
@ -1002,9 +715,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);
} }
@ -1073,17 +786,22 @@ mod tests {
let res = pus_tc.write_to_bytes(test_buf.as_mut_slice()); let res = pus_tc.write_to_bytes(test_buf.as_mut_slice());
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();
assert_eq!( if let PusError::ByteConversion(e) = err {
err, assert_eq!(
ByteConversionError::ToSliceTooSmall { e,
found: 12, ByteConversionError::ToSliceTooSmall {
expected: 13 found: 12,
} expected: 13
); }
assert_eq!( );
err.to_string(), assert_eq!(
"target slice with size 12 is too small, expected size of at least 13" err.to_string(),
); "pus error: target slice with size 12 is too small, expected size of at least 13"
);
assert_eq!(err.source().unwrap().to_string(), e.to_string());
} else {
panic!("unexpected error {err}");
}
} }
#[test] #[test]
@ -1151,7 +869,7 @@ mod tests {
assert_eq!(*tc.sp_header(), comp_header); assert_eq!(*tc.sp_header(), comp_header);
} }
fn verify_test_tc_generic(tc: &(impl PusPacket + GenericPusTcSecondaryHeader)) { fn verify_test_tc_generic(tc: &(impl CcsdsPacket + 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);
@ -1215,8 +933,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()); assert_eq!(pus_tc, PusTcReader::new(&buf).unwrap().0);
assert_eq!(PusTcReader::new(&buf).unwrap(), pus_tc); assert_eq!(PusTcReader::new(&buf).unwrap().0, pus_tc);
} }
#[test] #[test]

View File

@ -35,14 +35,13 @@
//! 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 = PusTmReader::new(&test_buf, 7).expect("Deserialization failed"); //! let (ping_tm_reader, read_size) = PusTmReader::new(&test_buf, 7).expect("Deserialization failed");
//! assert_eq!(written_size, ping_tm_reader.total_len()); //! assert_eq!(written_size, read_size);
//! 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,
@ -50,7 +49,7 @@ use crate::ecss::{
}; };
use crate::{ use crate::{
ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SpHeader, CCSDS_HEADER_LEN, ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SpHeader, CCSDS_HEADER_LEN,
MAX_APID, MAX_SEQ_COUNT, CRC_CCITT_FALSE, MAX_APID, MAX_SEQ_COUNT,
}; };
use core::mem::size_of; use core::mem::size_of;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@ -65,8 +64,6 @@ 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
@ -123,7 +120,7 @@ pub mod zc {
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)
} }
@ -280,13 +277,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,
@ -392,45 +389,33 @@ 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 writer_unfinalized = self.common_write(slice)?; let mut curr_idx = 0;
Ok(writer_unfinalized.finalize()) let total_size = self.len_written();
} if total_size > slice.len() {
/// 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 writer_unfinalized = self.common_write(slice)?;
Ok(writer_unfinalized.finalize_crc_no_table())
}
/// 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 writer_unfinalized = self.common_write(slice)?;
Ok(writer_unfinalized.finalize_no_crc())
}
fn common_write<'a>(
&self,
slice: &'a mut [u8],
) -> Result<PusTmCreatorWithReservedSourceData<'a>, ByteConversionError> {
if self.len_written() > slice.len() {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
found: slice.len(), found: slice.len(),
expected: self.len_written(), expected: total_size,
}); });
} }
let mut writer_unfinalized = PusTmCreatorWithReservedSourceData::write_to_bytes_partially( self.sp_header
slice, .write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?;
self.sp_header, curr_idx += CCSDS_HEADER_LEN;
self.sec_header, let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
self.source_data.len(), let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
)?; sec_header
writer_unfinalized .write_to(&mut slice[curr_idx..curr_idx + sec_header_len])
.source_data_mut() .map_err(|_| ByteConversionError::ZeroCopyToError)?;
.copy_from_slice(self.source_data); curr_idx += sec_header_len;
Ok(writer_unfinalized) slice[curr_idx..curr_idx + self.sec_header.timestamp.len()]
.copy_from_slice(self.sec_header.timestamp);
curr_idx += self.sec_header.timestamp.len();
slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data);
curr_idx += self.source_data.len();
let 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)
} }
/// Append the raw PUS byte representation to a provided [alloc::vec::Vec] /// Append the raw PUS byte representation to a provided [alloc::vec::Vec]
@ -461,21 +446,12 @@ 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_no_crc(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes_no_crc(self, slice)?)
}
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> { fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes(self, slice)?) Ok(Self::write_to_bytes(self, slice)?)
} }
fn write_to_bytes_crc_no_table(&self, slice: &mut [u8]) -> Result<usize, PusError> {
Ok(Self::write_to_bytes_crc_no_table(self, slice)?)
}
} }
impl PartialEq for PusTmCreator<'_, '_> { impl PartialEq for PusTmCreator<'_, '_> {
#[inline]
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.sp_header == other.sp_header self.sp_header == other.sp_header
&& self.sec_header == other.sec_header && self.sec_header == other.sec_header
@ -503,7 +479,7 @@ impl PusPacket for PusTmCreator<'_, '_> {
} }
#[inline] #[inline]
fn opt_crc16(&self) -> Option<u16> { fn crc16(&self) -> Option<u16> {
Some(self.calc_own_crc16()) Some(self.calc_own_crc16())
} }
} }
@ -527,136 +503,6 @@ impl GenericPusTmSecondaryHeader for PusTmCreator<'_, '_> {
impl IsPusTelemetry for PusTmCreator<'_, '_> {} impl IsPusTelemetry for PusTmCreator<'_, '_> {}
/// A specialized variant of [PusTmCreator] designed for efficiency when handling large source
/// data.
///
/// Unlike [PusTmCreator], this type does not require the user to provide the source data
/// as a separate slice. Instead, it allows writing the source data directly into the provided
/// serialization buffer. This eliminates the need for an intermediate buffer and the associated
/// memory copy, improving performance, particularly when working with large payloads.
///
/// **Important:** The total length of the source data must be known and specified in advance
/// to ensure correct serialization behavior.
///
/// Note that this abstraction intentionally omits certain trait implementations that are available
/// on [PusTmCreator], as they are not applicable in this optimized usage pattern.
pub struct PusTmCreatorWithReservedSourceData<'buf> {
buf: &'buf mut [u8],
source_data_offset: usize,
full_len: usize,
}
impl<'buf> PusTmCreatorWithReservedSourceData<'buf> {
/// Generates a new instance with reserved space for the user source data.
///
/// # Arguments
///
/// * `sp_header` - Space packet header information. The correct packet type and the secondary
/// header flag are set correctly by the constructor.
/// * `sec_header` - Information contained in the secondary header, including the service
/// and subservice type
/// * `src_data_len` - Custom source data length
#[inline]
pub fn new(
buf: &'buf mut [u8],
mut sp_header: SpHeader,
sec_header: PusTmSecondaryHeader,
src_data_len: usize,
) -> Result<Self, ByteConversionError> {
sp_header.set_packet_type(PacketType::Tm);
sp_header.set_sec_header_flag();
let len_written =
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA + sec_header.timestamp.len() + src_data_len;
if len_written > buf.len() {
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
expected: len_written,
});
}
sp_header.data_len = len_written as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
Self::write_to_bytes_partially(buf, sp_header, sec_header, src_data_len)
}
fn write_to_bytes_partially(
buf: &'buf mut [u8],
sp_header: SpHeader,
sec_header: PusTmSecondaryHeader,
src_data_len: usize,
) -> Result<Self, ByteConversionError> {
let mut curr_idx = 0;
sp_header.write_to_be_bytes(&mut buf[0..CCSDS_HEADER_LEN])?;
curr_idx += CCSDS_HEADER_LEN;
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
let sec_header_zc = zc::PusTmSecHeaderWithoutTimestamp::try_from(sec_header).unwrap();
sec_header_zc
.write_to(&mut buf[curr_idx..curr_idx + sec_header_len])
.map_err(|_| ByteConversionError::ZeroCopyToError)?;
curr_idx += sec_header_len;
buf[curr_idx..curr_idx + sec_header.timestamp.len()].copy_from_slice(sec_header.timestamp);
curr_idx += sec_header.timestamp.len();
let source_data_offset = curr_idx;
curr_idx += src_data_len;
Ok(Self {
buf,
source_data_offset,
full_len: curr_idx + 2,
})
}
#[inline]
pub const fn len_written(&self) -> usize {
self.full_len
}
/// Mutable access to the source data buffer.
#[inline]
pub fn source_data_mut(&mut self) -> &mut [u8] {
&mut self.buf[self.source_data_offset..self.full_len - 2]
}
/// Access to the source data buffer.
#[inline]
pub fn source_data(&self) -> &[u8] {
&self.buf[self.source_data_offset..self.full_len - 2]
}
#[inline]
pub fn source_data_len(&self) -> usize {
self.full_len - 2 - self.source_data_offset
}
/// Finalize the TM packet by calculating and writing the CRC16.
///
/// Returns the full packet length.
pub fn finalize(self) -> usize {
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&self.buf[0..self.full_len - 2]);
self.buf[self.full_len - 2..self.full_len]
.copy_from_slice(&digest.finalize().to_be_bytes());
self.full_len
}
/// Finalize the TM packet by calculating and writing the CRC16 using a table-less
/// implementation.
///
/// Returns the full packet length.
pub fn finalize_crc_no_table(self) -> usize {
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
digest.update(&self.buf[0..self.full_len - 2]);
self.buf[self.full_len - 2..self.full_len]
.copy_from_slice(&digest.finalize().to_be_bytes());
self.full_len
}
/// Finalize the TM packet without writing the CRC16.
///
/// Returns the length WITHOUT the CRC16.
#[inline]
pub fn finalize_no_crc(self) -> usize {
self.full_len - 2
}
}
/// This class models the PUS C telemetry packet. It is the primary data structure to read /// This class models the PUS C telemetry packet. It is the primary data structure to read
/// a telemetry packet from raw bytes. /// a telemetry packet from raw bytes.
/// ///
@ -688,26 +534,7 @@ 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, PusError> { pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<(Self, usize), 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 {
@ -745,13 +572,15 @@ 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];
Ok(Self { let pus_tm = 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]
@ -769,11 +598,6 @@ 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] {
@ -810,8 +634,8 @@ impl PusPacket for PusTmReader<'_> {
} }
#[inline] #[inline]
fn opt_crc16(&self) -> Option<u16> { fn crc16(&self) -> Option<u16> {
Some(self.crc16()) Some(self.crc16)
} }
} }
@ -998,7 +822,7 @@ impl PusPacket for PusTmZeroCopyWriter<'_> {
} }
#[inline] #[inline]
fn opt_crc16(&self) -> Option<u16> { fn 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()
@ -1094,66 +918,7 @@ 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.opt_crc16(), &buf); verify_raw_ping_reply(pus_tm.crc16().unwrap(), &buf);
}
#[test]
fn test_serialization_no_source_data_alt_ctor() {
let timestamp = dummy_timestamp();
let sph = SpHeader::new_for_unseg_tm_checked(0x123, 0x234, 0).unwrap();
let tm_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
let mut buf: [u8; 32] = [0; 32];
let mut pus_tm =
PusTmCreatorWithReservedSourceData::new(&mut buf, sph, tm_header, 0).unwrap();
assert_eq!(pus_tm.source_data_len(), 0);
assert_eq!(pus_tm.source_data(), &[]);
assert_eq!(pus_tm.source_data_mut(), &[]);
let ser_len = pus_tm.finalize();
assert_eq!(ser_len, 22);
verify_raw_ping_reply(None, &buf);
}
#[test]
fn test_serialization_no_source_data_alt_ctor_no_crc() {
let timestamp = dummy_timestamp();
let sph = SpHeader::new_for_unseg_tm_checked(0x123, 0x234, 0).unwrap();
let tm_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
let mut buf: [u8; 32] = [0; 32];
let mut pus_tm =
PusTmCreatorWithReservedSourceData::new(&mut buf, sph, tm_header, 0).unwrap();
assert_eq!(pus_tm.source_data_len(), 0);
assert_eq!(pus_tm.source_data(), &[]);
assert_eq!(pus_tm.source_data_mut(), &[]);
let ser_len = pus_tm.finalize_no_crc();
assert_eq!(ser_len, 20);
verify_raw_ping_reply_no_crc(&buf);
assert_eq!(buf[20], 0);
assert_eq!(buf[21], 0);
}
#[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(), &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]
@ -1170,46 +935,6 @@ mod tests {
assert_eq!(buf[22], 3); assert_eq!(buf[22], 3);
} }
#[test]
fn test_serialization_with_source_data_alt_ctor() {
let src_data = &[1, 2, 3];
let mut buf: [u8; 32] = [0; 32];
let sph = SpHeader::new_for_unseg_tm_checked(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, dummy_timestamp());
let mut hk_reply_unwritten =
PusTmCreatorWithReservedSourceData::new(&mut buf, sph, tc_header, 3).unwrap();
assert_eq!(hk_reply_unwritten.source_data_len(), 3);
assert_eq!(hk_reply_unwritten.source_data(), &[0, 0, 0]);
assert_eq!(hk_reply_unwritten.source_data_mut(), &[0, 0, 0]);
let source_data_mut = hk_reply_unwritten.source_data_mut();
source_data_mut.copy_from_slice(src_data);
let ser_len = hk_reply_unwritten.finalize();
assert_eq!(ser_len, 25);
assert_eq!(buf[20], 1);
assert_eq!(buf[21], 2);
assert_eq!(buf[22], 3);
}
#[test]
fn test_serialization_with_source_data_alt_ctor_no_table() {
let src_data = &[1, 2, 3];
let mut buf: [u8; 32] = [0; 32];
let sph = SpHeader::new_for_unseg_tm_checked(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, dummy_timestamp());
let mut hk_reply_unwritten =
PusTmCreatorWithReservedSourceData::new(&mut buf, sph, tc_header, 3).unwrap();
assert_eq!(hk_reply_unwritten.source_data_len(), 3);
assert_eq!(hk_reply_unwritten.source_data(), &[0, 0, 0]);
assert_eq!(hk_reply_unwritten.source_data_mut(), &[0, 0, 0]);
let source_data_mut = hk_reply_unwritten.source_data_mut();
source_data_mut.copy_from_slice(src_data);
let ser_len = hk_reply_unwritten.finalize_crc_no_table();
assert_eq!(ser_len, 25);
assert_eq!(buf[20], 1);
assert_eq!(buf[21], 2);
assert_eq!(buf[22], 3);
}
#[test] #[test]
fn test_setters() { fn test_setters() {
let timestamp = dummy_timestamp(); let timestamp = dummy_timestamp();
@ -1230,12 +955,11 @@ 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 = let (tm_deserialized, size) =
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(), tm_deserialized.total_len()); assert_eq!(tm_vec.len(), size);
verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp()); verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp());
} }
#[test] #[test]
fn test_deserialization_no_source_data() { fn test_deserialization_no_source_data() {
let timestamp = dummy_timestamp(); let timestamp = dummy_timestamp();
@ -1245,48 +969,13 @@ 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 = PusTmReader::new(&buf, 7).expect("Deserialization failed"); let (tm_deserialized, size) = PusTmReader::new(&buf, 7).expect("Deserialization failed");
assert_eq!(ser_len, tm_deserialized.total_len()); assert_eq!(ser_len, size);
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(), pus_tm.opt_crc16().unwrap()); assert_eq!(tm_deserialized.crc16().unwrap(), pus_tm.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_source_data_with_trait() {
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len =
WritablePusPacket::write_to_bytes(&pus_tm, &mut buf).expect("Serialization failed");
assert_eq!(ser_len, 22);
let tm_deserialized = PusTmReader::new(&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]
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();
@ -1353,7 +1042,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.opt_crc16(), vec.as_slice()); verify_raw_ping_reply(pus_tm.crc16().unwrap(), vec.as_slice());
} }
#[test] #[test]
@ -1369,7 +1058,7 @@ mod tests {
assert_eq!(vec.len(), 26); assert_eq!(vec.len(), 26);
} }
fn verify_raw_ping_reply_no_crc(buf: &[u8]) { fn verify_raw_ping_reply(crc16: u16, buf: &[u8]) {
// Secondary header is set -> 0b0000_1001 , APID occupies last bit of first byte // Secondary header is set -> 0b0000_1001 , APID occupies last bit of first byte
assert_eq!(buf[0], 0x09); assert_eq!(buf[0], 0x09);
// Rest of APID 0x123 // Rest of APID 0x123
@ -1390,19 +1079,12 @@ mod tests {
assert_eq!(buf[12], 0x00); assert_eq!(buf[12], 0x00);
// Timestamp // Timestamp
assert_eq!(&buf[13..20], dummy_timestamp()); assert_eq!(&buf[13..20], dummy_timestamp());
}
fn verify_raw_ping_reply(crc16: Option<u16>, buf: &[u8]) {
verify_raw_ping_reply_no_crc(buf);
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&buf[0..20]); digest.update(&buf[0..20]);
let crc16_calced = digest.finalize(); let crc16_calced = digest.finalize();
let crc16_read = u16::from_be_bytes([buf[20], buf[21]]); assert_eq!(((crc16 >> 8) & 0xff) as u8, buf[20]);
assert_eq!(crc16_read, crc16_calced); assert_eq!((crc16 & 0xff) as u8, buf[21]);
if let Some(crc16) = crc16 { assert_eq!(crc16, crc16_calced);
assert_eq!(((crc16 >> 8) & 0xff) as u8, buf[20]);
assert_eq!((crc16 & 0xff) as u8, buf[21]);
}
} }
fn verify_ping_reply( fn verify_ping_reply(
@ -1429,7 +1111,7 @@ mod tests {
} }
fn verify_ping_reply_generic( fn verify_ping_reply_generic(
tm: &(impl GenericPusTmSecondaryHeader + PusPacket), tm: &(impl CcsdsPacket + GenericPusTmSecondaryHeader + PusPacket),
has_user_data: bool, has_user_data: bool,
exp_full_len: usize, exp_full_len: usize,
) { ) {
@ -1470,7 +1152,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()); assert_eq!(pus_tm, PusTmReader::new(&buf, timestamp.len()).unwrap().0);
} }
#[test] #[test]
@ -1490,8 +1172,9 @@ 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 = PusTmReader::new(&buf, 7).expect("Re-creating PUS TM failed"); let (tm_read_back, tm_size_read_back) =
assert_eq!(tm_read_back.total_len(), tm_size); PusTmReader::new(&buf, 7).expect("Re-creating PUS TM failed");
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);
@ -1539,7 +1222,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.opt_crc16(); let crc16 = writer.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();
@ -1557,8 +1240,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]
@ -1630,7 +1313,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);

View File

@ -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
//! //!
@ -62,6 +62,7 @@ extern crate alloc;
extern crate std; extern crate std;
use core::{fmt::Debug, hash::Hash}; use core::{fmt::Debug, hash::Hash};
use crc::{Crc, CRC_16_IBM_3740};
use delegate::delegate; use delegate::delegate;
use zerocopy::{FromBytes, IntoBytes}; use zerocopy::{FromBytes, IntoBytes};
@ -69,7 +70,6 @@ use zerocopy::{FromBytes, IntoBytes};
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 seq_count;
pub mod time; pub mod time;
@ -81,6 +81,9 @@ 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;
@ -449,9 +452,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)]

View File

@ -569,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 => (),
_ => { _ => {
@ -582,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,
}); });
} }
}; };