Merge pull request 'Refactor ECSS packet modules' (#16) from refactoring-ecss-packet-mods into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #16
This commit is contained in:
commit
0a59f3258a
17
CHANGELOG.md
17
CHANGELOG.md
@ -8,11 +8,28 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.7.0]
|
||||
|
||||
## Changed
|
||||
|
||||
- `PusPacket` trait: `user_data` now returns `&[u8]` instead of `Option<&[u8]>`. Empty user data
|
||||
can simply be an empty slice.
|
||||
- Moved ECSS TC components from `tc` to `ecss.tc`.
|
||||
- Moved ECSS TM components from `tm` to `ecss.tm`.
|
||||
- Converted `PusTc` class to more specialized `PusTcCreator` and `PusTcReader`
|
||||
classes. The old `PusTc` class is deprecated now.
|
||||
- Converted `PusTm` class to more specialized `PusTmCreator` and `PusTmReader`
|
||||
classes. The old `PusTm` class is deprecated now.
|
||||
- Implement `Display` and `Error` for `StdTimestampError` properly.
|
||||
- Remove some redundant `Error` suffixes for enum error variants.
|
||||
|
||||
## Added
|
||||
|
||||
- `source_data` and `app_data` API provided for PUS TM and PUS TC reader classes. These simply
|
||||
call `user_data` but are also in line with the PUS packet standard names for those fields.
|
||||
- Added new marker trait `IsPusTelemetry` implemented by `PusTmCreator` and `PusTmReader`.
|
||||
- Added new marker trait `IsPusTelecommand` implemented by `PusTcCreator` and `PusTcReader`.
|
||||
|
||||
# [v0.6.0] 2023-07-06
|
||||
|
||||
## Added
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "spacepackets"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
|
@ -1,8 +1,8 @@
|
||||
//! Common definitions and helpers required to create PUS TMTC packets according to
|
||||
//! [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/)
|
||||
//!
|
||||
//! You can find the PUS telecommand definitions in the [crate::tc] module and ithe PUS telemetry definitions
|
||||
//! inside the [crate::tm] module.
|
||||
//! You can find the PUS telecommand definitions in the [tc] module and ithe PUS telemetry definitions
|
||||
//! inside the [tm] module.
|
||||
use crate::{ByteConversionError, CcsdsPacket, CRC_CCITT_FALSE};
|
||||
use core::fmt::{Debug, Display, Formatter};
|
||||
use core::mem::size_of;
|
||||
@ -15,6 +15,8 @@ use std::error::Error;
|
||||
pub mod event;
|
||||
pub mod hk;
|
||||
pub mod scheduling;
|
||||
pub mod tc;
|
||||
pub mod tm;
|
||||
pub mod verification;
|
||||
|
||||
pub type CrcType = u16;
|
||||
@ -204,8 +206,7 @@ pub trait PusPacket: CcsdsPacket {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
|
||||
fn user_data(&self) -> Option<&[u8]>;
|
||||
fn user_data(&self) -> &[u8];
|
||||
fn crc16(&self) -> Option<u16>;
|
||||
}
|
||||
|
||||
@ -249,11 +250,10 @@ pub(crate) fn user_data_from_raw(
|
||||
total_len: usize,
|
||||
raw_data_len: usize,
|
||||
slice: &[u8],
|
||||
) -> Result<Option<&[u8]>, PusError> {
|
||||
) -> Result<&[u8], PusError> {
|
||||
match current_idx {
|
||||
_ if current_idx == total_len - 2 => Ok(None),
|
||||
_ if current_idx > total_len - 2 => Err(PusError::RawDataTooShort(raw_data_len)),
|
||||
_ => Ok(Some(&slice[current_idx..total_len - 2])),
|
||||
_ => Ok(&slice[current_idx..total_len - 2]),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,13 +5,13 @@
|
||||
//!
|
||||
//! ```rust
|
||||
//! use spacepackets::{CcsdsPacket, SpHeader};
|
||||
//! use spacepackets::tc::{PusTc, PusTcSecondaryHeader};
|
||||
//! use spacepackets::ecss::{PusPacket, SerializablePusPacket};
|
||||
//! use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader};
|
||||
//!
|
||||
//! // Create a ping telecommand with no user application data
|
||||
//! let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
|
||||
//! let tc_header = PusTcSecondaryHeader::new_simple(17, 1);
|
||||
//! let pus_tc = PusTc::new(&mut sph, tc_header, None, true);
|
||||
//! let pus_tc = PusTcCreator::new(&mut sph, tc_header, None, true);
|
||||
//! println!("{:?}", pus_tc);
|
||||
//! assert_eq!(pus_tc.service(), 17);
|
||||
//! assert_eq!(pus_tc.subservice(), 1);
|
||||
@ -26,13 +26,13 @@
|
||||
//! println!("{:?}", &test_buf[0..size]);
|
||||
//!
|
||||
//! // Deserialize from the raw byte representation
|
||||
//! let pus_tc_deserialized = PusTc::from_bytes(&test_buf).expect("Deserialization failed");
|
||||
//! let pus_tc_deserialized = PusTcReader::new(&test_buf).expect("Deserialization failed");
|
||||
//! assert_eq!(pus_tc.service(), 17);
|
||||
//! assert_eq!(pus_tc.subservice(), 1);
|
||||
//! assert_eq!(pus_tc.apid(), 0x02);
|
||||
//! ```
|
||||
use crate::ecss::{
|
||||
ccsds_impl, crc_from_raw_data, crc_procedure, 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,
|
||||
SerializablePusPacket,
|
||||
};
|
||||
@ -49,12 +49,17 @@ use zerocopy::AsBytes;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub use legacy_tc::*;
|
||||
|
||||
/// PUS C secondary header length is fixed
|
||||
pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::<zc::PusTcSecondaryHeader>();
|
||||
pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize =
|
||||
CCSDS_HEADER_LEN + PUC_TC_SECONDARY_HEADER_LEN + size_of::<CrcType>();
|
||||
const PUS_VERSION: PusVersion = PusVersion::PusC;
|
||||
|
||||
/// Marker trait for PUS telecommand structures.
|
||||
pub trait IsPusTelecommand {}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
enum AckOpts {
|
||||
Acceptance = 0b1000,
|
||||
@ -77,8 +82,8 @@ pub trait GenericPusTcSecondaryHeader {
|
||||
}
|
||||
|
||||
pub mod zc {
|
||||
use crate::ecss::tc::GenericPusTcSecondaryHeader;
|
||||
use crate::ecss::{PusError, PusVersion};
|
||||
use crate::tc::GenericPusTcSecondaryHeader;
|
||||
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
|
||||
|
||||
#[derive(FromBytes, AsBytes, Unaligned)]
|
||||
@ -90,9 +95,9 @@ pub mod zc {
|
||||
source_id: U16<NetworkEndian>,
|
||||
}
|
||||
|
||||
impl TryFrom<crate::tc::PusTcSecondaryHeader> for PusTcSecondaryHeader {
|
||||
impl TryFrom<crate::ecss::tc::PusTcSecondaryHeader> for PusTcSecondaryHeader {
|
||||
type Error = PusError;
|
||||
fn try_from(value: crate::tc::PusTcSecondaryHeader) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: crate::ecss::tc::PusTcSecondaryHeader) -> Result<Self, Self::Error> {
|
||||
if value.version != PusVersion::PusC {
|
||||
return Err(PusError::VersionNotSupported(value.version));
|
||||
}
|
||||
@ -206,35 +211,344 @@ impl PusTcSecondaryHeader {
|
||||
}
|
||||
}
|
||||
|
||||
/// This class models the PUS C telecommand packet. It is the primary data structure to generate the
|
||||
/// raw byte representation of a PUS telecommand or to deserialize from one from raw bytes.
|
||||
pub mod legacy_tc {
|
||||
use crate::ecss::tc::{
|
||||
zc, GenericPusTcSecondaryHeader, IsPusTelecommand, PusTcSecondaryHeader, ACK_ALL,
|
||||
PUC_TC_SECONDARY_HEADER_LEN, PUS_TC_MIN_LEN_WITHOUT_APP_DATA,
|
||||
};
|
||||
use crate::ecss::{
|
||||
ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls,
|
||||
verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, SerializablePusPacket,
|
||||
CCSDS_HEADER_LEN,
|
||||
};
|
||||
use crate::ecss::{user_data_from_raw, PusVersion};
|
||||
use crate::SequenceFlags;
|
||||
use crate::{
|
||||
ByteConversionError, CcsdsPacket, PacketType, SizeMissmatch, SpHeader, CRC_CCITT_FALSE,
|
||||
};
|
||||
use core::mem::size_of;
|
||||
use delegate::delegate;
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// This class models the PUS C telecommand packet. It is the primary data structure to generate the
|
||||
/// raw byte representation of a PUS telecommand or to deserialize from one from raw bytes.
|
||||
///
|
||||
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the
|
||||
/// [serde] feature is used, which allows to send around TC packets in a raw byte format using a
|
||||
/// serde provider like [postcard](https://docs.rs/postcard/latest/postcard/).
|
||||
///
|
||||
/// There is no spare bytes support yet.
|
||||
///
|
||||
/// # Lifetimes
|
||||
///
|
||||
/// * `'raw_data` - If the TC is not constructed from a raw slice, this will be the life time of
|
||||
/// a buffer where the user provided application data will be serialized into. If it
|
||||
/// is, this is the lifetime of the raw byte slice it is constructed from.
|
||||
#[derive(Eq, Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PusTc<'raw_data> {
|
||||
sp_header: SpHeader,
|
||||
pub sec_header: PusTcSecondaryHeader,
|
||||
/// If this is set to false, a manual call to [PusTc::calc_own_crc16] or
|
||||
/// [PusTc::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
|
||||
pub calc_crc_on_serialization: bool,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
raw_data: Option<&'raw_data [u8]>,
|
||||
app_data: &'raw_data [u8],
|
||||
crc16: Option<u16>,
|
||||
}
|
||||
|
||||
impl<'raw_data> PusTc<'raw_data> {
|
||||
/// Generates a new struct instance.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `sp_header` - Space packet header information. The correct packet type will be set
|
||||
/// automatically
|
||||
/// * `sec_header` - Information contained in the data field header, including the service
|
||||
/// and subservice type
|
||||
/// * `app_data` - Custom application data
|
||||
/// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length
|
||||
/// field. If this is not set to true, [PusTc::update_ccsds_data_len] can be called to set
|
||||
/// the correct value to this field manually
|
||||
#[deprecated(
|
||||
since = "0.7.0",
|
||||
note = "Use specialized PusTcCreator or PusTcReader classes instead"
|
||||
)]
|
||||
pub fn new(
|
||||
sp_header: &mut SpHeader,
|
||||
sec_header: PusTcSecondaryHeader,
|
||||
app_data: Option<&'raw_data [u8]>,
|
||||
set_ccsds_len: bool,
|
||||
) -> Self {
|
||||
sp_header.set_packet_type(PacketType::Tc);
|
||||
sp_header.set_sec_header_flag();
|
||||
let mut pus_tc = Self {
|
||||
sp_header: *sp_header,
|
||||
raw_data: None,
|
||||
app_data: app_data.unwrap_or(&[]),
|
||||
sec_header,
|
||||
calc_crc_on_serialization: true,
|
||||
crc16: None,
|
||||
};
|
||||
if set_ccsds_len {
|
||||
pus_tc.update_ccsds_data_len();
|
||||
}
|
||||
pus_tc
|
||||
}
|
||||
|
||||
/// Simplified version of the [PusTc::new] function which allows to only specify service and
|
||||
/// subservice instead of the full PUS TC secondary header.
|
||||
#[deprecated(
|
||||
since = "0.7.0",
|
||||
note = "Use specialized PusTcCreator or PusTcReader classes instead"
|
||||
)]
|
||||
pub fn new_simple(
|
||||
sph: &mut SpHeader,
|
||||
service: u8,
|
||||
subservice: u8,
|
||||
app_data: Option<&'raw_data [u8]>,
|
||||
set_ccsds_len: bool,
|
||||
) -> Self {
|
||||
#[allow(deprecated)]
|
||||
Self::new(
|
||||
sph,
|
||||
PusTcSecondaryHeader::new(service, subservice, ACK_ALL, 0),
|
||||
app_data,
|
||||
set_ccsds_len,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn sp_header(&self) -> &SpHeader {
|
||||
&self.sp_header
|
||||
}
|
||||
|
||||
pub fn set_ack_field(&mut self, ack: u8) -> bool {
|
||||
if ack > 0b1111 {
|
||||
return false;
|
||||
}
|
||||
self.sec_header.ack = ack & 0b1111;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_source_id(&mut self, source_id: u16) {
|
||||
self.sec_header.source_id = source_id;
|
||||
}
|
||||
|
||||
sp_header_impls!();
|
||||
|
||||
/// Calculate the CCSDS space packet data length field and sets it
|
||||
/// This is called automatically if the `set_ccsds_len` argument in the [PusTc::new] call was
|
||||
/// used.
|
||||
/// If this was not done or the application data is set or changed after construction,
|
||||
/// this function needs to be called to ensure that the data length field of the CCSDS header
|
||||
/// is set correctly.
|
||||
pub fn update_ccsds_data_len(&mut self) {
|
||||
self.sp_header.data_len =
|
||||
self.len_packed() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
|
||||
}
|
||||
|
||||
/// This function should be called before the TC packet is serialized if
|
||||
/// [PusTc::calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16.
|
||||
pub fn calc_own_crc16(&mut self) {
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||
digest.update(sph_zc.as_bytes());
|
||||
let pus_tc_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
|
||||
digest.update(pus_tc_header.as_bytes());
|
||||
if !self.app_data.is_empty() {
|
||||
digest.update(self.app_data);
|
||||
}
|
||||
self.crc16 = Some(digest.finalize())
|
||||
}
|
||||
|
||||
/// This helper function calls both [PusTc::update_ccsds_data_len] and [PusTc::calc_own_crc16].
|
||||
pub fn update_packet_fields(&mut self) {
|
||||
self.update_ccsds_data_len();
|
||||
self.calc_own_crc16();
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||
let appended_len = PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len();
|
||||
let start_idx = vec.len();
|
||||
let mut ser_len = 0;
|
||||
vec.extend_from_slice(sph_zc.as_bytes());
|
||||
ser_len += sph_zc.as_bytes().len();
|
||||
// The PUS version is hardcoded to PUS C
|
||||
let pus_tc_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
|
||||
vec.extend_from_slice(pus_tc_header.as_bytes());
|
||||
ser_len += pus_tc_header.as_bytes().len();
|
||||
vec.extend_from_slice(self.app_data);
|
||||
ser_len += self.app_data.len();
|
||||
let crc16 = crc_procedure(
|
||||
self.calc_crc_on_serialization,
|
||||
&self.crc16,
|
||||
start_idx,
|
||||
ser_len,
|
||||
&vec[start_idx..ser_len],
|
||||
)?;
|
||||
vec.extend_from_slice(crc16.to_be_bytes().as_slice());
|
||||
Ok(appended_len)
|
||||
}
|
||||
|
||||
/// Create a [PusTc] instance from a raw slice. On success, it returns a tuple containing
|
||||
/// the instance and the found byte length of the packet.
|
||||
#[deprecated(
|
||||
since = "0.7.0",
|
||||
note = "Use specialized PusTcCreator or PusTcReader classes instead"
|
||||
)]
|
||||
pub fn from_bytes(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> {
|
||||
let raw_data_len = slice.len();
|
||||
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let mut current_idx = 0;
|
||||
let (sp_header, _) = SpHeader::from_be_bytes(&slice[0..CCSDS_HEADER_LEN])?;
|
||||
current_idx += CCSDS_HEADER_LEN;
|
||||
let total_len = sp_header.total_len();
|
||||
if raw_data_len < total_len || total_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let sec_header = zc::PusTcSecondaryHeader::from_bytes(
|
||||
&slice[current_idx..current_idx + PUC_TC_SECONDARY_HEADER_LEN],
|
||||
)
|
||||
.ok_or(ByteConversionError::ZeroCopyFromError)?;
|
||||
current_idx += PUC_TC_SECONDARY_HEADER_LEN;
|
||||
let raw_data = &slice[0..total_len];
|
||||
let pus_tc = Self {
|
||||
sp_header,
|
||||
sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(),
|
||||
raw_data: Some(raw_data),
|
||||
app_data: user_data_from_raw(current_idx, total_len, raw_data_len, slice)?,
|
||||
calc_crc_on_serialization: false,
|
||||
crc16: Some(crc_from_raw_data(raw_data)?),
|
||||
};
|
||||
verify_crc16_ccitt_false_from_raw_to_pus_error(
|
||||
raw_data,
|
||||
pus_tc.crc16.expect("CRC16 invalid"),
|
||||
)?;
|
||||
Ok((pus_tc, total_len))
|
||||
}
|
||||
|
||||
#[deprecated(since = "0.5.2", note = "use raw_bytes() instead")]
|
||||
pub fn raw(&self) -> Option<&'raw_data [u8]> {
|
||||
self.raw_bytes()
|
||||
}
|
||||
|
||||
/// If [Self] was constructed [Self::from_bytes], this function will return the slice it was
|
||||
/// constructed from. Otherwise, [None] will be returned.
|
||||
pub fn raw_bytes(&self) -> Option<&'raw_data [u8]> {
|
||||
self.raw_data
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializablePusPacket for PusTc<'_> {
|
||||
fn len_packed(&self) -> usize {
|
||||
PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len()
|
||||
}
|
||||
|
||||
/// Write the raw PUS byte representation to a provided buffer.
|
||||
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
|
||||
let mut curr_idx = 0;
|
||||
let tc_header_len = size_of::<zc::PusTcSecondaryHeader>();
|
||||
let total_size = self.len_packed();
|
||||
if total_size > slice.len() {
|
||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||
found: slice.len(),
|
||||
expected: total_size,
|
||||
})
|
||||
.into());
|
||||
}
|
||||
self.sp_header.write_to_be_bytes(slice)?;
|
||||
curr_idx += CCSDS_HEADER_LEN;
|
||||
let sec_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
|
||||
sec_header
|
||||
.write_to_bytes(&mut slice[curr_idx..curr_idx + tc_header_len])
|
||||
.ok_or(ByteConversionError::ZeroCopyToError)?;
|
||||
|
||||
curr_idx += tc_header_len;
|
||||
slice[curr_idx..curr_idx + self.app_data.len()].copy_from_slice(self.app_data);
|
||||
curr_idx += self.app_data.len();
|
||||
let crc16 = crc_procedure(
|
||||
self.calc_crc_on_serialization,
|
||||
&self.crc16,
|
||||
0,
|
||||
curr_idx,
|
||||
slice,
|
||||
)?;
|
||||
slice[curr_idx..curr_idx + 2].copy_from_slice(crc16.to_be_bytes().as_slice());
|
||||
curr_idx += 2;
|
||||
Ok(curr_idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PusTc<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.sp_header == other.sp_header
|
||||
&& self.sec_header == other.sec_header
|
||||
&& self.app_data == other.app_data
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPacket for PusTc<'_> {
|
||||
ccsds_impl!();
|
||||
}
|
||||
|
||||
impl PusPacket for PusTc<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
});
|
||||
|
||||
fn user_data(&self) -> &[u8] {
|
||||
self.app_data
|
||||
}
|
||||
|
||||
fn crc16(&self) -> Option<u16> {
|
||||
self.crc16
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericPusTcSecondaryHeader for PusTc<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
fn source_id(&self) -> u16;
|
||||
fn ack_flags(&self) -> u8;
|
||||
});
|
||||
}
|
||||
|
||||
impl IsPusTelecommand for PusTc<'_> {}
|
||||
}
|
||||
|
||||
/// This class can be used to create PUS C telecommand packet. It is the primary data structure to
|
||||
/// generate the raw byte representation of a PUS telecommand.
|
||||
///
|
||||
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the
|
||||
/// [serde] feature is used, which allows to send around TC packets in a raw byte format using a
|
||||
/// serde provider like [postcard](https://docs.rs/postcard/latest/postcard/).
|
||||
///
|
||||
/// There is no spare bytes support yet.
|
||||
///
|
||||
/// # Lifetimes
|
||||
///
|
||||
/// * `'raw_data` - If the TC is not constructed from a raw slice, this will be the life time of
|
||||
/// a buffer where the user provided application data will be serialized into. If it
|
||||
/// is, this is the lifetime of the raw byte slice it is constructed from.
|
||||
#[derive(Eq, Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PusTc<'raw_data> {
|
||||
pub struct PusTcCreator<'raw_data> {
|
||||
sp_header: SpHeader,
|
||||
pub sec_header: PusTcSecondaryHeader,
|
||||
/// If this is set to false, a manual call to [PusTc::calc_own_crc16] or
|
||||
/// [PusTc::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
|
||||
pub calc_crc_on_serialization: bool,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
raw_data: Option<&'raw_data [u8]>,
|
||||
app_data: Option<&'raw_data [u8]>,
|
||||
crc16: Option<u16>,
|
||||
app_data: &'raw_data [u8],
|
||||
}
|
||||
|
||||
impl<'raw_data> PusTc<'raw_data> {
|
||||
impl<'raw_data> PusTcCreator<'raw_data> {
|
||||
/// Generates a new struct instance.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -255,13 +569,10 @@ impl<'raw_data> PusTc<'raw_data> {
|
||||
) -> Self {
|
||||
sp_header.set_packet_type(PacketType::Tc);
|
||||
sp_header.set_sec_header_flag();
|
||||
let mut pus_tc = PusTc {
|
||||
let mut pus_tc = Self {
|
||||
sp_header: *sp_header,
|
||||
raw_data: None,
|
||||
app_data,
|
||||
app_data: app_data.unwrap_or(&[]),
|
||||
sec_header,
|
||||
calc_crc_on_serialization: true,
|
||||
crc16: None,
|
||||
};
|
||||
if set_ccsds_len {
|
||||
pus_tc.update_ccsds_data_len();
|
||||
@ -269,8 +580,8 @@ impl<'raw_data> PusTc<'raw_data> {
|
||||
pus_tc
|
||||
}
|
||||
|
||||
/// Simplified version of the [PusTc::new] function which allows to only specify service and
|
||||
/// subservice instead of the full PUS TC secondary header.
|
||||
/// Simplified version of the [PusTcCreator::new] function which allows to only specify service
|
||||
/// and subservice instead of the full PUS TC secondary header.
|
||||
pub fn new_simple(
|
||||
sph: &mut SpHeader,
|
||||
service: u8,
|
||||
@ -317,22 +628,14 @@ impl<'raw_data> PusTc<'raw_data> {
|
||||
|
||||
/// This function should be called before the TC packet is serialized if
|
||||
/// [PusTc::calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16.
|
||||
pub fn calc_own_crc16(&mut self) {
|
||||
pub fn calc_own_crc16(&self) -> u16 {
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||
digest.update(sph_zc.as_bytes());
|
||||
let pus_tc_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
|
||||
digest.update(pus_tc_header.as_bytes());
|
||||
if let Some(app_data) = self.app_data {
|
||||
digest.update(app_data);
|
||||
}
|
||||
self.crc16 = Some(digest.finalize())
|
||||
}
|
||||
|
||||
/// This helper function calls both [PusTc::update_ccsds_data_len] and [PusTc::calc_own_crc16].
|
||||
pub fn update_packet_fields(&mut self) {
|
||||
self.update_ccsds_data_len();
|
||||
self.calc_own_crc16();
|
||||
digest.update(self.app_data);
|
||||
digest.finalize()
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
@ -340,86 +643,23 @@ impl<'raw_data> PusTc<'raw_data> {
|
||||
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||
let mut appended_len = PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
|
||||
if let Some(app_data) = self.app_data {
|
||||
appended_len += app_data.len();
|
||||
};
|
||||
appended_len += self.app_data.len();
|
||||
let start_idx = vec.len();
|
||||
let mut ser_len = 0;
|
||||
vec.extend_from_slice(sph_zc.as_bytes());
|
||||
ser_len += sph_zc.as_bytes().len();
|
||||
// The PUS version is hardcoded to PUS C
|
||||
let pus_tc_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
|
||||
vec.extend_from_slice(pus_tc_header.as_bytes());
|
||||
ser_len += pus_tc_header.as_bytes().len();
|
||||
if let Some(app_data) = self.app_data {
|
||||
vec.extend_from_slice(app_data);
|
||||
ser_len += app_data.len();
|
||||
}
|
||||
let crc16 = crc_procedure(
|
||||
self.calc_crc_on_serialization,
|
||||
&self.crc16,
|
||||
start_idx,
|
||||
ser_len,
|
||||
&vec[start_idx..ser_len],
|
||||
)?;
|
||||
vec.extend_from_slice(crc16.to_be_bytes().as_slice());
|
||||
vec.extend_from_slice(self.app_data);
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
digest.update(&vec[start_idx..start_idx + appended_len - 2]);
|
||||
vec.extend_from_slice(&digest.finalize().to_be_bytes());
|
||||
Ok(appended_len)
|
||||
}
|
||||
|
||||
/// Create a [PusTc] instance from a raw slice. On success, it returns a tuple containing
|
||||
/// the instance and the found byte length of the packet.
|
||||
pub fn from_bytes(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> {
|
||||
let raw_data_len = slice.len();
|
||||
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let mut current_idx = 0;
|
||||
let (sp_header, _) = SpHeader::from_be_bytes(&slice[0..CCSDS_HEADER_LEN])?;
|
||||
current_idx += CCSDS_HEADER_LEN;
|
||||
let total_len = sp_header.total_len();
|
||||
if raw_data_len < total_len || total_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let sec_header = zc::PusTcSecondaryHeader::from_bytes(
|
||||
&slice[current_idx..current_idx + PUC_TC_SECONDARY_HEADER_LEN],
|
||||
)
|
||||
.ok_or(ByteConversionError::ZeroCopyFromError)?;
|
||||
current_idx += PUC_TC_SECONDARY_HEADER_LEN;
|
||||
let raw_data = &slice[0..total_len];
|
||||
let pus_tc = PusTc {
|
||||
sp_header,
|
||||
sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(),
|
||||
raw_data: Some(raw_data),
|
||||
app_data: user_data_from_raw(current_idx, total_len, raw_data_len, slice)?,
|
||||
calc_crc_on_serialization: false,
|
||||
crc16: Some(crc_from_raw_data(raw_data)?),
|
||||
};
|
||||
verify_crc16_ccitt_false_from_raw_to_pus_error(
|
||||
raw_data,
|
||||
pus_tc.crc16.expect("CRC16 invalid"),
|
||||
)?;
|
||||
Ok((pus_tc, total_len))
|
||||
}
|
||||
|
||||
#[deprecated(since = "0.5.2", note = "use raw_bytes() instead")]
|
||||
pub fn raw(&self) -> Option<&'raw_data [u8]> {
|
||||
self.raw_bytes()
|
||||
}
|
||||
|
||||
/// If [Self] was constructed [Self::from_bytes], this function will return the slice it was
|
||||
/// constructed from. Otherwise, [None] will be returned.
|
||||
pub fn raw_bytes(&self) -> Option<&'raw_data [u8]> {
|
||||
self.raw_data
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializablePusPacket for PusTc<'_> {
|
||||
impl SerializablePusPacket for PusTcCreator<'_> {
|
||||
fn len_packed(&self) -> usize {
|
||||
let mut length = PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
|
||||
if let Some(app_data) = self.app_data {
|
||||
length += app_data.len();
|
||||
}
|
||||
length
|
||||
PUS_TC_MIN_LEN_WITHOUT_APP_DATA + self.app_data.len()
|
||||
}
|
||||
|
||||
/// Write the raw PUS byte representation to a provided buffer.
|
||||
@ -442,55 +682,37 @@ impl SerializablePusPacket for PusTc<'_> {
|
||||
.ok_or(ByteConversionError::ZeroCopyToError)?;
|
||||
|
||||
curr_idx += tc_header_len;
|
||||
if let Some(app_data) = self.app_data {
|
||||
slice[curr_idx..curr_idx + app_data.len()].copy_from_slice(app_data);
|
||||
curr_idx += app_data.len();
|
||||
}
|
||||
let crc16 = crc_procedure(
|
||||
self.calc_crc_on_serialization,
|
||||
&self.crc16,
|
||||
0,
|
||||
curr_idx,
|
||||
slice,
|
||||
)?;
|
||||
slice[curr_idx..curr_idx + 2].copy_from_slice(crc16.to_be_bytes().as_slice());
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PusTc<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.sp_header == other.sp_header
|
||||
&& self.sec_header == other.sec_header
|
||||
&& self.app_data == other.app_data
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl CcsdsPacket for PusTc<'_> {
|
||||
impl CcsdsPacket for PusTcCreator<'_> {
|
||||
ccsds_impl!();
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl PusPacket for PusTc<'_> {
|
||||
impl PusPacket for PusTcCreator<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
});
|
||||
|
||||
fn user_data(&self) -> Option<&[u8]> {
|
||||
fn user_data(&self) -> &[u8] {
|
||||
self.app_data
|
||||
}
|
||||
|
||||
fn crc16(&self) -> Option<u16> {
|
||||
self.crc16
|
||||
Some(self.calc_own_crc16())
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl GenericPusTcSecondaryHeader for PusTc<'_> {
|
||||
impl GenericPusTcSecondaryHeader for PusTcCreator<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
@ -500,36 +722,163 @@ impl GenericPusTcSecondaryHeader for PusTc<'_> {
|
||||
});
|
||||
}
|
||||
|
||||
impl IsPusTelecommand for PusTcCreator<'_> {}
|
||||
|
||||
/// 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
|
||||
/// [serde] feature is used, which allows to send around TC packets in a raw byte format using a
|
||||
/// serde provider like [postcard](https://docs.rs/postcard/latest/postcard/).
|
||||
///
|
||||
/// There is no spare bytes support yet.
|
||||
///
|
||||
/// # Lifetimes
|
||||
///
|
||||
/// * `'raw_data` - Lifetime of the provided raw slice.
|
||||
#[derive(Eq, Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PusTcReader<'raw_data> {
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
raw_data: &'raw_data [u8],
|
||||
sp_header: SpHeader,
|
||||
sec_header: PusTcSecondaryHeader,
|
||||
app_data: &'raw_data [u8],
|
||||
crc16: u16,
|
||||
}
|
||||
|
||||
impl<'raw_data> PusTcReader<'raw_data> {
|
||||
/// 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.
|
||||
pub fn new(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> {
|
||||
let raw_data_len = slice.len();
|
||||
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let mut current_idx = 0;
|
||||
let (sp_header, _) = SpHeader::from_be_bytes(&slice[0..CCSDS_HEADER_LEN])?;
|
||||
current_idx += CCSDS_HEADER_LEN;
|
||||
let total_len = sp_header.total_len();
|
||||
if raw_data_len < total_len || total_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let sec_header = zc::PusTcSecondaryHeader::from_bytes(
|
||||
&slice[current_idx..current_idx + PUC_TC_SECONDARY_HEADER_LEN],
|
||||
)
|
||||
.ok_or(ByteConversionError::ZeroCopyFromError)?;
|
||||
current_idx += PUC_TC_SECONDARY_HEADER_LEN;
|
||||
let raw_data = &slice[0..total_len];
|
||||
let pus_tc = Self {
|
||||
sp_header,
|
||||
sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(),
|
||||
raw_data,
|
||||
app_data: user_data_from_raw(current_idx, total_len, raw_data_len, slice)?,
|
||||
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))
|
||||
}
|
||||
|
||||
pub fn app_data(&self) -> &[u8] {
|
||||
self.user_data()
|
||||
}
|
||||
|
||||
pub fn raw_data(&self) -> &[u8] {
|
||||
self.raw_data
|
||||
}
|
||||
|
||||
pub fn len_packed(&self) -> usize {
|
||||
self.sp_header.total_len()
|
||||
}
|
||||
|
||||
pub fn sp_header(&self) -> &SpHeader {
|
||||
&self.sp_header
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PusTcReader<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.raw_data == other.raw_data
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPacket for PusTcReader<'_> {
|
||||
ccsds_impl!();
|
||||
}
|
||||
|
||||
impl PusPacket for PusTcReader<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
});
|
||||
|
||||
fn user_data(&self) -> &[u8] {
|
||||
self.app_data
|
||||
}
|
||||
|
||||
fn crc16(&self) -> Option<u16> {
|
||||
Some(self.crc16)
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericPusTcSecondaryHeader for PusTcReader<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
fn source_id(&self) -> u16;
|
||||
fn ack_flags(&self) -> u8;
|
||||
});
|
||||
}
|
||||
|
||||
impl IsPusTelecommand for PusTcReader<'_> {}
|
||||
|
||||
impl PartialEq<PusTcCreator<'_>> for PusTcReader<'_> {
|
||||
fn eq(&self, other: &PusTcCreator) -> bool {
|
||||
self.sp_header == other.sp_header
|
||||
&& self.sec_header == other.sec_header
|
||||
&& self.app_data == other.app_data
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<PusTcReader<'_>> for PusTcCreator<'_> {
|
||||
fn eq(&self, other: &PusTcReader) -> bool {
|
||||
self.sp_header == other.sp_header
|
||||
&& self.sec_header == other.sec_header
|
||||
&& self.app_data == other.app_data
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod tests {
|
||||
use crate::ecss::tc::{
|
||||
GenericPusTcSecondaryHeader, PusTcCreator, PusTcReader, PusTcSecondaryHeader, ACK_ALL,
|
||||
};
|
||||
use crate::ecss::PusVersion::PusC;
|
||||
use crate::ecss::{PusError, PusPacket, SerializablePusPacket};
|
||||
use crate::tc::ACK_ALL;
|
||||
use crate::tc::{GenericPusTcSecondaryHeader, PusTc, PusTcSecondaryHeader};
|
||||
use crate::{ByteConversionError, SpHeader};
|
||||
use crate::{CcsdsPacket, SequenceFlags};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
fn base_ping_tc_full_ctor() -> PusTc<'static> {
|
||||
fn base_ping_tc_full_ctor() -> PusTcCreator<'static> {
|
||||
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
|
||||
let tc_header = PusTcSecondaryHeader::new_simple(17, 1);
|
||||
PusTc::new(&mut sph, tc_header, None, true)
|
||||
PusTcCreator::new(&mut sph, tc_header, None, true)
|
||||
}
|
||||
|
||||
fn base_ping_tc_simple_ctor() -> PusTc<'static> {
|
||||
fn base_ping_tc_simple_ctor() -> PusTcCreator<'static> {
|
||||
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
|
||||
PusTc::new_simple(&mut sph, 17, 1, None, true)
|
||||
PusTcCreator::new_simple(&mut sph, 17, 1, None, true)
|
||||
}
|
||||
|
||||
fn base_ping_tc_simple_ctor_with_app_data(app_data: &'static [u8]) -> PusTc<'static> {
|
||||
fn base_ping_tc_simple_ctor_with_app_data(app_data: &'static [u8]) -> PusTcCreator<'static> {
|
||||
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
|
||||
PusTc::new_simple(&mut sph, 17, 1, Some(app_data), true)
|
||||
PusTcCreator::new_simple(&mut sph, 17, 1, Some(app_data), true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tc_fields() {
|
||||
let pus_tc = base_ping_tc_full_ctor();
|
||||
assert_eq!(pus_tc.crc16(), None);
|
||||
verify_test_tc(&pus_tc, false, 13);
|
||||
}
|
||||
|
||||
@ -552,10 +901,10 @@ mod tests {
|
||||
.expect("Error writing TC to buffer");
|
||||
assert_eq!(size, 13);
|
||||
let (tc_from_raw, size) =
|
||||
PusTc::from_bytes(&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, 13);
|
||||
verify_test_tc(&tc_from_raw, false, 13);
|
||||
assert!(tc_from_raw.user_data().is_none());
|
||||
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);
|
||||
}
|
||||
@ -563,10 +912,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_update_func() {
|
||||
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
|
||||
let mut tc = PusTc::new_simple(&mut sph, 17, 1, None, false);
|
||||
tc.calc_crc_on_serialization = false;
|
||||
let mut tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, false);
|
||||
assert_eq!(tc.data_len(), 0);
|
||||
tc.update_packet_fields();
|
||||
tc.update_ccsds_data_len();
|
||||
assert_eq!(tc.data_len(), 6);
|
||||
}
|
||||
#[test]
|
||||
@ -578,10 +926,10 @@ mod tests {
|
||||
.expect("Error writing TC to buffer");
|
||||
assert_eq!(size, 16);
|
||||
let (tc_from_raw, size) =
|
||||
PusTc::from_bytes(&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);
|
||||
verify_test_tc(&tc_from_raw, true, 16);
|
||||
let user_data = tc_from_raw.user_data().unwrap();
|
||||
verify_test_tc_with_reader(&tc_from_raw, true, 16);
|
||||
let user_data = tc_from_raw.user_data();
|
||||
assert_eq!(user_data[0], 1);
|
||||
assert_eq!(user_data[1], 2);
|
||||
assert_eq!(user_data[2], 3);
|
||||
@ -607,7 +955,7 @@ mod tests {
|
||||
.write_to_bytes(test_buf.as_mut_slice())
|
||||
.expect("Error writing TC to buffer");
|
||||
test_buf[12] = 0;
|
||||
let res = PusTc::from_bytes(&test_buf);
|
||||
let res = PusTcReader::new(&test_buf);
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
assert!(matches!(err, PusError::IncorrectCrc { .. }));
|
||||
@ -615,8 +963,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_manual_crc_calculation() {
|
||||
let mut pus_tc = base_ping_tc_simple_ctor();
|
||||
pus_tc.calc_crc_on_serialization = false;
|
||||
let pus_tc = base_ping_tc_simple_ctor();
|
||||
let mut test_buf: [u8; 32] = [0; 32];
|
||||
pus_tc.calc_own_crc16();
|
||||
pus_tc
|
||||
@ -626,17 +973,6 @@ mod tests {
|
||||
verify_crc_no_app_data(&test_buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manual_crc_calculation_no_calc_call() {
|
||||
let mut pus_tc = base_ping_tc_simple_ctor();
|
||||
pus_tc.calc_crc_on_serialization = false;
|
||||
let mut test_buf: [u8; 32] = [0; 32];
|
||||
let res = pus_tc.write_to_bytes(test_buf.as_mut_slice());
|
||||
assert!(res.is_err());
|
||||
let err = res.unwrap_err();
|
||||
assert!(matches!(err, PusError::CrcCalculationMissing { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_application_data_vec() {
|
||||
let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]);
|
||||
@ -712,24 +1048,37 @@ mod tests {
|
||||
assert_eq!(test_buf[10], 0xff);
|
||||
}
|
||||
|
||||
fn verify_test_tc(tc: &PusTc, has_user_data: bool, exp_full_len: usize) {
|
||||
fn verify_test_tc(tc: &PusTcCreator, has_user_data: bool, exp_full_len: usize) {
|
||||
verify_test_tc_generic(tc);
|
||||
if !has_user_data {
|
||||
assert!(tc.user_data().is_empty());
|
||||
}
|
||||
let mut comp_header = SpHeader::tc_unseg(0x02, 0x34, exp_full_len as u16 - 7).unwrap();
|
||||
comp_header.set_sec_header_flag();
|
||||
assert_eq!(*tc.sp_header(), comp_header);
|
||||
}
|
||||
|
||||
fn verify_test_tc_with_reader(tc: &PusTcReader, has_user_data: bool, exp_full_len: usize) {
|
||||
verify_test_tc_generic(tc);
|
||||
if !has_user_data {
|
||||
assert!(tc.user_data().is_empty());
|
||||
}
|
||||
assert_eq!(tc.len_packed(), exp_full_len);
|
||||
let mut comp_header = SpHeader::tc_unseg(0x02, 0x34, exp_full_len as u16 - 7).unwrap();
|
||||
comp_header.set_sec_header_flag();
|
||||
assert_eq!(*tc.sp_header(), comp_header);
|
||||
}
|
||||
|
||||
fn verify_test_tc_generic(tc: &(impl CcsdsPacket + PusPacket + GenericPusTcSecondaryHeader)) {
|
||||
assert_eq!(PusPacket::service(tc), 17);
|
||||
assert_eq!(PusPacket::subservice(tc), 1);
|
||||
assert!(tc.sec_header_flag());
|
||||
assert_eq!(PusPacket::pus_version(tc), PusC);
|
||||
if !has_user_data {
|
||||
assert_eq!(tc.user_data(), None);
|
||||
}
|
||||
assert_eq!(tc.seq_count(), 0x34);
|
||||
assert_eq!(tc.source_id(), 0);
|
||||
assert_eq!(tc.apid(), 0x02);
|
||||
assert_eq!(tc.ack_flags(), ACK_ALL);
|
||||
assert_eq!(tc.len_packed(), exp_full_len);
|
||||
let mut comp_header = SpHeader::tc_unseg(0x02, 0x34, exp_full_len as u16 - 7).unwrap();
|
||||
comp_header.set_sec_header_flag();
|
||||
assert_eq!(tc.sp_header, comp_header);
|
||||
}
|
||||
|
||||
fn verify_test_tc_raw(slice: &impl AsRef<[u8]>) {
|
||||
// Reference comparison implementation:
|
||||
// https://github.com/us-irs/py-spacepackets/blob/v0.13.0/tests/ecss/test_pus_tc.py
|
||||
@ -777,6 +1126,7 @@ mod tests {
|
||||
let pus_tc = base_ping_tc_simple_ctor();
|
||||
let mut buf = [0; 32];
|
||||
pus_tc.write_to_bytes(&mut buf).unwrap();
|
||||
assert_eq!(pus_tc, PusTc::from_bytes(&buf).unwrap().0);
|
||||
assert_eq!(pus_tc, PusTcReader::new(&buf).unwrap().0);
|
||||
assert_eq!(PusTcReader::new(&buf).unwrap().0, pus_tc);
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
//! This module contains all components required to create a ECSS PUS C telemetry packets according
|
||||
//! to [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
||||
use crate::ecss::{
|
||||
calc_pus_crc16, ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls,
|
||||
user_data_from_raw, verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, PusError,
|
||||
PusPacket, PusVersion, SerializablePusPacket,
|
||||
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,
|
||||
SerializablePusPacket,
|
||||
};
|
||||
use crate::{
|
||||
ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SizeMissmatch, SpHeader,
|
||||
@ -18,6 +18,11 @@ use zerocopy::AsBytes;
|
||||
use alloc::vec::Vec;
|
||||
use delegate::delegate;
|
||||
|
||||
use crate::time::{TimeWriter, TimestampError};
|
||||
pub use legacy_tm::*;
|
||||
|
||||
pub trait IsPusTelemetry {}
|
||||
|
||||
/// Length without timestamp
|
||||
pub const PUC_TM_MIN_SEC_HEADER_LEN: usize = 7;
|
||||
pub const PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA: usize =
|
||||
@ -49,12 +54,12 @@ pub mod zc {
|
||||
|
||||
pub struct PusTmSecHeader<'slice> {
|
||||
pub(crate) zc_header: PusTmSecHeaderWithoutTimestamp,
|
||||
pub(crate) timestamp: Option<&'slice [u8]>,
|
||||
pub(crate) timestamp: &'slice [u8],
|
||||
}
|
||||
|
||||
impl TryFrom<crate::tm::PusTmSecondaryHeader<'_>> for PusTmSecHeaderWithoutTimestamp {
|
||||
impl TryFrom<crate::ecss::tm::PusTmSecondaryHeader<'_>> for PusTmSecHeaderWithoutTimestamp {
|
||||
type Error = PusError;
|
||||
fn try_from(header: crate::tm::PusTmSecondaryHeader) -> Result<Self, Self::Error> {
|
||||
fn try_from(header: crate::ecss::tm::PusTmSecondaryHeader) -> Result<Self, Self::Error> {
|
||||
if header.pus_version != PusVersion::PusC {
|
||||
return Err(PusError::VersionNotSupported(header.pus_version));
|
||||
}
|
||||
@ -116,7 +121,7 @@ pub struct PusTmSecondaryHeader<'stamp> {
|
||||
pub subservice: u8,
|
||||
pub msg_counter: u16,
|
||||
pub dest_id: u16,
|
||||
pub timestamp: Option<&'stamp [u8]>,
|
||||
pub timestamp: &'stamp [u8],
|
||||
}
|
||||
|
||||
impl<'stamp> PusTmSecondaryHeader<'stamp> {
|
||||
@ -143,7 +148,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> {
|
||||
subservice,
|
||||
msg_counter,
|
||||
dest_id,
|
||||
timestamp,
|
||||
timestamp: timestamp.unwrap_or(&[]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -190,8 +195,324 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
|
||||
}
|
||||
}
|
||||
|
||||
pub mod legacy_tm {
|
||||
use crate::ecss::tm::{
|
||||
zc, GenericPusTmSecondaryHeader, IsPusTelemetry, PusTmSecondaryHeader,
|
||||
PUC_TM_MIN_SEC_HEADER_LEN, PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA,
|
||||
};
|
||||
use crate::ecss::PusVersion;
|
||||
use crate::ecss::{
|
||||
ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls, user_data_from_raw,
|
||||
verify_crc16_ccitt_false_from_raw_to_pus_error, PusError, PusPacket, SerializablePusPacket,
|
||||
CCSDS_HEADER_LEN,
|
||||
};
|
||||
use crate::SequenceFlags;
|
||||
use crate::{
|
||||
ByteConversionError, CcsdsPacket, PacketType, SizeMissmatch, SpHeader, CRC_CCITT_FALSE,
|
||||
};
|
||||
use core::mem::size_of;
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
use delegate::delegate;
|
||||
|
||||
/// This class models the PUS C telemetry packet. It is the primary data structure to generate the
|
||||
/// raw byte representation of PUS telemetry or to deserialize from one from raw bytes.
|
||||
///
|
||||
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the [serde]
|
||||
/// feature is used which allows to send around TM packets in a raw byte format using a serde
|
||||
/// provider like [postcard](https://docs.rs/postcard/latest/postcard/).
|
||||
///
|
||||
/// There is no spare bytes support yet.
|
||||
///
|
||||
/// # Lifetimes
|
||||
///
|
||||
/// * `'raw_data` - If the TM is not constructed from a raw slice, this will be the life time of
|
||||
/// a buffer where the user provided time stamp and source data will be serialized into. If it
|
||||
/// is, this is the lifetime of the raw byte slice it is constructed from.
|
||||
#[derive(Eq, Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PusTm<'raw_data> {
|
||||
pub sp_header: SpHeader,
|
||||
pub sec_header: PusTmSecondaryHeader<'raw_data>,
|
||||
/// If this is set to false, a manual call to [PusTm::calc_own_crc16] or
|
||||
/// [PusTm::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
|
||||
pub calc_crc_on_serialization: bool,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
raw_data: Option<&'raw_data [u8]>,
|
||||
source_data: &'raw_data [u8],
|
||||
crc16: Option<u16>,
|
||||
}
|
||||
|
||||
impl<'raw_data> PusTm<'raw_data> {
|
||||
/// Generates a new struct instance.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `sp_header` - Space packet header information. The correct packet type will be set
|
||||
/// automatically
|
||||
/// * `sec_header` - Information contained in the secondary header, including the service
|
||||
/// and subservice type
|
||||
/// * `app_data` - Custom application data
|
||||
/// * `set_ccsds_len` - Can be used to automatically update the CCSDS space packet data length
|
||||
/// field. If this is not set to true, [PusTm::update_ccsds_data_len] can be called to set
|
||||
/// the correct value to this field manually
|
||||
#[deprecated(
|
||||
since = "0.7.0",
|
||||
note = "Use specialized PusTmCreator or PusTmReader classes instead"
|
||||
)]
|
||||
pub fn new(
|
||||
sp_header: &mut SpHeader,
|
||||
sec_header: PusTmSecondaryHeader<'raw_data>,
|
||||
source_data: Option<&'raw_data [u8]>,
|
||||
set_ccsds_len: bool,
|
||||
) -> Self {
|
||||
sp_header.set_packet_type(PacketType::Tm);
|
||||
sp_header.set_sec_header_flag();
|
||||
let mut pus_tm = PusTm {
|
||||
sp_header: *sp_header,
|
||||
raw_data: None,
|
||||
source_data: source_data.unwrap_or(&[]),
|
||||
sec_header,
|
||||
calc_crc_on_serialization: true,
|
||||
crc16: None,
|
||||
};
|
||||
if set_ccsds_len {
|
||||
pus_tm.update_ccsds_data_len();
|
||||
}
|
||||
pus_tm
|
||||
}
|
||||
|
||||
pub fn timestamp(&self) -> &[u8] {
|
||||
self.sec_header.timestamp
|
||||
}
|
||||
|
||||
pub fn source_data(&self) -> &[u8] {
|
||||
self.source_data
|
||||
}
|
||||
|
||||
pub fn set_dest_id(&mut self, dest_id: u16) {
|
||||
self.sec_header.dest_id = dest_id;
|
||||
}
|
||||
|
||||
pub fn set_msg_counter(&mut self, msg_counter: u16) {
|
||||
self.sec_header.msg_counter = msg_counter
|
||||
}
|
||||
|
||||
pub fn set_sc_time_ref_status(&mut self, sc_time_ref_status: u8) {
|
||||
self.sec_header.sc_time_ref_status = sc_time_ref_status & 0b1111;
|
||||
}
|
||||
|
||||
sp_header_impls!();
|
||||
|
||||
/// This is called automatically if the `set_ccsds_len` argument in the [PusTm::new] call was
|
||||
/// used.
|
||||
/// If this was not done or the time stamp or source data is set or changed after construction,
|
||||
/// this function needs to be called to ensure that the data length field of the CCSDS header
|
||||
/// is set correctly
|
||||
pub fn update_ccsds_data_len(&mut self) {
|
||||
self.sp_header.data_len =
|
||||
self.len_packed() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
|
||||
}
|
||||
|
||||
/// This function should be called before the TM packet is serialized if
|
||||
/// [PusTm.calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16.
|
||||
pub fn calc_own_crc16(&mut self) {
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||
digest.update(sph_zc.as_bytes());
|
||||
let pus_tc_header =
|
||||
zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||
digest.update(pus_tc_header.as_bytes());
|
||||
digest.update(self.sec_header.timestamp);
|
||||
digest.update(self.source_data);
|
||||
self.crc16 = Some(digest.finalize())
|
||||
}
|
||||
|
||||
/// This helper function calls both [PusTm.update_ccsds_data_len] and [PusTm.calc_own_crc16]
|
||||
pub fn update_packet_fields(&mut self) {
|
||||
self.update_ccsds_data_len();
|
||||
self.calc_own_crc16();
|
||||
}
|
||||
|
||||
/// Append the raw PUS byte representation to a provided [alloc::vec::Vec]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||
let mut appended_len = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
|
||||
appended_len += self.sec_header.timestamp.len();
|
||||
appended_len += self.source_data.len();
|
||||
let start_idx = vec.len();
|
||||
let mut ser_len = 0;
|
||||
vec.extend_from_slice(sph_zc.as_bytes());
|
||||
ser_len += sph_zc.as_bytes().len();
|
||||
// The PUS version is hardcoded to PUS C
|
||||
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||
vec.extend_from_slice(sec_header.as_bytes());
|
||||
ser_len += sec_header.as_bytes().len();
|
||||
ser_len += self.sec_header.timestamp.len();
|
||||
vec.extend_from_slice(self.sec_header.timestamp);
|
||||
vec.extend_from_slice(self.source_data);
|
||||
ser_len += self.source_data.len();
|
||||
let crc16 = crc_procedure(
|
||||
self.calc_crc_on_serialization,
|
||||
&self.crc16,
|
||||
start_idx,
|
||||
ser_len,
|
||||
&vec[start_idx..start_idx + ser_len],
|
||||
)?;
|
||||
vec.extend_from_slice(crc16.to_be_bytes().as_slice());
|
||||
Ok(appended_len)
|
||||
}
|
||||
|
||||
/// Create a [PusTm] instance from a raw slice. On success, it returns a tuple containing
|
||||
/// the instance and the found byte length of the packet. The timestamp length needs to be
|
||||
/// known beforehand.
|
||||
#[deprecated(
|
||||
since = "0.7.0",
|
||||
note = "Use specialized PusTmCreator or PusTmReader classes instead"
|
||||
)]
|
||||
pub fn from_bytes(
|
||||
slice: &'raw_data [u8],
|
||||
timestamp_len: usize,
|
||||
) -> Result<(Self, usize), PusError> {
|
||||
let raw_data_len = slice.len();
|
||||
if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let mut current_idx = 0;
|
||||
let (sp_header, _) = SpHeader::from_be_bytes(&slice[0..CCSDS_HEADER_LEN])?;
|
||||
current_idx += 6;
|
||||
let total_len = sp_header.total_len();
|
||||
if raw_data_len < total_len || total_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let sec_header_zc = zc::PusTmSecHeaderWithoutTimestamp::from_bytes(
|
||||
&slice[current_idx..current_idx + PUC_TM_MIN_SEC_HEADER_LEN],
|
||||
)
|
||||
.ok_or(ByteConversionError::ZeroCopyFromError)?;
|
||||
current_idx += PUC_TM_MIN_SEC_HEADER_LEN;
|
||||
let zc_sec_header_wrapper = zc::PusTmSecHeader {
|
||||
zc_header: sec_header_zc,
|
||||
timestamp: &slice[current_idx..current_idx + timestamp_len],
|
||||
};
|
||||
current_idx += timestamp_len;
|
||||
let raw_data = &slice[0..total_len];
|
||||
let pus_tm = PusTm {
|
||||
sp_header,
|
||||
sec_header: PusTmSecondaryHeader::try_from(zc_sec_header_wrapper).unwrap(),
|
||||
raw_data: Some(&slice[0..total_len]),
|
||||
source_data: user_data_from_raw(current_idx, total_len, raw_data_len, slice)?,
|
||||
calc_crc_on_serialization: false,
|
||||
crc16: Some(crc_from_raw_data(raw_data)?),
|
||||
};
|
||||
verify_crc16_ccitt_false_from_raw_to_pus_error(
|
||||
raw_data,
|
||||
pus_tm.crc16.expect("CRC16 invalid"),
|
||||
)?;
|
||||
Ok((pus_tm, total_len))
|
||||
}
|
||||
|
||||
/// If [Self] was constructed [Self::from_bytes], this function will return the slice it was
|
||||
/// constructed from. Otherwise, [None] will be returned.
|
||||
pub fn raw_bytes(&self) -> Option<&'raw_data [u8]> {
|
||||
self.raw_data
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializablePusPacket for PusTm<'_> {
|
||||
fn len_packed(&self) -> usize {
|
||||
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA
|
||||
+ self.sec_header.timestamp.len()
|
||||
+ self.source_data.len()
|
||||
}
|
||||
/// Write the raw PUS byte representation to a provided buffer.
|
||||
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
|
||||
let mut curr_idx = 0;
|
||||
let total_size = self.len_packed();
|
||||
if total_size > slice.len() {
|
||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||
found: slice.len(),
|
||||
expected: total_size,
|
||||
})
|
||||
.into());
|
||||
}
|
||||
self.sp_header
|
||||
.write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?;
|
||||
curr_idx += CCSDS_HEADER_LEN;
|
||||
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
|
||||
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||
sec_header
|
||||
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
|
||||
.ok_or(ByteConversionError::ZeroCopyToError)?;
|
||||
curr_idx += sec_header_len;
|
||||
slice[curr_idx..curr_idx + self.sec_header.timestamp.len()]
|
||||
.copy_from_slice(self.sec_header.timestamp);
|
||||
curr_idx += self.sec_header.timestamp.len();
|
||||
slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data);
|
||||
curr_idx += self.source_data.len();
|
||||
let crc16 = crc_procedure(
|
||||
self.calc_crc_on_serialization,
|
||||
&self.crc16,
|
||||
0,
|
||||
curr_idx,
|
||||
slice,
|
||||
)?;
|
||||
slice[curr_idx..curr_idx + 2].copy_from_slice(crc16.to_be_bytes().as_slice());
|
||||
curr_idx += 2;
|
||||
Ok(curr_idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PusTm<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.sp_header == other.sp_header
|
||||
&& self.sec_header == other.sec_header
|
||||
&& self.source_data == other.source_data
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPacket for PusTm<'_> {
|
||||
ccsds_impl!();
|
||||
}
|
||||
|
||||
impl PusPacket for PusTm<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
});
|
||||
|
||||
fn user_data(&self) -> &[u8] {
|
||||
self.source_data
|
||||
}
|
||||
|
||||
fn crc16(&self) -> Option<u16> {
|
||||
self.crc16
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericPusTmSecondaryHeader for PusTm<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
fn dest_id(&self) -> u16;
|
||||
fn msg_counter(&self) -> u16;
|
||||
fn sc_time_ref_status(&self) -> u8;
|
||||
});
|
||||
}
|
||||
|
||||
impl IsPusTelemetry for PusTm<'_> {}
|
||||
}
|
||||
|
||||
/// This class models the PUS C telemetry packet. It is the primary data structure to generate the
|
||||
/// raw byte representation of PUS telemetry or to deserialize from one from raw bytes.
|
||||
/// raw byte representation of PUS telemetry.
|
||||
///
|
||||
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the [serde]
|
||||
/// feature is used which allows to send around TM packets in a raw byte format using a serde
|
||||
@ -201,24 +522,19 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
|
||||
///
|
||||
/// # Lifetimes
|
||||
///
|
||||
/// * `'raw_data` - If the TM is not constructed from a raw slice, this will be the life time of
|
||||
/// a buffer where the user provided time stamp and source data will be serialized into. If it
|
||||
/// is, this is the lifetime of the raw byte slice it is constructed from.
|
||||
/// * `'raw_data` - This is the lifetime of the user provided time stamp and source data.
|
||||
#[derive(Eq, Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PusTm<'raw_data> {
|
||||
pub struct PusTmCreator<'raw_data> {
|
||||
pub sp_header: SpHeader,
|
||||
pub sec_header: PusTmSecondaryHeader<'raw_data>,
|
||||
source_data: &'raw_data [u8],
|
||||
/// If this is set to false, a manual call to [PusTm::calc_own_crc16] or
|
||||
/// [PusTm::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
|
||||
pub calc_crc_on_serialization: bool,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
raw_data: Option<&'raw_data [u8]>,
|
||||
source_data: Option<&'raw_data [u8]>,
|
||||
crc16: Option<u16>,
|
||||
}
|
||||
|
||||
impl<'raw_data> PusTm<'raw_data> {
|
||||
impl<'raw_data> PusTmCreator<'raw_data> {
|
||||
/// Generates a new struct instance.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -239,13 +555,11 @@ impl<'raw_data> PusTm<'raw_data> {
|
||||
) -> Self {
|
||||
sp_header.set_packet_type(PacketType::Tm);
|
||||
sp_header.set_sec_header_flag();
|
||||
let mut pus_tm = PusTm {
|
||||
let mut pus_tm = Self {
|
||||
sp_header: *sp_header,
|
||||
raw_data: None,
|
||||
source_data,
|
||||
source_data: source_data.unwrap_or(&[]),
|
||||
sec_header,
|
||||
calc_crc_on_serialization: true,
|
||||
crc16: None,
|
||||
};
|
||||
if set_ccsds_len {
|
||||
pus_tm.update_ccsds_data_len();
|
||||
@ -253,11 +567,25 @@ impl<'raw_data> PusTm<'raw_data> {
|
||||
pus_tm
|
||||
}
|
||||
|
||||
pub fn timestamp(&self) -> Option<&'raw_data [u8]> {
|
||||
pub fn new_simple(
|
||||
sp_header: &mut SpHeader,
|
||||
service: u8,
|
||||
subservice: u8,
|
||||
time_provider: &impl TimeWriter,
|
||||
stamp_buf: &'raw_data mut [u8],
|
||||
source_data: Option<&'raw_data [u8]>,
|
||||
set_ccsds_len: bool,
|
||||
) -> Result<Self, TimestampError> {
|
||||
let stamp_size = time_provider.write_to_bytes(stamp_buf)?;
|
||||
let sec_header =
|
||||
PusTmSecondaryHeader::new_simple(service, subservice, &stamp_buf[0..stamp_size]);
|
||||
Ok(Self::new(sp_header, sec_header, source_data, set_ccsds_len))
|
||||
}
|
||||
pub fn timestamp(&self) -> &[u8] {
|
||||
self.sec_header.timestamp
|
||||
}
|
||||
|
||||
pub fn source_data(&self) -> Option<&'raw_data [u8]> {
|
||||
pub fn source_data(&self) -> &[u8] {
|
||||
self.source_data
|
||||
}
|
||||
|
||||
@ -287,25 +615,20 @@ impl<'raw_data> PusTm<'raw_data> {
|
||||
|
||||
/// This function should be called before the TM packet is serialized if
|
||||
/// [PusTm.calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16.
|
||||
pub fn calc_own_crc16(&mut self) {
|
||||
pub fn calc_own_crc16(&self) -> u16 {
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||
digest.update(sph_zc.as_bytes());
|
||||
let pus_tc_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||
digest.update(pus_tc_header.as_bytes());
|
||||
if let Some(stamp) = self.sec_header.timestamp {
|
||||
digest.update(stamp);
|
||||
}
|
||||
if let Some(src_data) = self.source_data {
|
||||
digest.update(src_data);
|
||||
}
|
||||
self.crc16 = Some(digest.finalize())
|
||||
digest.update(self.sec_header.timestamp);
|
||||
digest.update(self.source_data);
|
||||
digest.finalize()
|
||||
}
|
||||
|
||||
/// This helper function calls both [PusTm.update_ccsds_data_len] and [PusTm.calc_own_crc16]
|
||||
pub fn update_packet_fields(&mut self) {
|
||||
self.update_ccsds_data_len();
|
||||
self.calc_own_crc16();
|
||||
}
|
||||
|
||||
/// Append the raw PUS byte representation to a provided [alloc::vec::Vec]
|
||||
@ -313,47 +636,130 @@ impl<'raw_data> PusTm<'raw_data> {
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
|
||||
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
|
||||
let mut appended_len = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
|
||||
if let Some(timestamp) = self.sec_header.timestamp {
|
||||
appended_len += timestamp.len();
|
||||
}
|
||||
if let Some(src_data) = self.source_data {
|
||||
appended_len += src_data.len();
|
||||
};
|
||||
let mut appended_len = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA + self.sec_header.timestamp.len();
|
||||
appended_len += self.source_data.len();
|
||||
let start_idx = vec.len();
|
||||
let mut ser_len = 0;
|
||||
vec.extend_from_slice(sph_zc.as_bytes());
|
||||
ser_len += sph_zc.as_bytes().len();
|
||||
// The PUS version is hardcoded to PUS C
|
||||
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||
vec.extend_from_slice(sec_header.as_bytes());
|
||||
ser_len += sec_header.as_bytes().len();
|
||||
if let Some(timestamp) = self.sec_header.timestamp {
|
||||
ser_len += timestamp.len();
|
||||
vec.extend_from_slice(timestamp);
|
||||
}
|
||||
if let Some(src_data) = self.source_data {
|
||||
vec.extend_from_slice(src_data);
|
||||
ser_len += src_data.len();
|
||||
}
|
||||
let crc16 = crc_procedure(
|
||||
self.calc_crc_on_serialization,
|
||||
&self.crc16,
|
||||
start_idx,
|
||||
ser_len,
|
||||
&vec[start_idx..start_idx + ser_len],
|
||||
)?;
|
||||
vec.extend_from_slice(crc16.to_be_bytes().as_slice());
|
||||
vec.extend_from_slice(self.sec_header.timestamp);
|
||||
vec.extend_from_slice(self.source_data);
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
digest.update(&vec[start_idx..start_idx + appended_len - 2]);
|
||||
vec.extend_from_slice(&digest.finalize().to_be_bytes());
|
||||
Ok(appended_len)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [PusTm] instance from a raw slice. On success, it returns a tuple containing
|
||||
impl SerializablePusPacket for PusTmCreator<'_> {
|
||||
fn len_packed(&self) -> usize {
|
||||
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA
|
||||
+ self.sec_header.timestamp.len()
|
||||
+ self.source_data.len()
|
||||
}
|
||||
/// Write the raw PUS byte representation to a provided buffer.
|
||||
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
|
||||
let mut curr_idx = 0;
|
||||
let total_size = self.len_packed();
|
||||
if total_size > slice.len() {
|
||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||
found: slice.len(),
|
||||
expected: total_size,
|
||||
})
|
||||
.into());
|
||||
}
|
||||
self.sp_header
|
||||
.write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?;
|
||||
curr_idx += CCSDS_HEADER_LEN;
|
||||
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
|
||||
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||
sec_header
|
||||
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
|
||||
.ok_or(ByteConversionError::ZeroCopyToError)?;
|
||||
curr_idx += sec_header_len;
|
||||
slice[curr_idx..curr_idx + self.sec_header.timestamp.len()]
|
||||
.copy_from_slice(self.sec_header.timestamp);
|
||||
curr_idx += self.sec_header.timestamp.len();
|
||||
slice[curr_idx..curr_idx + self.source_data.len()].copy_from_slice(self.source_data);
|
||||
curr_idx += self.source_data.len();
|
||||
let 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)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PusTmCreator<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.sp_header == other.sp_header
|
||||
&& self.sec_header == other.sec_header
|
||||
&& self.source_data == other.source_data
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPacket for PusTmCreator<'_> {
|
||||
ccsds_impl!();
|
||||
}
|
||||
|
||||
impl PusPacket for PusTmCreator<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
});
|
||||
|
||||
fn user_data(&self) -> &[u8] {
|
||||
self.source_data
|
||||
}
|
||||
|
||||
fn crc16(&self) -> Option<u16> {
|
||||
Some(self.calc_own_crc16())
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericPusTmSecondaryHeader for PusTmCreator<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
fn dest_id(&self) -> u16;
|
||||
fn msg_counter(&self) -> u16;
|
||||
fn sc_time_ref_status(&self) -> u8;
|
||||
});
|
||||
}
|
||||
|
||||
impl IsPusTelemetry for PusTmCreator<'_> {}
|
||||
|
||||
/// This class models the PUS C telemetry packet. It is the primary data structure to read
|
||||
/// a telemetry packet from raw bytes.
|
||||
///
|
||||
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the [serde]
|
||||
/// feature is used which allows to send around TM packets in a raw byte format using a serde
|
||||
/// provider like [postcard](https://docs.rs/postcard/latest/postcard/).
|
||||
///
|
||||
/// There is no spare bytes support yet.
|
||||
///
|
||||
/// # Lifetimes
|
||||
///
|
||||
/// * `'raw_data` - Lifetime of the raw slice this class is constructed from.
|
||||
#[derive(Eq, Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PusTmReader<'raw_data> {
|
||||
pub sp_header: SpHeader,
|
||||
pub sec_header: PusTmSecondaryHeader<'raw_data>,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
raw_data: &'raw_data [u8],
|
||||
source_data: &'raw_data [u8],
|
||||
crc16: u16,
|
||||
}
|
||||
|
||||
impl<'raw_data> PusTmReader<'raw_data> {
|
||||
/// Create a [PusTmReader] instance from a raw slice. On success, it returns a tuple containing
|
||||
/// the instance and the found byte length of the packet. The timestamp length needs to be
|
||||
/// known beforehand.
|
||||
pub fn from_bytes(
|
||||
slice: &'raw_data [u8],
|
||||
timestamp_len: usize,
|
||||
) -> Result<(Self, usize), PusError> {
|
||||
pub fn new(slice: &'raw_data [u8], timestamp_len: usize) -> Result<(Self, usize), PusError> {
|
||||
let raw_data_len = slice.len();
|
||||
if raw_data_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
@ -370,38 +776,97 @@ impl<'raw_data> PusTm<'raw_data> {
|
||||
)
|
||||
.ok_or(ByteConversionError::ZeroCopyFromError)?;
|
||||
current_idx += PUC_TM_MIN_SEC_HEADER_LEN;
|
||||
let mut timestamp = None;
|
||||
if timestamp_len > 0 {
|
||||
timestamp = Some(&slice[current_idx..current_idx + timestamp_len]);
|
||||
}
|
||||
let zc_sec_header_wrapper = zc::PusTmSecHeader {
|
||||
zc_header: sec_header_zc,
|
||||
timestamp,
|
||||
timestamp: &slice[current_idx..current_idx + timestamp_len],
|
||||
};
|
||||
current_idx += timestamp_len;
|
||||
let raw_data = &slice[0..total_len];
|
||||
let pus_tm = PusTm {
|
||||
let pus_tm = Self {
|
||||
sp_header,
|
||||
sec_header: PusTmSecondaryHeader::try_from(zc_sec_header_wrapper).unwrap(),
|
||||
raw_data: Some(&slice[0..total_len]),
|
||||
raw_data: &slice[0..total_len],
|
||||
source_data: user_data_from_raw(current_idx, total_len, raw_data_len, slice)?,
|
||||
calc_crc_on_serialization: false,
|
||||
crc16: Some(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.expect("CRC16 invalid"),
|
||||
)?;
|
||||
verify_crc16_ccitt_false_from_raw_to_pus_error(raw_data, pus_tm.crc16)?;
|
||||
Ok((pus_tm, total_len))
|
||||
}
|
||||
|
||||
pub fn len_packed(&self) -> usize {
|
||||
self.sp_header.total_len()
|
||||
}
|
||||
|
||||
pub fn source_data(&self) -> &[u8] {
|
||||
self.user_data()
|
||||
}
|
||||
|
||||
pub fn timestamp(&self) -> &[u8] {
|
||||
self.sec_header.timestamp
|
||||
}
|
||||
|
||||
/// If [Self] was constructed [Self::from_bytes], this function will return the slice it was
|
||||
/// constructed from. Otherwise, [None] will be returned.
|
||||
pub fn raw_bytes(&self) -> Option<&'raw_data [u8]> {
|
||||
pub fn raw_data(&self) -> &[u8] {
|
||||
self.raw_data
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PusTmReader<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.raw_data == other.raw_data
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPacket for PusTmReader<'_> {
|
||||
ccsds_impl!();
|
||||
}
|
||||
|
||||
impl PusPacket for PusTmReader<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
});
|
||||
|
||||
fn user_data(&self) -> &[u8] {
|
||||
self.source_data
|
||||
}
|
||||
|
||||
fn crc16(&self) -> Option<u16> {
|
||||
Some(self.crc16)
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericPusTmSecondaryHeader for PusTmReader<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
fn dest_id(&self) -> u16;
|
||||
fn msg_counter(&self) -> u16;
|
||||
fn sc_time_ref_status(&self) -> u8;
|
||||
});
|
||||
}
|
||||
|
||||
impl IsPusTelemetry for PusTmReader<'_> {}
|
||||
|
||||
impl PartialEq<PusTmCreator<'_>> for PusTmReader<'_> {
|
||||
fn eq(&self, other: &PusTmCreator<'_>) -> bool {
|
||||
self.sp_header == other.sp_header
|
||||
&& self.sec_header == other.sec_header
|
||||
&& self.source_data == other.source_data
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<PusTmReader<'_>> for PusTmCreator<'_> {
|
||||
fn eq(&self, other: &PusTmReader<'_>) -> bool {
|
||||
self.sp_header == other.sp_header
|
||||
&& self.sec_header == other.sec_header
|
||||
&& self.source_data == other.source_data
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a helper class to update certain fields in a raw PUS telemetry packet directly in place.
|
||||
/// This can be more efficient than creating a full [PusTm], modifying the fields and then writing
|
||||
/// it back to another buffer.
|
||||
@ -479,117 +944,22 @@ impl<'raw> PusTmZeroCopyWriter<'raw> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializablePusPacket for PusTm<'_> {
|
||||
fn len_packed(&self) -> usize {
|
||||
let mut length = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
|
||||
if let Some(timestamp) = self.sec_header.timestamp {
|
||||
length += timestamp.len();
|
||||
}
|
||||
if let Some(src_data) = self.source_data {
|
||||
length += src_data.len();
|
||||
}
|
||||
length
|
||||
}
|
||||
/// Write the raw PUS byte representation to a provided buffer.
|
||||
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
|
||||
let mut curr_idx = 0;
|
||||
let total_size = self.len_packed();
|
||||
if total_size > slice.len() {
|
||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||
found: slice.len(),
|
||||
expected: total_size,
|
||||
})
|
||||
.into());
|
||||
}
|
||||
self.sp_header
|
||||
.write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?;
|
||||
curr_idx += CCSDS_HEADER_LEN;
|
||||
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
|
||||
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
|
||||
sec_header
|
||||
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
|
||||
.ok_or(ByteConversionError::ZeroCopyToError)?;
|
||||
curr_idx += sec_header_len;
|
||||
if let Some(timestamp) = self.sec_header.timestamp {
|
||||
let timestamp_len = timestamp.len();
|
||||
slice[curr_idx..curr_idx + timestamp_len].copy_from_slice(timestamp);
|
||||
curr_idx += timestamp_len;
|
||||
}
|
||||
if let Some(src_data) = self.source_data {
|
||||
slice[curr_idx..curr_idx + src_data.len()].copy_from_slice(src_data);
|
||||
curr_idx += src_data.len();
|
||||
}
|
||||
let crc16 = crc_procedure(
|
||||
self.calc_crc_on_serialization,
|
||||
&self.crc16,
|
||||
0,
|
||||
curr_idx,
|
||||
slice,
|
||||
)?;
|
||||
slice[curr_idx..curr_idx + 2].copy_from_slice(crc16.to_be_bytes().as_slice());
|
||||
curr_idx += 2;
|
||||
Ok(curr_idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PusTm<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.sp_header == other.sp_header
|
||||
&& self.sec_header == other.sec_header
|
||||
&& self.source_data == other.source_data
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl CcsdsPacket for PusTm<'_> {
|
||||
ccsds_impl!();
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl PusPacket for PusTm<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
});
|
||||
|
||||
fn user_data(&self) -> Option<&[u8]> {
|
||||
self.source_data
|
||||
}
|
||||
|
||||
fn crc16(&self) -> Option<u16> {
|
||||
self.crc16
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl GenericPusTmSecondaryHeader for PusTm<'_> {
|
||||
delegate!(to self.sec_header {
|
||||
fn pus_version(&self) -> PusVersion;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
fn dest_id(&self) -> u16;
|
||||
fn msg_counter(&self) -> u16;
|
||||
fn sc_time_ref_status(&self) -> u8;
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ecss::PusVersion::PusC;
|
||||
use crate::SpHeader;
|
||||
|
||||
fn base_ping_reply_full_ctor(timestamp: &[u8]) -> PusTm {
|
||||
fn base_ping_reply_full_ctor(timestamp: &[u8]) -> PusTmCreator {
|
||||
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
|
||||
let tm_header = PusTmSecondaryHeader::new_simple(17, 2, ×tamp);
|
||||
PusTm::new(&mut sph, tm_header, None, true)
|
||||
PusTmCreator::new(&mut sph, tm_header, None, true)
|
||||
}
|
||||
|
||||
fn base_hk_reply<'a>(timestamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> {
|
||||
fn base_hk_reply<'a>(timestamp: &'a [u8], src_data: &'a [u8]) -> PusTmCreator<'a> {
|
||||
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
|
||||
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, ×tamp);
|
||||
PusTm::new(&mut sph, tc_header, Some(src_data), true)
|
||||
PusTmCreator::new(&mut sph, tc_header, Some(src_data), true)
|
||||
}
|
||||
|
||||
fn dummy_timestamp() -> &'static [u8] {
|
||||
@ -652,22 +1022,19 @@ mod tests {
|
||||
.write_to_bytes(&mut buf)
|
||||
.expect("Serialization failed");
|
||||
assert_eq!(ser_len, 22);
|
||||
let (tm_deserialized, size) = PusTm::from_bytes(&buf, 7).expect("Deserialization failed");
|
||||
let (tm_deserialized, size) = PusTmReader::new(&buf, 7).expect("Deserialization failed");
|
||||
assert_eq!(ser_len, size);
|
||||
verify_ping_reply(&tm_deserialized, false, 22, dummy_timestamp());
|
||||
verify_ping_reply_with_reader(&tm_deserialized, false, 22, dummy_timestamp());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manual_field_update() {
|
||||
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
|
||||
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, dummy_timestamp());
|
||||
let mut tm = PusTm::new(&mut sph, tc_header, None, false);
|
||||
let mut tm = PusTmCreator::new(&mut sph, tc_header, None, false);
|
||||
tm.calc_crc_on_serialization = false;
|
||||
assert_eq!(tm.data_len(), 0x00);
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let res = tm.write_to_bytes(&mut buf);
|
||||
assert!(res.is_err());
|
||||
assert!(matches!(res.unwrap_err(), PusError::CrcCalculationMissing));
|
||||
tm.update_ccsds_data_len();
|
||||
assert_eq!(tm.data_len(), 15);
|
||||
tm.calc_own_crc16();
|
||||
@ -755,19 +1122,38 @@ mod tests {
|
||||
}
|
||||
|
||||
fn verify_ping_reply(
|
||||
tm: &PusTm,
|
||||
tm: &PusTmCreator,
|
||||
has_user_data: bool,
|
||||
exp_full_len: usize,
|
||||
exp_timestamp: &[u8],
|
||||
) {
|
||||
assert_eq!(tm.len_packed(), exp_full_len);
|
||||
assert_eq!(tm.timestamp(), exp_timestamp);
|
||||
verify_ping_reply_generic(tm, has_user_data, exp_full_len);
|
||||
}
|
||||
|
||||
fn verify_ping_reply_with_reader(
|
||||
tm: &PusTmReader,
|
||||
has_user_data: bool,
|
||||
exp_full_len: usize,
|
||||
exp_timestamp: &[u8],
|
||||
) {
|
||||
assert_eq!(tm.len_packed(), exp_full_len);
|
||||
assert_eq!(tm.timestamp(), exp_timestamp);
|
||||
verify_ping_reply_generic(tm, has_user_data, exp_full_len);
|
||||
}
|
||||
|
||||
fn verify_ping_reply_generic(
|
||||
tm: &(impl CcsdsPacket + GenericPusTmSecondaryHeader + PusPacket),
|
||||
has_user_data: bool,
|
||||
exp_full_len: usize,
|
||||
) {
|
||||
assert!(tm.is_tm());
|
||||
assert_eq!(PusPacket::service(tm), 17);
|
||||
assert_eq!(PusPacket::subservice(tm), 2);
|
||||
assert!(tm.sec_header_flag());
|
||||
assert_eq!(tm.len_packed(), exp_full_len);
|
||||
assert_eq!(tm.timestamp().unwrap(), exp_timestamp);
|
||||
if has_user_data {
|
||||
assert!(!tm.user_data().is_none());
|
||||
assert!(!tm.user_data().is_empty());
|
||||
}
|
||||
assert_eq!(PusPacket::pus_version(tm), PusC);
|
||||
assert_eq!(tm.apid(), 0x123);
|
||||
@ -792,7 +1178,7 @@ mod tests {
|
||||
let pus_tm = base_ping_reply_full_ctor(timestamp);
|
||||
let mut buf = [0; 32];
|
||||
pus_tm.write_to_bytes(&mut buf).unwrap();
|
||||
assert_eq!(pus_tm, PusTm::from_bytes(&buf, timestamp.len()).unwrap().0);
|
||||
assert_eq!(pus_tm, PusTmReader::new(&buf, timestamp.len()).unwrap().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -811,7 +1197,7 @@ mod tests {
|
||||
writer.finish();
|
||||
// This performs all necessary checks, including the CRC check.
|
||||
let (tm_read_back, tm_size_read_back) =
|
||||
PusTm::from_bytes(&buf, 7).expect("Re-creating PUS TM failed");
|
||||
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.dest_id(), 55);
|
@ -74,9 +74,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod cfdp;
|
||||
pub mod ecss;
|
||||
pub mod tc;
|
||||
pub mod time;
|
||||
pub mod tm;
|
||||
pub mod util;
|
||||
|
||||
mod private {
|
||||
|
Loading…
x
Reference in New Issue
Block a user