add docs and minor changes

This commit is contained in:
Robin Mueller
2025-10-31 11:39:23 +01:00
parent 3f35e9dba9
commit 8f2096ca35
15 changed files with 478 additions and 75 deletions

View File

@@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- `CdsCommon` renamed to `CdsBase` - `CdsCommon` renamed to `CdsBase`
- Simplified CDS short timestamp, contains one less field which reduced serialization length. - 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.
- Renamed `CcsdsPrimaryHeader::from_composite_fields` to
`CcsdsPrimaryHeader::new_from_composite_fields`
## Added ## Added

View File

@@ -8,6 +8,7 @@ use std::string::String;
use super::TlvLvDataTooLargeError; use super::TlvLvDataTooLargeError;
/// Minmum length of a CFDP length-value structure in bytes.
pub const MIN_LV_LEN: usize = 1; pub const MIN_LV_LEN: usize = 1;
/// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. /// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8.
@@ -63,8 +64,10 @@ pub(crate) fn generic_len_check_deserialization(
} }
impl<'data> Lv<'data> { impl<'data> Lv<'data> {
/// Minimum length of a LV structure in bytes.
pub const MIN_LEN: usize = MIN_LV_LEN; pub const MIN_LEN: usize = MIN_LV_LEN;
/// Generic constructor.
#[inline] #[inline]
pub fn new(data: &[u8]) -> Result<Lv<'_>, TlvLvDataTooLargeError> { pub fn new(data: &[u8]) -> Result<Lv<'_>, TlvLvDataTooLargeError> {
if data.len() > u8::MAX as usize { if data.len() > u8::MAX as usize {
@@ -118,6 +121,7 @@ impl<'data> Lv<'data> {
self.data.len() == 0 self.data.len() == 0
} }
/// Raw value part of the LV.
#[inline] #[inline]
pub fn value(&self) -> &[u8] { pub fn value(&self) -> &[u8] {
self.data self.data

View File

@@ -13,43 +13,55 @@ pub const CFDP_VERSION_2_NAME: &str = "CCSDS 727.0-B-5";
/// Currently, only this version is supported. /// Currently, only this version is supported.
pub const CFDP_VERSION_2: u8 = 0b001; pub const CFDP_VERSION_2: u8 = 0b001;
/// PDU type.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)] #[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum PduType { pub enum PduType {
/// File directive PDU.
FileDirective = 0, FileDirective = 0,
/// File data PDU.
FileData = 1, FileData = 1,
} }
/// PDU direction.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)] #[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum Direction { pub enum Direction {
/// Going towards the file receiver.
TowardsReceiver = 0, TowardsReceiver = 0,
/// Going towards the file sender.
TowardsSender = 1, TowardsSender = 1,
} }
/// PDU transmission mode.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)] #[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum TransmissionMode { pub enum TransmissionMode {
/// Acknowledged (class 1) transfer.
Acknowledged = 0, Acknowledged = 0,
/// Unacknowledged (class 2) transfer.
Unacknowledged = 1, Unacknowledged = 1,
} }
/// CRC flag.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)] #[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum CrcFlag { pub enum CrcFlag {
/// No CRC for the packet.
NoCrc = 0, NoCrc = 0,
/// Packet has CRC.
WithCrc = 1, WithCrc = 1,
} }
@@ -78,7 +90,9 @@ impl From<CrcFlag> for bool {
#[bitbybit::bitenum(u1, exhaustive = true)] #[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum SegmentMetadataFlag { pub enum SegmentMetadataFlag {
/// Segment metadata not present.
NotPresent = 0, NotPresent = 0,
/// Segment metadata present.
Present = 1, Present = 1,
} }
@@ -89,22 +103,30 @@ pub enum SegmentMetadataFlag {
#[bitbybit::bitenum(u1, exhaustive = true)] #[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum SegmentationControl { pub enum SegmentationControl {
/// No record boundary preservation.
NoRecordBoundaryPreservation = 0, NoRecordBoundaryPreservation = 0,
/// With record boundary preservation.
WithRecordBoundaryPreservation = 1, WithRecordBoundaryPreservation = 1,
} }
/// Fault handler codes according to the CFDP standard.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u3, exhaustive = false)] #[bitbybit::bitenum(u3, exhaustive = false)]
#[repr(u8)] #[repr(u8)]
pub enum FaultHandlerCode { pub enum FaultHandlerCode {
/// Notice of cancellation fault handler code.
NoticeOfCancellation = 0b0001, NoticeOfCancellation = 0b0001,
/// Notice of suspension fault handler code.
NoticeOfSuspension = 0b0010, NoticeOfSuspension = 0b0010,
/// Ignore error fault handler code.
IgnoreError = 0b0011, IgnoreError = 0b0011,
/// Abandon transaction fault handler code.
AbandonTransaction = 0b0100, AbandonTransaction = 0b0100,
} }
/// CFDP condition codes.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -113,15 +135,25 @@ pub enum FaultHandlerCode {
pub enum ConditionCode { pub enum ConditionCode {
/// This is not an error condition for which a faulty handler override can be specified /// This is not an error condition for which a faulty handler override can be specified
NoError = 0b0000, NoError = 0b0000,
/// Positive acknowledgement limit reached.
PositiveAckLimitReached = 0b0001, PositiveAckLimitReached = 0b0001,
/// Keep-alive limit reached.
KeepAliveLimitReached = 0b0010, KeepAliveLimitReached = 0b0010,
/// Invalid transmission mode.
InvalidTransmissionMode = 0b0011, InvalidTransmissionMode = 0b0011,
/// Filestore rejection.
FilestoreRejection = 0b0100, FilestoreRejection = 0b0100,
/// File checksum error.
FileChecksumFailure = 0b0101, FileChecksumFailure = 0b0101,
/// File size error.
FileSizeError = 0b0110, FileSizeError = 0b0110,
/// NAK limit reached.
NakLimitReached = 0b0111, NakLimitReached = 0b0111,
/// Inactivity detected.
InactivityDetected = 0b1000, InactivityDetected = 0b1000,
/// Check limit reached.
CheckLimitReached = 0b1010, CheckLimitReached = 0b1010,
/// Unsupported checksum type.
UnsupportedChecksumType = 0b1011, UnsupportedChecksumType = 0b1011,
/// Not an actual fault condition for which fault handler overrides can be specified /// Not an actual fault condition for which fault handler overrides can be specified
SuspendRequestReceived = 0b1110, SuspendRequestReceived = 0b1110,
@@ -129,6 +161,7 @@ pub enum ConditionCode {
CancelRequestReceived = 0b1111, CancelRequestReceived = 0b1111,
} }
/// Large file flag.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -151,6 +184,7 @@ pub enum TransactionStatus {
/// Transaction is not currently active and the CFDP implementation does not retain a /// Transaction is not currently active and the CFDP implementation does not retain a
/// transaction history. /// transaction history.
Undefined = 0b00, Undefined = 0b00,
/// Transaction is currently active.
Active = 0b01, Active = 0b01,
/// Transaction was active in the past and was terminated. /// Transaction was active in the past and was terminated.
Terminated = 0b10, Terminated = 0b10,
@@ -168,10 +202,13 @@ pub enum TransactionStatus {
pub enum ChecksumType { pub enum ChecksumType {
/// Modular legacy checksum /// Modular legacy checksum
Modular = 0, Modular = 0,
/// CRC32 Proximity-1.
Crc32Proximity1 = 1, Crc32Proximity1 = 1,
/// CRC32C.
Crc32C = 2, Crc32C = 2,
/// Polynomial: 0x4C11DB7. Preferred checksum for now. /// CRC32. Polynomial: 0x4C11DB7. Preferred checksum for now.
Crc32 = 3, Crc32 = 3,
/// Null checksum (no checksum).
NullChecksum = 15, NullChecksum = 15,
} }
@@ -181,8 +218,10 @@ impl Default for ChecksumType {
} }
} }
/// Raw null checksum.
pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4]; pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4];
/// TLV or LV data larger than allowed [u8::MAX].
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -199,16 +238,21 @@ pub struct InvalidTlvTypeFieldError {
expected: Option<u8>, expected: Option<u8>,
} }
/// Generic TLV/LV error.
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TlvLvError { pub enum TlvLvError {
/// Data too large error.
#[error("{0}")] #[error("{0}")]
DataTooLarge(#[from] TlvLvDataTooLargeError), DataTooLarge(#[from] TlvLvDataTooLargeError),
/// Byte conversion error.
#[error("byte conversion error: {0}")] #[error("byte conversion error: {0}")]
ByteConversion(#[from] ByteConversionError), ByteConversion(#[from] ByteConversionError),
/// Invalid TLV type field error.
#[error("{0}")] #[error("{0}")]
InvalidTlvTypeField(#[from] InvalidTlvTypeFieldError), InvalidTlvTypeField(#[from] InvalidTlvTypeFieldError),
/// Invalid value length.
#[error("invalid value length {0}")] #[error("invalid value length {0}")]
InvalidValueLength(usize), InvalidValueLength(usize),
/// Only applies to filestore requests and responses. Second name was missing where one is /// Only applies to filestore requests and responses. Second name was missing where one is

View File

@@ -626,7 +626,7 @@ mod tests {
assert_eq!(finished_pdu_vec.len(), 12); assert_eq!(finished_pdu_vec.len(), 12);
assert_eq!(finished_pdu_vec[9], TlvType::EntityId.into()); assert_eq!(finished_pdu_vec[9], TlvType::EntityId.into());
assert_eq!(finished_pdu_vec[10], 1); assert_eq!(finished_pdu_vec[10], 1);
assert_eq!(finished_pdu_vec[11], TEST_DEST_ID.value_typed()); assert_eq!(finished_pdu_vec[11], TEST_DEST_ID.value());
assert_eq!( assert_eq!(
finished_pdu.fault_location().unwrap().entity_id(), finished_pdu.fault_location().unwrap().entity_id(),
&TEST_DEST_ID.into() &TEST_DEST_ID.into()

View File

@@ -19,9 +19,11 @@ use super::{InvalidTlvTypeFieldError, TlvLvDataTooLargeError};
pub mod msg_to_user; pub mod msg_to_user;
/// Minimum length of a type-length-value structure, including type and length fields.
pub const MIN_TLV_LEN: usize = 2; pub const MIN_TLV_LEN: usize = 2;
pub trait GenericTlv { pub trait GenericTlv {
/// TLV type field.
fn tlv_type_field(&self) -> TlvTypeField; fn tlv_type_field(&self) -> TlvTypeField;
/// Checks whether the type field contains one of the standard types specified in the CFDP /// Checks whether the type field contains one of the standard types specified in the CFDP
@@ -45,7 +47,9 @@ pub trait GenericTlv {
} }
} }
/// Readable TLV structure trait.
pub trait ReadableTlv { pub trait ReadableTlv {
/// Value field of the TLV.
fn value(&self) -> &[u8]; fn value(&self) -> &[u8];
/// Checks whether the value field is empty. /// Checks whether the value field is empty.
@@ -68,9 +72,15 @@ pub trait ReadableTlv {
} }
} }
/// Writable TLV structure trait.
pub trait WritableTlv { pub trait WritableTlv {
/// Write the TLV to bytes.
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>; fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
/// Length of the written TLV.
fn len_written(&self) -> usize; fn len_written(&self) -> usize;
/// Convenience method to write the TLV to an owned [alloc::vec::Vec].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> Vec<u8> { fn to_vec(&self) -> Vec<u8> {
let mut buf = vec![0; self.len_written()]; let mut buf = vec![0; self.len_written()];
@@ -79,16 +89,23 @@ pub trait WritableTlv {
} }
} }
/// TLV type.
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum TlvType { pub enum TlvType {
/// Filestore request.
FilestoreRequest = 0x00, FilestoreRequest = 0x00,
/// Filestore response.
FilestoreResponse = 0x01, FilestoreResponse = 0x01,
/// Message to user.
MsgToUser = 0x02, MsgToUser = 0x02,
/// Fault handler.
FaultHandler = 0x04, FaultHandler = 0x04,
/// Flow label.
FlowLabel = 0x05, FlowLabel = 0x05,
/// Entity ID.
EntityId = 0x06, EntityId = 0x06,
} }

View File

@@ -7,13 +7,21 @@ use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)] #[repr(u8)]
pub enum Subservice { pub enum Subservice {
/// Telemetry - Info report.
TmInfoReport = 1, TmInfoReport = 1,
/// Telemetry - Low severity report.
TmLowSeverityReport = 2, TmLowSeverityReport = 2,
/// Telemetry - Medium severity report.
TmMediumSeverityReport = 3, TmMediumSeverityReport = 3,
/// Telemetry - High severity report.
TmHighSeverityReport = 4, TmHighSeverityReport = 4,
/// Telecommand - Enable event generation.
TcEnableEventGeneration = 5, TcEnableEventGeneration = 5,
/// Telecommand - Disable event generation.
TcDisableEventGeneration = 6, TcDisableEventGeneration = 6,
/// Telecommand - Report disabled list.
TcReportDisabledList = 7, TcReportDisabledList = 7,
/// Telemetry - Disabled events report.
TmDisabledEventsReport = 8, TmDisabledEventsReport = 8,
} }

View File

@@ -9,13 +9,21 @@ use serde::{Deserialize, Serialize};
#[repr(u8)] #[repr(u8)]
pub enum Subservice { pub enum Subservice {
// Regular HK // Regular HK
/// Telecommand - Create Housekeeping Report Structure.
TcCreateHkReportStructure = 1, TcCreateHkReportStructure = 1,
/// Telecommand - Delete HK report structures.
TcDeleteHkReportStructures = 3, TcDeleteHkReportStructures = 3,
/// Telecommand - Enable HK generation.
TcEnableHkGeneration = 5, TcEnableHkGeneration = 5,
/// Telecommand - Disable HK generation.
TcDisableHkGeneration = 6, TcDisableHkGeneration = 6,
/// Telecommand - Report HK report structures.
TcReportHkReportStructures = 9, TcReportHkReportStructures = 9,
/// Telemetry - HK report.
TmHkPacket = 25, TmHkPacket = 25,
/// Telecommand - Generate one-shot report.
TcGenerateOneShotHk = 27, TcGenerateOneShotHk = 27,
/// Telecommand - Modify collection interval.
TcModifyHkCollectionInterval = 31, TcModifyHkCollectionInterval = 31,
// Diagnostics HK // Diagnostics HK

View File

@@ -25,53 +25,55 @@ pub mod tm;
pub mod tm_pus_a; pub mod tm_pus_a;
pub mod verification; pub mod verification;
/// Type alias for the CRC16 type.
pub type CrcType = u16; pub type CrcType = u16;
/// Standard PUS service IDs.
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
#[non_exhaustive] #[non_exhaustive]
pub enum PusServiceId { pub enum PusServiceId {
/// Service 1 /// Service 1 Verification
Verification = 1, Verification = 1,
/// Service 2 /// Service 2 Device Access
DeviceAccess = 2, DeviceAccess = 2,
/// Service 3 /// Service 3 Housekeeping
Housekeeping = 3, Housekeeping = 3,
/// Service 4 /// Service 4 Parameter Statistics
ParameterStatistics = 4, ParameterStatistics = 4,
/// Service 5 /// Service 5 Event
Event = 5, Event = 5,
/// Service 6 /// Service 6 Memory Management
MemoryManagement = 6, MemoryManagement = 6,
/// Service 8 /// Service 8 Action
Action = 8, Action = 8,
/// Service 9 /// Service 9 Time Management
TimeManagement = 9, TimeManagement = 9,
/// Service 11 /// Service 11 Scheduling
Scheduling = 11, Scheduling = 11,
/// Service 12 /// Service 12 On-Board Monitoring
OnBoardMonitoring = 12, OnBoardMonitoring = 12,
/// Service 13 /// Service 13 Large Packet Transfer
LargePacketTransfer = 13, LargePacketTransfer = 13,
/// Service 14 /// Service 14 Real-Time Forwarding Control
RealTimeForwardingControl = 14, RealTimeForwardingControl = 14,
/// Service 15 /// Service 15 Storage And Retrival
StorageAndRetrival = 15, StorageAndRetrival = 15,
/// Service 17 /// Service 17 Test
Test = 17, Test = 17,
/// Service 18 /// Service 18 Operations And Procedures
OpsAndProcedures = 18, OpsAndProcedures = 18,
/// Service 19 /// Service 19 Event Action
EventAction = 19, EventAction = 19,
/// Service 20 /// Service 20 Parameter
Parameter = 20, Parameter = 20,
/// Service 21 /// Service 21 Request Sequencing
RequestSequencing = 21, RequestSequencing = 21,
/// Service 22 /// Service 22 Position Based Scheduling
PositionBasedScheduling = 22, PositionBasedScheduling = 22,
/// Service 23 /// Service 23 File Management
FileManagement = 23, FileManagement = 23,
} }
@@ -83,8 +85,11 @@ pub enum PusServiceId {
#[repr(u8)] #[repr(u8)]
#[non_exhaustive] #[non_exhaustive]
pub enum PusVersion { pub enum PusVersion {
/// ESA PUS
EsaPus = 0, EsaPus = 0,
/// PUS A
PusA = 1, PusA = 1,
/// PUS C
PusC = 2, PusC = 2,
} }
@@ -107,20 +112,33 @@ impl TryFrom<u4> for PusVersion {
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum PacketTypeCodes { pub enum PacketTypeCodes {
/// Boolean.
Boolean = 1, Boolean = 1,
/// Enumerated.
Enumerated = 2, Enumerated = 2,
/// Unsigned Integer.
UnsignedInt = 3, UnsignedInt = 3,
/// Signed Integer.
SignedInt = 4, SignedInt = 4,
/// Real (floating point).
Real = 5, Real = 5,
/// Bit string.
BitString = 6, BitString = 6,
/// Octet (byte) string.
OctetString = 7, OctetString = 7,
/// Character string.
CharString = 8, CharString = 8,
/// Absolute time.
AbsoluteTime = 9, AbsoluteTime = 9,
/// Relative time.
RelativeTime = 10, RelativeTime = 10,
/// Deduced.
Deduced = 11, Deduced = 11,
/// Packet.
Packet = 12, Packet = 12,
} }
/// Type alias for the ECSS Packet Type Codes (PTC)s.
pub type Ptc = PacketTypeCodes; pub type Ptc = PacketTypeCodes;
/// ECSS Packet Field Codes (PFC)s for the unsigned [Ptc]. /// ECSS Packet Field Codes (PFC)s for the unsigned [Ptc].
@@ -129,15 +147,25 @@ pub type Ptc = PacketTypeCodes;
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum PfcUnsigned { pub enum PfcUnsigned {
/// 1 byte.
OneByte = 4, OneByte = 4,
/// 12 bits.
TwelveBits = 8, TwelveBits = 8,
/// 2 bytes.
TwoBytes = 12, TwoBytes = 12,
/// 3 bytes.
ThreeBytes = 13, ThreeBytes = 13,
/// 4 bytes.
FourBytes = 14, FourBytes = 14,
/// 6 bytes.
SixBytes = 15, SixBytes = 15,
/// 8 bytes.
EightBytes = 16, EightBytes = 16,
/// 1 bit.
OneBit = 17, OneBit = 17,
/// 2 bits.
TwoBits = 18, TwoBits = 18,
/// 3 bits.
ThreeBits = 19, ThreeBits = 19,
} }
@@ -157,12 +185,15 @@ pub enum PfcReal {
DoubleMilStd = 4, DoubleMilStd = 4,
} }
/// Generic PUS error.
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PusError { pub enum PusError {
/// PUS version is not supported.
#[error("PUS version {0:?} not supported")] #[error("PUS version {0:?} not supported")]
VersionNotSupported(u4), VersionNotSupported(u4),
/// Checksum failure.
#[error("checksum verification for crc16 {0:#06x} failed")] #[error("checksum verification for crc16 {0:#06x} failed")]
ChecksumFailure(u16), ChecksumFailure(u16),
/// CRC16 needs to be calculated first /// CRC16 needs to be calculated first
@@ -175,12 +206,21 @@ pub enum PusError {
/// Generic trait to describe common attributes for both PUS Telecommands (TC) and PUS Telemetry /// Generic trait to describe common attributes for both PUS Telecommands (TC) and PUS Telemetry
/// (TM) packets. All PUS packets are also a special type of [CcsdsPacket]s. /// (TM) packets. All PUS packets are also a special type of [CcsdsPacket]s.
pub trait PusPacket: CcsdsPacket { pub trait PusPacket: CcsdsPacket {
/// PUS version.
fn pus_version(&self) -> Result<PusVersion, u4>; fn pus_version(&self) -> Result<PusVersion, u4>;
/// Service ID.
fn service(&self) -> u8; fn service(&self) -> u8;
/// Subservice ID.
fn subservice(&self) -> u8; fn subservice(&self) -> u8;
/// User data field.
fn user_data(&self) -> &[u8]; fn user_data(&self) -> &[u8];
/// CRC-16-CCITT checksum. /// CRC-16-CCITT checksum.
fn checksum(&self) -> Option<u16>; fn checksum(&self) -> Option<u16>;
/// The presence of the CRC-16-CCITT checksum is optional. /// The presence of the CRC-16-CCITT checksum is optional.
fn has_checksum(&self) -> bool { fn has_checksum(&self) -> bool {
self.checksum().is_some() self.checksum().is_some()
@@ -226,6 +266,7 @@ pub(crate) fn user_data_from_raw(
} }
} }
/// Verify the CRC16 of a raw packet and return a [PusError] on failure.
pub fn verify_crc16_ccitt_false_from_raw_to_pus_error( pub fn verify_crc16_ccitt_false_from_raw_to_pus_error(
raw_data: &[u8], raw_data: &[u8],
crc16: u16, crc16: u16,
@@ -235,6 +276,8 @@ pub fn verify_crc16_ccitt_false_from_raw_to_pus_error(
.ok_or(PusError::ChecksumFailure(crc16)) .ok_or(PusError::ChecksumFailure(crc16))
} }
/// Verify the CRC16 of a raw packet using a table-less implementation and return a [PusError] on
/// failure.
pub fn verify_crc16_ccitt_false_from_raw_to_pus_error_no_table( pub fn verify_crc16_ccitt_false_from_raw_to_pus_error_no_table(
raw_data: &[u8], raw_data: &[u8],
crc16: u16, crc16: u16,
@@ -267,10 +310,15 @@ pub fn verify_crc16_ccitt_false_from_raw_no_table(raw_data: &[u8]) -> bool {
macro_rules! sp_header_impls { macro_rules! sp_header_impls {
() => { () => {
delegate!(to self.sp_header { delegate!(to self.sp_header {
/// Set the CCSDS APID.
#[inline] #[inline]
pub fn set_apid(&mut self, apid: u11); pub fn set_apid(&mut self, apid: u11);
/// Set the CCSDS sequence count.
#[inline] #[inline]
pub fn set_seq_count(&mut self, seq_count: u14); pub fn set_seq_count(&mut self, seq_count: u14);
/// Set the CCSDS sequence flags.
#[inline] #[inline]
pub fn set_seq_flags(&mut self, seq_flag: SequenceFlags); pub fn set_seq_flags(&mut self, seq_flag: SequenceFlags);
}); });
@@ -289,27 +337,28 @@ pub trait EcssEnumeration: UnsignedEnum {
fn pfc(&self) -> u8; fn pfc(&self) -> u8;
} }
/// Extension trait for [EcssEnumeration] which adds common trait bounds.
pub trait EcssEnumerationExt: EcssEnumeration + Debug + Copy + Clone + PartialEq + Eq {} pub trait EcssEnumerationExt: EcssEnumeration + Debug + Copy + Clone + PartialEq + Eq {}
/// ECSS enumerated type wrapper.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GenericEcssEnumWrapper<TYPE: Copy + Into<u64>> { pub struct GenericEcssEnumWrapper<TYPE: Copy + Into<u64>>(GenericUnsignedByteField<TYPE>);
field: GenericUnsignedByteField<TYPE>,
}
impl<TYPE: Copy + Into<u64>> GenericEcssEnumWrapper<TYPE> { impl<TYPE: Copy + Into<u64>> GenericEcssEnumWrapper<TYPE> {
/// Returns [PacketTypeCodes::Enumerated].
pub const fn ptc() -> PacketTypeCodes { pub const fn ptc() -> PacketTypeCodes {
PacketTypeCodes::Enumerated PacketTypeCodes::Enumerated
} }
pub const fn value_typed(&self) -> TYPE { /// Value.
self.field.value_typed() pub const fn value(&self) -> TYPE {
self.0.value()
} }
pub fn new(val: TYPE) -> Self { /// Generic constructor.
Self { pub const fn new(val: TYPE) -> Self {
field: GenericUnsignedByteField::new(val), Self(GenericUnsignedByteField::new(val))
}
} }
} }
@@ -319,11 +368,11 @@ impl<TYPE: Copy + ToBeBytes + Into<u64>> UnsignedEnum for GenericEcssEnumWrapper
} }
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.field.write_to_be_bytes(buf) self.0.write_to_be_bytes(buf)
} }
fn value(&self) -> u64 { fn value_raw(&self) -> u64 {
self.field.value() self.0.value().into()
} }
} }
@@ -347,11 +396,12 @@ impl<T: Copy + Into<u64>> From<T> for GenericEcssEnumWrapper<T> {
macro_rules! generic_ecss_enum_typedefs_and_from_impls { macro_rules! generic_ecss_enum_typedefs_and_from_impls {
($($ty:ty => $Enum:ident),*) => { ($($ty:ty => $Enum:ident),*) => {
$( $(
/// Type alias for ECSS enumeration wrapper around `$ty`
pub type $Enum = GenericEcssEnumWrapper<$ty>; pub type $Enum = GenericEcssEnumWrapper<$ty>;
impl From<$Enum> for $ty { impl From<$Enum> for $ty {
fn from(value: $Enum) -> Self { fn from(value: $Enum) -> Self {
value.value_typed() value.value()
} }
} }
)* )*
@@ -412,6 +462,7 @@ pub trait WritablePusPacket {
Ok(curr_idx) Ok(curr_idx)
} }
/// Converts the packet into an owned [alloc::vec::Vec].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> Result<Vec<u8>, PusError> { fn to_vec(&self) -> Result<Vec<u8>, PusError> {
// This is the correct way to do this. See // This is the correct way to do this. See
@@ -423,6 +474,7 @@ pub trait WritablePusPacket {
} }
} }
/// PUS packet creator configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -465,7 +517,7 @@ mod tests {
.expect("To byte conversion of u8 failed"); .expect("To byte conversion of u8 failed");
assert_eq!(buf[1], 1); assert_eq!(buf[1], 1);
assert_eq!(my_enum.value(), 1); assert_eq!(my_enum.value(), 1);
assert_eq!(my_enum.value_typed(), 1); assert_eq!(my_enum.value(), 1);
let enum_as_u8: u8 = my_enum.into(); let enum_as_u8: u8 = my_enum.into();
assert_eq!(enum_as_u8, 1); assert_eq!(enum_as_u8, 1);
let vec = my_enum.to_vec(); let vec = my_enum.to_vec();
@@ -484,7 +536,7 @@ mod tests {
assert_eq!(buf[1], 0x1f); assert_eq!(buf[1], 0x1f);
assert_eq!(buf[2], 0x2f); assert_eq!(buf[2], 0x2f);
assert_eq!(my_enum.value(), 0x1f2f); assert_eq!(my_enum.value(), 0x1f2f);
assert_eq!(my_enum.value_typed(), 0x1f2f); assert_eq!(my_enum.value(), 0x1f2f);
let enum_as_raw: u16 = my_enum.into(); let enum_as_raw: u16 = my_enum.into();
assert_eq!(enum_as_raw, 0x1f2f); assert_eq!(enum_as_raw, 0x1f2f);
let vec = my_enum.to_vec(); let vec = my_enum.to_vec();
@@ -521,7 +573,7 @@ mod tests {
assert_eq!(buf[3], 0x3f); assert_eq!(buf[3], 0x3f);
assert_eq!(buf[4], 0x4f); assert_eq!(buf[4], 0x4f);
assert_eq!(my_enum.value(), 0x1f2f3f4f); assert_eq!(my_enum.value(), 0x1f2f3f4f);
assert_eq!(my_enum.value_typed(), 0x1f2f3f4f); assert_eq!(my_enum.value(), 0x1f2f3f4f);
let enum_as_raw: u32 = my_enum.into(); let enum_as_raw: u32 = my_enum.into();
assert_eq!(enum_as_raw, 0x1f2f3f4f); assert_eq!(enum_as_raw, 0x1f2f3f4f);
let vec = my_enum.to_vec(); let vec = my_enum.to_vec();
@@ -559,7 +611,7 @@ mod tests {
assert_eq!(buf[6], 0x4f); assert_eq!(buf[6], 0x4f);
assert_eq!(buf[7], 0x5f); assert_eq!(buf[7], 0x5f);
assert_eq!(my_enum.value(), 0x1f2f3f4f5f); assert_eq!(my_enum.value(), 0x1f2f3f4f5f);
assert_eq!(my_enum.value_typed(), 0x1f2f3f4f5f); assert_eq!(my_enum.value(), 0x1f2f3f4f5f);
let enum_as_raw: u64 = my_enum.into(); let enum_as_raw: u64 = my_enum.into();
assert_eq!(enum_as_raw, 0x1f2f3f4f5f); assert_eq!(enum_as_raw, 0x1f2f3f4f5f);
assert_eq!(u64::from_be_bytes(buf), 0x1f2f3f4f5f); assert_eq!(u64::from_be_bytes(buf), 0x1f2f3f4f5f);

View File

@@ -61,6 +61,8 @@
//! ``` //! ```
#![no_std] #![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
// TODO: Add docs everywhere.
//#![warn(missing_docs)]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
extern crate alloc; extern crate alloc;
#[cfg(any(feature = "std", test))] #[cfg(any(feature = "std", test))]
@@ -102,6 +104,7 @@ pub const MAX_SEQ_COUNT: u14 = u14::MAX;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive] #[non_exhaustive]
pub enum ChecksumType { pub enum ChecksumType {
/// Default CRC16-CCITT checksum.
Crc16CcittFalse, Crc16CcittFalse,
} }
@@ -112,12 +115,23 @@ pub enum ChecksumType {
pub enum ByteConversionError { pub enum ByteConversionError {
/// The passed slice is too small. Returns the passed slice length and expected minimum size /// The passed slice is too small. Returns the passed slice length and expected minimum size
#[error("target slice with size {found} is too small, expected size of at least {expected}")] #[error("target slice with size {found} is too small, expected size of at least {expected}")]
ToSliceTooSmall { found: usize, expected: usize }, ToSliceTooSmall {
/// Found slice size.
found: usize,
/// Expected slice size.
expected: usize,
},
/// The provider buffer is too small. Returns the passed slice length and expected minimum size /// The provider buffer is too small. Returns the passed slice length and expected minimum size
#[error("source slice with size {found} too small, expected at least {expected} bytes")] #[error("source slice with size {found} too small, expected at least {expected} bytes")]
FromSliceTooSmall { found: usize, expected: usize }, FromSliceTooSmall {
/// Found slice size.
found: usize,
/// Expected slice size.
expected: usize,
},
} }
/// [zerocopy] serialization and deserialization errors.
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -137,12 +151,15 @@ pub enum ZeroCopyError {
#[error("invalid payload length: {0}")] #[error("invalid payload length: {0}")]
pub struct InvalidPayloadLengthError(usize); pub struct InvalidPayloadLengthError(usize);
/// Errors during CCSDS packet creation.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CcsdsPacketCreationError { pub enum CcsdsPacketCreationError {
/// Byte conversion error.
#[error("byte conversion: {0}")] #[error("byte conversion: {0}")]
ByteConversion(#[from] ByteConversionError), ByteConversion(#[from] ByteConversionError),
/// Invalid payload length which exceeded [u16::MAX].
#[error("invalid payload length: {0}")] #[error("invalid payload length: {0}")]
InvalidPayloadLength(#[from] InvalidPayloadLengthError), InvalidPayloadLength(#[from] InvalidPayloadLengthError),
} }
@@ -154,22 +171,30 @@ pub enum CcsdsPacketCreationError {
#[bitbybit::bitenum(u1, exhaustive = true)] #[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum PacketType { pub enum PacketType {
/// Telemetry packet.
Tm = 0, Tm = 0,
/// Telecommand packet.
Tc = 1, Tc = 1,
} }
/// CCSDS packet sequence flags.
#[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive)] #[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u2, exhaustive = true)] #[bitbybit::bitenum(u2, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum SequenceFlags { pub enum SequenceFlags {
/// Continuation segment of a segmented packet.
ContinuationSegment = 0b00, ContinuationSegment = 0b00,
/// First segment of a sequence.
FirstSegment = 0b01, FirstSegment = 0b01,
/// Last segment of a sequence.
LastSegment = 0b10, LastSegment = 0b10,
/// Unsegmented packet.
Unsegmented = 0b11, Unsegmented = 0b11,
} }
/// Retrieve the [PacketType] from a raw packet ID.
#[inline] #[inline]
pub fn packet_type_in_raw_packet_id(packet_id: u16) -> PacketType { pub fn packet_type_in_raw_packet_id(packet_id: u16) -> PacketType {
PacketType::try_from((packet_id >> 12) as u8 & 0b1).unwrap() PacketType::try_from((packet_id >> 12) as u8 & 0b1).unwrap()
@@ -200,6 +225,9 @@ pub const fn ccsds_packet_len_for_user_data_len(
Some(len) Some(len)
} }
/// Calculate the full CCSDS packet length for a given user data length.
///
/// Returns [None] if the packet length exceeds the maximum allowed size [u16::MAX].
#[inline] #[inline]
pub fn ccsds_packet_len_for_user_data_len_with_checksum(data_len: usize) -> Option<usize> { pub fn ccsds_packet_len_for_user_data_len_with_checksum(data_len: usize) -> Option<usize> {
ccsds_packet_len_for_user_data_len(data_len, Some(ChecksumType::Crc16CcittFalse)) ccsds_packet_len_for_user_data_len(data_len, Some(ChecksumType::Crc16CcittFalse))
@@ -211,8 +239,11 @@ pub fn ccsds_packet_len_for_user_data_len_with_checksum(data_len: usize) -> Opti
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PacketId { pub struct PacketId {
/// Packet type (telemetry or telecommand).
pub packet_type: PacketType, pub packet_type: PacketType,
/// Secondary header flag.
pub sec_header_flag: bool, pub sec_header_flag: bool,
/// Application Process ID (APID).
pub apid: u11, pub apid: u11,
} }
@@ -256,16 +287,19 @@ impl Default for PacketId {
} }
impl PacketId { impl PacketId {
/// Generic constructor for telecommands.
#[inline] #[inline]
pub const fn new_for_tc(sec_header: bool, apid: u11) -> Self { pub const fn new_for_tc(sec_header: bool, apid: u11) -> Self {
Self::new(PacketType::Tc, sec_header, apid) Self::new(PacketType::Tc, sec_header, apid)
} }
/// Generic constructor for telemetry.
#[inline] #[inline]
pub const fn new_for_tm(sec_header: bool, apid: u11) -> Self { pub const fn new_for_tm(sec_header: bool, apid: u11) -> Self {
Self::new(PacketType::Tm, sec_header, apid) Self::new(PacketType::Tm, sec_header, apid)
} }
/// Generic constructor.
#[inline] #[inline]
pub const fn new(packet_type: PacketType, sec_header_flag: bool, apid: u11) -> Self { pub const fn new(packet_type: PacketType, sec_header_flag: bool, apid: u11) -> Self {
PacketId { PacketId {
@@ -289,6 +323,7 @@ impl PacketId {
self.apid self.apid
} }
/// Raw numeric value.
#[inline] #[inline]
pub const fn raw(&self) -> u16 { pub const fn raw(&self) -> u16 {
((self.packet_type as u16) << 12) ((self.packet_type as u16) << 12)
@@ -307,6 +342,7 @@ impl From<u16> for PacketId {
} }
} }
/// Deprecated type alias.
#[deprecated(since = "0.16.0", note = "use PacketSequenceControl instead")] #[deprecated(since = "0.16.0", note = "use PacketSequenceControl instead")]
pub type PacketSequenceCtrl = PacketSequenceControl; pub type PacketSequenceCtrl = PacketSequenceControl;
@@ -316,11 +352,14 @@ pub type PacketSequenceCtrl = PacketSequenceControl;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PacketSequenceControl { pub struct PacketSequenceControl {
/// CCSDS sequence flags.
pub seq_flags: SequenceFlags, pub seq_flags: SequenceFlags,
/// CCSDS sequence count.
pub seq_count: u14, pub seq_count: u14,
} }
impl PacketSequenceControl { impl PacketSequenceControl {
/// Generic constructor.
#[inline] #[inline]
pub const fn new(seq_flags: SequenceFlags, seq_count: u14) -> PacketSequenceControl { pub const fn new(seq_flags: SequenceFlags, seq_count: u14) -> PacketSequenceControl {
PacketSequenceControl { PacketSequenceControl {
@@ -329,6 +368,7 @@ impl PacketSequenceControl {
} }
} }
/// Raw value.
#[inline] #[inline]
pub const fn raw(&self) -> u16 { pub const fn raw(&self) -> u16 {
((self.seq_flags as u16) << 14) | self.seq_count.value() ((self.seq_flags as u16) << 14) | self.seq_count.value()
@@ -348,7 +388,7 @@ macro_rules! sph_from_other {
($Self: path, $other: path) => { ($Self: path, $other: path) => {
impl From<$other> for $Self { impl From<$other> for $Self {
fn from(other: $other) -> Self { fn from(other: $other) -> Self {
Self::from_composite_fields( Self::new_from_composite_fields(
other.packet_id(), other.packet_id(),
other.psc(), other.psc(),
other.data_len(), other.data_len(),
@@ -364,19 +404,33 @@ const VERSION_MASK: u16 = 0xE000;
/// Generic trait to access fields of a CCSDS space packet header according to CCSDS 133.0-B-2. /// Generic trait to access fields of a CCSDS space packet header according to CCSDS 133.0-B-2.
pub trait CcsdsPacket { pub trait CcsdsPacket {
/// CCSDS version field.
fn ccsds_version(&self) -> u3; fn ccsds_version(&self) -> u3;
/// CCSDS packet ID.
///
/// First two bytes of the CCSDS primary header without the first three bits.
fn packet_id(&self) -> PacketId; fn packet_id(&self) -> PacketId;
/// CCSDS packet sequence control.
///
/// Third and fourth byte of the CCSDS primary header.
fn psc(&self) -> PacketSequenceControl; fn psc(&self) -> PacketSequenceControl;
/// Retrieve data length field /// Data length field.
///
/// Please note that this is NOT the full packet length.
/// The full length can be calculated by adding the header length [CCSDS_HEADER_LEN] + 1 or
/// using [Self::packet_len].
fn data_len(&self) -> u16; fn data_len(&self) -> u16;
/// Retrieve the total packet size based on the data length field /// Total packet size based on the data length field
#[inline] #[inline]
fn packet_len(&self) -> usize { fn packet_len(&self) -> usize {
usize::from(self.data_len()) + CCSDS_HEADER_LEN + 1 usize::from(self.data_len()) + CCSDS_HEADER_LEN + 1
} }
/// Deprecated alias for [Self::packet_len].
#[deprecated(since = "0.16.0", note = "use packet_len instead")] #[deprecated(since = "0.16.0", note = "use packet_len instead")]
#[inline] #[inline]
fn total_len(&self) -> usize { fn total_len(&self) -> usize {
@@ -395,40 +449,45 @@ pub trait CcsdsPacket {
self.psc().raw() self.psc().raw()
} }
/// CCSDS packet type.
#[inline] #[inline]
fn packet_type(&self) -> PacketType { fn packet_type(&self) -> PacketType {
// This call should never fail because only 0 and 1 can be passed to the try_from call // This call should never fail because only 0 and 1 can be passed to the try_from call
self.packet_id().packet_type self.packet_id().packet_type
} }
/// Is this a telemetry packet?
#[inline] #[inline]
fn is_tm(&self) -> bool { fn is_tm(&self) -> bool {
self.packet_type() == PacketType::Tm self.packet_type() == PacketType::Tm
} }
/// Is this a telecommand packet?
#[inline] #[inline]
fn is_tc(&self) -> bool { fn is_tc(&self) -> bool {
self.packet_type() == PacketType::Tc self.packet_type() == PacketType::Tc
} }
/// Retrieve the secondary header flag. Returns true if a secondary header is present /// CCSDS secondary header flag. Returns true if a secondary header is present
/// and false if it is not. /// and false if it is not.
#[inline] #[inline]
fn sec_header_flag(&self) -> bool { fn sec_header_flag(&self) -> bool {
self.packet_id().sec_header_flag self.packet_id().sec_header_flag
} }
/// Retrieve Application Process ID. /// CCSDS Application Process ID (APID).
#[inline] #[inline]
fn apid(&self) -> u11 { fn apid(&self) -> u11 {
self.packet_id().apid self.packet_id().apid
} }
/// CCSDS sequence count.
#[inline] #[inline]
fn seq_count(&self) -> u14 { fn seq_count(&self) -> u14 {
self.psc().seq_count self.psc().seq_count
} }
/// CCSDS sequence flags.
#[inline] #[inline]
fn sequence_flags(&self) -> SequenceFlags { fn sequence_flags(&self) -> SequenceFlags {
// This call should never fail because the mask ensures that only valid values are passed // This call should never fail because the mask ensures that only valid values are passed
@@ -437,8 +496,10 @@ pub trait CcsdsPacket {
} }
} }
/// Helper trait to generate the primary header from the composite fields.
pub trait CcsdsPrimaryHeader { pub trait CcsdsPrimaryHeader {
fn from_composite_fields( /// Constructor.
fn new_from_composite_fields(
packet_id: PacketId, packet_id: PacketId,
psc: PacketSequenceControl, psc: PacketSequenceControl,
data_len: u16, data_len: u16,
@@ -447,25 +508,23 @@ pub trait CcsdsPrimaryHeader {
} }
/// Space Packet Primary Header according to CCSDS 133.0-B-2. /// Space Packet Primary Header according to CCSDS 133.0-B-2.
///
/// # Arguments
///
/// * `version` - CCSDS version field, occupies the first 3 bits of the raw header. Will generally
/// be set to 0b000 in all constructors provided by this crate.
/// * `packet_id` - Packet Identifier, which can also be used as a start marker. Occupies the last
/// 13 bits of the first two bytes of the raw header
/// * `psc` - Packet Sequence Control, occupies the third and fourth byte of the raw header
/// * `data_len` - Data length field occupies the fifth and the sixth byte of the raw header
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SpacePacketHeader { pub struct SpacePacketHeader {
/// CCSDS version field, occupies the first 3 bits of the raw header. Will generally
/// be set to 0b000 in all constructors provided by this crate.
pub version: u3, pub version: u3,
/// CCSDS Packet Identifier, which can also be used as a start marker. Occupies the last
/// 13 bits of the first two bytes of the raw header
pub packet_id: PacketId, pub packet_id: PacketId,
/// CCSDS Packet Sequence Control, occupies the third and fourth byte of the raw header
pub psc: PacketSequenceControl, pub psc: PacketSequenceControl,
/// Data length field occupies the fifth and the sixth byte of the raw header
pub data_len: u16, pub data_len: u16,
} }
/// Alias for [SpacePacketHeader].
pub type SpHeader = SpacePacketHeader; pub type SpHeader = SpacePacketHeader;
impl Default for SpacePacketHeader { impl Default for SpacePacketHeader {
@@ -486,8 +545,10 @@ impl Default for SpacePacketHeader {
} }
impl SpacePacketHeader { impl SpacePacketHeader {
/// Length of the CCSDS primary header.
pub const LENGTH: usize = CCSDS_HEADER_LEN; pub const LENGTH: usize = CCSDS_HEADER_LEN;
/// Generic constructor.
#[inline] #[inline]
pub const fn new(packet_id: PacketId, psc: PacketSequenceControl, data_len: u16) -> Self { pub const fn new(packet_id: PacketId, psc: PacketSequenceControl, data_len: u16) -> Self {
Self { Self {
@@ -513,6 +574,7 @@ impl SpacePacketHeader {
} }
} }
/// Constructor from individual fields.
#[inline] #[inline]
pub const fn new_from_fields( pub const fn new_from_fields(
ptype: PacketType, ptype: PacketType,
@@ -530,6 +592,7 @@ impl SpacePacketHeader {
} }
} }
/// Constructor for telemetry packets.
#[inline] #[inline]
pub const fn new_for_tm( pub const fn new_for_tm(
apid: u11, apid: u11,
@@ -540,6 +603,7 @@ impl SpacePacketHeader {
Self::new_from_fields(PacketType::Tm, false, apid, seq_flags, seq_count, data_len) Self::new_from_fields(PacketType::Tm, false, apid, seq_flags, seq_count, data_len)
} }
/// Constructor for telecommand packets.
#[inline] #[inline]
pub const fn new_for_tc( pub const fn new_for_tc(
apid: u11, apid: u11,
@@ -564,6 +628,7 @@ impl SpacePacketHeader {
delegate! { delegate! {
to self.packet_id { to self.packet_id {
/// Set the application process ID (APID).
#[inline] #[inline]
pub fn set_apid(&mut self, apid: u11); pub fn set_apid(&mut self, apid: u11);
} }
@@ -575,26 +640,31 @@ impl SpacePacketHeader {
usize::from(self.data_len()) + Self::LENGTH + 1 usize::from(self.data_len()) + Self::LENGTH + 1
} }
/// Set the CCSDS sequence count.
#[inline] #[inline]
pub fn set_seq_count(&mut self, seq_count: u14) { pub fn set_seq_count(&mut self, seq_count: u14) {
self.psc.seq_count = seq_count; self.psc.seq_count = seq_count;
} }
/// Set the CCSDS sequence flags.
#[inline] #[inline]
pub fn set_seq_flags(&mut self, seq_flags: SequenceFlags) { pub fn set_seq_flags(&mut self, seq_flags: SequenceFlags) {
self.psc.seq_flags = seq_flags; self.psc.seq_flags = seq_flags;
} }
/// Set the CCSDS secondary header flag.
#[inline] #[inline]
pub fn set_sec_header_flag(&mut self) { pub fn set_sec_header_flag(&mut self) {
self.packet_id.sec_header_flag = true; self.packet_id.sec_header_flag = true;
} }
/// Clear the CCSDS secondary header flag.
#[inline] #[inline]
pub fn clear_sec_header_flag(&mut self) { pub fn clear_sec_header_flag(&mut self) {
self.packet_id.sec_header_flag = false; self.packet_id.sec_header_flag = false;
} }
/// Set the CCSDS packet type.
#[inline] #[inline]
pub fn set_packet_type(&mut self, packet_type: PacketType) { pub fn set_packet_type(&mut self, packet_type: PacketType) {
self.packet_id.packet_type = packet_type; self.packet_id.packet_type = packet_type;
@@ -677,7 +747,7 @@ impl CcsdsPacket for SpacePacketHeader {
impl CcsdsPrimaryHeader for SpacePacketHeader { impl CcsdsPrimaryHeader for SpacePacketHeader {
#[inline] #[inline]
fn from_composite_fields( fn new_from_composite_fields(
packet_id: PacketId, packet_id: PacketId,
psc: PacketSequenceControl, psc: PacketSequenceControl,
data_len: u16, data_len: u16,
@@ -698,6 +768,7 @@ impl CcsdsPrimaryHeader for SpacePacketHeader {
sph_from_other!(SpHeader, crate::zc::SpHeader); sph_from_other!(SpHeader, crate::zc::SpHeader);
/// [zerocopy] based CCSDS Space Packet Primary Header implementation.
pub mod zc { pub mod zc {
use crate::{CcsdsPacket, CcsdsPrimaryHeader, PacketId, PacketSequenceControl, VERSION_MASK}; use crate::{CcsdsPacket, CcsdsPrimaryHeader, PacketId, PacketSequenceControl, VERSION_MASK};
use arbitrary_int::traits::Integer; use arbitrary_int::traits::Integer;
@@ -705,6 +776,7 @@ pub mod zc {
use zerocopy::byteorder::NetworkEndian; use zerocopy::byteorder::NetworkEndian;
use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned, U16}; use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned, U16};
/// [zerocopy] space packet header.
#[derive(FromBytes, IntoBytes, Immutable, Unaligned, Debug)] #[derive(FromBytes, IntoBytes, Immutable, Unaligned, Debug)]
#[repr(C)] #[repr(C)]
pub struct SpHeader { pub struct SpHeader {
@@ -714,6 +786,7 @@ pub mod zc {
} }
impl SpHeader { impl SpHeader {
/// Generic constructor.
pub fn new( pub fn new(
packet_id: PacketId, packet_id: PacketId,
psc: PacketSequenceControl, psc: PacketSequenceControl,
@@ -769,7 +842,7 @@ pub mod zc {
} }
impl CcsdsPrimaryHeader for SpHeader { impl CcsdsPrimaryHeader for SpHeader {
fn from_composite_fields( fn new_from_composite_fields(
packet_id: PacketId, packet_id: PacketId,
psc: PacketSequenceControl, psc: PacketSequenceControl,
data_len: u16, data_len: u16,
@@ -799,8 +872,11 @@ pub struct CcsdsPacketCreatorWithReservedData<'buf> {
} }
impl<'buf> CcsdsPacketCreatorWithReservedData<'buf> { impl<'buf> CcsdsPacketCreatorWithReservedData<'buf> {
/// CCSDS header length.
pub const HEADER_LEN: usize = CCSDS_HEADER_LEN; pub const HEADER_LEN: usize = CCSDS_HEADER_LEN;
/// Calculate the full CCSDS packet length for a given user data length and with a CRC16
/// checksum.
#[inline] #[inline]
pub fn packet_len_for_user_data_with_checksum(user_data_len: usize) -> Option<usize> { pub fn packet_len_for_user_data_with_checksum(user_data_len: usize) -> Option<usize> {
ccsds_packet_len_for_user_data_len(user_data_len, Some(ChecksumType::Crc16CcittFalse)) ccsds_packet_len_for_user_data_len(user_data_len, Some(ChecksumType::Crc16CcittFalse))
@@ -896,11 +972,13 @@ impl<'buf> CcsdsPacketCreatorWithReservedData<'buf> {
} }
impl CcsdsPacketCreatorWithReservedData<'_> { impl CcsdsPacketCreatorWithReservedData<'_> {
/// Raw full buffer this packet is constructed in.
#[inline] #[inline]
pub fn raw_buffer(&self) -> &[u8] { pub fn raw_buffer(&self) -> &[u8] {
self.buf self.buf
} }
/// Full packet length.
#[inline] #[inline]
pub fn packet_len(&self) -> usize { pub fn packet_len(&self) -> usize {
<Self as CcsdsPacket>::packet_len(self) <Self as CcsdsPacket>::packet_len(self)
@@ -986,7 +1064,9 @@ impl CcsdsPacket for CcsdsPacketCreatorWithReservedData<'_> {
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CcsdsPacketId { pub struct CcsdsPacketId {
/// CCSDS Packet ID.
pub packet_id: PacketId, pub packet_id: PacketId,
/// CCSDS Packet Sequence Control.
pub psc: PacketSequenceControl, pub psc: PacketSequenceControl,
} }
@@ -998,6 +1078,7 @@ impl Hash for CcsdsPacketId {
} }
impl CcsdsPacketId { impl CcsdsPacketId {
/// Generic constructor.
#[inline] #[inline]
pub const fn new(packet_id: PacketId, psc: PacketSequenceControl) -> Self { pub const fn new(packet_id: PacketId, psc: PacketSequenceControl) -> Self {
Self { packet_id, psc } Self { packet_id, psc }
@@ -1012,6 +1093,7 @@ impl CcsdsPacketId {
} }
} }
/// Raw numeric value.
#[inline] #[inline]
pub const fn raw(&self) -> u32 { pub const fn raw(&self) -> u32 {
((self.packet_id.raw() as u32) << 16) | self.psc.raw() as u32 ((self.packet_id.raw() as u32) << 16) | self.psc.raw() as u32
@@ -1133,6 +1215,7 @@ pub struct CcsdsPacketCreator<'app_data> {
} }
impl<'app_data> CcsdsPacketCreator<'app_data> { impl<'app_data> CcsdsPacketCreator<'app_data> {
/// CCSDS header length.
pub const HEADER_LEN: usize = CCSDS_HEADER_LEN; pub const HEADER_LEN: usize = CCSDS_HEADER_LEN;
/// Helper function which can be used to determine the full packet length from the user /// Helper function which can be used to determine the full packet length from the user
@@ -1211,6 +1294,7 @@ impl CcsdsPacketCreator<'_> {
.write_to_bytes(buf, self.len_written(), self.packet_data) .write_to_bytes(buf, self.len_written(), self.packet_data)
} }
/// CCSDS space packet header.
#[inline] #[inline]
pub fn sp_header(&self) -> &SpHeader { pub fn sp_header(&self) -> &SpHeader {
&self.common.sp_header &self.common.sp_header
@@ -1256,7 +1340,9 @@ pub struct CcsdsPacketCreatorOwned {
packet_data: alloc::vec::Vec<u8>, packet_data: alloc::vec::Vec<u8>,
} }
#[cfg(feature = "alloc")]
impl CcsdsPacketCreatorOwned { impl CcsdsPacketCreatorOwned {
/// CCSDS header length.
pub const HEADER_LEN: usize = CCSDS_HEADER_LEN; pub const HEADER_LEN: usize = CCSDS_HEADER_LEN;
/// Helper function which can be used to determine the full packet length from the user /// Helper function which can be used to determine the full packet length from the user
@@ -1332,6 +1418,7 @@ impl CcsdsPacketCreatorOwned {
.write_to_bytes(buf, self.len_written(), &self.packet_data) .write_to_bytes(buf, self.len_written(), &self.packet_data)
} }
/// CCSDS space packet header.
#[inline] #[inline]
pub fn sp_header(&self) -> &SpHeader { pub fn sp_header(&self) -> &SpHeader {
&self.common.sp_header &self.common.sp_header
@@ -1344,6 +1431,7 @@ impl CcsdsPacketCreatorOwned {
} }
} }
#[cfg(feature = "alloc")]
impl CcsdsPacket for CcsdsPacketCreatorOwned { impl CcsdsPacket for CcsdsPacketCreatorOwned {
/// CCSDS version field. /// CCSDS version field.
#[inline] #[inline]
@@ -1370,12 +1458,15 @@ impl CcsdsPacket for CcsdsPacketCreatorOwned {
} }
} }
/// CCSDS packet read error.
#[derive(thiserror::Error, Debug, PartialEq, Eq)] #[derive(thiserror::Error, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CcsdsPacketReadError { pub enum CcsdsPacketReadError {
/// Byte conversion error.
#[error("byte conversion: {0}")] #[error("byte conversion: {0}")]
ByteConversion(#[from] ByteConversionError), ByteConversion(#[from] ByteConversionError),
/// CRC error.
#[error("CRC error")] #[error("CRC error")]
CrcError, CrcError,
} }
@@ -1390,14 +1481,17 @@ pub struct CcsdsPacketReader<'buf> {
} }
impl<'buf> CcsdsPacketReader<'buf> { impl<'buf> CcsdsPacketReader<'buf> {
/// CCSDS header length.
pub const HEADER_LEN: usize = CCSDS_HEADER_LEN; pub const HEADER_LEN: usize = CCSDS_HEADER_LEN;
/// Constructor which expects a CRC16 checksum.
pub fn new_with_checksum( pub fn new_with_checksum(
buf: &'buf [u8], buf: &'buf [u8],
) -> Result<CcsdsPacketReader<'buf>, CcsdsPacketReadError> { ) -> Result<CcsdsPacketReader<'buf>, CcsdsPacketReadError> {
Self::new(buf, Some(ChecksumType::Crc16CcittFalse)) Self::new(buf, Some(ChecksumType::Crc16CcittFalse))
} }
/// Generic constructor.
pub fn new( pub fn new(
buf: &'buf [u8], buf: &'buf [u8],
checksum: Option<ChecksumType>, checksum: Option<ChecksumType>,
@@ -1672,7 +1766,7 @@ pub(crate) mod tests {
assert_eq!(sp_header.data_len(), 36); assert_eq!(sp_header.data_len(), 36);
assert_eq!(sp_header.ccsds_version().value(), 0b000); assert_eq!(sp_header.ccsds_version().value(), 0b000);
let from_comp_fields = SpHeader::from_composite_fields( let from_comp_fields = SpHeader::new_from_composite_fields(
PacketId::new(PacketType::Tc, true, u11::new(0x42)), PacketId::new(PacketType::Tc, true, u11::new(0x42)),
PacketSequenceControl::new(SequenceFlags::Unsegmented, u14::new(0x7)), PacketSequenceControl::new(SequenceFlags::Unsegmented, u14::new(0x7)),
0, 0,

View File

@@ -14,28 +14,37 @@ use paste::paste;
/// static structs when using the interior mutability pattern. This can be achieved by using /// static structs when using the interior mutability pattern. This can be achieved by using
/// [Cell], [core::cell::RefCell] or atomic types. /// [Cell], [core::cell::RefCell] or atomic types.
pub trait SequenceCounter { pub trait SequenceCounter {
/// Raw type of the counter.
type Raw: Into<u64>; type Raw: Into<u64>;
/// Bit width of the counter.
const MAX_BIT_WIDTH: usize; const MAX_BIT_WIDTH: usize;
/// Get the current sequence count value.
fn get(&self) -> Self::Raw; fn get(&self) -> Self::Raw;
/// Increment the sequence count by one.
fn increment(&self); fn increment(&self);
/// Increment the sequence count by one, mutable API.
fn increment_mut(&mut self) { fn increment_mut(&mut self) {
self.increment(); self.increment();
} }
/// Get the current sequence count value and increment the counter by one.
fn get_and_increment(&self) -> Self::Raw { fn get_and_increment(&self) -> Self::Raw {
let val = self.get(); let val = self.get();
self.increment(); self.increment();
val val
} }
/// Get the current sequence count value and increment the counter by one, mutable API.
fn get_and_increment_mut(&mut self) -> Self::Raw { fn get_and_increment_mut(&mut self) -> Self::Raw {
self.get_and_increment() self.get_and_increment()
} }
} }
/// Simple sequence counter which wraps at [T::MAX].
#[derive(Clone)] #[derive(Clone)]
pub struct SequenceCounterSimple<T: Copy> { pub struct SequenceCounterSimple<T: Copy> {
seq_count: Cell<T>, seq_count: Cell<T>,
@@ -48,12 +57,15 @@ macro_rules! impl_for_primitives {
$( $(
paste! { paste! {
impl SequenceCounterSimple<$ty> { impl SequenceCounterSimple<$ty> {
/// Constructor with a custom maximum value.
pub fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self { pub fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self {
Self { Self {
seq_count: Cell::new(0), seq_count: Cell::new(0),
max_val, max_val,
} }
} }
/// Generic constructor.
pub fn [<new_ $ty>]() -> Self { pub fn [<new_ $ty>]() -> Self {
Self { Self {
seq_count: Cell::new(0), seq_count: Cell::new(0),
@@ -275,6 +287,7 @@ macro_rules! sync_clonable_seq_counter_impl {
} }
impl [<SequenceCounterSyncCustomWrap $ty:upper>] { impl [<SequenceCounterSyncCustomWrap $ty:upper>] {
/// Generic constructor.
pub fn new(max_val: $ty) -> Self { pub fn new(max_val: $ty) -> Self {
Self { Self {
seq_count: core::sync::atomic::[<Atomic $ty:upper>]::new(0), seq_count: core::sync::atomic::[<Atomic $ty:upper>]::new(0),

View File

@@ -31,6 +31,7 @@ pub const FMT_STR_CODE_B_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3f", 21);
/// Three digits are used for the decimal fraction and a terminator is added at the end. /// Three digits are used for the decimal fraction and a terminator is added at the end.
pub const FMT_STR_CODE_B_TERMINATED_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3fZ", 22); pub const FMT_STR_CODE_B_TERMINATED_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3fZ", 22);
/// Functions requiring both [chrono] and [alloc] support.
#[cfg(all(feature = "alloc", feature = "chrono"))] #[cfg(all(feature = "alloc", feature = "chrono"))]
pub mod alloc_mod_chrono { pub mod alloc_mod_chrono {
use super::*; use super::*;

View File

@@ -37,12 +37,15 @@ use super::{
/// Base value for the preamble field for a time field parser to determine the time field type. /// Base value for the preamble field for a time field parser to determine the time field type.
pub const P_FIELD_BASE: u8 = (CcsdsTimeCode::Cds as u8) << 4; pub const P_FIELD_BASE: u8 = (CcsdsTimeCode::Cds as u8) << 4;
/// Minimum allowed length for a CDS timestamp.
pub const MIN_CDS_FIELD_LEN: usize = 7; pub const MIN_CDS_FIELD_LEN: usize = 7;
/// Maximum allowed days value for 24 bit days field.
pub const MAX_DAYS_24_BITS: u32 = 2_u32.pow(24) - 1; pub const MAX_DAYS_24_BITS: u32 = 2_u32.pow(24) - 1;
/// Generic trait implemented by token structs to specify the length of day field at type /// Generic trait implemented by token structs to specify the length of day field at type
/// level. This trait is only meant to be implemented in this crate and therefore sealed. /// level. This trait is only meant to be implemented in this crate and therefore sealed.
pub trait ProvidesDaysLength: Sealed + Clone { pub trait ProvidesDaysLength: Sealed + Clone {
/// Raw field type.
type FieldType: Debug type FieldType: Debug
+ Copy + Copy
+ Clone + Clone
@@ -73,24 +76,33 @@ impl ProvidesDaysLength for DaysLen24Bits {
type FieldType = u32; type FieldType = u32;
} }
/// Length of day segment.
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LengthOfDaySegment { pub enum LengthOfDaySegment {
/// Shorter 16 bits length of days field.
Short16Bits = 0, Short16Bits = 0,
/// Larger 24 bits length of days field.
Long24Bits = 1, Long24Bits = 1,
} }
/// Sub-millisecond precision indicator.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SubmillisPrecision { pub enum SubmillisPrecision {
/// No sub-millisecond precision present.
Absent = 0b00, Absent = 0b00,
/// Microsecond precision present.
Microseconds = 0b01, Microseconds = 0b01,
/// Picoseconds precision present.
Picoseconds = 0b10, Picoseconds = 0b10,
/// Reserved.
Reserved = 0b11, Reserved = 0b11,
} }
/// CDS timestamp error.
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)] #[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -102,10 +114,12 @@ pub enum CdsError {
/// field. This error will be returned if there is a missmatch. /// field. This error will be returned if there is a missmatch.
#[error("wrong constructor for length of day {0:?} detected in preamble")] #[error("wrong constructor for length of day {0:?} detected in preamble")]
InvalidCtorForDaysOfLenInPreamble(LengthOfDaySegment), InvalidCtorForDaysOfLenInPreamble(LengthOfDaySegment),
/// Date is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
#[error("date before CCSDS epoch: {0}")] #[error("date before CCSDS epoch: {0}")]
DateBeforeCcsdsEpoch(#[from] DateBeforeCcsdsEpochError), DateBeforeCcsdsEpoch(#[from] DateBeforeCcsdsEpochError),
} }
/// Retrieve the [LengthOfDaySegment] from the p-field byte.
pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment { pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment {
if (pfield >> 2) & 0b1 == 1 { if (pfield >> 2) & 0b1 == 1 {
return LengthOfDaySegment::Long24Bits; return LengthOfDaySegment::Long24Bits;
@@ -113,6 +127,7 @@ pub fn length_of_day_segment_from_pfield(pfield: u8) -> LengthOfDaySegment {
LengthOfDaySegment::Short16Bits LengthOfDaySegment::Short16Bits
} }
/// Retrieve the [SubmillisPrecision] from the p-field byte.
#[inline] #[inline]
pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision {
match pfield & 0b11 { match pfield & 0b11 {
@@ -180,19 +195,29 @@ pub struct CdsTime<DaysLen: ProvidesDaysLength = DaysLen16Bits> {
/// ///
/// Also exists to encapsulate properties used by private converters. /// Also exists to encapsulate properties used by private converters.
pub trait CdsBase { pub trait CdsBase {
/// Sub-millisecond precision indicator.
fn submillis_precision(&self) -> SubmillisPrecision; fn submillis_precision(&self) -> SubmillisPrecision;
/// Sub-milliseconds.
fn submillis(&self) -> u32; fn submillis(&self) -> u32;
/// Milliseconds of day.
fn ms_of_day(&self) -> u32; fn ms_of_day(&self) -> u32;
/// CCSDS days since the epoch as [u32].
fn ccsds_days_as_u32(&self) -> u32; fn ccsds_days_as_u32(&self) -> u32;
} }
/// Generic properties for all CDS time providers. /// Generic properties for all CDS time providers.
pub trait CdsTimestamp: CdsBase { pub trait CdsTimestamp: CdsBase {
/// Length of day segment.
fn len_of_day_seg(&self) -> LengthOfDaySegment; fn len_of_day_seg(&self) -> LengthOfDaySegment;
} }
/// Generic implementation of [CcsdsTimeProvider] for CDS time providers.
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub trait DynCdsTimeProvider: CcsdsTimeProvider + CdsTimestamp + TimeWriter + Any {} pub trait DynCdsTimeProvider: CcsdsTimeProvider + CdsTimestamp + TimeWriter + Any {}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
impl DynCdsTimeProvider for CdsTime<DaysLen16Bits> {} impl DynCdsTimeProvider for CdsTime<DaysLen16Bits> {}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
@@ -294,11 +319,13 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
true true
} }
/// Clear the submillisecond field.
pub fn clear_submillis(&mut self) { pub fn clear_submillis(&mut self) {
self.pfield &= !(0b11); self.pfield &= !(0b11);
self.submillis = 0; self.submillis = 0;
} }
/// CCSDS days since the CCSDS epoch.
pub fn ccsds_days(&self) -> ProvidesDaysLen::FieldType { pub fn ccsds_days(&self) -> ProvidesDaysLen::FieldType {
self.ccsds_days self.ccsds_days
} }
@@ -522,6 +549,7 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CdsTime<ProvidesDaysLen> {
pfield pfield
} }
/// Update the time from the current time.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> { pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> {
let conversion_from_now = self.generic_conversion_from_now()?; let conversion_from_now = self.generic_conversion_from_now()?;
@@ -612,6 +640,7 @@ impl CdsTime<DaysLen24Bits> {
Self::now_generic_with_us_prec(LengthOfDaySegment::Long24Bits) Self::now_generic_with_us_prec(LengthOfDaySegment::Long24Bits)
} }
/// Constructor from the CDS timestamp as a raw byte array.
pub fn from_bytes_with_u24_days(buf: &[u8]) -> Result<Self, TimestampError> { pub fn from_bytes_with_u24_days(buf: &[u8]) -> Result<Self, TimestampError> {
let submillis_precision = let submillis_precision =
Self::generic_raw_read_checks(buf, LengthOfDaySegment::Long24Bits)?; Self::generic_raw_read_checks(buf, LengthOfDaySegment::Long24Bits)?;
@@ -704,6 +733,7 @@ impl CdsTime<DaysLen16Bits> {
Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits) Self::from_now_generic_ps_prec(LengthOfDaySegment::Short16Bits)
} }
/// Write the CDS timestamp to a raw byte array.
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.length_check(buf, self.len_as_bytes())?; self.length_check(buf, self.len_as_bytes())?;
buf[0] = self.pfield; buf[0] = self.pfield;
@@ -721,6 +751,7 @@ impl CdsTime<DaysLen16Bits> {
Ok(self.len_as_bytes()) Ok(self.len_as_bytes())
} }
/// Constructor from the CDS timestamp as a raw byte array.
pub fn from_bytes_with_u16_days(buf: &[u8]) -> Result<Self, TimestampError> { pub fn from_bytes_with_u16_days(buf: &[u8]) -> Result<Self, TimestampError> {
let submillis_precision = let submillis_precision =
Self::generic_raw_read_checks(buf, LengthOfDaySegment::Short16Bits)?; Self::generic_raw_read_checks(buf, LengthOfDaySegment::Short16Bits)?;
@@ -892,6 +923,7 @@ impl TimeWriter for CdsTime<DaysLen16Bits> {
} }
impl CdsTime<DaysLen24Bits> { impl CdsTime<DaysLen24Bits> {
/// Write the CDS timestamp to a raw byte array.
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.length_check(buf, self.len_as_bytes())?; self.length_check(buf, self.len_as_bytes())?;
buf[0] = self.pfield; buf[0] = self.pfield;

View File

@@ -32,6 +32,7 @@ pub const P_FIELD_BASE: u8 = (CcsdsTimeCode::CucCcsdsEpoch as u8) << 4;
/// Maximum length if the preamble field is not extended. /// Maximum length if the preamble field is not extended.
pub const MAX_CUC_LEN_SMALL_PREAMBLE: usize = 8; pub const MAX_CUC_LEN_SMALL_PREAMBLE: usize = 8;
/// Fractional resolution for the fractional part of the CUC time code.
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -70,13 +71,14 @@ pub fn convert_fractional_part_to_ns(fractional_part: FractionalPart) -> u64 {
10_u64.pow(9) * fractional_part.counter as u64 / div as u64 10_u64.pow(9) * fractional_part.counter as u64 / div as u64
} }
/// Convert the fractional resolution to the divisor used to calculate the fractional part.
#[inline(always)] #[inline(always)]
pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 { pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 {
// We do not use the full possible range for a given resolution. This is because if we did // We do not use the full possible range for a given resolution. This is because if we did
// that, the largest value would be equal to the counter being incremented by one. Thus, the // that, the largest value would be equal to the counter being incremented by one. Thus, the
// smallest allowed fractions value is 0 while the largest allowed fractions value is the // smallest allowed fractions value is 0 while the largest allowed fractions value is the
// closest fractions value to the next counter increment. // closest fractions value to the next counter increment.
2_u32.pow(8 * res as u32) - 1 (1u32 << (8 * res as u32)) - 1
} }
/// Calculate the fractional part for a given resolution and subsecond nanoseconds. /// Calculate the fractional part for a given resolution and subsecond nanoseconds.
@@ -101,22 +103,34 @@ pub fn fractional_part_from_subsec_ns(res: FractionalResolution, ns: u64) -> Fra
} }
} }
/// CUC error.
#[derive(Copy, Clone, PartialEq, Eq, Debug, thiserror::Error)] #[derive(Copy, Clone, PartialEq, Eq, Debug, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CucError { pub enum CucError {
/// Invalid CUC counter width.
#[error("invalid cuc counter byte width {0}")] #[error("invalid cuc counter byte width {0}")]
InvalidCounterWidth(u8), InvalidCounterWidth(u8),
/// Invalid counter supplied. /// Invalid counter supplied.
#[error("invalid cuc counter {counter} for width {width}")] #[error("invalid cuc counter {counter} for width {width}")]
InvalidCounter { width: u8, counter: u64 }, InvalidCounter {
/// Width.
width: u8,
/// Counter.
counter: u64,
},
/// Invalid fractions.
#[error("invalid cuc fractional part {value} for resolution {resolution:?}")] #[error("invalid cuc fractional part {value} for resolution {resolution:?}")]
InvalidFractions { InvalidFractions {
/// Resolution.
resolution: FractionalResolution, resolution: FractionalResolution,
/// Value.
value: u64, value: u64,
}, },
/// Error while correcting for leap seconds.
#[error("error while correcting for leap seconds")] #[error("error while correcting for leap seconds")]
LeapSecondCorrectionError, LeapSecondCorrectionError,
/// Data is before the CCSDS epoch.
#[error("date before ccsds epoch: {0}")] #[error("date before ccsds epoch: {0}")]
DateBeforeCcsdsEpoch(#[from] DateBeforeCcsdsEpochError), DateBeforeCcsdsEpoch(#[from] DateBeforeCcsdsEpochError),
} }
@@ -127,14 +141,20 @@ pub enum CucError {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WidthCounterPair(pub u8, pub u32); pub struct WidthCounterPair(pub u8, pub u32);
/// Fractional part of the CUC time code.
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FractionalPart { pub struct FractionalPart {
/// Resolution.
pub resolution: FractionalResolution, pub resolution: FractionalResolution,
/// Counter.
pub counter: u32, pub counter: u32,
} }
impl FractionalPart { impl FractionalPart {
/// Generic constructor.
///
/// This function will panic if the counter is smaller than the calculated divisor.
#[inline] #[inline]
pub const fn new(resolution: FractionalResolution, counter: u32) -> Self { pub const fn new(resolution: FractionalResolution, counter: u32) -> Self {
let div = fractional_res_to_div(resolution); let div = fractional_res_to_div(resolution);
@@ -157,6 +177,7 @@ impl FractionalPart {
Self::new_with_seconds_resolution() Self::new_with_seconds_resolution()
} }
/// Check constructor which verifies that the counter is larger than the divisor.
#[inline] #[inline]
pub fn new_checked(resolution: FractionalResolution, counter: u32) -> Option<Self> { pub fn new_checked(resolution: FractionalResolution, counter: u32) -> Option<Self> {
let div = fractional_res_to_div(resolution); let div = fractional_res_to_div(resolution);
@@ -169,16 +190,19 @@ impl FractionalPart {
}) })
} }
/// Fractional resolution.
#[inline] #[inline]
pub fn resolution(&self) -> FractionalResolution { pub fn resolution(&self) -> FractionalResolution {
self.resolution self.resolution
} }
/// Counter value.
#[inline] #[inline]
pub fn counter(&self) -> u32 { pub fn counter(&self) -> u32 {
self.counter self.counter
} }
/// Check whether the timestamp does not have a fractional part.
#[inline] #[inline]
pub fn no_fractional_part(&self) -> bool { pub fn no_fractional_part(&self) -> bool {
self.resolution == FractionalResolution::Seconds self.resolution == FractionalResolution::Seconds
@@ -245,17 +269,21 @@ pub struct CucTime {
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CucTimeWithLeapSecs { pub struct CucTimeWithLeapSecs {
/// CUC time.
pub time: CucTime, pub time: CucTime,
/// Leap seconds.
pub leap_seconds: u32, pub leap_seconds: u32,
} }
impl CucTimeWithLeapSecs { impl CucTimeWithLeapSecs {
/// Generic constructor.
#[inline] #[inline]
pub fn new(time: CucTime, leap_seconds: u32) -> Self { pub fn new(time: CucTime, leap_seconds: u32) -> Self {
Self { time, leap_seconds } Self { time, leap_seconds }
} }
} }
/// p-field length.
#[inline] #[inline]
pub fn pfield_len(pfield: u8) -> usize { pub fn pfield_len(pfield: u8) -> usize {
if ((pfield >> 7) & 0b1) == 1 { if ((pfield >> 7) & 0b1) == 1 {
@@ -381,6 +409,7 @@ impl CucTime {
Ok(()) Ok(())
} }
/// Creates a CUC timestamp from a Chrono DateTime object.
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn from_chrono_date_time( pub fn from_chrono_date_time(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
@@ -448,21 +477,25 @@ impl CucTime {
}) })
} }
/// CCSDS time code.
#[inline] #[inline]
pub fn ccsds_time_code(&self) -> CcsdsTimeCode { pub fn ccsds_time_code(&self) -> CcsdsTimeCode {
CcsdsTimeCode::CucCcsdsEpoch CcsdsTimeCode::CucCcsdsEpoch
} }
/// Width and counter pair.
#[inline] #[inline]
pub fn width_counter_pair(&self) -> WidthCounterPair { pub fn width_counter_pair(&self) -> WidthCounterPair {
self.counter self.counter
} }
/// Counter width.
#[inline] #[inline]
pub fn counter_width(&self) -> u8 { pub fn counter_width(&self) -> u8 {
self.counter.0 self.counter.0
} }
/// Counter value.
#[inline] #[inline]
pub fn counter(&self) -> u32 { pub fn counter(&self) -> u32 {
self.counter.1 self.counter.1
@@ -474,11 +507,13 @@ impl CucTime {
self.fractions self.fractions
} }
/// Convert to the leap seconds helper.
#[inline] #[inline]
pub fn to_leap_sec_helper(&self, leap_seconds: u32) -> CucTimeWithLeapSecs { pub fn to_leap_sec_helper(&self, leap_seconds: u32) -> CucTimeWithLeapSecs {
CucTimeWithLeapSecs::new(*self, leap_seconds) CucTimeWithLeapSecs::new(*self, leap_seconds)
} }
/// Set the fractional part.
#[inline] #[inline]
pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> { pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> {
Self::verify_fractions_value(fractions)?; Self::verify_fractions_value(fractions)?;
@@ -525,16 +560,19 @@ impl CucTime {
self.pfield |= self.fractions.resolution() as u8; self.pfield |= self.fractions.resolution() as u8;
} }
/// Length of the counter from the p-field.
#[inline] #[inline]
pub fn len_cntr_from_pfield(pfield: u8) -> u8 { pub fn len_cntr_from_pfield(pfield: u8) -> u8 {
((pfield >> 2) & 0b11) + 1 ((pfield >> 2) & 0b11) + 1
} }
/// Length of the fractional part from the p-field.
#[inline] #[inline]
pub fn len_fractions_from_pfield(pfield: u8) -> u8 { pub fn len_fractions_from_pfield(pfield: u8) -> u8 {
pfield & 0b11 pfield & 0b11
} }
/// UNIX seconds.
#[inline] #[inline]
pub fn unix_secs(&self, leap_seconds: u32) -> i64 { pub fn unix_secs(&self, leap_seconds: u32) -> i64 {
ccsds_epoch_to_unix_epoch(self.counter.1 as i64) ccsds_epoch_to_unix_epoch(self.counter.1 as i64)
@@ -542,6 +580,7 @@ impl CucTime {
.unwrap() .unwrap()
} }
/// Subsecond milliseconds part of the CUC time.
#[inline] #[inline]
pub fn subsec_millis(&self) -> u16 { pub fn subsec_millis(&self) -> u16 {
(self.subsec_nanos() / 1_000_000) as u16 (self.subsec_nanos() / 1_000_000) as u16
@@ -564,6 +603,7 @@ impl CucTime {
) )
} }
/// Packed length from the raw p-field.
#[inline] #[inline]
pub fn len_packed_from_pfield(pfield: u8) -> usize { pub fn len_packed_from_pfield(pfield: u8) -> usize {
let mut base_len: usize = 1; let mut base_len: usize = 1;

View File

@@ -22,19 +22,29 @@ pub mod ascii;
pub mod cds; pub mod cds;
pub mod cuc; pub mod cuc;
/// Conversion constant for converting CCSDS days to UNIX days.
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383; pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
/// Seconds per day.
pub const SECONDS_PER_DAY: u32 = 86400; pub const SECONDS_PER_DAY: u32 = 86400;
/// Milliseconds per day.
pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000; pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000;
/// Nanoseconds per second.
pub const NANOS_PER_SECOND: u32 = 1_000_000_000; pub const NANOS_PER_SECOND: u32 = 1_000_000_000;
/// CCSDS time code identifiers.
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CcsdsTimeCode { pub enum CcsdsTimeCode {
/// CUC with a CCSDS epoch (1958-01-01T00:00:00+00:00).
CucCcsdsEpoch = 0b001, CucCcsdsEpoch = 0b001,
/// CUC with a custom agency epoch.
CucAgencyEpoch = 0b010, CucAgencyEpoch = 0b010,
/// CDS time code.
Cds = 0b100, Cds = 0b100,
/// CCS time code.
Ccs = 0b101, Ccs = 0b101,
/// Agency defined time code.
AgencyDefined = 0b110, AgencyDefined = 0b110,
} }
@@ -60,44 +70,61 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCode, u8> {
CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits) CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits)
} }
/// Date is before the CCSDS epoch.
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)] #[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("date before ccsds epoch: {0:?}")] #[error("date before ccsds epoch: {0:?}")]
pub struct DateBeforeCcsdsEpochError(UnixTime); pub struct DateBeforeCcsdsEpochError(UnixTime);
/// Generic timestamp error.
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)] #[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum TimestampError { pub enum TimestampError {
/// Invalid time code.
#[error("invalid time code, expected {expected:?}, found {found}")] #[error("invalid time code, expected {expected:?}, found {found}")]
InvalidTimeCode { expected: CcsdsTimeCode, found: u8 }, InvalidTimeCode {
/// Expected time code.
expected: CcsdsTimeCode,
/// Found raw time code.
found: u8,
},
/// Byte conversion error.
#[error("time stamp: byte conversion error: {0}")] #[error("time stamp: byte conversion error: {0}")]
ByteConversion(#[from] ByteConversionError), ByteConversion(#[from] ByteConversionError),
/// CDS timestamp error.
#[error("CDS error: {0}")] #[error("CDS error: {0}")]
Cds(#[from] cds::CdsError), Cds(#[from] cds::CdsError),
/// CUC timestamp error.
#[error("CUC error: {0}")] #[error("CUC error: {0}")]
Cuc(#[from] cuc::CucError), Cuc(#[from] cuc::CucError),
/// Custom epoch is not supported.
#[error("custom epoch not supported")] #[error("custom epoch not supported")]
CustomEpochNotSupported, CustomEpochNotSupported,
} }
/// [std] module.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod std_mod { pub mod std_mod {
use crate::time::TimestampError; use crate::time::TimestampError;
use std::time::SystemTimeError; use std::time::SystemTimeError;
use thiserror::Error; use thiserror::Error;
/// [std] timestamp error.
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum StdTimestampError { pub enum StdTimestampError {
/// System time error.
#[error("system time error: {0:?}")] #[error("system time error: {0:?}")]
SystemTime(#[from] SystemTimeError), SystemTime(#[from] SystemTimeError),
/// Generic timestamp error.
#[error("timestamp error: {0}")] #[error("timestamp error: {0}")]
Timestamp(#[from] TimestampError), Timestamp(#[from] TimestampError),
} }
} }
/// Seconds since epoch for the current system time.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn seconds_since_epoch() -> f64 { pub fn seconds_since_epoch() -> f64 {
SystemTime::now() SystemTime::now()
@@ -131,16 +158,19 @@ pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: i64) -> i64 {
unix_epoch - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64) unix_epoch - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
} }
/// Convert CCSDS epoch to UNIX epoch.
#[inline] #[inline]
pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: i64) -> i64 { pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: i64) -> i64 {
ccsds_epoch + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64) ccsds_epoch + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
} }
/// Milliseconds of day for the current system time.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn ms_of_day_using_sysclock() -> u32 { pub fn ms_of_day_using_sysclock() -> u32 {
ms_of_day(seconds_since_epoch()) ms_of_day(seconds_since_epoch())
} }
/// Milliseconds for the given seconds since epoch.
pub fn ms_of_day(seconds_since_epoch: f64) -> u32 { pub fn ms_of_day(seconds_since_epoch: f64) -> u32 {
let fraction_ms = seconds_since_epoch - seconds_since_epoch.floor(); let fraction_ms = seconds_since_epoch - seconds_since_epoch.floor();
let ms_of_day: u32 = (((seconds_since_epoch.floor() as u32 % SECONDS_PER_DAY) * 1000) as f64 let ms_of_day: u32 = (((seconds_since_epoch.floor() as u32 % SECONDS_PER_DAY) * 1000) as f64
@@ -149,13 +179,16 @@ pub fn ms_of_day(seconds_since_epoch: f64) -> u32 {
ms_of_day ms_of_day
} }
/// Generic writable timestamp trait.
pub trait TimeWriter { pub trait TimeWriter {
/// Written length.
fn len_written(&self) -> usize; fn len_written(&self) -> usize;
/// Generic function to convert write a timestamp into a raw buffer. /// Generic function to convert write a timestamp into a raw buffer.
/// Returns the number of written bytes on success. /// Returns the number of written bytes on success.
fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError>; fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError>;
/// Convert to a owned [alloc::vec::Vec].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> Result<alloc::vec::Vec<u8>, TimestampError> { fn to_vec(&self) -> Result<alloc::vec::Vec<u8>, TimestampError> {
let mut vec = alloc::vec![0; self.len_written()]; let mut vec = alloc::vec![0; self.len_written()];
@@ -164,7 +197,9 @@ pub trait TimeWriter {
} }
} }
/// Genmeric readable timestamp trait.
pub trait TimeReader: Sized { pub trait TimeReader: Sized {
/// Create a timestamp from a raw byte buffer.
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>; fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>;
} }
@@ -174,6 +209,7 @@ pub trait TimeReader: Sized {
/// practical because they are a very common and simple exchange format for time information. /// practical because they are a very common and simple exchange format for time information.
/// Therefore, it was decided to keep them in this trait as well. /// Therefore, it was decided to keep them in this trait as well.
pub trait CcsdsTimeProvider { pub trait CcsdsTimeProvider {
/// Length when written to bytes.
fn len_as_bytes(&self) -> usize; fn len_as_bytes(&self) -> usize;
/// Returns the pfield of the time provider. The pfield can have one or two bytes depending /// Returns the pfield of the time provider. The pfield can have one or two bytes depending
@@ -181,29 +217,37 @@ pub trait CcsdsTimeProvider {
/// entry denotes the length of the pfield and the second entry is the value of the pfield /// entry denotes the length of the pfield and the second entry is the value of the pfield
/// in big endian format. /// in big endian format.
fn p_field(&self) -> (usize, [u8; 2]); fn p_field(&self) -> (usize, [u8; 2]);
/// CCSDS time code field.
fn ccdsd_time_code(&self) -> CcsdsTimeCode; fn ccdsd_time_code(&self) -> CcsdsTimeCode;
/// UNIX time as seconds.
fn unix_secs(&self) -> i64 { fn unix_secs(&self) -> i64 {
self.unix_time().secs self.unix_time().secs
} }
/// Subsecond nanoseconds.
fn subsec_nanos(&self) -> u32 { fn subsec_nanos(&self) -> u32 {
self.unix_time().subsec_nanos self.unix_time().subsec_nanos
} }
/// Subsecond milliseconds.
fn subsec_millis(&self) -> u16 { fn subsec_millis(&self) -> u16 {
(self.subsec_nanos() / 1_000_000) as u16 (self.subsec_nanos() / 1_000_000) as u16
} }
/// UNIX time.
fn unix_time(&self) -> UnixTime { fn unix_time(&self) -> UnixTime {
UnixTime::new(self.unix_secs(), self.subsec_nanos()) UnixTime::new(self.unix_secs(), self.subsec_nanos())
} }
/// [chrono] date time.
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> { fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos()) chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos())
} }
/// [time] library date] library date time.
#[cfg(feature = "timelib")] #[cfg(feature = "timelib")]
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> { fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())? Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())?
@@ -285,6 +329,7 @@ impl UnixTime {
} }
} }
/// New UNIX time with only seconds, subseconds set to zero.
pub fn new_only_secs(unix_seconds: i64) -> Self { pub fn new_only_secs(unix_seconds: i64) -> Self {
Self { Self {
secs: unix_seconds, secs: unix_seconds,
@@ -292,15 +337,18 @@ impl UnixTime {
} }
} }
/// Sub-second milliseconds.
#[inline] #[inline]
pub fn subsec_millis(&self) -> u16 { pub fn subsec_millis(&self) -> u16 {
(self.subsec_nanos / 1_000_000) as u16 (self.subsec_nanos / 1_000_000) as u16
} }
/// Sub-second nanoseconds.
pub fn subsec_nanos(&self) -> u32 { pub fn subsec_nanos(&self) -> u32 {
self.subsec_nanos self.subsec_nanos
} }
/// Create a UNIX timestamp from the current system time.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn now() -> Result<Self, SystemTimeError> { pub fn now() -> Result<Self, SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
@@ -308,27 +356,31 @@ impl UnixTime {
Ok(Self::new(epoch as i64, now.subsec_nanos())) Ok(Self::new(epoch as i64, now.subsec_nanos()))
} }
/// UNIX timestamp as a floating point number in seconds.
#[inline] #[inline]
pub fn unix_secs_f64(&self) -> f64 { pub fn unix_secs_f64(&self) -> f64 {
self.secs as f64 + (self.subsec_nanos as f64 / 1_000_000_000.0) self.secs as f64 + (self.subsec_nanos as f64 / 1_000_000_000.0)
} }
/// UNIX timestamp as seconds, discards the sub-second part.
pub fn as_secs(&self) -> i64 { pub fn as_secs(&self) -> i64 {
self.secs self.secs
} }
/// UNIX timestamp as [chrono] date time.
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> { pub fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
Utc.timestamp_opt(self.secs, self.subsec_nanos) Utc.timestamp_opt(self.secs, self.subsec_nanos)
} }
/// UNIX timestamp as [time] library date time.
#[cfg(feature = "timelib")] #[cfg(feature = "timelib")]
pub fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> { pub fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.as_secs())? Ok(time::OffsetDateTime::from_unix_timestamp(self.as_secs())?
+ time::Duration::nanoseconds(self.subsec_nanos().into())) + time::Duration::nanoseconds(self.subsec_nanos().into()))
} }
// Calculate the difference in milliseconds between two UnixTimestamps /// Calculate the difference in milliseconds between two UnixTimestamps
pub fn diff_in_millis(&self, other: &UnixTime) -> Option<i64> { pub fn diff_in_millis(&self, other: &UnixTime) -> Option<i64> {
let seconds_difference = self.secs.checked_sub(other.secs)?; let seconds_difference = self.secs.checked_sub(other.secs)?;
// Convert seconds difference to milliseconds // Convert seconds difference to milliseconds
@@ -398,7 +450,9 @@ impl Ord for UnixTime {
/// so the sign information is supplied separately. /// so the sign information is supplied separately.
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub struct StampDiff { pub struct StampDiff {
/// Positive duration flag.
pub positive_duration: bool, pub positive_duration: bool,
/// Absolute duration.
pub duration_absolute: Duration, pub duration_absolute: Duration,
} }

View File

@@ -1,12 +1,16 @@
//! # Utility module.
use crate::ByteConversionError; use crate::ByteConversionError;
use core::fmt::Debug; use core::fmt::Debug;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Helper traits for types which can be converted to a byte array.
pub trait ToBeBytes { pub trait ToBeBytes {
/// Concrete byte array type.
type ByteArray: AsRef<[u8]>; type ByteArray: AsRef<[u8]>;
/// Length when written to big endian bytes. /// Length when written to big endian bytes.
fn written_len(&self) -> usize; fn written_len(&self) -> usize;
/// Convert to big endian byte array.
fn to_be_bytes(&self) -> Self::ByteArray; fn to_be_bytes(&self) -> Self::ByteArray;
} }
@@ -80,14 +84,17 @@ impl ToBeBytes for u64 {
} }
} }
/// Helper trait for unsigned enumerations.
pub trait UnsignedEnum { pub trait UnsignedEnum {
/// Size of the unsigned enumeration in bytes. /// Size of the unsigned enumeration in bytes.
fn size(&self) -> usize; fn size(&self) -> usize;
/// Write the unsigned enumeration to a raw buffer. Returns the written size on success. /// Write the unsigned enumeration to a raw buffer. Returns the written size on success.
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>; fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
fn value(&self) -> u64; /// Type-erased raw value.
fn value_raw(&self) -> u64;
/// Convert to a [alloc::vec::Vec].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> alloc::vec::Vec<u8> { fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut buf = alloc::vec![0; self.size()]; let mut buf = alloc::vec![0; self.size()];
@@ -96,22 +103,32 @@ pub trait UnsignedEnum {
} }
} }
/// Extension trait for unsigned enumerations.
pub trait UnsignedEnumExt: UnsignedEnum + Debug + Copy + Clone + PartialEq + Eq {} pub trait UnsignedEnumExt: UnsignedEnum + Debug + Copy + Clone + PartialEq + Eq {}
/// Unsigned byte field errors.
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum UnsignedByteFieldError { pub enum UnsignedByteFieldError {
/// Value is too large for specified width of byte field. /// Value is too large for specified width of byte field.
#[error("value {value} too large for width {width}")] #[error("value {value} too large for width {width}")]
ValueTooLargeForWidth { width: usize, value: u64 }, ValueTooLargeForWidth {
/// Width in bytes.
width: usize,
/// Value.
value: u64,
},
/// Only 1, 2, 4 and 8 are allow width values. Optionally contains the expected width if /// Only 1, 2, 4 and 8 are allow width values. Optionally contains the expected width if
/// applicable, for example for conversions. /// applicable, for example for conversions.
#[error("invalid width {found}, expected {expected:?}")] #[error("invalid width {found}, expected {expected:?}")]
InvalidWidth { InvalidWidth {
/// Found width.
found: usize, found: usize,
/// Expected width.
expected: Option<usize>, expected: Option<usize>,
}, },
/// Error during byte conversion.
#[error("byte conversion error: {0}")] #[error("byte conversion error: {0}")]
ByteConversionError(#[from] ByteConversionError), ByteConversionError(#[from] ByteConversionError),
} }
@@ -126,16 +143,19 @@ pub struct UnsignedByteField {
} }
impl UnsignedByteField { impl UnsignedByteField {
/// Generic constructor.
#[inline] #[inline]
pub const fn new(width: usize, value: u64) -> Self { pub const fn new(width: usize, value: u64) -> Self {
Self { width, value } Self { width, value }
} }
/// Type-erased raw value.
#[inline] #[inline]
pub const fn value_const(&self) -> u64 { pub const fn value(&self) -> u64 {
self.value self.value
} }
/// Construct from raw bytes, assuming big-endian byte order.
#[inline] #[inline]
pub fn new_from_be_bytes(width: usize, buf: &[u8]) -> Result<Self, UnsignedByteFieldError> { pub fn new_from_be_bytes(width: usize, buf: &[u8]) -> Result<Self, UnsignedByteFieldError> {
if width > buf.len() { if width > buf.len() {
@@ -175,8 +195,8 @@ impl UnsignedEnum for UnsignedByteField {
} }
#[inline] #[inline]
fn value(&self) -> u64 { fn value_raw(&self) -> u64 {
self.value_const() self.value()
} }
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
@@ -212,6 +232,7 @@ impl UnsignedEnum for UnsignedByteField {
} }
} }
/// Generic type erased unsigned byte field.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -220,11 +241,13 @@ pub struct GenericUnsignedByteField<TYPE: Copy + Into<u64>> {
} }
impl<TYPE: Copy + Into<u64>> GenericUnsignedByteField<TYPE> { impl<TYPE: Copy + Into<u64>> GenericUnsignedByteField<TYPE> {
/// Generic constructor.
pub const fn new(val: TYPE) -> Self { pub const fn new(val: TYPE) -> Self {
Self { value: val } Self { value: val }
} }
pub const fn value_typed(&self) -> TYPE { /// Raw value.
pub const fn value(&self) -> TYPE {
self.value self.value
} }
} }
@@ -247,20 +270,29 @@ impl<TYPE: Copy + ToBeBytes + Into<u64>> UnsignedEnum for GenericUnsignedByteFie
} }
#[inline] #[inline]
fn value(&self) -> u64 { fn value_raw(&self) -> u64 {
self.value_typed().into() self.value().into()
} }
} }
/// Alias for [GenericUnsignedByteField] with [()] generic.
pub type UnsignedByteFieldEmpty = GenericUnsignedByteField<()>; pub type UnsignedByteFieldEmpty = GenericUnsignedByteField<()>;
/// Alias for [GenericUnsignedByteField] with [u8] generic.
pub type UnsignedByteFieldU8 = GenericUnsignedByteField<u8>; pub type UnsignedByteFieldU8 = GenericUnsignedByteField<u8>;
/// Alias for [GenericUnsignedByteField] with [u16] generic.
pub type UnsignedByteFieldU16 = GenericUnsignedByteField<u16>; pub type UnsignedByteFieldU16 = GenericUnsignedByteField<u16>;
/// Alias for [GenericUnsignedByteField] with [u32] generic.
pub type UnsignedByteFieldU32 = GenericUnsignedByteField<u32>; pub type UnsignedByteFieldU32 = GenericUnsignedByteField<u32>;
/// Alias for [GenericUnsignedByteField] with [u64] generic.
pub type UnsignedByteFieldU64 = GenericUnsignedByteField<u64>; pub type UnsignedByteFieldU64 = GenericUnsignedByteField<u64>;
/// Alias for [UnsignedByteFieldU8]
pub type UbfU8 = UnsignedByteFieldU8; pub type UbfU8 = UnsignedByteFieldU8;
/// Alias for [UnsignedByteFieldU16]
pub type UbfU16 = UnsignedByteFieldU16; pub type UbfU16 = UnsignedByteFieldU16;
/// Alias for [UnsignedByteFieldU32]
pub type UbfU32 = UnsignedByteFieldU32; pub type UbfU32 = UnsignedByteFieldU32;
/// Alias for [UnsignedByteFieldU64]
pub type UbfU64 = UnsignedByteFieldU64; pub type UbfU64 = UnsignedByteFieldU64;
impl From<UnsignedByteFieldU8> for UnsignedByteField { impl From<UnsignedByteFieldU8> for UnsignedByteField {
@@ -372,7 +404,7 @@ pub mod tests {
for val in buf.iter().skip(1) { for val in buf.iter().skip(1) {
assert_eq!(*val, 0); assert_eq!(*val, 0);
} }
assert_eq!(u8.value_typed(), 5); assert_eq!(u8.value_raw(), 5);
assert_eq!(u8.value(), 5); assert_eq!(u8.value(), 5);
} }
@@ -390,7 +422,7 @@ pub mod tests {
for val in buf.iter().skip(2) { for val in buf.iter().skip(2) {
assert_eq!(*val, 0); assert_eq!(*val, 0);
} }
assert_eq!(u16.value_typed(), 3823); assert_eq!(u16.value_raw(), 3823);
assert_eq!(u16.value(), 3823); assert_eq!(u16.value(), 3823);
} }
@@ -408,7 +440,7 @@ pub mod tests {
(4..8).for_each(|i| { (4..8).for_each(|i| {
assert_eq!(buf[i], 0); assert_eq!(buf[i], 0);
}); });
assert_eq!(u32.value_typed(), 80932); assert_eq!(u32.value_raw(), 80932);
assert_eq!(u32.value(), 80932); assert_eq!(u32.value(), 80932);
} }
@@ -423,7 +455,7 @@ pub mod tests {
assert_eq!(len, 8); assert_eq!(len, 8);
let raw_val = u64::from_be_bytes(buf[0..8].try_into().unwrap()); let raw_val = u64::from_be_bytes(buf[0..8].try_into().unwrap());
assert_eq!(raw_val, 5999999); assert_eq!(raw_val, 5999999);
assert_eq!(u64.value_typed(), 5999999); assert_eq!(u64.value_raw(), 5999999);
assert_eq!(u64.value(), 5999999); assert_eq!(u64.value(), 5999999);
} }