From fbdc325d0d625f7ce865ac3fb5a80feb44793a30 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 4 Nov 2025 19:08:54 +0100 Subject: [PATCH] Finish full crate docs --- CHANGELOG.md | 1 + src/cfdp/mod.rs | 1 - src/ecss/event.rs | 11 +++--- src/ecss/hk.rs | 29 ++++++++++----- src/ecss/scheduling.rs | 44 +++++++++++++++++++--- src/ecss/tc.rs | 49 ++++++++++++++++++++++++ src/ecss/tc_pus_a.rs | 38 +++++++++++++++++++ src/ecss/tm.rs | 70 ++++++++++++++++++++++++++++++++++- src/ecss/tm_pus_a.rs | 80 ++++++++++++++++++++++++++++++++++------ src/ecss/verification.rs | 17 +++++++-- src/lib.rs | 9 ++--- src/seq_count.rs | 2 +- src/time/mod.rs | 1 - src/uslp/mod.rs | 73 ++++++++++++++++++++++++++++++++---- src/util.rs | 2 +- 15 files changed, 373 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 143dfe9..dd67c05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `CdsCommon` renamed to `CdsBase` - cfdp: Removed `FileDirectiveType` variant `*Pdu` suffix +- ecss: Renamed `Subservice` to `MessageSubtypeId` - Simplified CDS short timestamp, contains one less field which reduced serialization length. - Renamed `UnsignedEnum::value` to `UnsignedEnum::value_raw`, `value` is reserved for the `const` value getter. diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index e474f6e..23ad656 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -1,5 +1,4 @@ //! Low-level CCSDS File Delivery Protocol (CFDP) support according to [CCSDS 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf). -#![warn(missing_docs)] use crate::ByteConversionError; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] diff --git a/src/ecss/event.rs b/src/ecss/event.rs index f87deb7..0cd2246 100644 --- a/src/ecss/event.rs +++ b/src/ecss/event.rs @@ -3,10 +3,11 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +/// Event service subtype ID. #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(u8)] -pub enum Subservice { +pub enum MessageSubtypeId { /// Telemetry - Info report. TmInfoReport = 1, /// Telemetry - Low severity report. @@ -31,19 +32,19 @@ mod tests { #[test] fn test_conv_into_u8() { - let subservice: u8 = Subservice::TmLowSeverityReport.into(); + let subservice: u8 = MessageSubtypeId::TmLowSeverityReport.into(); assert_eq!(subservice, 2); } #[test] fn test_conv_from_u8() { - let subservice: Subservice = 2.try_into().unwrap(); - assert_eq!(subservice, Subservice::TmLowSeverityReport); + let subservice: MessageSubtypeId = 2.try_into().unwrap(); + assert_eq!(subservice, MessageSubtypeId::TmLowSeverityReport); } #[test] fn test_conv_fails() { - let conversion = Subservice::try_from(9); + let conversion = MessageSubtypeId::try_from(9); assert!(conversion.is_err()); let err = conversion.unwrap_err(); assert_eq!(err.number, 9); diff --git a/src/ecss/hk.rs b/src/ecss/hk.rs index ddc13d6..f368181 100644 --- a/src/ecss/hk.rs +++ b/src/ecss/hk.rs @@ -3,11 +3,12 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +/// Housekeeping service subtype ID. #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] -pub enum Subservice { +pub enum MessageSubtypeId { // Regular HK /// Telecommand - Create Housekeeping Report Structure. TcCreateHkReportStructure = 1, @@ -26,16 +27,25 @@ pub enum Subservice { /// Telecommand - Modify collection interval. TcModifyHkCollectionInterval = 31, - // Diagnostics HK + /// Telecommand - Create diagnostics report structures. TcCreateDiagReportStructure = 2, + /// Telecommand - Delete diagnostics report structures. TcDeleteDiagReportStructures = 4, + /// Telecommand - Enable diagnostics generation. TcEnableDiagGeneration = 7, + /// Telecommand - Disable diagnostics generation. TcDisableDiagGeneration = 8, + /// Telemetry - HK structures report. TmHkStructuresReport = 10, + /// Telecommand - Report diagnostics report structures. TcReportDiagReportStructures = 11, + /// Telemetry - Diagnostics report structures. TmDiagStructuresReport = 12, + /// Telemetry - Diagnostics packet. TmDiagPacket = 26, + /// Telecommand - Generate one-shot diagnostics report. TcGenerateOneShotDiag = 28, + /// Telecommand - Modify diagnostics interval report. TcModifyDiagCollectionInterval = 32, } @@ -45,25 +55,26 @@ mod tests { #[test] fn test_try_from_u8() { let hk_report_subservice_raw = 25; - let hk_report: Subservice = Subservice::try_from(hk_report_subservice_raw).unwrap(); - assert_eq!(hk_report, Subservice::TmHkPacket); + let hk_report: MessageSubtypeId = + MessageSubtypeId::try_from(hk_report_subservice_raw).unwrap(); + assert_eq!(hk_report, MessageSubtypeId::TmHkPacket); } #[test] fn test_into_u8() { - let hk_report_raw: u8 = Subservice::TmHkPacket.into(); + let hk_report_raw: u8 = MessageSubtypeId::TmHkPacket.into(); assert_eq!(hk_report_raw, 25); } #[test] fn test_partial_eq() { - let hk_report_raw = Subservice::TmHkPacket; - assert_ne!(hk_report_raw, Subservice::TcGenerateOneShotHk); - assert_eq!(hk_report_raw, Subservice::TmHkPacket); + let hk_report_raw = MessageSubtypeId::TmHkPacket; + assert_ne!(hk_report_raw, MessageSubtypeId::TcGenerateOneShotHk); + assert_eq!(hk_report_raw, MessageSubtypeId::TmHkPacket); } #[test] fn test_copy_clone() { - let hk_report = Subservice::TmHkPacket; + let hk_report = MessageSubtypeId::TmHkPacket; let hk_report_copy = hk_report; assert_eq!(hk_report, hk_report_copy); } diff --git a/src/ecss/scheduling.rs b/src/ecss/scheduling.rs index 5533812..0be74b5 100644 --- a/src/ecss/scheduling.rs +++ b/src/ecss/scheduling.rs @@ -3,46 +3,74 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +/// Scheduling service subtype ID. #[derive(Debug, PartialEq, Eq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] -pub enum Subservice { +pub enum MessageSubtypeId { // Core subservices + /// Telecommand - Enable scheduling. TcEnableScheduling = 1, + /// Telecommand - Disable scheduling. TcDisableScheduling = 2, + /// Telecommand - Reset scheduling. TcResetScheduling = 3, + /// Telecommand - Insert activity. TcInsertActivity = 4, + /// Telecommand - Delete activity by request ID. TcDeleteActivityByRequestId = 5, + /// Telecommand - Delete activity by filter. TcDeleteActivitiesByFilter = 6, // Time shift subservices + /// Telecommand - Time shift activity by request ID. TcTimeShiftActivityWithRequestId = 7, + /// Telecommand - Time shift activity by filter. TcTimeShiftActivitiesByFilter = 8, + /// Telecommand - Time shift all. TcTimeShiftAll = 15, // Reporting subservices + /// Telecommand - Detail report by request ID. TcDetailReportByRequestId = 9, + /// Telemetry - Detail report. TmDetailReport = 10, + /// Telecommand - Detail report by filter. TcDetailReportByFilter = 11, + /// Telecommand - Summary report by request ID. TcSummaryReportByRequestId = 12, + /// Telemetry - Summary report. TmSummaryReport = 13, + /// Telecommand - Summary report by filter. TcSummaryReportByFilter = 14, + /// Telecommand - Detail report all. TcDetailReportAll = 16, + /// Telecommand - Summary report all. TcSummaryReportAll = 17, // Subschedule subservices + /// Telecommand - Report subschedule status. TcReportSubscheduleStatus = 18, + /// Telemetry - Subschedule status report. TmReportSubscheduleStatus = 19, + /// Telecommand - Enable subschedule. TcEnableSubschedule = 20, + /// Telecommand - Disable subschedule. TcDisableSubschedule = 21, // Group subservices + /// Telecommand - Create schedule group. TcCreateScheduleGroup = 22, + /// Telecommand - Delete schedule group. TcDeleteScheduleGroup = 23, + /// Telecommand - Enable schedule group. TcEnableScheduleGroup = 24, + /// Telecommand - Disable schedule group. TcDisableScheduleGroup = 25, + /// Telecommand - Report all group status. TcReportAllGroupsStatus = 26, + /// Telemetry - All group status report. TmReportAllGroupsStatus = 27, } @@ -51,7 +79,9 @@ pub enum Subservice { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum SchedStatus { + /// Scheduling disabled. Disabled = 0, + /// Scheduling enabled. Enabled = 1, } @@ -71,9 +101,13 @@ impl From for SchedStatus { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TimeWindowType { + /// Select all. SelectAll = 0, + /// From time tag to time tag. TimeTagToTimeTag = 1, + /// Starting from a time tag. FromTimeTag = 2, + /// Until a time tag. ToTimeTag = 3, } @@ -99,20 +133,20 @@ mod tests { #[test] fn test_conv_into_u8() { - let subservice: u8 = Subservice::TcCreateScheduleGroup.into(); + let subservice: u8 = MessageSubtypeId::TcCreateScheduleGroup.into(); assert_eq!(subservice, 22); } #[test] fn test_conv_from_u8() { - let subservice: Subservice = 22u8.try_into().unwrap(); - assert_eq!(subservice, Subservice::TcCreateScheduleGroup); + let subservice: MessageSubtypeId = 22u8.try_into().unwrap(); + assert_eq!(subservice, MessageSubtypeId::TcCreateScheduleGroup); } #[test] #[cfg(feature = "serde")] fn test_serde_subservice_id() { - generic_serde_test(Subservice::TcEnableScheduling); + generic_serde_test(MessageSubtypeId::TcEnableScheduling); } #[test] diff --git a/src/ecss/tc.rs b/src/ecss/tc.rs index 16e89f1..83a4902 100644 --- a/src/ecss/tc.rs +++ b/src/ecss/tc.rs @@ -68,6 +68,7 @@ use super::verify_crc16_ccitt_false_from_raw_to_pus_error_no_table; /// PUS C secondary header length is fixed pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::(); +/// Minimum PUS C secondary header length without application data. pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize = CCSDS_HEADER_LEN + PUC_TC_SECONDARY_HEADER_LEN; const PUS_VERSION: PusVersion = PusVersion::PusC; @@ -79,12 +80,16 @@ pub trait IsPusTelecommand {} #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(PartialEq, Eq)] pub struct AckFlags { + /// Acceptance must be acknowledged. #[bit(3, rw)] acceptance: bool, + /// Start must be acknowledged. #[bit(2, rw)] start: bool, + /// Progress must be acknowledged. #[bit(1, rw)] progress: bool, + /// Completion must be acknowledged. #[bit(0, rw)] completion: bool, } @@ -98,9 +103,11 @@ pub const ACK_ALL: AckFlags = AckFlags::builder() .build(); impl AckFlags { + /// Constant to acknowledge all TC handling phases. pub const ALL: Self = ACK_ALL; } +/// Generic trait for PUS TC secondary header access. pub trait GenericPusTcSecondaryHeader { /// PUS version. fn pus_version(&self) -> Result; @@ -124,6 +131,7 @@ pub trait GenericPusTcSecondaryHeader { } } +/// [zerocopy] support. pub mod zc { use crate::ecss::tc::{AckFlags, GenericPusTcSecondaryHeader}; use crate::ecss::{MessageTypeId, PusError, PusVersion}; @@ -131,6 +139,7 @@ pub mod zc { use arbitrary_int::u4; use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16}; + /// PUS TC secondary header. #[derive(FromBytes, IntoBytes, Immutable, Unaligned)] #[repr(C)] pub struct PusTcSecondaryHeader { @@ -192,13 +201,18 @@ pub mod zc { } } +/// PUS C secondary header for telecommands. #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTcSecondaryHeader { + /// Message type ID. pub message_type_id: MessageTypeId, + /// Source ID. pub source_id: u16, + /// Acknowledgement flags. pub ack_flags: AckFlags, + /// PUS version. pub version: PusVersion, } @@ -238,8 +252,10 @@ impl TryFrom for PusTcSecondaryHeader { } impl PusTcSecondaryHeader { + /// Header length constant. pub const HEADER_LEN: usize = PUC_TC_SECONDARY_HEADER_LEN; + /// Simple constructor which only requires the [MessageTypeId]. #[inline] pub fn new_simple(message_type_id: MessageTypeId) -> Self { PusTcSecondaryHeader { @@ -250,6 +266,7 @@ impl PusTcSecondaryHeader { } } + /// General constructor. #[inline] pub fn new(message_type_id: MessageTypeId, ack_flags: AckFlags, source_id: u16) -> Self { PusTcSecondaryHeader { @@ -274,6 +291,7 @@ impl PusTcSecondaryHeader { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTcCreator<'app_data> { sp_header: SpHeader, + /// Secondary header. pub sec_header: PusTcSecondaryHeader, app_data: &'app_data [u8], has_checksum: bool, @@ -328,6 +346,7 @@ impl<'app_data> PusTcCreator<'app_data> { ) } + /// Constructor for a PUS TC packet without application data. #[inline] pub fn new_no_app_data( sp_header: SpHeader, @@ -337,40 +356,48 @@ impl<'app_data> PusTcCreator<'app_data> { Self::new(sp_header, sec_header, &[], packet_config) } + /// Get a builder whih allows building [Self] step-by-step. pub fn builder<'a>() -> PusTcBuilder<'a> { PusTcBuilder::default() } + /// Space packet header. #[inline] pub fn sp_header(&self) -> &SpHeader { &self.sp_header } + /// Mutable access to the space packet header. #[inline] pub fn sp_header_mut(&mut self) -> &mut SpHeader { &mut self.sp_header } + /// Service type ID. #[inline] pub fn service_type_id(&self) -> u8 { self.sec_header.service_type_id() } + /// Message subtype ID. #[inline] pub fn message_subtype_id(&self) -> u8 { self.sec_header.message_subtype_id() } + /// Application Process ID (APID). #[inline] pub fn apid(&self) -> u11 { self.sp_header.packet_id.apid } + /// Set acknowledgement flags. #[inline] pub fn set_ack_flags(&mut self, ack_flags: AckFlags) { self.sec_header.ack_flags = ack_flags; } + /// Set the source ID. #[inline] pub fn set_source_id(&mut self, source_id: u16) { self.sec_header.source_id = source_id; @@ -412,11 +439,13 @@ impl<'app_data> PusTcCreator<'app_data> { digest.finalize() } + /// Current packet has a checksum. #[inline] pub fn has_checksum(&self) -> bool { self.has_checksum } + /// Append the raw PUS byte representation to a provided vector. #[cfg(feature = "alloc")] pub fn append_to_vec(&self, vec: &mut Vec) -> usize { let sph_zc = crate::zc::SpHeader::from(self.sp_header); @@ -658,6 +687,7 @@ impl<'buf> PusTcCreatorWithReservedAppData<'buf> { }) } + /// Length of the full packet written into a buffer. #[inline] pub const fn len_written(&self) -> usize { self.full_len @@ -685,6 +715,7 @@ impl<'buf> PusTcCreatorWithReservedAppData<'buf> { &self.buf[self.app_data_offset..end_index] } + /// Length of the application data. #[inline] pub fn app_data_len(&self) -> usize { let mut len = self.full_len - self.app_data_offset; @@ -747,6 +778,7 @@ pub struct PusTcBuilder<'a> { } impl PusTcBuilder<'_> { + /// Constructor. pub fn new() -> Self { Self { sp_header: SpHeader::new( @@ -760,6 +792,7 @@ impl PusTcBuilder<'_> { } } + /// Set the packet ID. #[inline] pub fn with_packet_id(mut self, mut packet_id: PacketId) -> Self { packet_id.packet_type = PacketType::Tc; @@ -767,54 +800,63 @@ impl PusTcBuilder<'_> { self } + /// Set the packet sequence control. #[inline] pub fn with_packet_sequence_control(mut self, psc: PacketSequenceControl) -> Self { self.sp_header.psc = psc; self } + /// Set the sequence count. #[inline] pub fn with_sequence_count(mut self, seq_count: u14) -> Self { self.sp_header.psc.seq_count = seq_count; self } + /// Set the message type ID. #[inline] pub fn with_message_type_id(mut self, message_type: MessageTypeId) -> Self { self.sec_header.message_type_id = message_type; self } + /// Set the service type ID. #[inline] pub fn with_service_type_id(mut self, service: u8) -> Self { self.sec_header.message_type_id.type_id = service; self } + /// Set the message subtype ID. #[inline] pub fn with_message_subtype_id(mut self, subtype_id: u8) -> Self { self.sec_header.message_type_id.subtype_id = subtype_id; self } + /// Set the source ID. #[inline] pub fn with_source_id(mut self, source_id: u16) -> Self { self.sec_header.source_id = source_id; self } + /// Set the acknowledgmenet flags. #[inline] pub fn with_ack_flags(mut self, ack_flags: AckFlags) -> Self { self.sec_header.ack_flags = ack_flags; self } + /// Set the Application Process ID (APID). #[inline] pub fn with_apid(mut self, apid: u11) -> Self { self.sp_header.packet_id.set_apid(apid); self } + /// Enable or disable the checksum. #[inline] pub fn with_checksum(mut self, has_checksum: bool) -> Self { self.has_checksum = has_checksum; @@ -829,6 +871,7 @@ impl Default for PusTcBuilder<'_> { } impl<'a> PusTcBuilder<'a> { + /// Constructor. pub fn build(self) -> PusTcCreator<'a> { PusTcCreator::new( self.sp_header, @@ -841,6 +884,7 @@ impl<'a> PusTcBuilder<'a> { ) } + /// Application data slice. #[inline] pub fn with_app_data(mut self, app_data: &'a [u8]) -> Self { self.app_data = app_data; @@ -949,26 +993,31 @@ impl<'raw_data> PusTcReader<'raw_data> { }) } + /// Application data slice. #[inline] pub fn app_data(&self) -> &[u8] { self.user_data() } + /// Full raw data slice. #[inline] pub fn raw_data(&self) -> &[u8] { self.raw_data } + /// Length of the packed data. #[inline] pub fn len_packed(&self) -> usize { self.sp_header.packet_len() } + /// Space packet header. #[inline] pub fn sp_header(&self) -> &SpHeader { &self.sp_header } + /// CRC16 checksum if present. #[inline] pub fn crc16(&self) -> Option { self.crc16 diff --git a/src/ecss/tc_pus_a.rs b/src/ecss/tc_pus_a.rs index c1f5a8b..3db446d 100644 --- a/src/ecss/tc_pus_a.rs +++ b/src/ecss/tc_pus_a.rs @@ -74,33 +74,49 @@ enum AckOpts { /// Assuming 8 bytes of source ID and 7 bytes of spare. pub const MAX_SEC_HEADER_LEN: usize = 18; +/// Maximum allowed number of space bytes. pub const MAX_SPARE_BYTES: usize = 7; +/// Invalid number of spare bytes which must be between 0 and 7. #[derive(Debug, Eq, PartialEq, Copy, Clone, thiserror::Error)] #[error("invalid number of spare bytes, must be between 0 and 7")] pub struct InvalidNumberOfSpareBytesError; +/// PUS version error. #[derive(Debug, Eq, PartialEq, Copy, Clone, thiserror::Error)] #[error("invalid version, expected PUS A (1), got {0}")] pub struct VersionError(pub u8); +/// Trait for accessing PUS Telecommand secondary header fields. pub trait GenericPusTcSecondaryHeader { + /// PUS version number. fn pus_version(&self) -> Result; + /// Acknowledgment flags. fn ack_flags(&self) -> AckFlags; + /// Service type identifier. fn service(&self) -> u8; + /// Subservice type identifier. fn subservice(&self) -> u8; + /// Source ID. fn source_id(&self) -> Option; + /// Number of spare bytes. fn spare_bytes(&self) -> usize; } +/// PUS TC secondary header. #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTcSecondaryHeader { + /// Service type identifier. pub service: u8, + /// Subservice type identifier. pub subservice: u8, + /// Source ID. pub source_id: Option, + /// Acknowledgment flags. pub ack: AckFlags, + /// PUS version. pub version: PusVersion, spare_bytes: usize, } @@ -138,6 +154,8 @@ impl GenericPusTcSecondaryHeader for PusTcSecondaryHeader { } impl PusTcSecondaryHeader { + /// Constructor which allows modifying the service and subservice and sets + /// default values for the other fields. #[inline] pub fn new_simple(service: u8, subservice: u8) -> Self { PusTcSecondaryHeader { @@ -150,6 +168,7 @@ impl PusTcSecondaryHeader { } } + /// Generic constructor. #[inline] pub fn new( service: u8, @@ -176,6 +195,7 @@ impl PusTcSecondaryHeader { self.spare_bytes = spare_bytes; } + /// Length of the written secondary header in bytes. pub fn written_len(&self) -> usize { let mut len = 3 + self.spare_bytes; if let Some(source_id) = self.source_id { @@ -184,6 +204,7 @@ impl PusTcSecondaryHeader { len } + /// Converts the secondary header into a vector of bytes. #[cfg(feature = "alloc")] pub fn to_vec(&self) -> Result, ByteConversionError> { let mut buf = alloc::vec![0; self.written_len()]; @@ -191,6 +212,7 @@ impl PusTcSecondaryHeader { Ok(buf) } + /// Write the secondary header to a provided buffer. pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { if buf.len() < self.written_len() { return Err(ByteConversionError::ToSliceTooSmall { @@ -216,6 +238,7 @@ impl PusTcSecondaryHeader { Ok(current_idx) } + /// Read the secondary header from a raw byte slice. pub fn from_bytes( data: &[u8], source_id_size: Option, @@ -268,6 +291,7 @@ impl PusTcSecondaryHeader { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTcCreator<'app_data> { sp_header: SpHeader, + /// Secondary header. pub sec_header: PusTcSecondaryHeader, app_data: &'app_data [u8], } @@ -323,6 +347,7 @@ impl<'app_data> PusTcCreator<'app_data> { ) } + /// Constructor without application data. #[inline] pub fn new_no_app_data( sp_header: SpHeader, @@ -332,26 +357,31 @@ impl<'app_data> PusTcCreator<'app_data> { Self::new(sp_header, sec_header, &[], set_ccsds_len) } + /// Space packet header. #[inline] pub fn sp_header(&self) -> &SpHeader { &self.sp_header } + /// Mutable access to the space packet header. #[inline] pub fn sp_header_mut(&mut self) -> &mut SpHeader { &mut self.sp_header } + /// Set the acknowledgment flags. #[inline] pub fn set_ack_field(&mut self, ack: AckFlags) { self.sec_header.ack = ack; } + /// Set the source ID. #[inline] pub fn set_source_id(&mut self, source_id: Option) { self.sec_header.source_id = source_id; } + /// Application data slice. #[inline] pub fn app_data(&'app_data self) -> &'app_data [u8] { self.user_data() @@ -397,6 +427,7 @@ impl<'app_data> PusTcCreator<'app_data> { digest.finalize() } + /// Append the raw PUS byte representation to a provided vector. #[cfg(feature = "alloc")] pub fn append_to_vec(&self, vec: &mut Vec) -> usize { let sph_zc = crate::zc::SpHeader::from(self.sp_header); @@ -616,6 +647,7 @@ impl<'buf> PusTcCreatorWithReservedAppData<'buf> { }) } + /// Length of the written TC packet. #[inline] pub const fn len_written(&self) -> usize { self.full_len @@ -633,6 +665,7 @@ impl<'buf> PusTcCreatorWithReservedAppData<'buf> { &self.buf[self.app_data_offset..self.full_len - 2] } + /// Application data length. #[inline] pub fn app_data_len(&self) -> usize { self.full_len - 2 - self.app_data_offset @@ -757,26 +790,31 @@ impl<'raw_data> PusTcReader<'raw_data> { }) } + /// Application data slice. #[inline] pub fn app_data(&self) -> &[u8] { self.user_data() } + /// Full raw data slice. #[inline] pub fn raw_data(&self) -> &[u8] { self.raw_data } + /// Length of the packed PUS TC packet in bytes. #[inline] pub fn len_packed(&self) -> usize { self.sp_header.packet_len() } + /// Space packet header. #[inline] pub fn sp_header(&self) -> &SpHeader { &self.sp_header } + /// CRC16 checksum value. #[inline] pub fn crc16(&self) -> u16 { self.crc16 diff --git a/src/ecss/tm.rs b/src/ecss/tm.rs index 78dff85..2216155 100644 --- a/src/ecss/tm.rs +++ b/src/ecss/tm.rs @@ -78,12 +78,15 @@ use self::zc::PusTmSecHeaderWithoutTimestamp; use super::verify_crc16_ccitt_false_from_raw_to_pus_error_no_table; +/// Marker trait for PUS telemetry packets. pub trait IsPusTelemetry {} /// Length without timestamp pub const PUS_TM_MIN_SEC_HEADER_LEN: usize = 7; +/// Minimum length of a PUS telemetry packet without source data and timestamp pub const PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA: usize = CCSDS_HEADER_LEN + PUS_TM_MIN_SEC_HEADER_LEN; +/// Generic properties of a PUS TM secondary header. pub trait GenericPusTmSecondaryHeader { /// PUS version. fn pus_version(&self) -> Result; @@ -110,12 +113,14 @@ pub trait GenericPusTmSecondaryHeader { } } +/// [zerocopy] support module. pub mod zc { use super::GenericPusTmSecondaryHeader; use crate::ecss::{MessageTypeId, PusError, PusVersion}; use arbitrary_int::{traits::Integer as _, u4}; use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16}; + /// PUS TM secondary header without a timestamp. #[derive(FromBytes, IntoBytes, Immutable, Unaligned)] #[repr(C)] pub struct PusTmSecHeaderWithoutTimestamp { @@ -126,9 +131,10 @@ pub mod zc { dest_id: U16, } - pub struct PusTmSecHeader<'slice> { + /// PUS TM secondary header with timestamp. + pub struct PusTmSecHeader<'time> { pub(crate) zc_header: PusTmSecHeaderWithoutTimestamp, - pub(crate) timestamp: &'slice [u8], + pub(crate) timestamp: &'time [u8], } impl TryFrom> for PusTmSecHeaderWithoutTimestamp { @@ -194,19 +200,27 @@ pub mod zc { } } +/// PUS TM secondary header. #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTmSecondaryHeader<'stamp> { pus_version: PusVersion, + /// Spacecraft time reference status. pub sc_time_ref_status: u4, + /// Message type ID. pub message_type_id: MessageTypeId, + /// Message counter for the message service type ID. pub msg_counter: u16, + /// Destination ID. pub dest_id: u16, + /// Raw timestamp slice. pub timestamp: &'stamp [u8], } impl<'stamp> PusTmSecondaryHeader<'stamp> { + /// Constructor where only the message type ID and timestamp are specified. The other fields + /// are set to default values. #[inline] pub fn new_simple(message_type_id: MessageTypeId, timestamp: &'stamp [u8]) -> Self { Self::new(message_type_id, 0, 0, timestamp) @@ -218,6 +232,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> { Self::new(message_type_id, 0, 0, &[]) } + /// Generic constructor. #[inline] pub fn new( message_type_id: MessageTypeId, @@ -300,7 +315,9 @@ impl<'slice> TryFrom> for PusTmSecondaryHeader<'slice #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTmCreator<'time, 'src_data> { + /// Space packet header. pub sp_header: SpHeader, + /// Secondary header. #[cfg_attr(feature = "serde", serde(borrow))] pub sec_header: PusTmSecondaryHeader<'time>, source_data: &'src_data [u8], @@ -343,6 +360,8 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { pus_tm } + /// Simple constructor which builds the [PusTmSecondaryHeader] internally from the + /// provided arguments. #[inline] pub fn new_simple( sp_header: SpHeader, @@ -358,6 +377,7 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { Ok(Self::new(sp_header, sec_header, source_data, packet_config)) } + /// Constructor for PUS TM packets without source data. #[inline] pub fn new_no_source_data( sp_header: SpHeader, @@ -367,50 +387,60 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { Self::new(sp_header, sec_header, &[], packet_config) } + /// Builder API. pub fn builder() -> PusTmBuilder<'time, 'src_data> { PusTmBuilder::new() } + /// Does the packet have a checksum? #[inline] pub fn has_checksum(&self) -> bool { self.has_checksum } + /// Raw timestamp slice. #[inline] pub fn timestamp(&self) -> &[u8] { self.sec_header.timestamp } + /// Raw source data slice. #[inline] pub fn source_data(&self) -> &[u8] { self.source_data } + /// Servcie type ID. #[inline] pub fn service_type_id(&self) -> u8 { self.sec_header.service_type_id() } + /// Message subtype ID. #[inline] pub fn message_subtype_id(&self) -> u8 { self.sec_header.message_subtype_id() } + /// Application Process Identifier (APID). #[inline] pub fn apid(&self) -> u11 { self.sp_header.packet_id.apid } + /// Set the destination ID. #[inline] pub fn set_dest_id(&mut self, dest_id: u16) { self.sec_header.dest_id = dest_id; } + /// Set the message counter for the service type ID. #[inline] pub fn set_msg_counter(&mut self, msg_counter: u16) { self.sec_header.msg_counter = msg_counter } + /// Set the spacecraft time reference status. #[inline] pub fn set_sc_time_ref_status(&mut self, sc_time_ref_status: u4) { self.sec_header.sc_time_ref_status = sc_time_ref_status; @@ -626,6 +656,7 @@ impl GenericPusTmSecondaryHeader for PusTmCreator<'_, '_> { impl IsPusTelemetry for PusTmCreator<'_, '_> {} +/// PUS TM bulder API. #[derive(Debug)] pub struct PusTmBuilder<'time, 'src_data> { sp_header: SpHeader, @@ -641,6 +672,7 @@ impl Default for PusTmBuilder<'_, '_> { } impl PusTmBuilder<'_, '_> { + /// Constructor. pub fn new() -> Self { Self { sp_header: SpHeader::new( @@ -662,12 +694,14 @@ impl PusTmBuilder<'_, '_> { } } + /// Set the application process identifier (APID). #[inline] pub fn with_apid(mut self, apid: u11) -> Self { self.sp_header.packet_id.set_apid(apid); self } + /// Set the packet ID. #[inline] pub fn with_packet_id(mut self, mut packet_id: PacketId) -> Self { packet_id.packet_type = PacketType::Tc; @@ -675,54 +709,63 @@ impl PusTmBuilder<'_, '_> { self } + /// Set the packet sequence control. #[inline] pub fn with_packet_sequence_control(mut self, psc: PacketSequenceControl) -> Self { self.sp_header.psc = psc; self } + /// Set the sequence count. #[inline] pub fn with_sequence_count(mut self, seq_count: u14) -> Self { self.sp_header.psc.seq_count = seq_count; self } + /// Set the message type ID. #[inline] pub fn with_message_type_id(mut self, message_type_id: MessageTypeId) -> Self { self.sec_header.message_type_id = message_type_id; self } + /// Set the service type ID. #[inline] pub fn with_service_type_id(mut self, type_id: u8) -> Self { self.sec_header.message_type_id.type_id = type_id; self } + /// Set the message subtype ID. #[inline] pub fn with_message_subtype_id(mut self, subtype_id: u8) -> Self { self.sec_header.message_type_id.subtype_id = subtype_id; self } + /// Set the destination ID. #[inline] pub fn with_dest_id(mut self, dest_id: u16) -> Self { self.sec_header.dest_id = dest_id; self } + /// Set the message counter for the service type ID. #[inline] pub fn with_msg_counter(mut self, msg_counter: u16) -> Self { self.sec_header.msg_counter = msg_counter; self } + /// Set the spacecraft time reference status. #[inline] pub fn with_sc_time_ref_status(mut self, sc_time_ref_status: u4) -> Self { self.sec_header.sc_time_ref_status = sc_time_ref_status; self } + /// Enable or disable checksum generation. #[inline] pub fn with_checksum(mut self, has_checksum: bool) -> Self { self.has_checksum = has_checksum; @@ -731,6 +774,7 @@ impl PusTmBuilder<'_, '_> { } impl<'src_data> PusTmBuilder<'_, 'src_data> { + /// Set the source data. #[inline] pub fn with_source_data(mut self, source_data: &'src_data [u8]) -> Self { self.source_data = source_data; @@ -739,6 +783,7 @@ impl<'src_data> PusTmBuilder<'_, 'src_data> { } impl<'time> PusTmBuilder<'time, '_> { + /// Set the timestamp. #[inline] pub fn with_timestamp(mut self, timestamp: &'time [u8]) -> Self { self.sec_header.timestamp = timestamp; @@ -747,6 +792,7 @@ impl<'time> PusTmBuilder<'time, '_> { } impl<'time, 'src_data> PusTmBuilder<'time, 'src_data> { + /// Constructor. pub fn build(self) -> PusTmCreator<'time, 'src_data> { PusTmCreator::new( self.sp_header, @@ -847,6 +893,7 @@ impl<'buf> PusTmCreatorWithReservedSourceData<'buf> { }) } + /// Length of the full packet written to the buffer. #[inline] pub const fn len_written(&self) -> usize { self.full_len @@ -872,6 +919,7 @@ impl<'buf> PusTmCreatorWithReservedSourceData<'buf> { } } + /// Length of the source data. #[inline] pub fn source_data_len(&self) -> usize { let mut len = self.full_len - self.source_data_offset; @@ -921,11 +969,16 @@ impl<'buf> PusTmCreatorWithReservedSourceData<'buf> { } } +/// Configuration options for the [PusTmReader]. +/// +/// This includes managed parameters which can not be deduced from the raw byte slice itself. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ReaderConfig { + /// Expected timestamp length. pub timestamp_len: usize, + /// Does the packet have a checksum? pub has_checksum: bool, } @@ -945,7 +998,9 @@ pub struct ReaderConfig { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTmReader<'raw_data> { + /// Space packet header. pub sp_header: SpHeader, + /// PUS TM secondary header. pub sec_header: PusTmSecondaryHeader<'raw_data>, #[cfg_attr(feature = "serde", serde(skip))] raw_data: &'raw_data [u8], @@ -1003,6 +1058,7 @@ impl<'raw_data> PusTmReader<'raw_data> { ) } + /// Constructor which does not perform checksum verification. pub fn new_no_checksum_verification( slice: &'raw_data [u8], reader_config: ReaderConfig, @@ -1064,41 +1120,49 @@ impl<'raw_data> PusTmReader<'raw_data> { }) } + /// Length of the full packed PUS TM packet. #[inline] pub fn len_packed(&self) -> usize { self.sp_header.packet_len() } + /// Source data slice. #[inline] pub fn source_data(&self) -> &[u8] { self.user_data() } + /// Service type ID. #[inline] pub fn service_type_id(&self) -> u8 { self.sec_header.service_type_id() } + /// Message subtype ID. #[inline] pub fn message_subtype_id(&self) -> u8 { self.sec_header.message_subtype_id() } + /// Full packet length. #[inline] pub fn packet_len(&self) -> usize { self.sp_header.packet_len() } + /// Application Process Identifier (APID). #[inline] pub fn apid(&self) -> u11 { self.sp_header.packet_id.apid } + /// Raw timestamp slice. #[inline] pub fn timestamp(&self) -> &[u8] { self.sec_header.timestamp } + /// Does the packet have a checksum? #[inline] pub fn checksum(&self) -> Option { self.checksum @@ -1244,6 +1308,7 @@ impl<'raw> PusTmZeroCopyWriter<'raw> { Some(writer) } + /// Set the application process identifier (APID). #[inline] pub fn set_apid(&mut self, apid: u11) { // Clear APID part of the raw packet ID @@ -1283,6 +1348,7 @@ impl<'raw> PusTmZeroCopyWriter<'raw> { .unwrap() } + /// Set the sequence count in the CCSDS packet header. #[inline] pub fn set_seq_count(&mut self, seq_count: u14) { let new_psc = (u16::from_be_bytes(self.raw_tm[2..4].try_into().unwrap()) & 0xC000) diff --git a/src/ecss/tm_pus_a.rs b/src/ecss/tm_pus_a.rs index 0e43de1..47ddcef 100644 --- a/src/ecss/tm_pus_a.rs +++ b/src/ecss/tm_pus_a.rs @@ -48,6 +48,7 @@ //! assert_eq!(ping_tm_reader.timestamp(), &time_buf); //! ``` use crate::crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE}; +use crate::ecss::tm::IsPusTelemetry; use crate::ecss::{ calc_pus_crc16, crc_from_raw_data, sp_header_impls, user_data_from_raw, verify_crc16_ccitt_false_from_raw_to_pus_error, CrcType, MessageTypeId, PusError, PusPacket, @@ -73,55 +74,78 @@ use crate::time::{TimeWriter, TimestampError}; use super::verify_crc16_ccitt_false_from_raw_to_pus_error_no_table; -pub trait IsPusTelemetry {} - /// Length without timestamp pub const PUS_TM_MIN_SEC_HEADER_LEN: usize = 3; +/// Minimal length of a PUS TM packet without source data. pub const PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA: usize = CCSDS_HEADER_LEN + PUS_TM_MIN_SEC_HEADER_LEN + size_of::(); +/// Generic properties of a PUS-A PUS TM secondary header. pub trait GenericPusTmSecondaryHeader { + /// PUS version field. fn pus_version(&self) -> PusVersion; + /// Service field. fn service(&self) -> u8; + /// Subservice field. fn subservice(&self) -> u8; + /// Message counter field for the service ID. fn msg_counter(&self) -> Option; + /// Destination ID. fn dest_id(&self) -> Option; + /// Number of spare bytes. fn spare_bytes(&self) -> usize; } +/// Managed parameters for the secondary header which can not be deduced from the packet itself. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct SecondaryHeaderParameters { + /// Timestamp length in bytes. pub timestamp_len: usize, + /// Does the packet have a message counter? pub has_msg_counter: bool, - pub dest_id_len: Option, + /// Present and length of the destination ID. + pub opt_dest_id_len: Option, + /// Number of spare bytes. pub spare_bytes: usize, } impl SecondaryHeaderParameters { + /// Minimal constructor. pub const fn new_minimal(timestamp_len: usize) -> Self { Self { timestamp_len, has_msg_counter: false, - dest_id_len: None, + opt_dest_id_len: None, spare_bytes: 0, } } } +/// PUS TM secondary header. #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTmSecondaryHeader<'stamp> { pus_version: PusVersion, + /// Service field. pub service: u8, + /// Subservice field. pub subservice: u8, + /// Service message counter. pub msg_counter: Option, + /// Destination ID field. pub dest_id: Option, + /// Timestamp slice. pub timestamp: &'stamp [u8], + /// Number of spare bytes to add after the timestamp. pub spare_bytes: usize, } impl<'stamp> PusTmSecondaryHeader<'stamp> { + /// Simple constructor. + /// + /// Allows setting the service, subservice and timestamp and sets default values for the + /// other fields. #[inline] pub fn new_simple(service: u8, subservice: u8, timestamp: &'stamp [u8]) -> Self { Self::new(service, subservice, None, None, timestamp, 0) @@ -133,6 +157,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> { Self::new(service, subservice, None, None, &[], 0) } + /// Generic constructor. #[inline] pub fn new( service: u8, @@ -153,6 +178,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> { } } + /// Construct the secondary header from a raw byte slice. pub fn from_bytes( buf: &'stamp [u8], params: &SecondaryHeaderParameters, @@ -180,7 +206,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> { current_idx += 1; } let mut dest_id = None; - if let Some(dest_id_len) = params.dest_id_len { + if let Some(dest_id_len) = params.opt_dest_id_len { dest_id = Some( UnsignedByteField::new_from_be_bytes( dest_id_len, @@ -201,6 +227,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> { }) } + /// Write the PUS TM secondary header to a provided buffer. pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { let written_len = self.written_len(); if buf.len() < written_len { @@ -229,6 +256,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> { Ok(written_len) } + /// Convert the PUS TM packet secondary header to a [alloc::vec::Vec]. #[cfg(feature = "alloc")] pub fn to_vec(&self) -> Vec { let mut vec = alloc::vec![0; self.written_len()]; @@ -236,6 +264,7 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> { vec } + /// Length of the secondary header when written to bytes. pub fn written_len(&self) -> usize { let mut len = PUS_TM_MIN_SEC_HEADER_LEN + self.timestamp.len() + self.spare_bytes; if let Some(dest_id) = self.dest_id { @@ -247,9 +276,10 @@ impl<'stamp> PusTmSecondaryHeader<'stamp> { len } + /// Length for the provided packet parameters. pub fn len_for_params(params: &SecondaryHeaderParameters) -> usize { let mut len = PUS_TM_MIN_SEC_HEADER_LEN + params.timestamp_len + params.spare_bytes; - if let Some(dest_id) = params.dest_id_len { + if let Some(dest_id) = params.opt_dest_id_len { len += dest_id; } if params.has_msg_counter { @@ -308,7 +338,9 @@ impl GenericPusTmSecondaryHeader for PusTmSecondaryHeader<'_> { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTmCreator<'time, 'src_data> { + /// Space packet header. pub sp_header: SpHeader, + /// PUS TM secondary header. #[cfg_attr(feature = "serde", serde(borrow))] pub sec_header: PusTmSecondaryHeader<'time>, source_data: &'src_data [u8], @@ -351,6 +383,8 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { pus_tm } + /// Simple constructor which builds the [PusTmSecondaryHeader] internally from the + /// provided arguments. #[inline] pub fn new_simple( sp_header: SpHeader, @@ -367,6 +401,7 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { Ok(Self::new(sp_header, sec_header, source_data, set_ccsds_len)) } + /// Constructor for PUS TM packets without source data. #[inline] pub fn new_no_source_data( sp_header: SpHeader, @@ -376,21 +411,25 @@ impl<'time, 'src_data> PusTmCreator<'time, 'src_data> { Self::new(sp_header, sec_header, &[], set_ccsds_len) } + /// Raw timestamp slice. #[inline] pub fn timestamp(&self) -> &[u8] { self.sec_header.timestamp } + /// Raw source data slice. #[inline] pub fn source_data(&self) -> &[u8] { self.source_data } + /// Set the destination ID. #[inline] pub fn set_dest_id(&mut self, dest_id: Option) { self.sec_header.dest_id = dest_id; } + /// Set the message counter for the current service ID. #[inline] pub fn set_msg_counter(&mut self, msg_counter: Option) { self.sec_header.msg_counter = msg_counter @@ -675,6 +714,7 @@ impl<'buf> PusTmCreatorWithReservedSourceData<'buf> { }) } + /// Length of the full packet when written to bytes. #[inline] pub const fn len_written(&self) -> usize { self.full_len @@ -692,6 +732,7 @@ impl<'buf> PusTmCreatorWithReservedSourceData<'buf> { &self.buf[self.source_data_offset..self.full_len - 2] } + /// Source data length. #[inline] pub fn source_data_len(&self) -> usize { self.full_len - 2 - self.source_data_offset @@ -745,7 +786,9 @@ impl<'buf> PusTmCreatorWithReservedSourceData<'buf> { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PusTmReader<'raw_data> { + /// Space packet header. pub sp_header: SpHeader, + /// PUS TM secondary header. pub sec_header: PusTmSecondaryHeader<'raw_data>, #[cfg_attr(feature = "serde", serde(skip))] raw_data: &'raw_data [u8], @@ -779,6 +822,7 @@ impl<'raw_data> PusTmReader<'raw_data> { Ok(tc) } + /// New constructor which does not check the CRC16. pub fn new_no_crc_check( slice: &'raw_data [u8], sec_header_params: &SecondaryHeaderParameters, @@ -822,21 +866,25 @@ impl<'raw_data> PusTmReader<'raw_data> { }) } + /// Length of the full packet as packed in the raw data. #[inline] pub fn len_packed(&self) -> usize { self.sp_header.packet_len() } + /// Raw source data slice. #[inline] pub fn source_data(&self) -> &[u8] { self.user_data() } + /// Raw timestamp slice. #[inline] pub fn timestamp(&self) -> &[u8] { self.sec_header.timestamp } + /// CRC16 checksum of the packet. #[inline] pub fn crc16(&self) -> u16 { self.crc16 @@ -941,16 +989,21 @@ impl PartialEq> for PusTmCreator<'_, '_> { } } +/// Secondary header field not present error. #[derive(Debug, thiserror::Error)] #[error("this field is not present in the secondary header")] pub struct SecondaryHeaderFieldNotPresentError; +/// Destination ID operation error. #[derive(Debug, thiserror::Error)] pub enum DestIdOperationError { + /// Field is not present in the secondary header. #[error("this field is not present in the secondary header")] FieldNotPresent(#[from] SecondaryHeaderFieldNotPresentError), + /// Invalid field length. #[error("invalid byte field length")] InvalidFieldLen, + /// Bzyte conversion error. #[error("byte conversion error")] ByteConversionError(#[from] ByteConversionError), } @@ -1008,15 +1061,16 @@ impl<'raw> PusTmZeroCopyWriter<'raw> { self.raw_tm[0..2].copy_from_slice(&updated_apid.to_be_bytes()); } + /// Destination ID field if present. pub fn dest_id(&self) -> Result, ByteConversionError> { - if self.sec_header_params.dest_id_len.is_none() { + if self.sec_header_params.opt_dest_id_len.is_none() { return Ok(None); } let mut base_idx = 10; if self.sec_header_params.has_msg_counter { base_idx += 1; } - let dest_id_len = self.sec_header_params.dest_id_len.unwrap(); + let dest_id_len = self.sec_header_params.opt_dest_id_len.unwrap(); if self.raw_tm.len() < base_idx + dest_id_len { return Err(ByteConversionError::FromSliceTooSmall { found: self.raw_tm.len(), @@ -1032,6 +1086,7 @@ impl<'raw> PusTmZeroCopyWriter<'raw> { )) } + /// Message counter if present. pub fn msg_counter(&self) -> Option { if !self.sec_header_params.has_msg_counter { return None; @@ -1061,10 +1116,10 @@ impl<'raw> PusTmZeroCopyWriter<'raw> { &mut self, dest_id: UnsignedByteField, ) -> Result<(), DestIdOperationError> { - if self.sec_header_params.dest_id_len.is_none() { + if self.sec_header_params.opt_dest_id_len.is_none() { return Err(SecondaryHeaderFieldNotPresentError.into()); } - let dest_id_len = self.sec_header_params.dest_id_len.unwrap(); + let dest_id_len = self.sec_header_params.opt_dest_id_len.unwrap(); if dest_id.size() != dest_id_len { return Err(DestIdOperationError::InvalidFieldLen); } @@ -1093,6 +1148,7 @@ impl<'raw> PusTmZeroCopyWriter<'raw> { crate::zc::SpHeader::read_from_bytes(&self.raw_tm[0..CCSDS_HEADER_LEN]).unwrap() } + /// Set the sequence count. #[inline] pub fn set_seq_count(&mut self, seq_count: u14) { let new_psc = (u16::from_be_bytes(self.raw_tm[2..4].try_into().unwrap()) & 0xC000) @@ -1523,7 +1579,7 @@ mod tests { &SecondaryHeaderParameters { timestamp_len: 7, has_msg_counter: msg_counter.is_some(), - dest_id_len: dest_id.as_ref().map(|id| id.size()), + opt_dest_id_len: dest_id.as_ref().map(|id| id.size()), spare_bytes: 0, }, ) @@ -1878,7 +1934,7 @@ mod tests { &SecondaryHeaderParameters { timestamp_len: dummy_timestamp().len(), has_msg_counter: false, - dest_id_len: Some(2), + opt_dest_id_len: Some(2), spare_bytes: 0, }, ) diff --git a/src/ecss/verification.rs b/src/ecss/verification.rs index 53ca69c..40fb277 100644 --- a/src/ecss/verification.rs +++ b/src/ecss/verification.rs @@ -3,18 +3,27 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +/// Message subtype ID. #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] -pub enum Subservice { +pub enum MessageSubtypeId { + /// Telemetry - Acceptance success. TmAcceptanceSuccess = 1, + /// Telemetry - Acceptance failure. TmAcceptanceFailure = 2, + /// Telemetry - Start success. TmStartSuccess = 3, + /// Telemetry - Start failure. TmStartFailure = 4, + /// Telemetry - Step success. TmStepSuccess = 5, + /// Telemetry - Step failure. TmStepFailure = 6, + /// Telemetry - Completion success. TmCompletionSuccess = 7, + /// Telemetry - Completion failure. TmCompletionFailure = 8, } @@ -24,13 +33,13 @@ mod tests { #[test] fn test_conv_into_u8() { - let subservice: u8 = Subservice::TmCompletionSuccess.into(); + let subservice: u8 = MessageSubtypeId::TmCompletionSuccess.into(); assert_eq!(subservice, 7); } #[test] fn test_conv_from_u8() { - let subservice: Subservice = 7.try_into().unwrap(); - assert_eq!(subservice, Subservice::TmCompletionSuccess); + let subservice: MessageSubtypeId = 7.try_into().unwrap(); + assert_eq!(subservice, MessageSubtypeId::TmCompletionSuccess); } } diff --git a/src/lib.rs b/src/lib.rs index f410729..cb61659 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,9 +37,9 @@ //! - [`timelib`](https://crates.io/crates/time): Add basic support for the `time` time library. //! - [`defmt`](https://defmt.ferrous-systems.com/): Add support for the `defmt` by adding the //! [`defmt::Format`](https://defmt.ferrous-systems.com/format) derive on many types. -//! - [`portable-atomic`]: Basic support for `portable-atomic` crate in addition to the support -//! for core atomic types. This support requires atomic CAS support enabled in the portable -//! atomic crate. +//! - [`portable-atomic`](https://github.com/taiki-e/portable-atomic): Basic support for +//! `portable-atomic` crate in addition to the support for core atomic types. This support +//! requires atomic CAS support enabled in the portable atomic crate. //! //! ## Module //! @@ -61,8 +61,7 @@ //! ``` #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] -// TODO: Add docs everywhere. -//#![warn(missing_docs)] +#![warn(missing_docs)] #[cfg(feature = "alloc")] extern crate alloc; #[cfg(any(feature = "std", test))] diff --git a/src/seq_count.rs b/src/seq_count.rs index 1640dca..4e72aac 100644 --- a/src/seq_count.rs +++ b/src/seq_count.rs @@ -44,7 +44,7 @@ pub trait SequenceCounter { } } -/// Simple sequence counter which wraps at [T::MAX]. +/// Simple sequence counter which wraps at ´T::MAX´. #[derive(Clone)] pub struct SequenceCounterSimple { seq_count: Cell, diff --git a/src/time/mod.rs b/src/time/mod.rs index 43bf4ee..fb3d373 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -1,5 +1,4 @@ //! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) -#![warn(missing_docs)] use crate::ByteConversionError; #[cfg(feature = "chrono")] use chrono::{TimeZone, Utc}; diff --git a/src/uslp/mod.rs b/src/uslp/mod.rs index 22359e0..9bbc86f 100644 --- a/src/uslp/mod.rs +++ b/src/uslp/mod.rs @@ -1,4 +1,5 @@ //! # Support of the CCSDS Unified Space Data Link Protocol (USLP) +#![warn(missing_docs)] use crate::{crc::CRC_CCITT_FALSE, ByteConversionError}; /// Only this version is supported by the library @@ -17,6 +18,7 @@ pub enum SourceOrDestField { Dest = 1, } +/// Bypass sequence control flag. #[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -30,6 +32,7 @@ pub enum BypassSequenceControlFlag { ExpeditedQoS = 1, } +/// Protcol Control Command Flag. #[derive( Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive, )] @@ -37,55 +40,77 @@ pub enum BypassSequenceControlFlag { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum ProtocolControlCommandFlag { + /// Transfer frame data field contains user data. TfdfContainsUserData = 0, + /// Transfer frame data field contains protocol information. TfdfContainsProtocolInfo = 1, } +/// USLP error enumeration. #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] pub enum UslpError { + /// Byte conversion error. #[error("byte conversion error: {0}")] ByteConversion(#[from] ByteConversionError), + /// Header is truncated, which is not supported. #[error("header is truncated, which is not supported")] HeaderIsTruncated, + /// Invalid protocol ID. #[error("invalid protocol id: {0}")] InvalidProtocolId(u8), + /// Invalid construction rule. #[error("invalid construction rule: {0}")] InvalidConstructionRule(u8), + /// Invalid version number. #[error("invalid version number: {0}")] InvalidVersionNumber(u8), + /// Invalid virtual channel ID. #[error("invalid virtual channel ID: {0}")] InvalidVcId(u8), + /// Invalid MAP ID. #[error("invalid MAP ID: {0}")] InvalidMapId(u8), + /// Checksum failure. #[error("checksum failure")] ChecksumFailure(u16), } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// Invalid value for length. +#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct InvalidValueForLen { +#[error("invalid value for length of the field")] +pub struct InvalidValueForLenError { value: u64, len: u8, } +/// Primary header of a USLP transfer frame. #[derive(Debug, Copy, Clone, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PrimaryHeader { + /// Spacecraft ID. pub spacecraft_id: u16, + /// Source or destination identifier. pub source_or_dest_field: SourceOrDestField, + /// Virtual channel ID. pub vc_id: u8, + /// MAP ID. pub map_id: u8, frame_len_field: u16, + /// Bypass sequence control flag. pub sequence_control_flag: BypassSequenceControlFlag, + /// Procol control command flag. pub protocol_control_command_flag: ProtocolControlCommandFlag, + /// Operational control field flag. pub ocf_flag: bool, vc_frame_count_len: u8, vc_frame_count: u64, } impl PrimaryHeader { + /// Generic constructor. pub fn new( spacecraft_id: u16, source_or_dest_field: SourceOrDestField, @@ -113,13 +138,14 @@ impl PrimaryHeader { }) } + /// Set the virtual channel frame count. pub fn set_vc_frame_count( &mut self, count_len: u8, count: u64, - ) -> Result<(), InvalidValueForLen> { + ) -> Result<(), InvalidValueForLenError> { if count > 2_u64.pow(count_len as u32 * 8) - 1 { - return Err(InvalidValueForLen { + return Err(InvalidValueForLenError { value: count, len: count_len, }); @@ -129,16 +155,19 @@ impl PrimaryHeader { Ok(()) } + /// Virtual channel frame count. #[inline] pub fn vc_frame_count(&self) -> u64 { self.vc_frame_count } + /// Length of the virtual channel frame count field. #[inline] pub fn vc_frame_count_len(&self) -> u8 { self.vc_frame_count_len } + /// Parse [Self] from raw bytes. pub fn from_bytes(buf: &[u8]) -> Result { if buf.len() < 4 { return Err(ByteConversionError::FromSliceTooSmall { @@ -207,6 +236,7 @@ impl PrimaryHeader { }) } + /// Write primary header to bytes. pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { if buf.len() < self.len_header() { return Err(ByteConversionError::ToSliceTooSmall { @@ -233,6 +263,7 @@ impl PrimaryHeader { Ok(self.len_header()) } + /// Set frame length field. #[inline(always)] pub fn set_frame_len(&mut self, frame_len: usize) { // 4.1.2.7.2 @@ -241,11 +272,13 @@ impl PrimaryHeader { self.frame_len_field = frame_len.saturating_sub(1) as u16; } + /// Length of primary header when written to bytes. #[inline(always)] pub fn len_header(&self) -> usize { 7 + self.vc_frame_count_len as usize } + /// Length of the entire frame. #[inline(always)] pub fn len_frame(&self) -> usize { // 4.1.2.7.2 @@ -271,6 +304,7 @@ impl PartialEq for PrimaryHeader { } } +/// USLP protocol ID enumeration. #[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -278,6 +312,7 @@ impl PartialEq for PrimaryHeader { #[repr(u8)] #[non_exhaustive] pub enum UslpProtocolId { + /// Space packets or encapsulation packets. SpacePacketsOrEncapsulation = 0b00000, /// COP-1 control commands within the TFDZ. Cop1ControlCommands = 0b00001, @@ -285,6 +320,7 @@ pub enum UslpProtocolId { CopPControlCommands = 0b00010, /// SDLS control commands within the TFDZ. Sdls = 0b00011, + /// User defined octet stream. UserDefinedOctetStream = 0b00100, /// Proximity-1 Supervisory Protocol Data Units (SPDUs) within the TFDZ. Spdu = 0b00111, @@ -292,6 +328,7 @@ pub enum UslpProtocolId { Idle = 0b11111, } +/// USLP construction rule enumeration. #[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -302,16 +339,25 @@ pub enum ConstructionRule { /// span transfer frame boundaries. The First Header Pointer (FHP) is required for packet /// extraction. PacketSpanningMultipleFrames = 0b000, + /// Start of a MAPA SDU or VCA SDU. StartOfMapaSduOrVcaSdu = 0b001, + /// Continuing portion of a MAPA SDU. ContinuingPortionOfMapaSdu = 0b010, + /// Octet stream. OctetStream = 0b011, + /// Starting segment. StartingSegment = 0b100, + /// Continuing segment. ContinuingSegment = 0b101, + /// Last segment. LastSegment = 0b110, + /// No segmentation. NoSegmentation = 0b111, } impl ConstructionRule { + /// Is the construction rule applicable to fixed-length TFDZs? + #[inline] pub const fn applicable_to_fixed_len_tfdz(&self) -> bool { match self { ConstructionRule::PacketSpanningMultipleFrames => true, @@ -326,6 +372,7 @@ impl ConstructionRule { } } +/// Transfer frame data field header. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct TransferFrameDataFieldHeader { /// Construction rule for the TFDZ. @@ -337,6 +384,7 @@ pub struct TransferFrameDataFieldHeader { } impl TransferFrameDataFieldHeader { + /// Length of the header when written to bytes. #[inline] pub const fn len_header(&self) -> usize { if self.construction_rule.applicable_to_fixed_len_tfdz() { @@ -346,21 +394,25 @@ impl TransferFrameDataFieldHeader { } } + /// Construction rule. #[inline] pub const fn construction_rule(&self) -> ConstructionRule { self.construction_rule } + /// USLP protocol ID. #[inline] pub const fn uslp_protocol_id(&self) -> UslpProtocolId { self.uslp_protocol_id } + /// FHP or LVO field when present. #[inline] pub const fn fhp_or_lvo(&self) -> Option { self.fhp_or_lvo } + /// Parse [Self] from raw bytes. pub fn from_bytes(buf: &[u8]) -> Result { if buf.is_empty() { return Err(ByteConversionError::FromSliceTooSmall { @@ -451,29 +503,34 @@ impl<'buf> TransferFrameReader<'buf> { }) } + /// Length of the entire frame. #[inline] pub fn len_frame(&self) -> usize { self.primary_header.len_frame() } + /// Primary header. #[inline] pub fn primary_header(&self) -> &PrimaryHeader { &self.primary_header } + /// Transfer frame data field header. #[inline] pub fn data_field_header(&self) -> &TransferFrameDataFieldHeader { &self.data_field_header } + /// Data contained in the transfer frame data field. #[inline] pub fn data(&self) -> &'buf [u8] { self.data } + /// Operational control field when present. #[inline] - pub fn operational_control_field(&self) -> &Option { - &self.operational_control_field + pub fn operational_control_field(&self) -> Option { + self.operational_control_field } } @@ -705,11 +762,11 @@ mod tests { .unwrap(); matches!( primary_header.set_vc_frame_count(0, 1).unwrap_err(), - InvalidValueForLen { value: 1, len: 0 } + InvalidValueForLenError { value: 1, len: 0 } ); matches!( primary_header.set_vc_frame_count(1, 256).unwrap_err(), - InvalidValueForLen { value: 256, len: 1 } + InvalidValueForLenError { value: 256, len: 1 } ); } diff --git a/src/util.rs b/src/util.rs index 0a5f7a3..968ee36 100644 --- a/src/util.rs +++ b/src/util.rs @@ -383,7 +383,7 @@ impl TryFrom for UnsignedByteFieldU64 { } #[cfg(test)] -pub mod tests { +mod tests { use crate::util::{ UnsignedByteField, UnsignedByteFieldError, UnsignedByteFieldU16, UnsignedByteFieldU32, UnsignedByteFieldU64, UnsignedByteFieldU8, UnsignedEnum,