Merge pull request 'Finish full crate docs' (#188) from renaming-docs-for-ecss into main

Reviewed-on: #188
This commit was merged in pull request #188.
This commit is contained in:
2025-11-05 20:20:35 +01:00
15 changed files with 373 additions and 54 deletions

View File

@@ -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.

View File

@@ -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")]

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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<bool> 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]

View File

@@ -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::<zc::PusTcSecondaryHeader>();
/// 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<PusVersion, u4>;
@@ -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<zc::PusTcSecondaryHeader> 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<u8>) -> 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<u16> {
self.crc16

View File

@@ -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<PusVersion, u4>;
/// 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<UnsignedByteField>;
/// 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<UnsignedByteField>,
/// 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<Vec<u8>, 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<usize, ByteConversionError> {
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<usize>,
@@ -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<UnsignedByteField>) {
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<u8>) -> 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

View File

@@ -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<PusVersion, u4>;
@@ -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<NetworkEndian>,
}
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<crate::ecss::tm::PusTmSecondaryHeader<'_>> 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<zc::PusTmSecHeader<'slice>> 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<u16> {
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)

View File

@@ -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::<CrcType>();
/// 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<u8>;
/// Destination ID.
fn dest_id(&self) -> Option<UnsignedByteField>;
/// 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<usize>,
/// Present and length of the destination ID.
pub opt_dest_id_len: Option<usize>,
/// 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<u8>,
/// Destination ID field.
pub dest_id: Option<UnsignedByteField>,
/// 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<usize, ByteConversionError> {
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<u8> {
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<UnsignedByteField>) {
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<u8>) {
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<PusTmReader<'_>> 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<Option<UnsignedByteField>, 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<u8> {
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,
},
)

View File

@@ -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);
}
}

View File

@@ -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))]

View File

@@ -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<T: Copy> {
seq_count: Cell<T>,

View File

@@ -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};

View File

@@ -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<Self, UslpError> {
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<usize, ByteConversionError> {
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<u16> {
self.fhp_or_lvo
}
/// Parse [Self] from raw bytes.
pub fn from_bytes(buf: &[u8]) -> Result<Self, UslpError> {
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<u32> {
&self.operational_control_field
pub fn operational_control_field(&self) -> Option<u32> {
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 }
);
}

View File

@@ -383,7 +383,7 @@ impl TryFrom<UnsignedByteField> for UnsignedByteFieldU64 {
}
#[cfg(test)]
pub mod tests {
mod tests {
use crate::util::{
UnsignedByteField, UnsignedByteFieldError, UnsignedByteFieldU16, UnsignedByteFieldU32,
UnsignedByteFieldU64, UnsignedByteFieldU8, UnsignedEnum,