From d2f944580ce3f4b38170a84335d4dd87d1b15583 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 14 May 2023 20:10:34 +0200 Subject: [PATCH] thats a lot --- CHANGELOG.md | 15 +++ src/cfdp/mod.rs | 2 + src/cfdp/pdu/mod.rs | 271 ++++++++++++++++++++++++++++++++++++++++++++ src/ecss/mod.rs | 83 +++----------- src/lib.rs | 5 +- src/util.rs | 257 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 567 insertions(+), 66 deletions(-) create mode 100644 src/cfdp/pdu/mod.rs create mode 100644 src/util.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a2f15..0370147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +## Added + +- Added new `util` module which contains the following (new) helper modules: + - `UnsignedEnum` trait as an abstraction for unsigned byte fields with variable lengths. It is + not tied to the ECSS PFC value like the `EcssEnumeration` trait. + - `UnsignedByteField` as a type-erased helper. + - `UnsignedU8`, `UnsignedU16`, `UnsignedU32` and `UnsignedU64` as helper types implementing + `UnsignedEnum` +- Initial CFDP support: Added PDU packet implementation. + +## Changed + +- The `EcssEnumeration` now requires the `UnsignedEnum` trait and only adds the `pfc` method to it. +- Renamed `byte_width` usages to `len` (part of new `UnsignedEnum` trait) + # [v0.5.4] 2023-02-12 ## Added diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index 29330a6..194d9a7 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -2,6 +2,8 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +pub mod pdu; + pub const CFDP_VERSION_2_NAME: &str = "CCSDS 727.0-B-5"; pub const CFDP_VERSION_2: u8 = 0b001; diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs new file mode 100644 index 0000000..38860bb --- /dev/null +++ b/src/cfdp/pdu/mod.rs @@ -0,0 +1,271 @@ +use crate::cfdp::*; +use crate::util::{UnsignedByteField, UnsignedEnum}; +use crate::{ByteConversionError, SizeMissmatch}; +use core::fmt::{Display, Formatter}; +#[cfg(feature = "std")] +use std::error::Error; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum PduError { + ByteConversionError(ByteConversionError), + /// Found version ID invalid, not equal to [CFDP_VERSION_2]. + CfdpVersionMissmatch(u8), + /// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported. + InvalidEntityLen(u8), + /// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported. + InvalidTransactionSeqNumLen(u8), + /// The first entry will be the source entity ID length, the second one the destination entity + /// ID length. + SourceDestIdLenMissmatch((usize, usize)), +} + +impl Display for PduError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + PduError::InvalidEntityLen(raw_id) => { + write!( + f, + "Invalid entity ID length {raw_id}, only [1, 2, 4, 8] are allowed" + ) + } + PduError::InvalidTransactionSeqNumLen(raw_id) => { + write!( + f, + "Invalid transaction seq num length {raw_id}, only [1, 2, 4, 8] are allowed" + ) + } + PduError::CfdpVersionMissmatch(raw) => { + write!( + f, + "cfdp version missmatch, found {raw}, expected {CFDP_VERSION_2}" + ) + } + PduError::SourceDestIdLenMissmatch((src_len, dest_len)) => { + write!( + f, + "missmatch of source length {src_len} and destination length {dest_len}" + ) + } + PduError::ByteConversionError(e) => { + write!(f, "low level byte conversion error: {e}") + } + } + } +} + +#[cfg(feature = "std")] +impl Error for PduError {} + +impl From for PduError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversionError(value) + } +} + +/// Common configuration fields for a PDU. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CommonPduConfig { + source_entity_id: UnsignedByteField, + dest_entity_id: UnsignedByteField, + transaction_seq_num: UnsignedByteField, + trans_mode: TransmissionMode, + file_flag: LargeFileFlag, + crc_flag: CrcFlag, + direction: Direction, +} + +impl CommonPduConfig { + pub fn new( + source_id: UnsignedByteField, + dest_id: UnsignedByteField, + transaction_seq_num: UnsignedByteField, + trans_mode: TransmissionMode, + file_flag: LargeFileFlag, + crc_flag: CrcFlag, + direction: Direction, + ) -> Result { + if source_id.len() != dest_id.len() { + return Err(PduError::SourceDestIdLenMissmatch(( + source_id.len(), + dest_id.len(), + ))); + } + Ok(Self { + source_entity_id: source_id, + dest_entity_id: dest_id, + transaction_seq_num, + trans_mode, + file_flag, + crc_flag, + direction, + }) + } +} +/// Abstraction for the PDU header common to all CFDP PDUs +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PduHeader { + pdu_type: PduType, + pdu_conf: CommonPduConfig, + seg_metadata_flag: SegmentMetadataFlag, + seg_ctrl: SegmentationControl, + pdu_datafield_len: u16, +} + +impl PduHeader { + pub fn new_for_file_data( + pdu_conf: CommonPduConfig, + seg_metadata_flag: SegmentMetadataFlag, + seg_ctrl: SegmentationControl, + pdu_datafield_len: u16, + ) -> Self { + PduHeader { + pdu_type: PduType::FileData, + pdu_conf, + seg_metadata_flag, + seg_ctrl, + pdu_datafield_len, + } + } + + pub fn new_no_file_data(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self { + PduHeader { + pdu_type: PduType::FileData, + pdu_conf, + seg_metadata_flag: SegmentMetadataFlag::NotPresent, + seg_ctrl: SegmentationControl::NoRecordBoundaryPreservation, + pdu_datafield_len, + } + } + + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), PduError> { + if self.pdu_conf.source_entity_id.len() != self.pdu_conf.dest_entity_id.len() { + return Err(PduError::SourceDestIdLenMissmatch(( + self.pdu_conf.source_entity_id.len(), + self.pdu_conf.dest_entity_id.len(), + ))); + } + if buf.len() + < 4 + self.pdu_conf.source_entity_id.len() + self.pdu_conf.transaction_seq_num.len() + { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 4, + }) + .into()); + } + let mut current_idx = 0; + buf[current_idx] = (CFDP_VERSION_2 << 5) + | ((self.pdu_type as u8) << 4) + | ((self.pdu_conf.direction as u8) << 3) + | ((self.pdu_conf.trans_mode as u8) << 2) + | ((self.pdu_conf.crc_flag as u8) << 1) + | (self.pdu_conf.file_flag as u8); + current_idx += 1; + buf[current_idx..current_idx + 2].copy_from_slice(&self.pdu_datafield_len.to_be_bytes()); + current_idx += 2; + buf[current_idx] = ((self.seg_ctrl as u8) << 7) + | ((self.pdu_conf.source_entity_id.len() as u8) << 4) + | ((self.seg_metadata_flag as u8) << 3) + | (self.pdu_conf.transaction_seq_num.len() as u8); + self.pdu_conf.source_entity_id.write_to_be_bytes( + &mut buf[current_idx..current_idx + self.pdu_conf.source_entity_id.len()], + )?; + current_idx += self.pdu_conf.source_entity_id.len(); + self.pdu_conf.transaction_seq_num.write_to_be_bytes( + &mut buf[current_idx..current_idx + self.pdu_conf.transaction_seq_num.len()], + )?; + current_idx += self.pdu_conf.transaction_seq_num.len(); + self.pdu_conf.dest_entity_id.write_to_be_bytes( + &mut buf[current_idx..current_idx + self.pdu_conf.dest_entity_id.len()], + )?; + Ok(()) + } + + pub fn from_be_bytes(buf: &[u8]) -> Result { + if buf.len() < 4 { + return Err(PduError::ByteConversionError( + ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 4, + }), + )); + } + let cfdp_version_raw = buf[0] >> 5 & 0b111; + if cfdp_version_raw != CFDP_VERSION_2 { + return Err(PduError::CfdpVersionMissmatch(cfdp_version_raw)); + } + // Conversion for 1 bit value always works + let pdu_type = PduType::try_from((buf[0] >> 4) & 0b1).unwrap(); + let direction = Direction::try_from((buf[0] >> 3) & 0b1).unwrap(); + let trans_mode = TransmissionMode::try_from((buf[0] >> 2) & 0b1).unwrap(); + let crc_flag = CrcFlag::try_from((buf[0] >> 1) & 0b1).unwrap(); + let file_flag = LargeFileFlag::try_from(buf[0] & 0b1).unwrap(); + let pdu_datafield_len = u16::from_be_bytes(buf[1..3].try_into().unwrap()); + let seg_ctrl = SegmentationControl::try_from((buf[3] >> 7) & 0b1).unwrap(); + let expected_len_entity_ids = ((buf[3] >> 4) & 0b111) as usize; + if (expected_len_entity_ids != 1) + && (expected_len_entity_ids != 2) + && (expected_len_entity_ids != 4) + && (expected_len_entity_ids != 8) + { + return Err(PduError::InvalidEntityLen(expected_len_entity_ids as u8)); + } + let seg_metadata_flag = SegmentMetadataFlag::try_from((buf[3] >> 3) & 0b1).unwrap(); + let expected_len_seq_num = (buf[3] & 0b111) as usize; + if (expected_len_seq_num != 1) + && (expected_len_seq_num != 2) + && (expected_len_seq_num != 4) + && (expected_len_seq_num != 8) + { + return Err(PduError::InvalidTransactionSeqNumLen( + expected_len_seq_num as u8, + )); + } + if buf.len() < (4 + 2 * expected_len_entity_ids + expected_len_seq_num) { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 4 + 2 * expected_len_entity_ids + expected_len_seq_num, + }) + .into()); + } + let mut current_idx = 4; + let source_id = + UnsignedByteField::new_from_be_bytes(expected_len_entity_ids, &buf[current_idx..]) + .unwrap(); + current_idx += expected_len_entity_ids; + let transaction_seq_num = + UnsignedByteField::new_from_be_bytes(expected_len_seq_num, &buf[current_idx..]) + .unwrap(); + current_idx += expected_len_seq_num; + let dest_id = + UnsignedByteField::new_from_be_bytes(expected_len_entity_ids, &buf[current_idx..]) + .unwrap(); + let common_pdu_conf = CommonPduConfig::new( + source_id, + dest_id, + transaction_seq_num, + trans_mode, + file_flag, + crc_flag, + direction, + ) + .unwrap(); + Ok(PduHeader { + pdu_type, + pdu_conf: common_pdu_conf, + seg_metadata_flag, + seg_ctrl, + pdu_datafield_len, + }) + } + pub fn pdu_type(&self) -> PduType { + self.pdu_type + } + + pub fn common_pdu_conf(&self) -> &CommonPduConfig { + &self.pdu_conf + } +} diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index 8f5cd74..5d755bc 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -3,7 +3,7 @@ //! //! You can find the PUS telecommand definitions in the [crate::tc] module and ithe PUS telemetry definitions //! inside the [crate::tm] module. -use crate::{ByteConversionError, CcsdsPacket, SizeMissmatch}; +use crate::{ByteConversionError, CcsdsPacket}; use core::fmt::{Debug, Display, Formatter}; use core::mem::size_of; use crc::{Crc, CRC_16_IBM_3740}; @@ -291,6 +291,7 @@ macro_rules! sp_header_impls { } } +use crate::util::{GenericUnsignedByteField, ToBeBytes, UnsignedEnum}; pub(crate) use ccsds_impl; pub(crate) use sp_header_impls; @@ -298,66 +299,17 @@ pub(crate) use sp_header_impls; /// and an unsigned value. The trait makes no assumptions about the actual type of the unsigned /// value and only requires implementors to implement a function which writes the enumeration into /// a raw byte format. -pub trait EcssEnumeration { +pub trait EcssEnumeration: UnsignedEnum { /// Packet Format Code, which denotes the number of bits of the enumeration fn pfc(&self) -> u8; - fn byte_width(&self) -> usize { - (self.pfc() / 8) as usize - } - fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError>; } pub trait EcssEnumerationExt: EcssEnumeration + Debug + Copy + Clone + PartialEq + Eq {} -pub trait ToBeBytes { - type ByteArray: AsRef<[u8]>; - fn to_be_bytes(&self) -> Self::ByteArray; -} - -impl ToBeBytes for () { - type ByteArray = [u8; 0]; - - fn to_be_bytes(&self) -> Self::ByteArray { - [] - } -} - -impl ToBeBytes for u8 { - type ByteArray = [u8; 1]; - - fn to_be_bytes(&self) -> Self::ByteArray { - u8::to_be_bytes(*self) - } -} - -impl ToBeBytes for u16 { - type ByteArray = [u8; 2]; - - fn to_be_bytes(&self) -> Self::ByteArray { - u16::to_be_bytes(*self) - } -} - -impl ToBeBytes for u32 { - type ByteArray = [u8; 4]; - - fn to_be_bytes(&self) -> Self::ByteArray { - u32::to_be_bytes(*self) - } -} - -impl ToBeBytes for u64 { - type ByteArray = [u8; 8]; - - fn to_be_bytes(&self) -> Self::ByteArray { - u64::to_be_bytes(*self) - } -} - #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct GenericEcssEnumWrapper { - val: TYPE, + field: GenericUnsignedByteField, } impl GenericEcssEnumWrapper { @@ -366,7 +318,19 @@ impl GenericEcssEnumWrapper { } pub fn new(val: TYPE) -> Self { - Self { val } + Self { + field: GenericUnsignedByteField::new(val), + } + } +} + +impl UnsignedEnum for GenericEcssEnumWrapper { + fn len(&self) -> usize { + (self.pfc() / 8) as usize + } + + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { + self.field.write_to_be_bytes(buf) } } @@ -374,17 +338,6 @@ impl EcssEnumeration for GenericEcssEnumWrapper { fn pfc(&self) -> u8 { size_of::() as u8 * 8_u8 } - - fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { - if buf.len() < self.byte_width() { - return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { - found: buf.len(), - expected: self.byte_width(), - })); - } - buf[0..self.byte_width()].copy_from_slice(self.val.to_be_bytes().as_ref()); - Ok(()) - } } impl EcssEnumerationExt @@ -399,7 +352,7 @@ pub type EcssEnumU64 = GenericEcssEnumWrapper; #[cfg(test)] mod tests { - use crate::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU8, EcssEnumeration}; + use crate::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU8, UnsignedEnum}; use crate::ByteConversionError; #[test] diff --git a/src/lib.rs b/src/lib.rs index bdb2187..2c3dbda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,8 +60,10 @@ extern crate alloc; extern crate std; use crate::ecss::CCSDS_HEADER_LEN; -use core::fmt::{Display, Formatter}; +use core::fmt::{Debug, Display, Formatter}; use delegate::delegate; +#[cfg(not(feature = "std"))] +use num_traits::Unsigned; #[cfg(feature = "std")] use std::error::Error; @@ -73,6 +75,7 @@ pub mod ecss; pub mod tc; pub mod time; pub mod tm; +pub mod util; mod private { pub trait Sealed {} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..ee599a3 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,257 @@ +use crate::{ByteConversionError, SizeMissmatch}; +use core::fmt::Debug; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub trait ToBeBytes { + type ByteArray: AsRef<[u8]>; + /// Length when written to big endian bytes. + fn written_len(&self) -> usize; + fn to_be_bytes(&self) -> Self::ByteArray; +} + +impl ToBeBytes for () { + type ByteArray = [u8; 0]; + + fn written_len(&self) -> usize { + 0 + } + + fn to_be_bytes(&self) -> Self::ByteArray { + [] + } +} + +impl ToBeBytes for u8 { + type ByteArray = [u8; 1]; + + fn written_len(&self) -> usize { + 1 + } + fn to_be_bytes(&self) -> Self::ByteArray { + u8::to_be_bytes(*self) + } +} + +impl ToBeBytes for u16 { + type ByteArray = [u8; 2]; + + fn written_len(&self) -> usize { + 2 + } + fn to_be_bytes(&self) -> Self::ByteArray { + u16::to_be_bytes(*self) + } +} + +impl ToBeBytes for u32 { + type ByteArray = [u8; 4]; + + fn written_len(&self) -> usize { + 4 + } + fn to_be_bytes(&self) -> Self::ByteArray { + u32::to_be_bytes(*self) + } +} + +impl ToBeBytes for u64 { + type ByteArray = [u8; 8]; + + fn written_len(&self) -> usize { + 8 + } + fn to_be_bytes(&self) -> Self::ByteArray { + u64::to_be_bytes(*self) + } +} + +#[allow(clippy::len_without_is_empty)] +pub trait UnsignedEnum { + fn len(&self) -> usize; + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError>; +} + +pub trait UnsignedEnumExt: UnsignedEnum + Debug + Copy + Clone + PartialEq + Eq {} + +/// Type erased variant. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct UnsignedByteField { + width: usize, + value: u64, +} + +impl UnsignedByteField { + pub fn new(width: usize, value: u64) -> Self { + Self { width, value } + } + + pub fn new_from_be_bytes(width: usize, buf: &[u8]) -> Result { + if width > buf.len() { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: width, + })); + } + match width { + 0 => Ok(Self::new(0, 0)), + 1 => Ok(Self::new(1, buf[0] as u64)), + 2 => Ok(Self::new( + 2, + u16::from_be_bytes(buf[0..2].try_into().unwrap()) as u64, + )), + 4 => Ok(Self::new( + 2, + u32::from_be_bytes(buf[0..4].try_into().unwrap()) as u64, + )), + 8 => Ok(Self::new( + 2, + u64::from_be_bytes(buf[0..8].try_into().unwrap()), + )), + // TODO: I don't know whether it is a good idea to panic here. + _ => panic!("invalid width"), + } + } +} + +impl UnsignedEnum for UnsignedByteField { + fn len(&self) -> usize { + self.width + } + + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { + if buf.len() < self.len() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + expected: self.len(), + found: buf.len(), + })); + } + match self.len() { + 0 => Ok(()), + 1 => { + let u8 = UnsignedU8::try_from(*self).unwrap(); + u8.write_to_be_bytes(buf) + } + 2 => { + let u16 = UnsignedU16::try_from(*self).unwrap(); + u16.write_to_be_bytes(buf) + } + 4 => { + let u32 = UnsignedU32::try_from(*self).unwrap(); + u32.write_to_be_bytes(buf) + } + 8 => { + let u64 = UnsignedU64::try_from(*self).unwrap(); + u64.write_to_be_bytes(buf) + } + _ => { + // The API does not allow this. + panic!("unexpected written length"); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct GenericUnsignedByteField { + val: TYPE, +} + +impl GenericUnsignedByteField { + pub fn new(val: TYPE) -> Self { + Self { val } + } +} + +impl UnsignedEnum for GenericUnsignedByteField { + fn len(&self) -> usize { + self.val.written_len() + } + + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { + if buf.len() < self.len() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.len(), + })); + } + buf[0..self.len()].copy_from_slice(self.val.to_be_bytes().as_ref()); + Ok(()) + } +} + +pub type UnsignedByteFieldEmpty = GenericUnsignedByteField<()>; +pub type UnsignedU8 = GenericUnsignedByteField; +pub type UnsignedU16 = GenericUnsignedByteField; +pub type UnsignedU32 = GenericUnsignedByteField; +pub type UnsignedU64 = GenericUnsignedByteField; + +impl From for UnsignedByteField { + fn from(value: UnsignedU8) -> Self { + Self::new(1, value.val as u64) + } +} + +impl TryFrom for UnsignedU8 { + type Error = (); + + fn try_from(value: UnsignedByteField) -> Result { + if value.value > 2_u64.pow(8) - 1 { + return Err(()); + } + Ok(Self::new(value.value as u8)) + } +} + +impl From for UnsignedByteField { + fn from(value: UnsignedU16) -> Self { + Self::new(2, value.val as u64) + } +} + +impl TryFrom for UnsignedU16 { + type Error = (); + + fn try_from(value: UnsignedByteField) -> Result { + if value.value > 2_u64.pow(16) - 1 { + return Err(()); + } + Ok(Self::new(value.value as u16)) + } +} + +impl From for UnsignedByteField { + fn from(value: UnsignedU32) -> Self { + Self::new(4, value.val as u64) + } +} + +impl TryFrom for UnsignedU32 { + type Error = (); + + fn try_from(value: UnsignedByteField) -> Result { + if value.value > 2_u64.pow(32) - 1 { + return Err(()); + } + Ok(Self::new(value.value as u32)) + } +} + +impl From for UnsignedByteField { + fn from(value: UnsignedU64) -> Self { + Self::new(8, value.val) + } +} + +impl TryFrom for UnsignedU64 { + type Error = (); + + fn try_from(value: UnsignedByteField) -> Result { + if value.value > 2_u64.pow(64) - 1 { + return Err(()); + } + Ok(Self::new(value.value)) + } +}