From 38df1c233557502b03b2f0aa432713fd8cb50239 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 31 Jul 2022 13:31:14 +0200 Subject: [PATCH] add more and better docs, clippy --- src/ecss.rs | 56 ++++++++++++---- src/lib.rs | 46 +++++++++++-- src/tc.rs | 124 +++++++++++++++++++++-------------- src/time.rs | 1 + src/tm.rs | 182 +++++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 338 insertions(+), 71 deletions(-) diff --git a/src/ecss.rs b/src/ecss.rs index cfbfca8..cb0d48e 100644 --- a/src/ecss.rs +++ b/src/ecss.rs @@ -1,3 +1,5 @@ +//! 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/) use crate::{CcsdsPacket, PacketError}; use core::mem::size_of; use crc::{Crc, CRC_16_IBM_3740}; @@ -53,18 +55,15 @@ pub trait PusPacket: CcsdsPacket { fn crc16(&self) -> Option; } -pub(crate) fn crc_from_raw_data(raw_data: Option<&[u8]>) -> Result { - if let Some(raw_data) = raw_data { - if raw_data.len() < 2 { - return Err(PusError::RawDataTooShort(raw_data.len())); - } - return Ok(u16::from_be_bytes( - raw_data[raw_data.len() - 2..raw_data.len()] - .try_into() - .unwrap(), - )); +pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result { + if raw_data.len() < 2 { + return Err(PusError::RawDataTooShort(raw_data.len())); } - Err(PusError::NoRawData) + Ok(u16::from_be_bytes( + raw_data[raw_data.len() - 2..raw_data.len()] + .try_into() + .unwrap(), + )) } pub(crate) fn calc_pus_crc16(bytes: &[u8]) -> u16 { @@ -89,3 +88,38 @@ pub(crate) fn crc_procedure( } Ok(crc16) } + +pub(crate) fn user_data_from_raw( + current_idx: usize, + total_len: usize, + raw_data_len: usize, + slice: &[u8], +) -> Result, 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])), + } +} + +pub(crate) fn verify_crc16_from_raw(raw_data: &[u8], crc16: u16) -> Result<(), PusError> { + let mut digest = CRC_CCITT_FALSE.digest(); + digest.update(raw_data); + if digest.finalize() == 0 { + return Ok(()); + } + Err(PusError::IncorrectCrc(crc16)) +} + +macro_rules! ccsds_impl { + () => { + delegate!(to self.sp_header { + fn ccsds_version(&self) -> u8; + fn packet_id(&self) -> crate::PacketId; + fn psc(&self) -> crate::PacketSequenceCtrl; + fn data_len(&self) -> u16; + }); + } +} + +pub(crate) use ccsds_impl; diff --git a/src/lib.rs b/src/lib.rs index 8b9db18..5b2e1df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,36 @@ -//! # Space related components including CCSDS and ECSS packet standards +//! # CCSDS and ECSS packet standards implementations +//! +//! This crate contains generic implementations for various +//! CCSDS (Consultative Committee for Space Data Systems) and +//! ECSS (European Cooperation for Space Standardization) packet standards. +//! Currently, this includes the following components: +//! +//! - [Space Packet][crate::SpHeader] implementation according to +//! [CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf) +//! - [PUS Telecommand][crate::tc] and [PUS Telemetry][crate::tm] implementation according to the +//! [ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). +//! +//! # Module +//! +//! This module contains helpers and data structures to generate Space Packets according to the +//! [CCSDS 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf). This includes the +//! [SpHeader] class to generate the Space Packet Header component common to all space packets +//! +//! # Example +//! +//! ```rust +//! use spacepackets::SpHeader; +//! let sp_header = SpHeader::tc(0x42, 12, 0).expect("Error creating SP header"); +//! println!("{:?}", sp_header); +//! ``` #![no_std] extern crate alloc; #[cfg(feature = "std")] extern crate std; -use delegate::delegate; use crate::ecss::CCSDS_HEADER_LEN; +use delegate::delegate; use serde::{Deserialize, Serialize}; pub mod ecss; @@ -261,6 +285,14 @@ pub trait CcsdsPrimaryHeader { } /// Space Packet Primary Header according to CCSDS 133.0-B-2 +/// +/// # Arguments +/// +/// * `version` - CCSDS version field, occupies the first 3 bits of the raw header +/// * `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 +/// * `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 #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Copy, Clone)] pub struct SpHeader { pub version: u8, @@ -286,7 +318,13 @@ impl Default for SpHeader { } } impl SpHeader { - pub fn new(ptype: PacketType, sec_header: bool, apid: u16, ssc: u16, data_len: u16) -> Option { + pub fn new( + ptype: PacketType, + sec_header: bool, + apid: u16, + ssc: u16, + data_len: u16, + ) -> Option { if ssc > num::pow(2, 14) - 1 || apid > num::pow(2, 11) - 1 { return None; } @@ -300,7 +338,7 @@ impl SpHeader { } pub fn tm(apid: u16, seq_count: u16, data_len: u16) -> Option { - Self::new(PacketType::Tm, false, apid, seq_count, data_len) + Self::new(PacketType::Tm, false, apid, seq_count, data_len) } pub fn tc(apid: u16, seq_count: u16, data_len: u16) -> Option { diff --git a/src/tc.rs b/src/tc.rs index 77c3911..c2c50a3 100644 --- a/src/tc.rs +++ b/src/tc.rs @@ -1,5 +1,39 @@ +//! This module contains all components required to create a ECSS PUS C telecommand 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/). +//! +//! # Examples +//! +//! ```rust +//! use spacepackets::{CcsdsPacket, SpHeader}; +//! use spacepackets::tc::{PusTc, PusTcSecondaryHeader}; +//! use spacepackets::ecss::PusPacket; +//! +//! // Create a ping telecommand +//! let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap(); +//! let tc_header = PusTcSecondaryHeader::new_simple(17, 1); +//! let pus_tc = PusTc::new(&mut sph, tc_header, None, true); +//! println!("{:?}", pus_tc); +//! assert_eq!(pus_tc.service(), 17); +//! assert_eq!(pus_tc.subservice(), 1); +//! assert_eq!(pus_tc.apid(), 0x02); +//! +//! // Serialize TC into a raw buffer +//! let mut test_buf: [u8; 32] = [0; 32]; +//! let size = pus_tc +//! .write_to(test_buf.as_mut_slice()) +//! .expect("Error writing TC to buffer"); +//! assert_eq!(size, 13); +//! println!("{:?}", &test_buf[0..size]); +//! +//! // Deserialize from the raw byte representation +//! let pus_tc_deserialized = PusTc::new_from_raw_slice(&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::{ - crc_from_raw_data, crc_procedure, CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE, + ccsds_impl, crc_from_raw_data, crc_procedure, user_data_from_raw, verify_crc16_from_raw, + CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE, }; use crate::SpHeader; use crate::{CcsdsPacket, PacketError, PacketType, SequenceFlags, SizeMissmatch, CCSDS_HEADER_LEN}; @@ -167,10 +201,14 @@ impl PusTcSecondaryHeader { } } -/// This struct models a PUS telecommand. It is the primary data structure to generate the raw byte +/// This class models a PUS telecommand. It is the primary data structure to generate the raw byte /// representation of a PUS telecommand or to deserialize from one from raw bytes. /// -/// There is no spare bytes support yet +/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait 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. #[derive(PartialEq, Copy, Clone, Serialize, Deserialize, Debug)] pub struct PusTc<'slice> { sp_header: SpHeader, @@ -263,7 +301,8 @@ impl<'slice> PusTc<'slice> { }); /// Calculate the CCSDS space packet data length field and sets it - /// This is called automatically if the [set_ccsds_len] argument in the [new] call was used. + /// 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 @@ -273,7 +312,7 @@ impl<'slice> PusTc<'slice> { } /// This function should be called before the TC packet is serialized if - /// [calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16. + /// [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); @@ -286,12 +325,13 @@ impl<'slice> PusTc<'slice> { self.crc16 = Some(digest.finalize()) } - /// This helper function calls both [update_ccsds_data_len] and [calc_own_crc16] + /// This helper function calls both [PusTc.update_ccsds_data_len] and [PusTc.calc_own_crc16] pub fn update_packet_fields(&mut self) { self.update_ccsds_data_len(); self.calc_own_crc16(); } + /// Write the raw PUS byte representation to a provided buffer. pub fn write_to(&self, slice: &mut [u8]) -> Result { let mut curr_idx = 0; let sph_zc = crate::zc::SpHeader::from(self.sp_header); @@ -359,11 +399,9 @@ impl<'slice> PusTc<'slice> { Ok(appended_len) } - /// Create a PusTc instance from a raw slice. On success, it returns a tuple containing + /// Create a [PusTc] instance from a raw slice. On success, it returns a tuple containing /// the instance and the found byte length of the packet - pub fn new_from_raw_slice( - slice: &'slice [u8], - ) -> Result<(Self, usize), PusError> { + pub fn new_from_raw_slice(slice: &'slice [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)); @@ -384,48 +422,23 @@ impl<'slice> PusTc<'slice> { PacketError::FromBytesZeroCopyError, ))?; current_idx += PUC_TC_SECONDARY_HEADER_LEN; - let mut pus_tc = PusTc { + let raw_data = &slice[0..total_len]; + let pus_tc = PusTc { sp_header: SpHeader::from(sph), sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(), - raw_data: Some(slice), - app_data: match current_idx { - _ if current_idx == total_len - 2 => None, - _ if current_idx > total_len - 2 => { - return Err(PusError::RawDataTooShort(raw_data_len)) - } - _ => Some(&slice[current_idx..total_len - 2]), - }, + 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: None, + crc16: Some(crc_from_raw_data(raw_data)?), }; - crc_from_raw_data(pus_tc.raw_data)?; - pus_tc.verify()?; + verify_crc16_from_raw(raw_data, pus_tc.crc16.expect("CRC16 invalid"))?; Ok((pus_tc, total_len)) } - - fn verify(&mut self) -> Result<(), PusError> { - let mut digest = CRC_CCITT_FALSE.digest(); - if self.raw_data.is_none() { - return Err(PusError::NoRawData); - } - let raw_data = self.raw_data.unwrap(); - digest.update(raw_data.as_ref()); - if digest.finalize() == 0 { - return Ok(()); - } - let crc16 = crc_from_raw_data(self.raw_data)?; - Err(PusError::IncorrectCrc(crc16)) - } } //noinspection RsTraitImplementation impl CcsdsPacket for PusTc<'_> { - delegate!(to self.sp_header { - fn ccsds_version(&self) -> u8; - fn packet_id(&self) -> crate::PacketId; - fn psc(&self) -> crate::PacketSequenceCtrl; - fn data_len(&self) -> u16; - }); + ccsds_impl!(); } //noinspection RsTraitImplementation @@ -460,9 +473,9 @@ impl PusTcSecondaryHeaderT for PusTc<'_> { mod tests { use crate::ecss::PusVersion::PusC; use crate::ecss::{PusError, PusPacket}; - use crate::SpHeader; use crate::tc::ACK_ALL; use crate::tc::{PusTc, PusTcSecondaryHeader, PusTcSecondaryHeaderT}; + use crate::SpHeader; use crate::{CcsdsPacket, SequenceFlags}; use alloc::vec::Vec; @@ -498,6 +511,7 @@ mod tests { .expect("Error writing TC to buffer"); assert_eq!(size, 13); } + #[test] fn test_deserialization() { let pus_tc = base_ping_tc_simple_ctor(); @@ -510,10 +524,29 @@ mod tests { .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_raw(&test_buf); verify_crc_no_app_data(&test_buf); } + #[test] + fn test_deserialization_with_app_data() { + let pus_tc = base_ping_tc_simple_ctor_with_app_data(&[1, 2, 3]); + let mut test_buf: [u8; 32] = [0; 32]; + let size = pus_tc + .write_to(test_buf.as_mut_slice()) + .expect("Error writing TC to buffer"); + assert_eq!(size, 16); + let (tc_from_raw, size) = PusTc::new_from_raw_slice(&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(); + assert_eq!(user_data[0], 1); + assert_eq!(user_data[1], 2); + assert_eq!(user_data[2], 3); + } + #[test] fn test_vec_ser_deser() { let pus_tc = base_ping_tc_simple_ctor(); @@ -635,10 +668,7 @@ mod tests { assert_eq!(tc.len_packed(), exp_full_len); let mut comp_header = SpHeader::tc(0x02, 0x34, exp_full_len as u16 - 7).unwrap(); comp_header.set_sec_header_flag(); - assert_eq!( - tc.sp_header, - comp_header - ); + assert_eq!(tc.sp_header, comp_header); } fn verify_test_tc_raw(slice: &impl AsRef<[u8]>) { diff --git a/src/time.rs b/src/time.rs index b8fb78b..7898453 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,3 +1,4 @@ +//! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) use crate::{PacketError, SizeMissmatch}; use chrono::{DateTime, TimeZone, Utc}; diff --git a/src/tm.rs b/src/tm.rs index 4008376..bf19a3c 100644 --- a/src/tm.rs +++ b/src/tm.rs @@ -1,17 +1,32 @@ -use crate::ecss::{crc_procedure, CrcType, PusError, PusVersion, CRC_CCITT_FALSE}; -use crate::{PacketError, PacketType, SizeMissmatch, SpHeader, CCSDS_HEADER_LEN}; +//! 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::{ + ccsds_impl, crc_from_raw_data, crc_procedure, user_data_from_raw, verify_crc16_from_raw, + CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE, +}; +use crate::{CcsdsPacket, PacketError, PacketType, SizeMissmatch, SpHeader, CCSDS_HEADER_LEN}; use core::mem::size_of; use serde::{Deserialize, Serialize}; use zerocopy::AsBytes; #[cfg(feature = "alloc")] use alloc::vec::Vec; +use delegate::delegate; /// Length without timestamp pub const PUC_TM_MIN_SEC_HEADER_LEN: usize = 7; pub const PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA: usize = CCSDS_HEADER_LEN + PUC_TM_MIN_SEC_HEADER_LEN + size_of::(); +pub trait PusTmSecondaryHeaderT { + fn pus_version(&self) -> PusVersion; + fn sc_time_ref_status(&self) -> u8; + fn service(&self) -> u8; + fn subservice(&self) -> u8; + fn msg_counter(&self) -> u16; + fn dest_id(&self) -> u16; +} + pub mod zc { use crate::ecss::{PusError, PusVersion}; use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16}; @@ -26,6 +41,11 @@ pub mod zc { dest_id: U16, } + pub struct PusTmSecHeader<'slice> { + pub(crate) zc_header: PusTmSecHeaderWithoutTimestamp, + pub(crate) timestamp: &'slice [u8], + } + impl TryFrom> for PusTmSecHeaderWithoutTimestamp { type Error = PusError; fn try_from(header: crate::tm::PusTmSecondaryHeader) -> Result { @@ -52,6 +72,33 @@ pub mod zc { Self::read_from(slice) } } + + impl super::PusTmSecondaryHeaderT for PusTmSecHeaderWithoutTimestamp { + fn pus_version(&self) -> PusVersion { + PusVersion::try_from(self.pus_version_and_sc_time_ref_status >> 4 & 0b1111) + .unwrap_or(PusVersion::Invalid) + } + + fn sc_time_ref_status(&self) -> u8 { + self.pus_version_and_sc_time_ref_status & 0b1111 + } + + fn service(&self) -> u8 { + self.service + } + + fn subservice(&self) -> u8 { + self.subservice + } + + fn msg_counter(&self) -> u16 { + self.msg_counter.get() + } + + fn dest_id(&self) -> u16 { + self.dest_id.get() + } + } } #[derive(PartialEq, Serialize, Deserialize, Copy, Clone, Debug)] @@ -97,10 +144,56 @@ impl<'slice> PusTmSecondaryHeader<'slice> { } } -/// This struct models a PUS telemetry and which can also be used. It is the primary data +impl PusTmSecondaryHeaderT for PusTmSecondaryHeader<'_> { + fn pus_version(&self) -> PusVersion { + self.pus_version + } + + fn sc_time_ref_status(&self) -> u8 { + self.sc_time_ref_status + } + + fn service(&self) -> u8 { + self.service + } + + fn subservice(&self) -> u8 { + self.subservice + } + + fn msg_counter(&self) -> u16 { + self.msg_counter + } + + fn dest_id(&self) -> u16 { + self.dest_id + } +} + +impl<'slice> TryFrom> for PusTmSecondaryHeader<'slice> { + type Error = (); + + fn try_from(sec_header: zc::PusTmSecHeader<'slice>) -> Result { + Ok(PusTmSecondaryHeader { + pus_version: sec_header.zc_header.pus_version(), + sc_time_ref_status: sec_header.zc_header.sc_time_ref_status(), + service: sec_header.zc_header.service(), + subservice: sec_header.zc_header.subservice(), + msg_counter: sec_header.zc_header.msg_counter(), + dest_id: sec_header.zc_header.dest_id(), + time_stamp: sec_header.timestamp, + }) + } +} + +/// This class models a PUS telemetry and which can also be used. 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 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. #[derive(PartialEq, Serialize, Deserialize, Debug, Copy, Clone)] pub struct PusTm<'slice> { @@ -122,12 +215,12 @@ impl<'slice> PusTm<'slice> { /// /// * `sp_header` - Space packet header information. The correct packet type will be set /// automatically - /// * `pus_params` - Information contained in the data field header, including the service + /// * `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 - /// * `app_data` - Custom application data pub fn new( sp_header: &mut SpHeader, sec_header: PusTmSecondaryHeader<'slice>, @@ -159,7 +252,8 @@ impl<'slice> PusTm<'slice> { length } - /// This is called automatically if the [set_ccsds_len] argument in the [new] call was used. + /// 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 @@ -169,7 +263,7 @@ impl<'slice> PusTm<'slice> { } /// This function should be called before the TM packet is serialized if - /// [calc_crc_on_serialization] is set to False. It will calculate and cache the CRC16. + /// [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); @@ -183,12 +277,13 @@ impl<'slice> PusTm<'slice> { self.crc16 = Some(digest.finalize()) } - /// This helper function calls both [update_ccsds_data_len] and [calc_own_crc16] + /// 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(); } + /// Write the raw PUS byte representation to a provided buffer. pub fn write_to(&self, slice: &mut [u8]) -> Result { let mut curr_idx = 0; let sph_zc = crate::zc::SpHeader::from(self.sp_header); @@ -229,10 +324,12 @@ impl<'slice> PusTm<'slice> { Ok(curr_idx) } + /// Append the raw PUS byte representation to a provided [alloc::vec::Vec] #[cfg(feature = "alloc")] pub fn append_to_vec(&self, vec: &mut Vec) -> Result { let sph_zc = crate::zc::SpHeader::from(self.sp_header); - let mut appended_len = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA + self.sec_header.time_stamp.len(); + let mut appended_len = + PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA + self.sec_header.time_stamp.len(); if let Some(src_data) = self.source_data { appended_len += src_data.len(); }; @@ -259,6 +356,73 @@ impl<'slice> PusTm<'slice> { 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. + pub fn new_from_raw_slice( + slice: &'slice [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 sph = crate::zc::SpHeader::from_bytes(&slice[current_idx..current_idx + 6]).ok_or( + PusError::OtherPacketError(PacketError::FromBytesZeroCopyError), + )?; + current_idx += 6; + let total_len = sph.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(PusError::OtherPacketError( + PacketError::FromBytesZeroCopyError, + ))?; + 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: SpHeader::from(sph), + 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_from_raw(raw_data, pus_tm.crc16.expect("CRC16 invalid"))?; + Ok((pus_tm, total_len)) + } +} + +//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 { + self.crc16 + } } #[cfg(test)]