Finish full crate docs #188
@@ -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.
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user