From 0304f132e3d431f08314b8fdc0c06d3459dec2cf Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 19 Feb 2023 18:45:14 +0100 Subject: [PATCH 01/48] add cfdp mod --- src/cfdp/mod.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/cfdp/mod.rs diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs new file mode 100644 index 0000000..e69de29 From 3457b3a8f958929da6ee48128b8f8e42d39a1f7d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 19 Feb 2023 20:52:42 +0100 Subject: [PATCH 02/48] add first definitions --- src/cfdp/mod.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 39 insertions(+) diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index e69de29..5943d78 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -0,0 +1,38 @@ +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub const CFDP_VERSION_2_NAME: &str = "CCSDS 727.0-B-5"; +pub const CFDP_VERSION_2: u8 = 0b001; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum PduType { + FileDirective = 0, + FileData = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum Direction { + TowardsReceiver = 0, + TowardsSender = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum TransmissionMode { + Acknowledged = 0, + Unacknowledged = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum CrcFlag { + NoCrc = 0, + WithCrc = 1, +} diff --git a/src/lib.rs b/src/lib.rs index a3b02e0..bdb2187 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ use std::error::Error; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +pub mod cfdp; pub mod ecss; pub mod tc; pub mod time; From 4bbf38916aa5763ece69b1722703361eac510234 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 14 May 2023 16:55:25 +0200 Subject: [PATCH 03/48] completed base definitions --- src/cfdp/mod.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index 5943d78..29330a6 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -36,3 +36,91 @@ pub enum CrcFlag { NoCrc = 0, WithCrc = 1, } + +/// Always 0 and ignores for File Directive PDUs (CCSDS 727.0-B-5 P.75) +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum SegmentMetadataFlag { + NotPresent = 0, + Present = 1, +} + +/// Always 0 and ignores for File Directive PDUs (CCSDS 727.0-B-5 P.75) +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum SegmentationControl { + NoRecordBoundaryPreservation = 0, + WithRecordBoundaryPreservation = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum FaultHandlerCode { + NoticeOfCancellation = 0b0001, + NoticeOfSuspension = 0b0010, + IgnoreError = 0b0011, + AbandonTransaction = 0b0100, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum LenInBytes { + ZeroOrNone = 0, + OneByte = 1, + TwoBytes = 2, + ThreeBytes = 4, + FourBytes = 8, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum ConditionCode { + /// This is not an error condition for which a faulty handler override can be specified + NoError = 0b0000, + PositiveAckLimitReached = 0b0001, + KeepAliveLimitReached = 0b0010, + InvalidTransmissionMode = 0b0011, + FilestoreRejection = 0b0100, + FileChecksumFailure = 0b0101, + FileSizeError = 0b0110, + NakLimitReached = 0b0111, + InactivityDetected = 0b1000, + CheckLimitReached = 0b1001, + UnsupportedChecksumType = 0b1011, + /// Not an actual fault condition for which fault handler overrides can be specified + SuspendRequestReceived = 0b1110, + /// Not an actual fault condition for which fault handler overrides can be specified + CancelRequestReceived = 0b1111, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum LargeFileFlag { + /// 32 bit maximum file size and FSS size + Normal = 0, + /// 64 bit maximum file size and FSS size + Large = 1, +} + +/// Checksum types according to the SANA Checksum Types registry +/// https://sanaregistry.org/r/checksum_identifiers/ +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum ChecksumType { + /// Modular legacy checksum + Modular = 0, + Crc32Proximity1 = 1, + Crc32C = 2, + /// Polynomial: 0x4C11DB7. Preferred checksum for now. + Crc32 = 3, + NullChecksum = 15, +} + +pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4]; From d2f944580ce3f4b38170a84335d4dd87d1b15583 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 14 May 2023 20:10:34 +0200 Subject: [PATCH 04/48] 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)) + } +} From 8d15091b42718451dd175a5ed31342fbb0efd14f Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 14 May 2023 20:11:31 +0200 Subject: [PATCH 05/48] added more to changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0370147..3e1d69f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - 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. + - `GenericUnsignedByteField` and helper typedefs `UnsignedU8`, `UnsignedU16`, `UnsignedU32` + and `UnsignedU64` as helper types implementing `UnsignedEnum` - `UnsignedByteField` as a type-erased helper. - - `UnsignedU8`, `UnsignedU16`, `UnsignedU32` and `UnsignedU64` as helper types implementing - `UnsignedEnum` - Initial CFDP support: Added PDU packet implementation. ## Changed From 8bb4d6d32e8751b05f354a6578cd6702752747af Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 14 May 2023 23:56:26 +0200 Subject: [PATCH 06/48] tests almost complete --- src/util.rs | 367 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 333 insertions(+), 34 deletions(-) diff --git a/src/util.rs b/src/util.rs index ee599a3..9705c5f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,9 @@ use crate::{ByteConversionError, SizeMissmatch}; -use core::fmt::Debug; +use core::fmt::{Debug, Display, Formatter}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "std")] +use std::error::Error; pub trait ToBeBytes { type ByteArray: AsRef<[u8]>; @@ -74,6 +76,43 @@ pub trait UnsignedEnum { pub trait UnsignedEnumExt: UnsignedEnum + Debug + Copy + Clone + PartialEq + Eq {} +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum UnsignedByteFieldError { + /// Value is too large for specified width of byte field. The first value contains the width, + /// the second value contains the detected value. + ValueTooLargeForWidth((usize, u64)), + /// Only 1, 2, 4 and 8 are allow width values. Optionally contains the expected width if + /// applicable, for example for conversions. + InvalidWidth(usize, Option), + ByteConversionError(ByteConversionError), +} + +impl From for UnsignedByteFieldError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversionError(value) + } +} + +impl Display for UnsignedByteFieldError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Self::ByteConversionError(e) => { + write!(f, "low level byte conversion error: {e}") + } + Self::InvalidWidth(val, _) => { + write!(f, "invalid width {val}, only 1, 2, 4 and 8 are allowed.") + } + Self::ValueTooLargeForWidth((width, value)) => { + write!(f, "value {value} too large for width {width}") + } + } + } +} + +#[cfg(feature = "std")] +impl Error for UnsignedByteFieldError {} + /// Type erased variant. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -87,30 +126,30 @@ impl UnsignedByteField { Self { width, value } } - pub fn new_from_be_bytes(width: usize, buf: &[u8]) -> Result { + 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, - })); + }) + .into()); } match width { - 0 => Ok(Self::new(0, 0)), - 1 => Ok(Self::new(1, buf[0] as u64)), + 0 => Ok(Self::new(width, 0)), + 1 => Ok(Self::new(width, buf[0] as u64)), 2 => Ok(Self::new( - 2, + width, u16::from_be_bytes(buf[0..2].try_into().unwrap()) as u64, )), 4 => Ok(Self::new( - 2, + width, u32::from_be_bytes(buf[0..4].try_into().unwrap()) as u64, )), 8 => Ok(Self::new( - 2, + width, 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"), + _ => Err(UnsignedByteFieldError::InvalidWidth(width, None)), } } } @@ -156,18 +195,18 @@ impl UnsignedEnum for UnsignedByteField { #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct GenericUnsignedByteField { - val: TYPE, + value: TYPE, } impl GenericUnsignedByteField { pub fn new(val: TYPE) -> Self { - Self { val } + Self { value: val } } } impl UnsignedEnum for GenericUnsignedByteField { fn len(&self) -> usize { - self.val.written_len() + self.value.written_len() } fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { @@ -177,7 +216,7 @@ impl UnsignedEnum for GenericUnsignedByteField { expected: self.len(), })); } - buf[0..self.len()].copy_from_slice(self.val.to_be_bytes().as_ref()); + buf[0..self.len()].copy_from_slice(self.value.to_be_bytes().as_ref()); Ok(()) } } @@ -190,16 +229,16 @@ pub type UnsignedU64 = GenericUnsignedByteField; impl From for UnsignedByteField { fn from(value: UnsignedU8) -> Self { - Self::new(1, value.val as u64) + Self::new(1, value.value as u64) } } impl TryFrom for UnsignedU8 { - type Error = (); + type Error = UnsignedByteFieldError; - fn try_from(value: UnsignedByteField) -> Result { - if value.value > 2_u64.pow(8) - 1 { - return Err(()); + fn try_from(value: UnsignedByteField) -> Result { + if value.width != 1 { + return Err(UnsignedByteFieldError::InvalidWidth(value.width, Some(1))); } Ok(Self::new(value.value as u8)) } @@ -207,16 +246,16 @@ impl TryFrom for UnsignedU8 { impl From for UnsignedByteField { fn from(value: UnsignedU16) -> Self { - Self::new(2, value.val as u64) + Self::new(2, value.value as u64) } } impl TryFrom for UnsignedU16 { - type Error = (); + type Error = UnsignedByteFieldError; - fn try_from(value: UnsignedByteField) -> Result { - if value.value > 2_u64.pow(16) - 1 { - return Err(()); + fn try_from(value: UnsignedByteField) -> Result { + if value.width != 2 { + return Err(UnsignedByteFieldError::InvalidWidth(value.width, Some(2))); } Ok(Self::new(value.value as u16)) } @@ -224,16 +263,16 @@ impl TryFrom for UnsignedU16 { impl From for UnsignedByteField { fn from(value: UnsignedU32) -> Self { - Self::new(4, value.val as u64) + Self::new(4, value.value as u64) } } impl TryFrom for UnsignedU32 { - type Error = (); + type Error = UnsignedByteFieldError; - fn try_from(value: UnsignedByteField) -> Result { - if value.value > 2_u64.pow(32) - 1 { - return Err(()); + fn try_from(value: UnsignedByteField) -> Result { + if value.width != 4 { + return Err(UnsignedByteFieldError::InvalidWidth(value.width, Some(4))); } Ok(Self::new(value.value as u32)) } @@ -241,17 +280,277 @@ impl TryFrom for UnsignedU32 { impl From for UnsignedByteField { fn from(value: UnsignedU64) -> Self { - Self::new(8, value.val) + Self::new(8, value.value) } } impl TryFrom for UnsignedU64 { - type Error = (); + type Error = UnsignedByteFieldError; - fn try_from(value: UnsignedByteField) -> Result { - if value.value > 2_u64.pow(64) - 1 { - return Err(()); + fn try_from(value: UnsignedByteField) -> Result { + if value.width != 8 { + return Err(UnsignedByteFieldError::InvalidWidth(value.width, Some(8))); } Ok(Self::new(value.value)) } } + +#[cfg(test)] +pub mod tests { + use crate::util::{ + UnsignedByteField, UnsignedByteFieldError, UnsignedEnum, UnsignedU16, UnsignedU32, + UnsignedU64, UnsignedU8, + }; + use std::format; + + #[test] + fn test_simple_u8() { + let u8 = UnsignedU8::new(5); + assert_eq!(u8.len(), 1); + let mut buf: [u8; 8] = [0; 8]; + u8.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + assert_eq!(buf[0], 5); + for i in 1..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn test_simple_u16() { + let u16 = UnsignedU16::new(3823); + assert_eq!(u16.len(), 2); + let mut buf: [u8; 8] = [0; 8]; + u16.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + let raw_val = u16::from_be_bytes(buf[0..2].try_into().unwrap()); + assert_eq!(raw_val, 3823); + for i in 2..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn test_simple_u32() { + let u32 = UnsignedU32::new(80932); + assert_eq!(u32.len(), 4); + let mut buf: [u8; 8] = [0; 8]; + u32.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + let raw_val = u32::from_be_bytes(buf[0..4].try_into().unwrap()); + assert_eq!(raw_val, 80932); + for i in 4..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn test_simple_u64() { + let u64 = UnsignedU64::new(5999999); + assert_eq!(u64.len(), 8); + let mut buf: [u8; 8] = [0; 8]; + u64.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + let raw_val = u64::from_be_bytes(buf[0..8].try_into().unwrap()); + assert_eq!(raw_val, 5999999); + } + + #[test] + fn conversions_u8() { + let u8 = UnsignedU8::new(5); + let u8_type_erased = UnsignedByteField::from(u8); + assert_eq!(u8_type_erased.width, 1); + assert_eq!(u8_type_erased.value, 5); + let u8_conv_back = UnsignedU8::try_from(u8_type_erased).expect("conversion failed for u8"); + assert_eq!(u8, u8_conv_back); + assert_eq!(u8_conv_back.value, 5); + } + + #[test] + fn conversion_u8_fails() { + let field = UnsignedByteField::new(2, 60000); + let conv_fails = UnsignedU8::try_from(field); + assert!(conv_fails.is_err()); + let err = conv_fails.unwrap_err(); + match err { + UnsignedByteFieldError::InvalidWidth(width, Some(expected)) => { + assert_eq!(width, 2); + assert_eq!(expected, 1); + } + _ => { + panic!("{}", format!("invalid error {err}")) + } + } + } + + #[test] + fn conversions_u16() { + let u16 = UnsignedU16::new(64444); + let u16_type_erased = UnsignedByteField::from(u16); + assert_eq!(u16_type_erased.width, 2); + assert_eq!(u16_type_erased.value, 64444); + let u16_conv_back = + UnsignedU16::try_from(u16_type_erased).expect("conversion failed for u16"); + assert_eq!(u16, u16_conv_back); + assert_eq!(u16_conv_back.value, 64444); + } + + #[test] + fn conversion_u16_fails() { + let field = UnsignedByteField::new(4, 75000); + let conv_fails = UnsignedU16::try_from(field); + assert!(conv_fails.is_err()); + let err = conv_fails.unwrap_err(); + match err { + UnsignedByteFieldError::InvalidWidth(width, Some(expected)) => { + assert_eq!(width, 4); + assert_eq!(expected, 2); + } + _ => { + panic!("{}", format!("invalid error {err}")) + } + } + } + + #[test] + fn conversions_u32() { + let u32 = UnsignedU32::new(75000); + let u32_type_erased = UnsignedByteField::from(u32); + assert_eq!(u32_type_erased.width, 4); + assert_eq!(u32_type_erased.value, 75000); + let u32_conv_back = + UnsignedU32::try_from(u32_type_erased).expect("conversion failed for u32"); + assert_eq!(u32, u32_conv_back); + assert_eq!(u32_conv_back.value, 75000); + } + + #[test] + fn conversion_u32_fails() { + let field = UnsignedByteField::new(8, 75000); + let conv_fails = UnsignedU32::try_from(field); + assert!(conv_fails.is_err()); + let err = conv_fails.unwrap_err(); + match err { + UnsignedByteFieldError::InvalidWidth(width, Some(expected)) => { + assert_eq!(width, 8); + assert_eq!(expected, 4); + } + _ => { + panic!("{}", format!("invalid error {err}")) + } + } + } + + #[test] + fn conversions_u64() { + let u64 = UnsignedU64::new(5999999); + let u64_type_erased = UnsignedByteField::from(u64); + assert_eq!(u64_type_erased.width, 8); + assert_eq!(u64_type_erased.value, 5999999); + let u64_conv_back = + UnsignedU64::try_from(u64_type_erased).expect("conversion failed for u64"); + assert_eq!(u64, u64_conv_back); + assert_eq!(u64_conv_back.value, 5999999); + } + + #[test] + fn conversion_u64_fails() { + let field = UnsignedByteField::new(4, 60000); + let conv_fails = UnsignedU64::try_from(field); + assert!(conv_fails.is_err()); + let err = conv_fails.unwrap_err(); + match err { + UnsignedByteFieldError::InvalidWidth(width, Some(expected)) => { + assert_eq!(width, 4); + assert_eq!(expected, 8); + } + _ => { + panic!("{}", format!("invalid error {err}")) + } + } + } + + #[test] + fn type_erased_u8_write() { + let u8 = UnsignedByteField::new(1, 5); + assert_eq!(u8.len(), 1); + let mut buf: [u8; 8] = [0; 8]; + u8.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + assert_eq!(buf[0], 5); + for i in 1..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn type_erased_u16_write() { + let u16 = UnsignedByteField::new(2, 3823); + assert_eq!(u16.len(), 2); + let mut buf: [u8; 8] = [0; 8]; + u16.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + let raw_val = u16::from_be_bytes(buf[0..2].try_into().unwrap()); + assert_eq!(raw_val, 3823); + for i in 2..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn type_erased_u32_write() { + let u32 = UnsignedByteField::new(4, 80932); + assert_eq!(u32.len(), 4); + let mut buf: [u8; 8] = [0; 8]; + u32.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + let raw_val = u32::from_be_bytes(buf[0..4].try_into().unwrap()); + assert_eq!(raw_val, 80932); + for i in 4..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn type_erased_u64_write() { + let u64 = UnsignedByteField::new(8, 5999999); + assert_eq!(u64.len(), 8); + let mut buf: [u8; 8] = [0; 8]; + u64.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + let raw_val = u64::from_be_bytes(buf[0..8].try_into().unwrap()); + assert_eq!(raw_val, 5999999); + } + + #[test] + fn type_erased_u8_construction() { + let buf: [u8; 2] = [5, 10]; + let u8 = UnsignedByteField::new_from_be_bytes(1, &buf).expect("construction failed"); + assert_eq!(u8.width, 1); + assert_eq!(u8.value, 5); + } + + #[test] + fn type_erased_u16_construction() { + let buf: [u8; 2] = [0x10, 0x15]; + let u16 = UnsignedByteField::new_from_be_bytes(2, &buf).expect("construction failed"); + assert_eq!(u16.width, 2); + assert_eq!(u16.value, 0x1015); + } + + #[test] + fn type_erased_u32_construction() { + let buf: [u8; 4] = [0x01, 0x02, 0x03, 0x04]; + let u32 = UnsignedByteField::new_from_be_bytes(4, &buf).expect("construction failed"); + assert_eq!(u32.width, 4); + assert_eq!(u32.value, 0x01020304); + } + + #[test] + fn type_erased_u64_construction() { + let buf: [u8; 8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]; + let u64 = UnsignedByteField::new_from_be_bytes(8, &buf).expect("construction failed"); + assert_eq!(u64.width, 8); + assert_eq!(u64.value, 0x0102030405060708); + } +} From 074882c16055a31119147928d96776e04e7a3c13 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 15 May 2023 00:11:41 +0200 Subject: [PATCH 07/48] tests done --- src/util.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/util.rs b/src/util.rs index 9705c5f..c59d4b3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -129,8 +129,8 @@ impl UnsignedByteField { 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, + found: buf.len(), }) .into()); } @@ -301,6 +301,7 @@ pub mod tests { UnsignedByteField, UnsignedByteFieldError, UnsignedEnum, UnsignedU16, UnsignedU32, UnsignedU64, UnsignedU8, }; + use crate::ByteConversionError; use std::format; #[test] @@ -553,4 +554,70 @@ pub mod tests { assert_eq!(u64.width, 8); assert_eq!(u64.value, 0x0102030405060708); } + + #[test] + fn type_u16_target_buf_too_small() { + let u16 = UnsignedU16::new(500); + let mut buf: [u8; 1] = [0; 1]; + let res = u16.write_to_be_bytes(&mut buf); + assert!(res.is_err()); + let err = res.unwrap_err(); + match err { + ByteConversionError::ToSliceTooSmall(missmatch) => { + assert_eq!(missmatch.found, 1); + assert_eq!(missmatch.expected, 2); + } + _ => { + panic!("invalid exception") + } + } + } + + #[test] + fn type_erased_u16_target_buf_too_small() { + let u16 = UnsignedByteField::new(2, 500); + let mut buf: [u8; 1] = [0; 1]; + let res = u16.write_to_be_bytes(&mut buf); + assert!(res.is_err()); + let err = res.unwrap_err(); + match err { + ByteConversionError::ToSliceTooSmall(missmatch) => { + assert_eq!(missmatch.found, 1); + assert_eq!(missmatch.expected, 2); + } + _ => { + panic!("invalid exception {}", err) + } + } + let u16 = UnsignedByteField::new_from_be_bytes(2, &buf); + assert!(u16.is_err()); + let err = u16.unwrap_err(); + if let UnsignedByteFieldError::ByteConversionError( + ByteConversionError::FromSliceTooSmall(missmatch), + ) = err + { + assert_eq!(missmatch.expected, 2); + assert_eq!(missmatch.found, 1); + } else { + panic!("unexpected exception {}", err); + } + } + + #[test] + fn type_u32_target_buf_too_small() { + let u16 = UnsignedU32::new(500); + let mut buf: [u8; 3] = [0; 3]; + let res = u16.write_to_be_bytes(&mut buf); + assert!(res.is_err()); + let err = res.unwrap_err(); + match err { + ByteConversionError::ToSliceTooSmall(missmatch) => { + assert_eq!(missmatch.found, 3); + assert_eq!(missmatch.expected, 4); + } + _ => { + panic!("invalid exception") + } + } + } } From 53eb1845345055c0da1041a576a64e3dbb74fa93 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 15 May 2023 01:01:46 +0200 Subject: [PATCH 08/48] add first PDU header tests --- src/cfdp/pdu/mod.rs | 158 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 141 insertions(+), 17 deletions(-) diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 38860bb..f789fdb 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -69,29 +69,49 @@ impl From for PduError { 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, + pub transaction_seq_num: UnsignedByteField, + pub trans_mode: TransmissionMode, + pub file_flag: LargeFileFlag, + pub crc_flag: CrcFlag, + pub direction: Direction, } +// TODO: Build might be applicable here.. impl CommonPduConfig { pub fn new( - source_id: UnsignedByteField, - dest_id: UnsignedByteField, - transaction_seq_num: UnsignedByteField, + source_id: impl Into, + dest_id: impl Into, + transaction_seq_num: impl Into, trans_mode: TransmissionMode, file_flag: LargeFileFlag, crc_flag: CrcFlag, direction: Direction, ) -> Result { + let source_id = source_id.into(); + let dest_id = dest_id.into(); + let transaction_seq_num = transaction_seq_num.into(); if source_id.len() != dest_id.len() { return Err(PduError::SourceDestIdLenMissmatch(( source_id.len(), dest_id.len(), ))); } + if source_id.len() != 1 + && source_id.len() != 2 + && source_id.len() != 4 + && source_id.len() != 8 + { + return Err(PduError::InvalidEntityLen(source_id.len() as u8)); + } + if transaction_seq_num.len() != 1 + && transaction_seq_num.len() != 2 + && transaction_seq_num.len() != 4 + && transaction_seq_num.len() != 8 + { + return Err(PduError::InvalidTransactionSeqNumLen( + transaction_seq_num.len() as u8, + )); + } Ok(Self { source_entity_id: source_id, dest_entity_id: dest_id, @@ -102,7 +122,34 @@ impl CommonPduConfig { direction, }) } + + pub fn new_with_defaults( + source_id: impl Into, + dest_id: impl Into, + transaction_seq_num: impl Into, + ) -> Result { + Self::new( + source_id, + dest_id, + transaction_seq_num, + TransmissionMode::Acknowledged, + LargeFileFlag::Normal, + CrcFlag::NoCrc, + Direction::TowardsReceiver, + ) + } + + pub fn source_id(&self) -> UnsignedByteField { + self.source_entity_id + } + + pub fn dest_id(&self) -> UnsignedByteField { + self.dest_entity_id + } } + +const MIN_HEADER_LEN: usize = 4; + /// Abstraction for the PDU header common to all CFDP PDUs #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -132,7 +179,7 @@ impl PduHeader { pub fn new_no_file_data(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self { PduHeader { - pdu_type: PduType::FileData, + pdu_type: PduType::FileDirective, pdu_conf, seg_metadata_flag: SegmentMetadataFlag::NotPresent, seg_ctrl: SegmentationControl::NoRecordBoundaryPreservation, @@ -148,11 +195,13 @@ impl PduHeader { ))); } if buf.len() - < 4 + self.pdu_conf.source_entity_id.len() + self.pdu_conf.transaction_seq_num.len() + < MIN_HEADER_LEN + + self.pdu_conf.source_entity_id.len() + + self.pdu_conf.transaction_seq_num.len() { return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { found: buf.len(), - expected: 4, + expected: MIN_HEADER_LEN, }) .into()); } @@ -167,9 +216,10 @@ impl PduHeader { 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.pdu_conf.source_entity_id.len() - 1) as u8) << 4) | ((self.seg_metadata_flag as u8) << 3) - | (self.pdu_conf.transaction_seq_num.len() as u8); + | ((self.pdu_conf.transaction_seq_num.len() - 1) as u8); + current_idx += 1; self.pdu_conf.source_entity_id.write_to_be_bytes( &mut buf[current_idx..current_idx + self.pdu_conf.source_entity_id.len()], )?; @@ -185,19 +235,19 @@ impl PduHeader { } pub fn from_be_bytes(buf: &[u8]) -> Result { - if buf.len() < 4 { + if buf.len() < MIN_HEADER_LEN { return Err(PduError::ByteConversionError( ByteConversionError::FromSliceTooSmall(SizeMissmatch { found: buf.len(), - expected: 4, + expected: MIN_HEADER_LEN, }), )); } - let cfdp_version_raw = buf[0] >> 5 & 0b111; + 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 + // unwrap for single bit fields: This operation will always succeed. 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(); @@ -232,6 +282,8 @@ impl PduHeader { .into()); } let mut current_idx = 4; + // It is okay to unwrap here because we checked the validity of the expected length and of + // the remaining buffer length. let source_id = UnsignedByteField::new_from_be_bytes(expected_len_entity_ids, &buf[current_idx..]) .unwrap(); @@ -268,4 +320,76 @@ impl PduHeader { pub fn common_pdu_conf(&self) -> &CommonPduConfig { &self.pdu_conf } + + pub fn seg_metadata_flag(&self) -> SegmentMetadataFlag { + self.seg_metadata_flag + } + pub fn seg_ctrl(&self) -> SegmentationControl { + self.seg_ctrl + } +} + +#[cfg(test)] +mod tests { + use crate::cfdp::pdu::{CommonPduConfig, PduHeader}; + use crate::cfdp::{ + Direction, PduType, SegmentMetadataFlag, SegmentationControl, CFDP_VERSION_2, + }; + use crate::util::UnsignedU8; + + #[test] + fn test_basic_state() { + let src_id = UnsignedU8::new(1); + let dest_id = UnsignedU8::new(2); + let transaction_id = UnsignedU8::new(3); + let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); + assert_eq!(pdu_header.pdu_type(), PduType::FileDirective); + let common_conf_ref = pdu_header.common_pdu_conf(); + assert_eq!(*common_conf_ref, common_pdu_cfg); + // These should be 0 and ignored for non-filedata PDUs + assert_eq!( + pdu_header.seg_metadata_flag(), + SegmentMetadataFlag::NotPresent + ); + assert_eq!( + pdu_header.seg_ctrl(), + SegmentationControl::NoRecordBoundaryPreservation + ); + } + + #[test] + fn test_serialization() { + let src_id = UnsignedU8::new(1); + let dest_id = UnsignedU8::new(2); + let transaction_id = UnsignedU8::new(3); + let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); + let mut buf: [u8; 7] = [0; 7]; + let res = pdu_header.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + assert_eq!((buf[0] >> 5) & 0b111, CFDP_VERSION_2); + // File directive + assert_eq!((buf[0] >> 4) & 1, 0); + // Towards receiver + assert_eq!((buf[0] >> 3) & 1, 0); + // Acknowledged + assert_eq!((buf[0] >> 2) & 1, 0); + // No CRC + assert_eq!((buf[0] >> 1) & 1, 0); + // Regular file size + assert_eq!(buf[0] & 1, 0); + let pdu_datafield_len = u16::from_be_bytes(buf[1..3].try_into().unwrap()); + assert_eq!(pdu_datafield_len, 5); + // No record boundary preservation + assert_eq!((buf[3] >> 7) & 1, 0); + // Entity ID length raw value is actual number of octets - 1 => 0 + assert_eq!((buf[3] >> 4) & 0b111, 0); + // No segment metadata + assert_eq!((buf[3] >> 3) & 0b1, 0); + // Transaction Sequence ID length raw value is actual number of octets - 1 => 0 + assert_eq!(buf[3] & 0b111, 0); + } } From a67718cff25e30e30db779ed7c02f5764d5b0c70 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 15 May 2023 01:03:15 +0200 Subject: [PATCH 09/48] bugfix --- src/cfdp/pdu/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index f789fdb..575aaf5 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -255,7 +255,7 @@ impl PduHeader { 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; + let expected_len_entity_ids = (((buf[3] >> 4) & 0b111) + 1) as usize; if (expected_len_entity_ids != 1) && (expected_len_entity_ids != 2) && (expected_len_entity_ids != 4) @@ -264,7 +264,7 @@ impl PduHeader { 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; + let expected_len_seq_num = ((buf[3] & 0b111) + 1) as usize; if (expected_len_seq_num != 1) && (expected_len_seq_num != 2) && (expected_len_seq_num != 4) From 0a3848d0a22ec50ca31088c4a28196d1981f0e8f Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 18 May 2023 00:46:58 +0200 Subject: [PATCH 10/48] add more tests --- src/cfdp/pdu/mod.rs | 92 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 6 deletions(-) diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 575aaf5..de239a3 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -164,9 +164,9 @@ pub struct PduHeader { impl PduHeader { pub fn new_for_file_data( pdu_conf: CommonPduConfig, + pdu_datafield_len: u16, seg_metadata_flag: SegmentMetadataFlag, seg_ctrl: SegmentationControl, - pdu_datafield_len: u16, ) -> Self { PduHeader { pdu_type: PduType::FileData, @@ -187,7 +187,7 @@ impl PduHeader { } } - pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), PduError> { + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { 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(), @@ -231,7 +231,8 @@ impl PduHeader { self.pdu_conf.dest_entity_id.write_to_be_bytes( &mut buf[current_idx..current_idx + self.pdu_conf.dest_entity_id.len()], )?; - Ok(()) + current_idx += self.pdu_conf.dest_entity_id.len(); + Ok(current_idx) } pub fn from_be_bytes(buf: &[u8]) -> Result { @@ -333,9 +334,11 @@ impl PduHeader { mod tests { use crate::cfdp::pdu::{CommonPduConfig, PduHeader}; use crate::cfdp::{ - Direction, PduType, SegmentMetadataFlag, SegmentationControl, CFDP_VERSION_2, + CrcFlag, Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, + TransmissionMode, CFDP_VERSION_2, }; - use crate::util::UnsignedU8; + use crate::util::{UnsignedU16, UnsignedU8}; + use std::format; #[test] fn test_basic_state() { @@ -360,7 +363,27 @@ mod tests { } #[test] - fn test_serialization() { + fn test_state() { + let src_id = UnsignedU8::new(1); + let dest_id = UnsignedU8::new(2); + let transaction_id = UnsignedU8::new(3); + let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); + assert_eq!(pdu_header.pdu_type, PduType::FileDirective); + assert_eq!(pdu_header.pdu_datafield_len, 5); + assert_eq!( + pdu_header.seg_ctrl, + SegmentationControl::NoRecordBoundaryPreservation + ); + assert_eq!( + pdu_header.seg_metadata_flag, + SegmentMetadataFlag::NotPresent + ); + } + + #[test] + fn test_serialization_1() { let src_id = UnsignedU8::new(1); let dest_id = UnsignedU8::new(2); let transaction_id = UnsignedU8::new(3); @@ -370,6 +393,8 @@ mod tests { let mut buf: [u8; 7] = [0; 7]; let res = pdu_header.write_to_be_bytes(&mut buf); assert!(res.is_ok()); + // 4 byte fixed header plus three bytes src, dest ID and transaction ID + assert_eq!(res.unwrap(), 7); assert_eq!((buf[0] >> 5) & 0b111, CFDP_VERSION_2); // File directive assert_eq!((buf[0] >> 4) & 1, 0); @@ -391,5 +416,60 @@ mod tests { assert_eq!((buf[3] >> 3) & 0b1, 0); // Transaction Sequence ID length raw value is actual number of octets - 1 => 0 assert_eq!(buf[3] & 0b111, 0); + assert_eq!(buf[4], 1); + assert_eq!(buf[5], 3); + assert_eq!(buf[6], 2); } + + #[test] + fn test_serialization_2() { + let src_id = UnsignedU16::new(0x0001); + let dest_id = UnsignedU16::new(0x0203); + let transaction_id = UnsignedU16::new(0x0405); + let mut common_pdu_cfg = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + common_pdu_cfg.crc_flag = CrcFlag::WithCrc; + common_pdu_cfg.direction = Direction::TowardsSender; + common_pdu_cfg.trans_mode = TransmissionMode::Unacknowledged; + common_pdu_cfg.file_flag = LargeFileFlag::Large; + let pdu_header = PduHeader::new_for_file_data( + common_pdu_cfg, + 5, + SegmentMetadataFlag::Present, + SegmentationControl::WithRecordBoundaryPreservation, + ); + let mut buf: [u8; 16] = [0; 16]; + let res = pdu_header.write_to_be_bytes(&mut buf); + assert!(res.is_ok(), "{}", format!("Result {res:?} not okay")); + // 4 byte fixed header, 6 bytes additional fields + assert_eq!(res.unwrap(), 10); + assert_eq!((buf[0] >> 5) & 0b111, CFDP_VERSION_2); + // File directive + assert_eq!((buf[0] >> 4) & 1, 1); + // Towards sender + assert_eq!((buf[0] >> 3) & 1, 1); + // Unacknowledged + assert_eq!((buf[0] >> 2) & 1, 1); + // With CRC + assert_eq!((buf[0] >> 1) & 1, 1); + // Large file size + assert_eq!(buf[0] & 1, 1); + let pdu_datafield_len = u16::from_be_bytes(buf[1..3].try_into().unwrap()); + assert_eq!(pdu_datafield_len, 5); + // With record boundary preservation + assert_eq!((buf[3] >> 7) & 1, 1); + // Entity ID length raw value is actual number of octets - 1 => 1 + assert_eq!((buf[3] >> 4) & 0b111, 1); + // With segment metadata + assert_eq!((buf[3] >> 3) & 0b1, 1); + // Transaction Sequence ID length raw value is actual number of octets - 1 => 1 + assert_eq!(buf[3] & 0b111, 1); + assert_eq!(u16::from_be_bytes(buf[4..6].try_into().unwrap()), 0x0001); + assert_eq!(u16::from_be_bytes(buf[6..8].try_into().unwrap()), 0x0405); + assert_eq!(u16::from_be_bytes(buf[8..10].try_into().unwrap()), 0x0203); + } + + #[test] + fn test_deserialization_1() {} } From 02bae5de6c5d0b4dc9784642bb4a83850e4a28bf Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 18 May 2023 11:08:46 +0200 Subject: [PATCH 11/48] almost completed PDU header impl --- src/cfdp/pdu/mod.rs | 160 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 125 insertions(+), 35 deletions(-) diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index de239a3..e3954ef 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -76,7 +76,7 @@ pub struct CommonPduConfig { pub direction: Direction, } -// TODO: Build might be applicable here.. +// TODO: Builder pattern might be applicable here.. impl CommonPduConfig { pub fn new( source_id: impl Into, @@ -148,7 +148,7 @@ impl CommonPduConfig { } } -const MIN_HEADER_LEN: usize = 4; +const FIXED_HEADER_LEN: usize = 4; /// Abstraction for the PDU header common to all CFDP PDUs #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -195,13 +195,13 @@ impl PduHeader { ))); } if buf.len() - < MIN_HEADER_LEN + < FIXED_HEADER_LEN + self.pdu_conf.source_entity_id.len() + self.pdu_conf.transaction_seq_num.len() { return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { found: buf.len(), - expected: MIN_HEADER_LEN, + expected: FIXED_HEADER_LEN, }) .into()); } @@ -235,12 +235,12 @@ impl PduHeader { Ok(current_idx) } - pub fn from_be_bytes(buf: &[u8]) -> Result { - if buf.len() < MIN_HEADER_LEN { + pub fn from_be_bytes(buf: &[u8]) -> Result<(Self, usize), PduError> { + if buf.len() < FIXED_HEADER_LEN { return Err(PduError::ByteConversionError( ByteConversionError::FromSliceTooSmall(SizeMissmatch { found: buf.len(), - expected: MIN_HEADER_LEN, + expected: FIXED_HEADER_LEN, }), )); } @@ -296,6 +296,7 @@ impl PduHeader { let dest_id = UnsignedByteField::new_from_be_bytes(expected_len_entity_ids, &buf[current_idx..]) .unwrap(); + current_idx += expected_len_entity_ids; let common_pdu_conf = CommonPduConfig::new( source_id, dest_id, @@ -306,13 +307,16 @@ impl PduHeader { direction, ) .unwrap(); - Ok(PduHeader { - pdu_type, - pdu_conf: common_pdu_conf, - seg_metadata_flag, - seg_ctrl, - pdu_datafield_len, - }) + Ok(( + PduHeader { + pdu_type, + pdu_conf: common_pdu_conf, + seg_metadata_flag, + seg_ctrl, + pdu_datafield_len, + }, + current_idx, + )) } pub fn pdu_type(&self) -> PduType { self.pdu_type @@ -332,12 +336,13 @@ impl PduHeader { #[cfg(test)] mod tests { - use crate::cfdp::pdu::{CommonPduConfig, PduHeader}; + use crate::cfdp::pdu::{CommonPduConfig, PduError, PduHeader, FIXED_HEADER_LEN}; use crate::cfdp::{ CrcFlag, Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, TransmissionMode, CFDP_VERSION_2, }; use crate::util::{UnsignedU16, UnsignedU8}; + use crate::ByteConversionError; use std::format; #[test] @@ -360,26 +365,7 @@ mod tests { pdu_header.seg_ctrl(), SegmentationControl::NoRecordBoundaryPreservation ); - } - - #[test] - fn test_state() { - let src_id = UnsignedU8::new(1); - let dest_id = UnsignedU8::new(2); - let transaction_id = UnsignedU8::new(3); - let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) - .expect("common config creation failed"); - let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); - assert_eq!(pdu_header.pdu_type, PduType::FileDirective); assert_eq!(pdu_header.pdu_datafield_len, 5); - assert_eq!( - pdu_header.seg_ctrl, - SegmentationControl::NoRecordBoundaryPreservation - ); - assert_eq!( - pdu_header.seg_metadata_flag, - SegmentMetadataFlag::NotPresent - ); } #[test] @@ -421,6 +407,24 @@ mod tests { assert_eq!(buf[6], 2); } + #[test] + fn test_deserialization_1() { + let src_id = UnsignedU8::new(1); + let dest_id = UnsignedU8::new(2); + let transaction_id = UnsignedU8::new(3); + let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); + let mut buf: [u8; 7] = [0; 7]; + let res = pdu_header.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let deser_res = PduHeader::from_be_bytes(&buf); + assert!(deser_res.is_ok()); + let (header_read_back, read_size) = deser_res.unwrap(); + assert_eq!(read_size, 7); + assert_eq!(header_read_back, pdu_header); + } + #[test] fn test_serialization_2() { let src_id = UnsignedU16::new(0x0001); @@ -471,5 +475,91 @@ mod tests { } #[test] - fn test_deserialization_1() {} + fn test_deserialization_2() { + let src_id = UnsignedU16::new(0x0001); + let dest_id = UnsignedU16::new(0x0203); + let transaction_id = UnsignedU16::new(0x0405); + let mut common_pdu_cfg = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + common_pdu_cfg.crc_flag = CrcFlag::WithCrc; + common_pdu_cfg.direction = Direction::TowardsSender; + common_pdu_cfg.trans_mode = TransmissionMode::Unacknowledged; + common_pdu_cfg.file_flag = LargeFileFlag::Large; + let pdu_header = PduHeader::new_for_file_data( + common_pdu_cfg, + 5, + SegmentMetadataFlag::Present, + SegmentationControl::WithRecordBoundaryPreservation, + ); + let mut buf: [u8; 16] = [0; 16]; + let res = pdu_header.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let deser_res = PduHeader::from_be_bytes(&buf); + assert!(deser_res.is_ok()); + let (header_read_back, read_size) = deser_res.unwrap(); + assert_eq!(read_size, 10); + assert_eq!(header_read_back, pdu_header); + } + + #[test] + fn test_invalid_raw_version() { + let src_id = UnsignedU8::new(1); + let dest_id = UnsignedU8::new(2); + let transaction_id = UnsignedU8::new(3); + let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); + let mut buf: [u8; 7] = [0; 7]; + let res = pdu_header.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + buf[0] &= !0b1110_0000; + buf[0] |= (CFDP_VERSION_2 + 1) << 5; + let res = PduHeader::from_be_bytes(&buf); + assert!(res.is_err()); + let error = res.unwrap_err(); + if let PduError::CfdpVersionMissmatch(raw_version) = error { + assert_eq!(raw_version, CFDP_VERSION_2 + 1); + } else { + panic!("invalid exception: {}", error); + } + } + + #[test] + fn test_buf_too_small_1() { + let buf: [u8; 3] = [0; 3]; + let res = PduHeader::from_be_bytes(&buf); + assert!(res.is_err()); + let error = res.unwrap_err(); + if let PduError::ByteConversionError(ByteConversionError::FromSliceTooSmall(missmatch)) = + error + { + assert_eq!(missmatch.found, 3); + assert_eq!(missmatch.expected, FIXED_HEADER_LEN); + } else { + panic!("invalid exception: {}", error); + } + } + + #[test] + fn test_buf_too_small_2() { + let src_id = UnsignedU8::new(1); + let dest_id = UnsignedU8::new(2); + let transaction_id = UnsignedU8::new(3); + let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); + let mut buf: [u8; 7] = [0; 7]; + let res = pdu_header.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let header = PduHeader::from_be_bytes(&buf[0..6]); + assert!(header.is_err()); + let error = header.unwrap_err(); + if let PduError::ByteConversionError(ByteConversionError::FromSliceTooSmall(missmatch)) = + error + { + assert_eq!(missmatch.found, 6); + assert_eq!(missmatch.expected, 7); + } + } } From d5a3e7c0d48029b8ab4db3ad60e86fcca5cf6b81 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 18 May 2023 13:32:45 +0200 Subject: [PATCH 12/48] PDU header base impl done --- src/cfdp/pdu/mod.rs | 141 ++++++++++++++++++++++++++++++++++++-------- src/util.rs | 86 ++++++++++++++------------- 2 files changed, 162 insertions(+), 65 deletions(-) diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index e3954ef..4a4f40d 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -26,13 +26,13 @@ impl Display for PduError { PduError::InvalidEntityLen(raw_id) => { write!( f, - "Invalid entity ID length {raw_id}, only [1, 2, 4, 8] are allowed" + "Invalid PDU 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" + "invalid PDUtransaction seq num length {raw_id}, only [1, 2, 4, 8] are allowed" ) } PduError::CfdpVersionMissmatch(raw) => { @@ -44,7 +44,7 @@ impl Display for PduError { PduError::SourceDestIdLenMissmatch((src_len, dest_len)) => { write!( f, - "missmatch of source length {src_len} and destination length {dest_len}" + "missmatch of PDU source length {src_len} and destination length {dest_len}" ) } PduError::ByteConversionError(e) => { @@ -188,6 +188,8 @@ impl PduHeader { } pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + // Internal note: There is currently no way to pass a PDU configuration like this, but + // this check is still kept for defensive programming. 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(), @@ -341,15 +343,15 @@ mod tests { CrcFlag, Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, TransmissionMode, CFDP_VERSION_2, }; - use crate::util::{UnsignedU16, UnsignedU8}; + use crate::util::{UbfU8, UnsignedByteField, UnsignedByteFieldU16, UnsignedByteFieldU8}; use crate::ByteConversionError; use std::format; #[test] fn test_basic_state() { - let src_id = UnsignedU8::new(1); - let dest_id = UnsignedU8::new(2); - let transaction_id = UnsignedU8::new(3); + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::new(3); let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); @@ -370,9 +372,9 @@ mod tests { #[test] fn test_serialization_1() { - let src_id = UnsignedU8::new(1); - let dest_id = UnsignedU8::new(2); - let transaction_id = UnsignedU8::new(3); + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::new(3); let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); @@ -409,9 +411,9 @@ mod tests { #[test] fn test_deserialization_1() { - let src_id = UnsignedU8::new(1); - let dest_id = UnsignedU8::new(2); - let transaction_id = UnsignedU8::new(3); + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::new(3); let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); @@ -427,9 +429,9 @@ mod tests { #[test] fn test_serialization_2() { - let src_id = UnsignedU16::new(0x0001); - let dest_id = UnsignedU16::new(0x0203); - let transaction_id = UnsignedU16::new(0x0405); + let src_id = UnsignedByteFieldU16::new(0x0001); + let dest_id = UnsignedByteFieldU16::new(0x0203); + let transaction_id = UnsignedByteFieldU16::new(0x0405); let mut common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) .expect("common config creation failed"); @@ -476,9 +478,9 @@ mod tests { #[test] fn test_deserialization_2() { - let src_id = UnsignedU16::new(0x0001); - let dest_id = UnsignedU16::new(0x0203); - let transaction_id = UnsignedU16::new(0x0405); + let src_id = UnsignedByteFieldU16::new(0x0001); + let dest_id = UnsignedByteFieldU16::new(0x0203); + let transaction_id = UnsignedByteFieldU16::new(0x0405); let mut common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) .expect("common config creation failed"); @@ -504,9 +506,9 @@ mod tests { #[test] fn test_invalid_raw_version() { - let src_id = UnsignedU8::new(1); - let dest_id = UnsignedU8::new(2); - let transaction_id = UnsignedU8::new(3); + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::new(3); let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); @@ -543,9 +545,9 @@ mod tests { #[test] fn test_buf_too_small_2() { - let src_id = UnsignedU8::new(1); - let dest_id = UnsignedU8::new(2); - let transaction_id = UnsignedU8::new(3); + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::new(3); let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); @@ -562,4 +564,93 @@ mod tests { assert_eq!(missmatch.expected, 7); } } + + #[test] + fn test_invalid_seq_len() { + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_id = UbfU8::new(3); + let invalid_byte_field = UnsignedByteField::new(3, 5); + let pdu_conf_res = CommonPduConfig::new_with_defaults(src_id, dest_id, invalid_byte_field); + assert!(pdu_conf_res.is_err()); + let error = pdu_conf_res.unwrap_err(); + if let PduError::InvalidTransactionSeqNumLen(len) = error { + assert_eq!(len, 3); + } else { + panic!("Invalid exception: {}", error) + } + let pdu_conf_res = CommonPduConfig::new_with_defaults( + invalid_byte_field, + invalid_byte_field, + transaction_seq_id, + ); + assert!(pdu_conf_res.is_err()); + let error = pdu_conf_res.unwrap_err(); + if let PduError::InvalidEntityLen(len) = error { + assert_eq!(len, 3); + } else { + panic!("Invalid exception: {}", error) + } + } + #[test] + fn test_missmatch_src_dest_id() { + let src_id = UnsignedByteField::new(1, 5); + let dest_id = UnsignedByteField::new(2, 5); + let transaction_seq_id = UbfU8::new(3); + let pdu_conf_res = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_id); + assert!(pdu_conf_res.is_err()); + let error = pdu_conf_res.unwrap_err(); + if let PduError::SourceDestIdLenMissmatch((src_len, dest_len)) = error { + assert_eq!(src_len, 1); + assert_eq!(dest_len, 2); + } + } + + #[test] + fn test_invalid_raw_src_id_len() { + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::new(3); + let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); + let mut buf: [u8; 7] = [0; 7]; + let res = pdu_header.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + buf[3] &= !0b0111_0000; + // Equivalent to the length of three + buf[3] |= 0b10 << 4; + let header_res = PduHeader::from_be_bytes(&buf); + assert!(header_res.is_err()); + let error = header_res.unwrap_err(); + if let PduError::InvalidEntityLen(len) = error { + assert_eq!(len, 3); + } else { + panic!("invalid exception {:?}", error) + } + } + + #[test] + fn test_invalid_transaction_seq_id_len() { + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::new(3); + let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); + let mut buf: [u8; 7] = [0; 7]; + let res = pdu_header.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + buf[3] &= !0b0000_0111; + // Equivalent to the length of three + buf[3] |= 0b10; + let header_res = PduHeader::from_be_bytes(&buf); + assert!(header_res.is_err()); + let error = header_res.unwrap_err(); + if let PduError::InvalidTransactionSeqNumLen(len) = error { + assert_eq!(len, 3); + } else { + panic!("invalid exception {:?}", error) + } + } } diff --git a/src/util.rs b/src/util.rs index c59d4b3..cb98955 100644 --- a/src/util.rs +++ b/src/util.rs @@ -169,19 +169,19 @@ impl UnsignedEnum for UnsignedByteField { match self.len() { 0 => Ok(()), 1 => { - let u8 = UnsignedU8::try_from(*self).unwrap(); + let u8 = UnsignedByteFieldU8::try_from(*self).unwrap(); u8.write_to_be_bytes(buf) } 2 => { - let u16 = UnsignedU16::try_from(*self).unwrap(); + let u16 = UnsignedByteFieldU16::try_from(*self).unwrap(); u16.write_to_be_bytes(buf) } 4 => { - let u32 = UnsignedU32::try_from(*self).unwrap(); + let u32 = UnsignedByteFieldU32::try_from(*self).unwrap(); u32.write_to_be_bytes(buf) } 8 => { - let u64 = UnsignedU64::try_from(*self).unwrap(); + let u64 = UnsignedByteFieldU64::try_from(*self).unwrap(); u64.write_to_be_bytes(buf) } _ => { @@ -222,18 +222,23 @@ impl UnsignedEnum for GenericUnsignedByteField { } pub type UnsignedByteFieldEmpty = GenericUnsignedByteField<()>; -pub type UnsignedU8 = GenericUnsignedByteField; -pub type UnsignedU16 = GenericUnsignedByteField; -pub type UnsignedU32 = GenericUnsignedByteField; -pub type UnsignedU64 = GenericUnsignedByteField; +pub type UnsignedByteFieldU8 = GenericUnsignedByteField; +pub type UnsignedByteFieldU16 = GenericUnsignedByteField; +pub type UnsignedByteFieldU32 = GenericUnsignedByteField; +pub type UnsignedByteFieldU64 = GenericUnsignedByteField; -impl From for UnsignedByteField { - fn from(value: UnsignedU8) -> Self { +pub type UbfU8 = UnsignedByteFieldU8; +pub type UbfU16 = UnsignedByteFieldU16; +pub type UbfU32 = UnsignedByteFieldU32; +pub type UbfU64 = UnsignedByteFieldU64; + +impl From for UnsignedByteField { + fn from(value: UnsignedByteFieldU8) -> Self { Self::new(1, value.value as u64) } } -impl TryFrom for UnsignedU8 { +impl TryFrom for UnsignedByteFieldU8 { type Error = UnsignedByteFieldError; fn try_from(value: UnsignedByteField) -> Result { @@ -244,13 +249,13 @@ impl TryFrom for UnsignedU8 { } } -impl From for UnsignedByteField { - fn from(value: UnsignedU16) -> Self { +impl From for UnsignedByteField { + fn from(value: UnsignedByteFieldU16) -> Self { Self::new(2, value.value as u64) } } -impl TryFrom for UnsignedU16 { +impl TryFrom for UnsignedByteFieldU16 { type Error = UnsignedByteFieldError; fn try_from(value: UnsignedByteField) -> Result { @@ -261,13 +266,13 @@ impl TryFrom for UnsignedU16 { } } -impl From for UnsignedByteField { - fn from(value: UnsignedU32) -> Self { +impl From for UnsignedByteField { + fn from(value: UnsignedByteFieldU32) -> Self { Self::new(4, value.value as u64) } } -impl TryFrom for UnsignedU32 { +impl TryFrom for UnsignedByteFieldU32 { type Error = UnsignedByteFieldError; fn try_from(value: UnsignedByteField) -> Result { @@ -278,13 +283,13 @@ impl TryFrom for UnsignedU32 { } } -impl From for UnsignedByteField { - fn from(value: UnsignedU64) -> Self { +impl From for UnsignedByteField { + fn from(value: UnsignedByteFieldU64) -> Self { Self::new(8, value.value) } } -impl TryFrom for UnsignedU64 { +impl TryFrom for UnsignedByteFieldU64 { type Error = UnsignedByteFieldError; fn try_from(value: UnsignedByteField) -> Result { @@ -298,15 +303,15 @@ impl TryFrom for UnsignedU64 { #[cfg(test)] pub mod tests { use crate::util::{ - UnsignedByteField, UnsignedByteFieldError, UnsignedEnum, UnsignedU16, UnsignedU32, - UnsignedU64, UnsignedU8, + UnsignedByteField, UnsignedByteFieldError, UnsignedByteFieldU16, UnsignedByteFieldU32, + UnsignedByteFieldU64, UnsignedByteFieldU8, UnsignedEnum, }; use crate::ByteConversionError; use std::format; #[test] fn test_simple_u8() { - let u8 = UnsignedU8::new(5); + let u8 = UnsignedByteFieldU8::new(5); assert_eq!(u8.len(), 1); let mut buf: [u8; 8] = [0; 8]; u8.write_to_be_bytes(&mut buf) @@ -319,7 +324,7 @@ pub mod tests { #[test] fn test_simple_u16() { - let u16 = UnsignedU16::new(3823); + let u16 = UnsignedByteFieldU16::new(3823); assert_eq!(u16.len(), 2); let mut buf: [u8; 8] = [0; 8]; u16.write_to_be_bytes(&mut buf) @@ -333,7 +338,7 @@ pub mod tests { #[test] fn test_simple_u32() { - let u32 = UnsignedU32::new(80932); + let u32 = UnsignedByteFieldU32::new(80932); assert_eq!(u32.len(), 4); let mut buf: [u8; 8] = [0; 8]; u32.write_to_be_bytes(&mut buf) @@ -347,7 +352,7 @@ pub mod tests { #[test] fn test_simple_u64() { - let u64 = UnsignedU64::new(5999999); + let u64 = UnsignedByteFieldU64::new(5999999); assert_eq!(u64.len(), 8); let mut buf: [u8; 8] = [0; 8]; u64.write_to_be_bytes(&mut buf) @@ -358,11 +363,12 @@ pub mod tests { #[test] fn conversions_u8() { - let u8 = UnsignedU8::new(5); + let u8 = UnsignedByteFieldU8::new(5); let u8_type_erased = UnsignedByteField::from(u8); assert_eq!(u8_type_erased.width, 1); assert_eq!(u8_type_erased.value, 5); - let u8_conv_back = UnsignedU8::try_from(u8_type_erased).expect("conversion failed for u8"); + let u8_conv_back = + UnsignedByteFieldU8::try_from(u8_type_erased).expect("conversion failed for u8"); assert_eq!(u8, u8_conv_back); assert_eq!(u8_conv_back.value, 5); } @@ -370,7 +376,7 @@ pub mod tests { #[test] fn conversion_u8_fails() { let field = UnsignedByteField::new(2, 60000); - let conv_fails = UnsignedU8::try_from(field); + let conv_fails = UnsignedByteFieldU8::try_from(field); assert!(conv_fails.is_err()); let err = conv_fails.unwrap_err(); match err { @@ -386,12 +392,12 @@ pub mod tests { #[test] fn conversions_u16() { - let u16 = UnsignedU16::new(64444); + let u16 = UnsignedByteFieldU16::new(64444); let u16_type_erased = UnsignedByteField::from(u16); assert_eq!(u16_type_erased.width, 2); assert_eq!(u16_type_erased.value, 64444); let u16_conv_back = - UnsignedU16::try_from(u16_type_erased).expect("conversion failed for u16"); + UnsignedByteFieldU16::try_from(u16_type_erased).expect("conversion failed for u16"); assert_eq!(u16, u16_conv_back); assert_eq!(u16_conv_back.value, 64444); } @@ -399,7 +405,7 @@ pub mod tests { #[test] fn conversion_u16_fails() { let field = UnsignedByteField::new(4, 75000); - let conv_fails = UnsignedU16::try_from(field); + let conv_fails = UnsignedByteFieldU16::try_from(field); assert!(conv_fails.is_err()); let err = conv_fails.unwrap_err(); match err { @@ -415,12 +421,12 @@ pub mod tests { #[test] fn conversions_u32() { - let u32 = UnsignedU32::new(75000); + let u32 = UnsignedByteFieldU32::new(75000); let u32_type_erased = UnsignedByteField::from(u32); assert_eq!(u32_type_erased.width, 4); assert_eq!(u32_type_erased.value, 75000); let u32_conv_back = - UnsignedU32::try_from(u32_type_erased).expect("conversion failed for u32"); + UnsignedByteFieldU32::try_from(u32_type_erased).expect("conversion failed for u32"); assert_eq!(u32, u32_conv_back); assert_eq!(u32_conv_back.value, 75000); } @@ -428,7 +434,7 @@ pub mod tests { #[test] fn conversion_u32_fails() { let field = UnsignedByteField::new(8, 75000); - let conv_fails = UnsignedU32::try_from(field); + let conv_fails = UnsignedByteFieldU32::try_from(field); assert!(conv_fails.is_err()); let err = conv_fails.unwrap_err(); match err { @@ -444,12 +450,12 @@ pub mod tests { #[test] fn conversions_u64() { - let u64 = UnsignedU64::new(5999999); + let u64 = UnsignedByteFieldU64::new(5999999); let u64_type_erased = UnsignedByteField::from(u64); assert_eq!(u64_type_erased.width, 8); assert_eq!(u64_type_erased.value, 5999999); let u64_conv_back = - UnsignedU64::try_from(u64_type_erased).expect("conversion failed for u64"); + UnsignedByteFieldU64::try_from(u64_type_erased).expect("conversion failed for u64"); assert_eq!(u64, u64_conv_back); assert_eq!(u64_conv_back.value, 5999999); } @@ -457,7 +463,7 @@ pub mod tests { #[test] fn conversion_u64_fails() { let field = UnsignedByteField::new(4, 60000); - let conv_fails = UnsignedU64::try_from(field); + let conv_fails = UnsignedByteFieldU64::try_from(field); assert!(conv_fails.is_err()); let err = conv_fails.unwrap_err(); match err { @@ -557,7 +563,7 @@ pub mod tests { #[test] fn type_u16_target_buf_too_small() { - let u16 = UnsignedU16::new(500); + let u16 = UnsignedByteFieldU16::new(500); let mut buf: [u8; 1] = [0; 1]; let res = u16.write_to_be_bytes(&mut buf); assert!(res.is_err()); @@ -605,7 +611,7 @@ pub mod tests { #[test] fn type_u32_target_buf_too_small() { - let u16 = UnsignedU32::new(500); + let u16 = UnsignedByteFieldU32::new(500); let mut buf: [u8; 3] = [0; 3]; let res = u16.write_to_be_bytes(&mut buf); assert!(res.is_err()); From 0c085ef27bd479f7ffec3151fc3ca41361eb8aa7 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 18 May 2023 14:05:51 +0200 Subject: [PATCH 13/48] added basic TLV impl --- src/cfdp/mod.rs | 1 + src/cfdp/pdu/metadata.rs | 15 +++++++ src/cfdp/pdu/mod.rs | 17 +++++++- src/cfdp/tlv.rs | 86 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/cfdp/pdu/metadata.rs create mode 100644 src/cfdp/tlv.rs diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index 194d9a7..2704bb6 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -3,6 +3,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use serde::{Deserialize, Serialize}; pub mod pdu; +pub mod tlv; 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/metadata.rs b/src/cfdp/pdu/metadata.rs new file mode 100644 index 0000000..2883478 --- /dev/null +++ b/src/cfdp/pdu/metadata.rs @@ -0,0 +1,15 @@ +use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; +use crate::cfdp::ChecksumType; + +pub struct MetadataParams { + closure_requested: bool, + checksum_type: ChecksumType, + file_size: u64, + //src_file_name: +} + +pub struct MetadataPdu { + pdu_header: PduHeader, + file_directive: FileDirectiveType, + metadata_params: MetadataParams, +} diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 4a4f40d..81bcc4a 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -5,6 +5,21 @@ use core::fmt::{Display, Formatter}; #[cfg(feature = "std")] use std::error::Error; +pub mod metadata; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum FileDirectiveType { + EofPdu = 0x04, + FinishedPdu = 0x05, + AckPdu = 0x06, + MetadataPdu = 0x07, + NakPdu = 0x08, + PromptPdu = 0x09, + KeepAlivePdu = 0x0c, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum PduError { @@ -148,7 +163,7 @@ impl CommonPduConfig { } } -const FIXED_HEADER_LEN: usize = 4; +pub const FIXED_HEADER_LEN: usize = 4; /// Abstraction for the PDU header common to all CFDP PDUs #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs new file mode 100644 index 0000000..b23e938 --- /dev/null +++ b/src/cfdp/tlv.rs @@ -0,0 +1,86 @@ +use crate::{ByteConversionError, SizeMissmatch}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub const MIN_TLV_LEN: usize = 2; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum TlvType { + FilestoreRequest = 0x00, + FilestoreResponse = 0x01, + MsgToUser = 0x02, + FaultHandler = 0x04, + FlowLabel = 0x05, + EntityId = 0x06, +} + +pub struct Tlv<'a> { + tlv_type: TlvType, + data: &'a [u8], +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TlvError { + DataTooLarge(usize), + ByteConversionError(ByteConversionError), + UnknownTlvType(u8), +} + +impl From for TlvError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversionError(value) + } +} + +impl<'a> Tlv<'a> { + pub fn new(tlv_type: TlvType, data: &[u8]) -> Result { + if data.len() > u8::MAX as usize { + return Err(TlvError::DataTooLarge(data.len())); + } + Ok(Tlv { tlv_type, data }) + } + + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.data.len() + MIN_TLV_LEN { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.data.len() + MIN_TLV_LEN, + })); + } + buf[0] = self.tlv_type as u8; + // Length check in constructor ensures the length always has a valid value. + buf[1] = self.data.len() as u8; + buf[MIN_TLV_LEN..self.data.len() + MIN_TLV_LEN].copy_from_slice(self.data); + Ok(MIN_TLV_LEN + self.data.len()) + } + + pub fn from_be_bytes(buf: &'a [u8]) -> Result, TlvError> { + if buf.len() < MIN_TLV_LEN { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: MIN_TLV_LEN, + }) + .into()); + } + let tlv_type_res = TlvType::try_from(buf[0]); + if tlv_type_res.is_err() { + return Err(TlvError::UnknownTlvType(buf[1])); + } + let value_len = buf[1] as usize; + if buf.len() < value_len { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: MIN_TLV_LEN + value_len, + }) + .into()); + } + Ok(Self { + tlv_type: tlv_type_res.unwrap(), + data: &buf[MIN_TLV_LEN..MIN_TLV_LEN + value_len], + }) + } +} From b37d932e4ff8cd4ecac2d5f04aee6a1b6d81636e Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 18 May 2023 15:01:08 +0200 Subject: [PATCH 14/48] TLV and TV abstractions complete --- src/cfdp/lv.rs | 98 ++++++++++++++++++++++++++++++++++++++++ src/cfdp/mod.rs | 51 +++++++++++++++++++++ src/cfdp/pdu/metadata.rs | 10 ++-- src/cfdp/pdu/mod.rs | 11 ++++- src/cfdp/tlv.rs | 70 ++++++++++------------------ 5 files changed, 187 insertions(+), 53 deletions(-) create mode 100644 src/cfdp/lv.rs diff --git a/src/cfdp/lv.rs b/src/cfdp/lv.rs new file mode 100644 index 0000000..279a313 --- /dev/null +++ b/src/cfdp/lv.rs @@ -0,0 +1,98 @@ +use crate::cfdp::TlvLvError; +use crate::{ByteConversionError, SizeMissmatch}; +use std::prelude::v1::String; + +pub const MIN_LV_LEN: usize = 1; + +/// Generic CFDP length-value (LV) abstraction. +/// +/// This is just a thin wrapper around a raw slice which performs some additional error handling. +pub struct Lv<'a> { + data: &'a [u8], +} + +pub(crate) fn generic_len_check_data_serialization( + buf: &[u8], + data: &[u8], + min_overhead: usize, +) -> Result<(), ByteConversionError> { + if buf.len() < data.len() + min_overhead { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: data.len() + min_overhead, + })); + } + Ok(()) +} + +pub(crate) fn generic_len_check_deserialization( + buf: &[u8], + min_overheader: usize, +) -> Result<(), ByteConversionError> { + if buf.len() < min_overheader { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: MIN_LV_LEN, + })); + } + Ok(()) +} +impl<'a> Lv<'a> { + pub fn new(data: &[u8]) -> Result { + if data.len() > u8::MAX as usize { + return Err(TlvLvError::DataTooLarge(data.len())); + } + Ok(Lv { data }) + } + + /// Helper function to build a string LV. This is especially useful for the file or directory + /// path LVs + pub fn new_from_str(str_slice: &str) -> Result { + Self::new(str_slice.as_bytes()) + } + + /// Helper function to build a string LV. This is especially useful for the file or directory + /// path LVs + #[cfg(feature = "std")] + pub fn new_from_string(string: &'a String) -> Result, TlvLvError> { + Self::new(string.as_bytes()) + } + + pub fn value(&self) -> &[u8] { + self.data + } + + /// Writes the LV to a raw buffer. Please note that the first byte will contain the length + /// of the value, but the values may not exceed a length of [u8::MAX]. + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + generic_len_check_data_serialization(buf, self.data, MIN_LV_LEN)?; + Ok(self.write_to_be_bytes_no_len_check(buf)) + } + + /// Reads a LV from a raw buffer. + pub fn from_be_bytes(buf: &'a [u8]) -> Result, TlvLvError> { + generic_len_check_deserialization(buf, MIN_LV_LEN)?; + Self::from_be_bytes_no_len_check(buf) + } + + pub(crate) fn write_to_be_bytes_no_len_check(&self, buf: &mut [u8]) -> usize { + // Length check in constructor ensures the length always has a valid value. + buf[0] = self.data.len() as u8; + buf[MIN_LV_LEN..self.data.len() + MIN_LV_LEN].copy_from_slice(self.data); + MIN_LV_LEN + self.data.len() + } + + pub(crate) fn from_be_bytes_no_len_check(buf: &'a [u8]) -> Result, TlvLvError> { + let value_len = buf[0] as usize; + if buf.len() < value_len + MIN_LV_LEN { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: MIN_LV_LEN + value_len, + }) + .into()); + } + Ok(Self { + data: &buf[MIN_LV_LEN..MIN_LV_LEN + value_len], + }) + } +} diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index 2704bb6..c51cdaf 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -1,7 +1,12 @@ +use crate::ByteConversionError; +use core::fmt::{Display, Formatter}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "std")] +use std::error::Error; +pub mod lv; pub mod pdu; pub mod tlv; @@ -127,3 +132,49 @@ pub enum ChecksumType { } pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4]; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TlvLvError { + DataTooLarge(usize), + ByteConversionError(ByteConversionError), + /// Only relevant for TLV de-serialization. + UnknownTlvType(u8), +} + +impl From for TlvLvError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversionError(value) + } +} + +impl Display for TlvLvError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + TlvLvError::UnknownTlvType(raw_tlv_id) => { + write!(f, "unknown TLV type {raw_tlv_id}") + } + TlvLvError::DataTooLarge(data_len) => { + write!( + f, + "data with size {} larger than allowed {} bytes", + data_len, + u8::MAX + ) + } + TlvLvError::ByteConversionError(e) => { + write!(f, "{}", e) + } + } + } +} + +#[cfg(feature = "std")] +impl Error for TlvLvError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + TlvLvError::ByteConversionError(e) => Some(e), + _ => None, + } + } +} diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 2883478..2bdea62 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -1,15 +1,17 @@ +use crate::cfdp::lv::Lv; use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; use crate::cfdp::ChecksumType; -pub struct MetadataParams { +pub struct MetadataGenericParams { closure_requested: bool, checksum_type: ChecksumType, file_size: u64, - //src_file_name: } -pub struct MetadataPdu { +pub struct MetadataPdu<'src_name, 'dest_name> { pdu_header: PduHeader, file_directive: FileDirectiveType, - metadata_params: MetadataParams, + metadata_params: MetadataGenericParams, + src_file_name: Option>, + dest_file_name: Option>, } diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 81bcc4a..a68b230 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -63,14 +63,21 @@ impl Display for PduError { ) } PduError::ByteConversionError(e) => { - write!(f, "low level byte conversion error: {e}") + write!(f, "{}", e) } } } } #[cfg(feature = "std")] -impl Error for PduError {} +impl Error for PduError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + PduError::ByteConversionError(e) => Some(e), + _ => None, + } + } +} impl From for PduError { fn from(value: ByteConversionError) -> Self { diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index b23e938..60dcab6 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -1,4 +1,8 @@ -use crate::{ByteConversionError, SizeMissmatch}; +use crate::cfdp::lv::{ + generic_len_check_data_serialization, generic_len_check_deserialization, Lv, MIN_LV_LEN, +}; +use crate::cfdp::TlvLvError; +use crate::ByteConversionError; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -19,68 +23,40 @@ pub enum TlvType { pub struct Tlv<'a> { tlv_type: TlvType, - data: &'a [u8], -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum TlvError { - DataTooLarge(usize), - ByteConversionError(ByteConversionError), - UnknownTlvType(u8), -} - -impl From for TlvError { - fn from(value: ByteConversionError) -> Self { - Self::ByteConversionError(value) - } + lv: Lv<'a>, } impl<'a> Tlv<'a> { - pub fn new(tlv_type: TlvType, data: &[u8]) -> Result { + pub fn new(tlv_type: TlvType, data: &[u8]) -> Result { if data.len() > u8::MAX as usize { - return Err(TlvError::DataTooLarge(data.len())); + return Err(TlvLvError::DataTooLarge(data.len())); } - Ok(Tlv { tlv_type, data }) + Ok(Tlv { + tlv_type, + lv: Lv::new(data)?, + }) } pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { - if buf.len() < self.data.len() + MIN_TLV_LEN { - return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { - found: buf.len(), - expected: self.data.len() + MIN_TLV_LEN, - })); - } + generic_len_check_data_serialization(buf, self.value(), MIN_TLV_LEN)?; buf[0] = self.tlv_type as u8; - // Length check in constructor ensures the length always has a valid value. - buf[1] = self.data.len() as u8; - buf[MIN_TLV_LEN..self.data.len() + MIN_TLV_LEN].copy_from_slice(self.data); - Ok(MIN_TLV_LEN + self.data.len()) + self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]); + Ok(MIN_TLV_LEN + self.value().len()) } - pub fn from_be_bytes(buf: &'a [u8]) -> Result, TlvError> { - if buf.len() < MIN_TLV_LEN { - return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { - found: buf.len(), - expected: MIN_TLV_LEN, - }) - .into()); - } + pub fn value(&self) -> &[u8] { + self.lv.value() + } + + pub fn from_be_bytes(buf: &'a [u8]) -> Result, TlvLvError> { + generic_len_check_deserialization(buf, MIN_TLV_LEN)?; let tlv_type_res = TlvType::try_from(buf[0]); if tlv_type_res.is_err() { - return Err(TlvError::UnknownTlvType(buf[1])); - } - let value_len = buf[1] as usize; - if buf.len() < value_len { - return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { - found: buf.len(), - expected: MIN_TLV_LEN + value_len, - }) - .into()); + return Err(TlvLvError::UnknownTlvType(buf[1])); } Ok(Self { tlv_type: tlv_type_res.unwrap(), - data: &buf[MIN_TLV_LEN..MIN_TLV_LEN + value_len], + lv: Lv::from_be_bytes(&buf[MIN_LV_LEN..])?, }) } } From 9a9694981a5dd35dfd1244d2b10743474e913054 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 18 May 2023 20:37:41 +0200 Subject: [PATCH 15/48] start adding LV tests --- src/cfdp/lv.rs | 179 +++++++++++++++++++++++++++++++++------ src/cfdp/pdu/metadata.rs | 1 + src/cfdp/tlv.rs | 38 +++++++-- 3 files changed, 188 insertions(+), 30 deletions(-) diff --git a/src/cfdp/lv.rs b/src/cfdp/lv.rs index 279a313..ea0f7f2 100644 --- a/src/cfdp/lv.rs +++ b/src/cfdp/lv.rs @@ -1,25 +1,30 @@ use crate::cfdp::TlvLvError; use crate::{ByteConversionError, SizeMissmatch}; -use std::prelude::v1::String; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "std")] +use std::string::String; pub const MIN_LV_LEN: usize = 1; -/// Generic CFDP length-value (LV) abstraction. +/// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. /// /// This is just a thin wrapper around a raw slice which performs some additional error handling. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Lv<'a> { - data: &'a [u8], + data: Option<&'a [u8]>, } pub(crate) fn generic_len_check_data_serialization( buf: &[u8], - data: &[u8], + data_len: usize, min_overhead: usize, ) -> Result<(), ByteConversionError> { - if buf.len() < data.len() + min_overhead { + if buf.len() < data_len + min_overhead { return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { found: buf.len(), - expected: data.len() + min_overhead, + expected: data_len + min_overhead, })); } Ok(()) @@ -27,22 +32,28 @@ pub(crate) fn generic_len_check_data_serialization( pub(crate) fn generic_len_check_deserialization( buf: &[u8], - min_overheader: usize, + min_overhead: usize, ) -> Result<(), ByteConversionError> { - if buf.len() < min_overheader { + if buf.len() < min_overhead { return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { found: buf.len(), - expected: MIN_LV_LEN, + expected: min_overhead, })); } Ok(()) } + impl<'a> Lv<'a> { pub fn new(data: &[u8]) -> Result { if data.len() > u8::MAX as usize { return Err(TlvLvError::DataTooLarge(data.len())); } - Ok(Lv { data }) + Ok(Lv { data: Some(data) }) + } + + /// Creates a LV with an empty value field. + pub fn new_empty() -> Lv<'a> { + Lv { data: None } } /// Helper function to build a string LV. This is especially useful for the file or directory @@ -58,14 +69,32 @@ impl<'a> Lv<'a> { Self::new(string.as_bytes()) } - pub fn value(&self) -> &[u8] { + /// Returns the length of the value part, not including the length byte. + pub fn len_value(&self) -> usize { + if self.data.is_none() { + return 0; + } + self.data.unwrap().len() + } + + /// Returns the full raw length, including the length byte. + pub fn len_raw(&self) -> usize { + self.len_value() + 1 + } + + /// Checks whether the value field is empty. + pub fn is_empty(&self) -> bool { + self.data.is_none() + } + + pub fn value(&self) -> Option<&[u8]> { self.data } /// Writes the LV to a raw buffer. Please note that the first byte will contain the length /// of the value, but the values may not exceed a length of [u8::MAX]. pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { - generic_len_check_data_serialization(buf, self.data, MIN_LV_LEN)?; + generic_len_check_data_serialization(buf, self.len_value(), MIN_LV_LEN)?; Ok(self.write_to_be_bytes_no_len_check(buf)) } @@ -76,23 +105,125 @@ impl<'a> Lv<'a> { } pub(crate) fn write_to_be_bytes_no_len_check(&self, buf: &mut [u8]) -> usize { + if self.data.is_none() { + buf[0] = 0; + return MIN_LV_LEN; + } + let data = self.data.unwrap(); // Length check in constructor ensures the length always has a valid value. - buf[0] = self.data.len() as u8; - buf[MIN_LV_LEN..self.data.len() + MIN_LV_LEN].copy_from_slice(self.data); - MIN_LV_LEN + self.data.len() + buf[0] = data.len() as u8; + buf[MIN_LV_LEN..data.len() + MIN_LV_LEN].copy_from_slice(data); + MIN_LV_LEN + data.len() } pub(crate) fn from_be_bytes_no_len_check(buf: &'a [u8]) -> Result, TlvLvError> { let value_len = buf[0] as usize; - if buf.len() < value_len + MIN_LV_LEN { - return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { - found: buf.len(), - expected: MIN_LV_LEN + value_len, - }) - .into()); + generic_len_check_deserialization(buf, value_len + MIN_LV_LEN)?; + let mut data = None; + if value_len > 0 { + data = Some(&buf[MIN_LV_LEN..MIN_LV_LEN + value_len]) + } + Ok(Self { data }) + } +} + +#[cfg(test)] +pub mod tests { + use crate::cfdp::lv::Lv; + use crate::cfdp::TlvLvError; + + #[test] + fn test_basic() { + let lv_data: [u8; 4] = [1, 2, 3, 4]; + let lv_res = Lv::new(&lv_data); + assert!(lv_res.is_ok()); + let lv = lv_res.unwrap(); + assert!(lv.value().is_some()); + let val = lv.value().unwrap(); + assert_eq!(val[0], 1); + assert_eq!(val[1], 2); + assert_eq!(val[2], 3); + assert_eq!(val[3], 4); + assert!(!lv.is_empty()); + assert_eq!(lv.len_raw(), 5); + assert_eq!(lv.len_value(), 4); + } + + #[test] + fn test_empty() { + let lv_empty = Lv::new_empty(); + assert_eq!(lv_empty.len_value(), 0); + assert_eq!(lv_empty.len_raw(), 1); + assert!(lv_empty.is_empty()); + assert_eq!(lv_empty.value(), None); + let mut buf: [u8; 4] = [0xff; 4]; + let res = lv_empty.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, 1); + assert_eq!(buf[0], 0); + } + + #[test] + fn test_serialization() { + let lv_data: [u8; 4] = [1, 2, 3, 4]; + let lv_res = Lv::new(&lv_data); + assert!(lv_res.is_ok()); + let lv = lv_res.unwrap(); + let mut buf: [u8; 16] = [0; 16]; + let res = lv.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, 5); + assert_eq!(buf[0], 4); + assert_eq!(buf[1], 1); + assert_eq!(buf[2], 2); + assert_eq!(buf[3], 3); + assert_eq!(buf[4], 4); + } + + #[test] + fn test_deserialization() { + let mut buf: [u8; 16] = [0; 16]; + buf[0] = 4; + buf[1] = 1; + buf[2] = 2; + buf[3] = 3; + buf[4] = 4; + let lv = Lv::from_be_bytes(&buf); + assert!(lv.is_ok()); + let lv = lv.unwrap(); + assert!(!lv.is_empty()); + assert!(lv.value().is_some()); + assert_eq!(lv.len_value(), 4); + assert_eq!(lv.len_raw(), 5); + let val = lv.value().unwrap(); + assert_eq!(val[0], 1); + assert_eq!(val[1], 2); + assert_eq!(val[2], 3); + assert_eq!(val[3], 4); + } + + #[test] + fn test_deserialization_empty() { + let buf: [u8; 2] = [0; 2]; + let lv_empty = Lv::from_be_bytes(&buf); + assert!(lv_empty.is_ok()); + let lv_empty = lv_empty.unwrap(); + assert!(lv_empty.is_empty()); + assert!(lv_empty.value().is_none()); + } + + #[test] + fn test_data_too_large() { + let data_big: [u8; u8::MAX as usize + 1] = [0; u8::MAX as usize + 1]; + let lv = Lv::new(&data_big); + assert!(lv.is_err()); + let error = lv.unwrap_err(); + if let TlvLvError::DataTooLarge(size) = error { + assert_eq!(size, u8::MAX as usize + 1); + } else { + panic!("invalid exception {:?}", error) } - Ok(Self { - data: &buf[MIN_LV_LEN..MIN_LV_LEN + value_len], - }) } } diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 2bdea62..ed6b8ee 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use crate::cfdp::lv::Lv; use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; use crate::cfdp::ChecksumType; diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 60dcab6..067fa21 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -21,6 +21,9 @@ pub enum TlvType { EntityId = 0x06, } +/// Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9. +/// +/// This is just a thin wrapper around a length-value (LV) object, which add the [TlvType]. pub struct Tlv<'a> { tlv_type: TlvType, lv: Lv<'a>, @@ -37,17 +40,40 @@ impl<'a> Tlv<'a> { }) } - pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { - generic_len_check_data_serialization(buf, self.value(), MIN_TLV_LEN)?; - buf[0] = self.tlv_type as u8; - self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]); - Ok(MIN_TLV_LEN + self.value().len()) + /// Creates a TLV with an empty value field. + pub fn new_empty(tlv_type: TlvType) -> Tlv<'a> { + Tlv { + tlv_type, + lv: Lv::new_empty(), + } } - pub fn value(&self) -> &[u8] { + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + generic_len_check_data_serialization(buf, self.len_value(), MIN_TLV_LEN)?; + buf[0] = self.tlv_type as u8; + self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]); + Ok(self.len_full()) + } + + pub fn value(&self) -> Option<&[u8]> { self.lv.value() } + /// Returns the length of the value part, not including the length byte. + pub fn len_value(&self) -> usize { + self.lv.len_value() + 1 + } + + /// Returns the full raw length, including the length byte. + pub fn len_full(&self) -> usize { + self.lv.len_raw() + 1 + } + + /// Checks whether the value field is empty. + pub fn is_empty(&self) -> bool { + self.lv.is_empty() + } + pub fn from_be_bytes(buf: &'a [u8]) -> Result, TlvLvError> { generic_len_check_deserialization(buf, MIN_TLV_LEN)?; let tlv_type_res = TlvType::try_from(buf[0]); From f6e309d2ee075e1c4fe9d024c6f08724bb55c3f0 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 19 May 2023 00:42:31 +0200 Subject: [PATCH 16/48] docs and stuff --- src/cfdp/lv.rs | 84 ++++++++++++++++++++++++++++++++++++++++----- src/cfdp/mod.rs | 6 ++-- src/cfdp/pdu/mod.rs | 1 + src/cfdp/tlv.rs | 9 +++-- 4 files changed, 87 insertions(+), 13 deletions(-) diff --git a/src/cfdp/lv.rs b/src/cfdp/lv.rs index ea0f7f2..5867173 100644 --- a/src/cfdp/lv.rs +++ b/src/cfdp/lv.rs @@ -1,3 +1,4 @@ +//! Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. use crate::cfdp::TlvLvError; use crate::{ByteConversionError, SizeMissmatch}; #[cfg(feature = "serde")] @@ -8,12 +9,10 @@ use std::string::String; pub const MIN_LV_LEN: usize = 1; /// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. -/// -/// This is just a thin wrapper around a raw slice which performs some additional error handling. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Lv<'a> { - data: Option<&'a [u8]>, +pub struct Lv<'value> { + data: Option<&'value [u8]>, } pub(crate) fn generic_len_check_data_serialization( @@ -43,7 +42,7 @@ pub(crate) fn generic_len_check_deserialization( Ok(()) } -impl<'a> Lv<'a> { +impl<'value> Lv<'value> { pub fn new(data: &[u8]) -> Result { if data.len() > u8::MAX as usize { return Err(TlvLvError::DataTooLarge(data.len())); @@ -52,7 +51,7 @@ impl<'a> Lv<'a> { } /// Creates a LV with an empty value field. - pub fn new_empty() -> Lv<'a> { + pub fn new_empty() -> Lv<'value> { Lv { data: None } } @@ -65,7 +64,7 @@ impl<'a> Lv<'a> { /// Helper function to build a string LV. This is especially useful for the file or directory /// path LVs #[cfg(feature = "std")] - pub fn new_from_string(string: &'a String) -> Result, TlvLvError> { + pub fn new_from_string(string: &'value String) -> Result, TlvLvError> { Self::new(string.as_bytes()) } @@ -99,7 +98,7 @@ impl<'a> Lv<'a> { } /// Reads a LV from a raw buffer. - pub fn from_be_bytes(buf: &'a [u8]) -> Result, TlvLvError> { + pub fn from_be_bytes(buf: &'value [u8]) -> Result, ByteConversionError> { generic_len_check_deserialization(buf, MIN_LV_LEN)?; Self::from_be_bytes_no_len_check(buf) } @@ -116,7 +115,9 @@ impl<'a> Lv<'a> { MIN_LV_LEN + data.len() } - pub(crate) fn from_be_bytes_no_len_check(buf: &'a [u8]) -> Result, TlvLvError> { + pub(crate) fn from_be_bytes_no_len_check( + buf: &'value [u8], + ) -> Result, ByteConversionError> { let value_len = buf[0] as usize; generic_len_check_deserialization(buf, value_len + MIN_LV_LEN)?; let mut data = None; @@ -131,6 +132,8 @@ impl<'a> Lv<'a> { pub mod tests { use crate::cfdp::lv::Lv; use crate::cfdp::TlvLvError; + use crate::ByteConversionError; + use std::string::String; #[test] fn test_basic() { @@ -226,4 +229,67 @@ pub mod tests { panic!("invalid exception {:?}", error) } } + + #[test] + fn test_serialization_buf_too_small() { + let mut buf: [u8; 3] = [0; 3]; + let lv_data: [u8; 4] = [1, 2, 3, 4]; + let lv = Lv::new(&lv_data).unwrap(); + let res = lv.write_to_be_bytes(&mut buf); + assert!(res.is_err()); + let error = res.unwrap_err(); + if let ByteConversionError::ToSliceTooSmall(missmatch) = error { + assert_eq!(missmatch.expected, 5); + assert_eq!(missmatch.found, 3); + } else { + panic!("invalid error {}", error); + } + } + + #[test] + fn test_deserialization_buf_too_small() { + let mut buf: [u8; 3] = [0; 3]; + buf[0] = 4; + let res = Lv::from_be_bytes(&buf); + assert!(res.is_err()); + let error = res.unwrap_err(); + if let ByteConversionError::FromSliceTooSmall(missmatch) = error { + assert_eq!(missmatch.found, 3); + assert_eq!(missmatch.expected, 5); + } else { + panic!("invalid error {}", error); + } + } + + fn verify_test_str_lv(lv: Lv) { + let mut buf: [u8; 16] = [0; 16]; + let res = lv.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let res = res.unwrap(); + assert_eq!(res, 8 + 1); + assert_eq!(buf[0], 8); + assert_eq!(buf[1], 't' as u8); + assert_eq!(buf[2], 'e' as u8); + assert_eq!(buf[3], 's' as u8); + assert_eq!(buf[4], 't' as u8); + assert_eq!(buf[5], '.' as u8); + assert_eq!(buf[6], 'b' as u8); + assert_eq!(buf[7], 'i' as u8); + assert_eq!(buf[8], 'n' as u8); + } + #[test] + fn test_str_helper() { + let test_str = "test.bin"; + let str_lv = Lv::new_from_str(test_str); + assert!(str_lv.is_ok()); + verify_test_str_lv(str_lv.unwrap()); + } + + #[test] + fn test_string_helper() { + let string = String::from("test.bin"); + let str_lv = Lv::new_from_string(&string); + assert!(str_lv.is_ok()); + verify_test_str_lv(str_lv.unwrap()); + } } diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index c51cdaf..9e96088 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -10,7 +10,9 @@ pub mod lv; pub mod pdu; pub mod tlv; +/// This is the name of the standard this module is based on. pub const CFDP_VERSION_2_NAME: &str = "CCSDS 727.0-B-5"; +/// Currently, only this version is supported. pub const CFDP_VERSION_2: u8 = 0b001; #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] @@ -116,8 +118,8 @@ pub enum LargeFileFlag { Large = 1, } -/// Checksum types according to the SANA Checksum Types registry -/// https://sanaregistry.org/r/checksum_identifiers/ +/// Checksum types according to the +/// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/) #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(u8)] diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index a68b230..0f14702 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -1,3 +1,4 @@ +//! CFDP Packet Data Unit (PDU) support. use crate::cfdp::*; use crate::util::{UnsignedByteField, UnsignedEnum}; use crate::{ByteConversionError, SizeMissmatch}; diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 067fa21..05a5c25 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -1,3 +1,4 @@ +//! Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9. use crate::cfdp::lv::{ generic_len_check_data_serialization, generic_len_check_deserialization, Lv, MIN_LV_LEN, }; @@ -22,8 +23,6 @@ pub enum TlvType { } /// Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9. -/// -/// This is just a thin wrapper around a length-value (LV) object, which add the [TlvType]. pub struct Tlv<'a> { tlv_type: TlvType, lv: Lv<'a>, @@ -86,3 +85,9 @@ impl<'a> Tlv<'a> { }) } } + +#[cfg(test)] +mod tests { + #[test] + fn test_basic() {} +} From e343faa1c559f5e3662e2ba349f6efae2b285369 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 21 May 2023 20:30:16 +0200 Subject: [PATCH 17/48] clippy fixes --- src/cfdp/lv.rs | 29 +++--- src/cfdp/mod.rs | 5 - src/cfdp/pdu/metadata.rs | 138 +++++++++++++++++++++++++-- src/cfdp/pdu/mod.rs | 10 ++ src/cfdp/tlv.rs | 197 ++++++++++++++++++++++++++++++++++----- 5 files changed, 332 insertions(+), 47 deletions(-) diff --git a/src/cfdp/lv.rs b/src/cfdp/lv.rs index 5867173..3ceb49a 100644 --- a/src/cfdp/lv.rs +++ b/src/cfdp/lv.rs @@ -9,10 +9,15 @@ use std::string::String; pub const MIN_LV_LEN: usize = 1; /// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. +/// +/// # Lifetimes +/// * `data`: If the LV is generated from a raw bytestream, this will be the lifetime of +/// the raw bytestream. If the LV is generated from a raw slice or a similar data reference, +/// this will be the lifetime of that data reference. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Lv<'value> { - data: Option<&'value [u8]>, +pub struct Lv<'data> { + data: Option<&'data [u8]>, } pub(crate) fn generic_len_check_data_serialization( @@ -42,7 +47,7 @@ pub(crate) fn generic_len_check_deserialization( Ok(()) } -impl<'value> Lv<'value> { +impl<'data> Lv<'data> { pub fn new(data: &[u8]) -> Result { if data.len() > u8::MAX as usize { return Err(TlvLvError::DataTooLarge(data.len())); @@ -51,7 +56,7 @@ impl<'value> Lv<'value> { } /// Creates a LV with an empty value field. - pub fn new_empty() -> Lv<'value> { + pub fn new_empty() -> Lv<'data> { Lv { data: None } } @@ -64,7 +69,7 @@ impl<'value> Lv<'value> { /// Helper function to build a string LV. This is especially useful for the file or directory /// path LVs #[cfg(feature = "std")] - pub fn new_from_string(string: &'value String) -> Result, TlvLvError> { + pub fn new_from_string(string: &'data String) -> Result, TlvLvError> { Self::new(string.as_bytes()) } @@ -77,7 +82,7 @@ impl<'value> Lv<'value> { } /// Returns the full raw length, including the length byte. - pub fn len_raw(&self) -> usize { + pub fn len_full(&self) -> usize { self.len_value() + 1 } @@ -98,7 +103,7 @@ impl<'value> Lv<'value> { } /// Reads a LV from a raw buffer. - pub fn from_be_bytes(buf: &'value [u8]) -> Result, ByteConversionError> { + pub fn from_be_bytes(buf: &'data [u8]) -> Result, ByteConversionError> { generic_len_check_deserialization(buf, MIN_LV_LEN)?; Self::from_be_bytes_no_len_check(buf) } @@ -116,8 +121,8 @@ impl<'value> Lv<'value> { } pub(crate) fn from_be_bytes_no_len_check( - buf: &'value [u8], - ) -> Result, ByteConversionError> { + buf: &'data [u8], + ) -> Result, ByteConversionError> { let value_len = buf[0] as usize; generic_len_check_deserialization(buf, value_len + MIN_LV_LEN)?; let mut data = None; @@ -148,7 +153,7 @@ pub mod tests { assert_eq!(val[2], 3); assert_eq!(val[3], 4); assert!(!lv.is_empty()); - assert_eq!(lv.len_raw(), 5); + assert_eq!(lv.len_full(), 5); assert_eq!(lv.len_value(), 4); } @@ -156,7 +161,7 @@ pub mod tests { fn test_empty() { let lv_empty = Lv::new_empty(); assert_eq!(lv_empty.len_value(), 0); - assert_eq!(lv_empty.len_raw(), 1); + assert_eq!(lv_empty.len_full(), 1); assert!(lv_empty.is_empty()); assert_eq!(lv_empty.value(), None); let mut buf: [u8; 4] = [0xff; 4]; @@ -199,7 +204,7 @@ pub mod tests { assert!(!lv.is_empty()); assert!(lv.value().is_some()); assert_eq!(lv.len_value(), 4); - assert_eq!(lv.len_raw(), 5); + assert_eq!(lv.len_full(), 5); let val = lv.value().unwrap(); assert_eq!(val[0], 1); assert_eq!(val[1], 2); diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index 9e96088..d501b5d 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -140,8 +140,6 @@ pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4]; pub enum TlvLvError { DataTooLarge(usize), ByteConversionError(ByteConversionError), - /// Only relevant for TLV de-serialization. - UnknownTlvType(u8), } impl From for TlvLvError { @@ -153,9 +151,6 @@ impl From for TlvLvError { impl Display for TlvLvError { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { - TlvLvError::UnknownTlvType(raw_tlv_id) => { - write!(f, "unknown TLV type {raw_tlv_id}") - } TlvLvError::DataTooLarge(data_len) => { write!( f, diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index ed6b8ee..b0701ab 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -1,18 +1,138 @@ -#![allow(dead_code)] use crate::cfdp::lv::Lv; -use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; -use crate::cfdp::ChecksumType; +use crate::cfdp::pdu::{FileDirectiveType, PduError, PduHeader}; +use crate::cfdp::tlv::Tlv; +use crate::cfdp::{ChecksumType, LargeFileFlag}; +use crate::{ByteConversionError, SizeMissmatch}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct MetadataGenericParams { closure_requested: bool, checksum_type: ChecksumType, file_size: u64, } -pub struct MetadataPdu<'src_name, 'dest_name> { - pdu_header: PduHeader, - file_directive: FileDirectiveType, - metadata_params: MetadataGenericParams, - src_file_name: Option>, - dest_file_name: Option>, +pub fn build_metadata_opts_from_slice( + buf: &mut [u8], + tlvs: &[Tlv], +) -> Result { + let mut written = 0; + for tlv in tlvs { + written += tlv.write_to_be_bytes(buf)?; + } + Ok(written) +} + +#[cfg(feature = "alloc")] +pub fn build_metadata_opts_from_vec( + buf: &mut [u8], + tlvs: Vec, +) -> Result { + build_metadata_opts_from_slice(buf, tlvs.as_slice()) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct MetadataPdu<'src_name, 'dest_name, 'opts> { + pdu_header: PduHeader, + metadata_params: MetadataGenericParams, + #[cfg_attr(feature = "serde", serde(borrow))] + src_file_name: Lv<'src_name>, + #[cfg_attr(feature = "serde", serde(borrow))] + dest_file_name: Lv<'dest_name>, + options: Option<&'opts [u8]>, +} + +impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { + pub fn new( + pdu_header: PduHeader, + metadata_params: MetadataGenericParams, + src_file_name: Lv<'src_name>, + dest_file_name: Lv<'dest_name>, + ) -> Self { + Self::new_with_opts( + pdu_header, + metadata_params, + src_file_name, + dest_file_name, + None, + ) + } + + pub fn new_with_opts( + pdu_header: PduHeader, + metadata_params: MetadataGenericParams, + src_file_name: Lv<'src_name>, + dest_file_name: Lv<'dest_name>, + options: Option<&'opts [u8]>, + ) -> Self { + Self { + pdu_header, + metadata_params, + src_file_name, + dest_file_name, + options, + } + } + + pub fn written_len(&self) -> usize { + // One directive type octet + let mut len = self.pdu_header.written_len() + 1; + if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { + len += 8; + } else { + len += 4; + } + len += self.src_file_name.len_full(); + len += self.dest_file_name.len_full(); + if let Some(opts) = self.options { + len += opts.len(); + } + len + } + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + let expected_len = self.written_len(); + if buf.len() < expected_len { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: expected_len, + }) + .into()); + } + let mut current_idx = self.pdu_header.write_to_be_bytes(buf)?; + buf[current_idx] = FileDirectiveType::MetadataPdu as u8; + current_idx += 1; + buf[current_idx] = ((self.metadata_params.closure_requested as u8) << 7) + | ((self.metadata_params.checksum_type as u8) << 4); + current_idx += 1; + if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { + buf[current_idx..current_idx + core::mem::size_of::()] + .copy_from_slice(&self.metadata_params.file_size.to_be_bytes()); + current_idx += core::mem::size_of::() + } else { + if self.metadata_params.file_size > u32::MAX as u64 { + return Err(PduError::FileSizeTooLarge(self.metadata_params.file_size)); + } + buf[current_idx..current_idx + core::mem::size_of::()] + .copy_from_slice(&(self.metadata_params.file_size as u32).to_be_bytes()); + current_idx += core::mem::size_of::() + } + current_idx += self.src_file_name.write_to_be_bytes(buf)?; + current_idx += self.dest_file_name.write_to_be_bytes(buf)?; + if let Some(opts) = self.options { + buf[current_idx..current_idx + opts.len()].copy_from_slice(opts); + current_idx += opts.len(); + } + Ok(current_idx) + } +} + +#[cfg(test)] +pub mod tests { + #[test] + fn test_basic() {} } diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 0f14702..408fc79 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -34,6 +34,7 @@ pub enum PduError { /// The first entry will be the source entity ID length, the second one the destination entity /// ID length. SourceDestIdLenMissmatch((usize, usize)), + FileSizeTooLarge(u64), } impl Display for PduError { @@ -66,6 +67,9 @@ impl Display for PduError { PduError::ByteConversionError(e) => { write!(f, "{}", e) } + PduError::FileSizeTooLarge(value) => { + write!(f, "file size value {} exceeds allowed 32 bit width", value) + } } } } @@ -210,6 +214,12 @@ impl PduHeader { } } + pub fn written_len(&self) -> usize { + FIXED_HEADER_LEN + + self.pdu_conf.source_entity_id.len() + + self.pdu_conf.transaction_seq_num.len() + + self.pdu_conf.dest_entity_id.len() + } pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { // Internal note: There is currently no way to pass a PDU configuration like this, but // this check is still kept for defensive programming. diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 05a5c25..5f9a9c0 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -22,34 +22,68 @@ pub enum TlvType { EntityId = 0x06, } -/// Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9. -pub struct Tlv<'a> { - tlv_type: TlvType, - lv: Lv<'a>, +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TlvTypeField { + Standard(TlvType), + Custom(u8), } -impl<'a> Tlv<'a> { - pub fn new(tlv_type: TlvType, data: &[u8]) -> Result { - if data.len() > u8::MAX as usize { - return Err(TlvLvError::DataTooLarge(data.len())); +impl From for TlvTypeField { + fn from(value: u8) -> Self { + match TlvType::try_from(value) { + Ok(tlv_type) => TlvTypeField::Standard(tlv_type), + Err(_) => TlvTypeField::Custom(value), } + } +} + +impl From for u8 { + fn from(value: TlvTypeField) -> Self { + match value { + TlvTypeField::Standard(std) => std as u8, + TlvTypeField::Custom(custom) => custom, + } + } +} + +/// Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9. +/// +/// # Lifetimes +/// * `data`: If the TLV is generated from a raw bytestream, this will be the lifetime of +/// the raw bytestream. If the TLV is generated from a raw slice or a similar data reference, +/// this will be the lifetime of that data reference. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Tlv<'data> { + tlv_type_field: TlvTypeField, + #[cfg_attr(feature = "serde", serde(borrow))] + lv: Lv<'data>, +} + +impl<'data> Tlv<'data> { + pub fn new(tlv_type: TlvType, data: &[u8]) -> Result { Ok(Tlv { - tlv_type, + tlv_type_field: TlvTypeField::Standard(tlv_type), lv: Lv::new(data)?, }) } /// Creates a TLV with an empty value field. - pub fn new_empty(tlv_type: TlvType) -> Tlv<'a> { + pub fn new_empty(tlv_type: TlvType) -> Tlv<'data> { Tlv { - tlv_type, + tlv_type_field: TlvTypeField::Standard(tlv_type), lv: Lv::new_empty(), } } + pub fn tlv_type_field(&self) -> TlvTypeField { + self.tlv_type_field + } + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { generic_len_check_data_serialization(buf, self.len_value(), MIN_TLV_LEN)?; - buf[0] = self.tlv_type as u8; + buf[0] = self.tlv_type_field.into(); self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]); Ok(self.len_full()) } @@ -60,12 +94,12 @@ impl<'a> Tlv<'a> { /// Returns the length of the value part, not including the length byte. pub fn len_value(&self) -> usize { - self.lv.len_value() + 1 + self.lv.len_value() } /// Returns the full raw length, including the length byte. pub fn len_full(&self) -> usize { - self.lv.len_raw() + 1 + self.lv.len_full() + 1 } /// Checks whether the value field is empty. @@ -73,14 +107,14 @@ impl<'a> Tlv<'a> { self.lv.is_empty() } - pub fn from_be_bytes(buf: &'a [u8]) -> Result, TlvLvError> { + /// Creates a TLV give a raw bytestream. Please note that is is not necessary to pass the + /// bytestream with the exact size of the expected TLV. This function will take care + /// of parsing the length byte, and the length of the parsed TLV can be retrieved using + /// [len_full]. + pub fn from_be_bytes(buf: &'data [u8]) -> Result, TlvLvError> { generic_len_check_deserialization(buf, MIN_TLV_LEN)?; - let tlv_type_res = TlvType::try_from(buf[0]); - if tlv_type_res.is_err() { - return Err(TlvLvError::UnknownTlvType(buf[1])); - } Ok(Self { - tlv_type: tlv_type_res.unwrap(), + tlv_type_field: TlvTypeField::from(buf[0]), lv: Lv::from_be_bytes(&buf[MIN_LV_LEN..])?, }) } @@ -88,6 +122,127 @@ impl<'a> Tlv<'a> { #[cfg(test)] mod tests { + use crate::cfdp::tlv::{Tlv, TlvType, TlvTypeField}; + use crate::cfdp::TlvLvError; + use crate::util::{UbfU8, UnsignedEnum}; + #[test] - fn test_basic() {} + fn test_basic() { + let entity_id = UbfU8::new(5); + let mut buf: [u8; 4] = [0; 4]; + assert!(entity_id.write_to_be_bytes(&mut buf).is_ok()); + let tlv_res = Tlv::new(TlvType::EntityId, &buf[0..1]); + assert!(tlv_res.is_ok()); + let tlv_res = tlv_res.unwrap(); + assert_eq!( + tlv_res.tlv_type_field(), + TlvTypeField::Standard(TlvType::EntityId) + ); + assert_eq!(tlv_res.len_full(), 3); + assert_eq!(tlv_res.len_value(), 1); + assert!(!tlv_res.is_empty()); + assert!(tlv_res.value().is_some()); + assert_eq!(tlv_res.value().unwrap()[0], 5); + } + + #[test] + fn test_serialization() { + let entity_id = UbfU8::new(5); + let mut buf: [u8; 4] = [0; 4]; + assert!(entity_id.write_to_be_bytes(&mut buf).is_ok()); + let tlv_res = Tlv::new(TlvType::EntityId, &buf[0..1]); + assert!(tlv_res.is_ok()); + let tlv_res = tlv_res.unwrap(); + let mut ser_buf: [u8; 4] = [0; 4]; + assert!(tlv_res.write_to_be_bytes(&mut ser_buf).is_ok()); + assert_eq!(ser_buf[0], TlvType::EntityId as u8); + assert_eq!(ser_buf[1], 1); + assert_eq!(ser_buf[2], 5); + } + + #[test] + fn test_deserialization() { + let entity_id = UbfU8::new(5); + let mut buf: [u8; 4] = [0; 4]; + assert!(entity_id.write_to_be_bytes(&mut buf[2..]).is_ok()); + buf[0] = TlvType::EntityId as u8; + buf[1] = 1; + let tlv_from_raw = Tlv::from_be_bytes(&mut buf); + assert!(tlv_from_raw.is_ok()); + let tlv_from_raw = tlv_from_raw.unwrap(); + assert_eq!( + tlv_from_raw.tlv_type_field(), + TlvTypeField::Standard(TlvType::EntityId) + ); + assert_eq!(tlv_from_raw.len_value(), 1); + assert_eq!(tlv_from_raw.len_full(), 3); + assert!(tlv_from_raw.value().is_some()); + assert_eq!(tlv_from_raw.value().unwrap()[0], 5); + } + + #[test] + fn test_empty() { + let tlv_empty = Tlv::new_empty(TlvType::MsgToUser); + assert!(tlv_empty.value().is_none()); + assert!(tlv_empty.is_empty()); + assert_eq!(tlv_empty.len_full(), 2); + assert_eq!(tlv_empty.len_value(), 0); + assert_eq!( + tlv_empty.tlv_type_field(), + TlvTypeField::Standard(TlvType::MsgToUser) + ); + } + + #[test] + fn test_empty_serialization() { + let tlv_empty = Tlv::new_empty(TlvType::MsgToUser); + let mut buf: [u8; 4] = [0; 4]; + assert!(tlv_empty.write_to_be_bytes(&mut buf).is_ok()); + assert_eq!(buf[0], TlvType::MsgToUser as u8); + assert_eq!(buf[1], 0); + } + + #[test] + fn test_empty_deserialization() { + let mut buf: [u8; 4] = [0; 4]; + buf[0] = TlvType::MsgToUser as u8; + buf[1] = 0; + let tlv_empty = Tlv::from_be_bytes(&mut buf); + assert!(tlv_empty.is_ok()); + let tlv_empty = tlv_empty.unwrap(); + assert!(tlv_empty.is_empty()); + assert!(tlv_empty.value().is_none()); + assert_eq!( + tlv_empty.tlv_type_field(), + TlvTypeField::Standard(TlvType::MsgToUser) + ); + assert_eq!(tlv_empty.len_full(), 2); + assert_eq!(tlv_empty.len_value(), 0); + } + + #[test] + fn test_buf_too_large() { + let buf_too_large: [u8; u8::MAX as usize + 1] = [0; u8::MAX as usize + 1]; + let tlv_res = Tlv::new(TlvType::MsgToUser, &buf_too_large); + assert!(tlv_res.is_err()); + let error = tlv_res.unwrap_err(); + if let TlvLvError::DataTooLarge(size) = error { + assert_eq!(size, u8::MAX as usize + 1); + } else { + panic!("unexpected error {:?}", error); + } + } + #[test] + fn test_deserialization_custom_tlv_type() { + let mut buf: [u8; 4] = [0; 4]; + buf[0] = 3; + buf[1] = 1; + buf[2] = 5; + let tlv = Tlv::from_be_bytes(&mut buf); + assert!(tlv.is_ok()); + let tlv = tlv.unwrap(); + assert_eq!(tlv.tlv_type_field(), TlvTypeField::Custom(3)); + assert_eq!(tlv.len_value(), 1); + assert_eq!(tlv.len_full(), 3); + } } From 6865898102449a1a7101b20f537a53d9480a679d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 28 May 2023 23:50:12 +0200 Subject: [PATCH 18/48] add first test --- src/cfdp/pdu/metadata.rs | 50 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index b0701ab..50ae7b4 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -16,6 +16,16 @@ pub struct MetadataGenericParams { file_size: u64, } +impl MetadataGenericParams { + pub fn new(closure_requested: bool, checksum_type: ChecksumType, file_size: u64) -> Self { + Self { + closure_requested, + checksum_type, + file_size + } + } +} + pub fn build_metadata_opts_from_slice( buf: &mut [u8], tlvs: &[Tlv], @@ -79,9 +89,17 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { } } + pub fn src_file_name(&self) -> Lv<'src_name> { + self.src_file_name + } + + pub fn dest_file_name(&self) -> Lv<'dest_name> { + self.dest_file_name + } + pub fn written_len(&self) -> usize { - // One directive type octet - let mut len = self.pdu_header.written_len() + 1; + // One directive type octet, and one byte of the parameter field. + let mut len = self.pdu_header.written_len() + 2; if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { len += 8; } else { @@ -133,6 +151,32 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { #[cfg(test)] pub mod tests { + use crate::cfdp::ChecksumType; + use crate::cfdp::lv::Lv; + use crate::cfdp::pdu::metadata::{MetadataGenericParams, MetadataPdu}; + use crate::util::UbfU8; + use crate::cfdp::pdu::{CommonPduConfig, PduHeader}; + #[test] - fn test_basic() {} + fn test_basic() { + let src_id = UbfU8::new(5); + let dest_id = UbfU8::new(10); + let transaction_seq_num = UbfU8::new(20); + let common_pdu_conf = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).expect("Generating common PDU config"); + let pdu_header = PduHeader::new_no_file_data(common_pdu_conf, 0); + let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 10); + let src_filename = Lv::new_from_str("hello-world.txt").expect("Generating string LV failed"); + let src_len = src_filename.len_full(); + let dest_filename = Lv::new_from_str("hello-world2.txt").expect("Generating destination LV failed"); + let dest_len = dest_filename.len_full(); + let metadata_pdu= MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); + assert_eq!(metadata_pdu.written_len(), pdu_header.written_len() + 1 + 1 + 4 + src_len + dest_len); + assert_eq!(metadata_pdu.src_file_name(), src_filename); + assert_eq!(metadata_pdu.dest_file_name(), dest_filename); + } + + #[test] + fn test_serialization() { + + } } From e13183764e4012a393d42a7d57834ed15600cf7a Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 29 May 2023 01:29:04 +0200 Subject: [PATCH 19/48] continue metadata PDU tests --- src/cfdp/pdu/metadata.rs | 67 +++++++++++++++++----- src/cfdp/pdu/mod.rs | 117 +++++++++++++++++++++++---------------- src/cfdp/tlv.rs | 1 + src/util.rs | 4 ++ 4 files changed, 126 insertions(+), 63 deletions(-) diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 50ae7b4..1849a2f 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -21,7 +21,7 @@ impl MetadataGenericParams { Self { closure_requested, checksum_type, - file_size + file_size, } } } @@ -97,6 +97,10 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { self.dest_file_name } + pub fn options(&self) -> Option<&'opts [u8]> { + self.options + } + pub fn written_len(&self) -> usize { // One directive type octet, and one byte of the parameter field. let mut len = self.pdu_header.written_len() + 2; @@ -139,8 +143,12 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { .copy_from_slice(&(self.metadata_params.file_size as u32).to_be_bytes()); current_idx += core::mem::size_of::() } - current_idx += self.src_file_name.write_to_be_bytes(buf)?; - current_idx += self.dest_file_name.write_to_be_bytes(buf)?; + current_idx += self + .src_file_name + .write_to_be_bytes(&mut buf[current_idx..])?; + current_idx += self + .dest_file_name + .write_to_be_bytes(&mut buf[current_idx..])?; if let Some(opts) = self.options { buf[current_idx..current_idx + opts.len()].copy_from_slice(opts); current_idx += opts.len(); @@ -151,32 +159,63 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { #[cfg(test)] pub mod tests { - use crate::cfdp::ChecksumType; use crate::cfdp::lv::Lv; use crate::cfdp::pdu::metadata::{MetadataGenericParams, MetadataPdu}; + use crate::cfdp::pdu::tests::verify_raw_header; + use crate::cfdp::pdu::{CommonPduConfig, FileDirectiveType, PduHeader}; + use crate::cfdp::ChecksumType; use crate::util::UbfU8; - use crate::cfdp::pdu::{CommonPduConfig, PduHeader}; - #[test] - fn test_basic() { + fn common_pdu_conf() -> CommonPduConfig { let src_id = UbfU8::new(5); let dest_id = UbfU8::new(10); let transaction_seq_num = UbfU8::new(20); - let common_pdu_conf = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).expect("Generating common PDU config"); - let pdu_header = PduHeader::new_no_file_data(common_pdu_conf, 0); + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num) + .expect("Generating common PDU config") + } + + #[test] + fn test_basic() { + let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(), 0); let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 10); - let src_filename = Lv::new_from_str("hello-world.txt").expect("Generating string LV failed"); + let src_filename = + Lv::new_from_str("hello-world.txt").expect("Generating string LV failed"); let src_len = src_filename.len_full(); - let dest_filename = Lv::new_from_str("hello-world2.txt").expect("Generating destination LV failed"); + let dest_filename = + Lv::new_from_str("hello-world2.txt").expect("Generating destination LV failed"); let dest_len = dest_filename.len_full(); - let metadata_pdu= MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); - assert_eq!(metadata_pdu.written_len(), pdu_header.written_len() + 1 + 1 + 4 + src_len + dest_len); + let metadata_pdu = + MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); + assert_eq!( + metadata_pdu.written_len(), + pdu_header.written_len() + 1 + 1 + 4 + src_len + dest_len + ); assert_eq!(metadata_pdu.src_file_name(), src_filename); assert_eq!(metadata_pdu.dest_file_name(), dest_filename); + assert_eq!(metadata_pdu.options(), None); } #[test] fn test_serialization() { - + let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(), 0); + let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 10); + let src_filename = + Lv::new_from_str("hello-world.txt").expect("Generating string LV failed"); + let src_len = src_filename.len_full(); + let dest_filename = + Lv::new_from_str("hello-world2.txt").expect("Generating destination LV failed"); + let dest_len = dest_filename.len_full(); + let metadata_pdu = + MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); + let mut buf: [u8; 64] = [0; 64]; + let res = metadata_pdu.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!( + written, + pdu_header.written_len() + 1 + 1 + 4 + src_len + dest_len + ); + verify_raw_header(&pdu_header, &buf); + assert_eq!(buf[7], FileDirectiveType::MetadataPdu as u8); } } diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 408fc79..23f2f77 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -376,10 +376,73 @@ mod tests { CrcFlag, Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, TransmissionMode, CFDP_VERSION_2, }; - use crate::util::{UbfU8, UnsignedByteField, UnsignedByteFieldU16, UnsignedByteFieldU8}; + use crate::util::{ + UbfU8, UnsignedByteField, UnsignedByteFieldU16, UnsignedByteFieldU8, UnsignedEnum, + }; use crate::ByteConversionError; use std::format; + pub(crate) fn verify_raw_header(pdu_conf: &PduHeader, buf: &[u8]) { + assert_eq!((buf[0] >> 5) & 0b111, CFDP_VERSION_2); + // File directive + assert_eq!((buf[0] >> 4) & 1, pdu_conf.pdu_type as u8); + // Towards receiver + assert_eq!((buf[0] >> 3) & 1, pdu_conf.pdu_conf.direction as u8); + // Acknowledged + assert_eq!((buf[0] >> 2) & 1, pdu_conf.pdu_conf.trans_mode as u8); + // No CRC + assert_eq!((buf[0] >> 1) & 1, pdu_conf.pdu_conf.crc_flag as u8); + // Regular file size + assert_eq!(buf[0] & 1, pdu_conf.pdu_conf.file_flag as u8); + let pdu_datafield_len = u16::from_be_bytes(buf[1..3].try_into().unwrap()); + assert_eq!(pdu_datafield_len, pdu_conf.pdu_datafield_len); + // No record boundary preservation + assert_eq!((buf[3] >> 7) & 1, pdu_conf.seg_ctrl as u8); + // Entity ID length raw value is actual number of octets - 1 => 0 + let entity_id_len = pdu_conf.pdu_conf.source_entity_id.len(); + assert_eq!((buf[3] >> 4) & 0b111, entity_id_len as u8 - 1); + // No segment metadata + assert_eq!((buf[3] >> 3) & 0b1, pdu_conf.seg_metadata_flag as u8); + // Transaction Sequence ID length raw value is actual number of octets - 1 => 0 + let seq_num_len = pdu_conf.pdu_conf.transaction_seq_num.len(); + assert_eq!(buf[3] & 0b111, seq_num_len as u8 - 1); + let mut current_idx = 4; + let mut byte_field_check = |field_len: usize, ubf: &UnsignedByteField| { + match field_len { + 1 => assert_eq!(buf[current_idx], ubf.value() as u8), + 2 => assert_eq!( + u16::from_be_bytes( + buf[current_idx..current_idx + field_len] + .try_into() + .unwrap() + ), + ubf.value() as u16 + ), + 4 => assert_eq!( + u32::from_be_bytes( + buf[current_idx..current_idx + field_len] + .try_into() + .unwrap() + ), + ubf.value() as u32 + ), + 8 => assert_eq!( + u64::from_be_bytes( + buf[current_idx..current_idx + field_len] + .try_into() + .unwrap() + ), + ubf.value() as u64 + ), + _ => panic!("invalid entity ID length"), + } + current_idx += field_len + }; + byte_field_check(entity_id_len, &pdu_conf.pdu_conf.source_entity_id); + byte_field_check(seq_num_len, &pdu_conf.pdu_conf.transaction_seq_num); + byte_field_check(entity_id_len, &pdu_conf.pdu_conf.dest_entity_id); + } + #[test] fn test_basic_state() { let src_id = UnsignedByteFieldU8::new(1); @@ -401,6 +464,7 @@ mod tests { SegmentationControl::NoRecordBoundaryPreservation ); assert_eq!(pdu_header.pdu_datafield_len, 5); + assert_eq!(pdu_header.written_len(), 7); } #[test] @@ -416,30 +480,7 @@ mod tests { assert!(res.is_ok()); // 4 byte fixed header plus three bytes src, dest ID and transaction ID assert_eq!(res.unwrap(), 7); - assert_eq!((buf[0] >> 5) & 0b111, CFDP_VERSION_2); - // File directive - assert_eq!((buf[0] >> 4) & 1, 0); - // Towards receiver - assert_eq!((buf[0] >> 3) & 1, 0); - // Acknowledged - assert_eq!((buf[0] >> 2) & 1, 0); - // No CRC - assert_eq!((buf[0] >> 1) & 1, 0); - // Regular file size - assert_eq!(buf[0] & 1, 0); - let pdu_datafield_len = u16::from_be_bytes(buf[1..3].try_into().unwrap()); - assert_eq!(pdu_datafield_len, 5); - // No record boundary preservation - assert_eq!((buf[3] >> 7) & 1, 0); - // Entity ID length raw value is actual number of octets - 1 => 0 - assert_eq!((buf[3] >> 4) & 0b111, 0); - // No segment metadata - assert_eq!((buf[3] >> 3) & 0b1, 0); - // Transaction Sequence ID length raw value is actual number of octets - 1 => 0 - assert_eq!(buf[3] & 0b111, 0); - assert_eq!(buf[4], 1); - assert_eq!(buf[5], 3); - assert_eq!(buf[6], 2); + verify_raw_header(&pdu_header, &buf); } #[test] @@ -478,35 +519,13 @@ mod tests { SegmentMetadataFlag::Present, SegmentationControl::WithRecordBoundaryPreservation, ); + assert_eq!(pdu_header.written_len(), 10); let mut buf: [u8; 16] = [0; 16]; let res = pdu_header.write_to_be_bytes(&mut buf); assert!(res.is_ok(), "{}", format!("Result {res:?} not okay")); // 4 byte fixed header, 6 bytes additional fields assert_eq!(res.unwrap(), 10); - assert_eq!((buf[0] >> 5) & 0b111, CFDP_VERSION_2); - // File directive - assert_eq!((buf[0] >> 4) & 1, 1); - // Towards sender - assert_eq!((buf[0] >> 3) & 1, 1); - // Unacknowledged - assert_eq!((buf[0] >> 2) & 1, 1); - // With CRC - assert_eq!((buf[0] >> 1) & 1, 1); - // Large file size - assert_eq!(buf[0] & 1, 1); - let pdu_datafield_len = u16::from_be_bytes(buf[1..3].try_into().unwrap()); - assert_eq!(pdu_datafield_len, 5); - // With record boundary preservation - assert_eq!((buf[3] >> 7) & 1, 1); - // Entity ID length raw value is actual number of octets - 1 => 1 - assert_eq!((buf[3] >> 4) & 0b111, 1); - // With segment metadata - assert_eq!((buf[3] >> 3) & 0b1, 1); - // Transaction Sequence ID length raw value is actual number of octets - 1 => 1 - assert_eq!(buf[3] & 0b111, 1); - assert_eq!(u16::from_be_bytes(buf[4..6].try_into().unwrap()), 0x0001); - assert_eq!(u16::from_be_bytes(buf[6..8].try_into().unwrap()), 0x0405); - assert_eq!(u16::from_be_bytes(buf[8..10].try_into().unwrap()), 0x0203); + verify_raw_header(&pdu_header, &buf); } #[test] diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 5f9a9c0..3f91af1 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -232,6 +232,7 @@ mod tests { panic!("unexpected error {:?}", error); } } + #[test] fn test_deserialization_custom_tlv_type() { let mut buf: [u8; 4] = [0; 4]; diff --git a/src/util.rs b/src/util.rs index cb98955..f37d5b6 100644 --- a/src/util.rs +++ b/src/util.rs @@ -126,6 +126,10 @@ impl UnsignedByteField { Self { width, value } } + pub fn value(&self) -> u64 { + self.value + } + pub fn new_from_be_bytes(width: usize, buf: &[u8]) -> Result { if width > buf.len() { return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { From ce0848dc28e4c4da6f3790d216e3f890ff026b6c Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 29 May 2023 13:46:19 +0200 Subject: [PATCH 20/48] base line metadata PDU impl done --- CHANGELOG.md | 2 + src/cfdp/pdu/metadata.rs | 86 ++++++++++++++++++++++++++++++++++++++-- src/cfdp/pdu/mod.rs | 78 ++++++++++++++++++++++++++++++++++-- src/ecss/mod.rs | 11 +++-- src/lib.rs | 4 ++ src/tc.rs | 6 +-- src/tm.rs | 6 +-- 7 files changed, 173 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e1d69f..50a2301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - 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) +- Moved `ecss::CRC_CCITT_FALSE` CRC constant to the root module. This CRC type is not just used by + the PUS standard, but by the CCSDS Telecommand standard and the CFDP standard as well. # [v0.5.4] 2023-02-12 diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 1849a2f..91f992b 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -103,7 +103,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { pub fn written_len(&self) -> usize { // One directive type octet, and one byte of the parameter field. - let mut len = self.pdu_header.written_len() + 2; + let mut len = self.pdu_header.header_len() + 2; if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { len += 8; } else { @@ -116,6 +116,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { } len } + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { let expected_len = self.written_len(); if buf.len() < expected_len { @@ -129,7 +130,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { buf[current_idx] = FileDirectiveType::MetadataPdu as u8; current_idx += 1; buf[current_idx] = ((self.metadata_params.closure_requested as u8) << 7) - | ((self.metadata_params.checksum_type as u8) << 4); + | (self.metadata_params.checksum_type as u8); current_idx += 1; if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { buf[current_idx..current_idx + core::mem::size_of::()] @@ -155,6 +156,69 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { } Ok(current_idx) } + + pub fn from_be_bytes<'longest: 'src_name + 'dest_name + 'opts>( + buf: &'longest [u8], + ) -> Result, PduError> { + let (pdu_header, mut current_idx) = PduHeader::from_be_bytes(buf)?; + let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; + let is_large_file = pdu_header.pdu_conf.file_flag == LargeFileFlag::Large; + // Minimal length: 1 byte + FSS (4 byte) + 2 empty LV (1 byte) + let mut min_expected_len = current_idx + 7; + if is_large_file { + min_expected_len += 4; + } + min_expected_len = core::cmp::max(min_expected_len, pdu_header.pdu_len()); + if buf.len() < min_expected_len { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: min_expected_len, + }) + .into()); + } + let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { + PduError::InvalidDirectiveType((buf[current_idx], FileDirectiveType::MetadataPdu)) + })?; + if directive_type != FileDirectiveType::MetadataPdu { + return Err(PduError::WrongDirectiveType(( + directive_type, + FileDirectiveType::MetadataPdu, + ))); + } + current_idx += 1; + + let file_size = if pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { + u64::from_be_bytes(buf[current_idx + 1..current_idx + 9].try_into().unwrap()) + } else { + u32::from_be_bytes(buf[current_idx + 1..current_idx + 5].try_into().unwrap()) as u64 + }; + let metadata_params = MetadataGenericParams { + closure_requested: ((buf[current_idx] >> 6) & 0b1) != 0, + checksum_type: ChecksumType::try_from(buf[current_idx] & 0b1111) + .map_err(|_| PduError::InvalidChecksumType(buf[current_idx] & 0b1111))?, + file_size, + }; + current_idx += 5; + if is_large_file { + current_idx += 4; + } + let src_file_name = Lv::from_be_bytes(&buf[current_idx..])?; + current_idx += src_file_name.len_full(); + let dest_file_name = Lv::from_be_bytes(&buf[current_idx..])?; + current_idx += dest_file_name.len_full(); + // All left-over bytes are options. + let mut options = None; + if current_idx < full_len_without_crc { + options = Some(&buf[current_idx..full_len_without_crc]); + } + Ok(Self { + pdu_header, + metadata_params, + src_file_name, + dest_file_name, + options, + }) + } } #[cfg(test)] @@ -188,7 +252,7 @@ pub mod tests { MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); assert_eq!( metadata_pdu.written_len(), - pdu_header.written_len() + 1 + 1 + 4 + src_len + dest_len + pdu_header.header_len() + 1 + 1 + 4 + src_len + dest_len ); assert_eq!(metadata_pdu.src_file_name(), src_filename); assert_eq!(metadata_pdu.dest_file_name(), dest_filename); @@ -213,9 +277,23 @@ pub mod tests { let written = res.unwrap(); assert_eq!( written, - pdu_header.written_len() + 1 + 1 + 4 + src_len + dest_len + pdu_header.header_len() + 1 + 1 + 4 + src_len + dest_len ); verify_raw_header(&pdu_header, &buf); assert_eq!(buf[7], FileDirectiveType::MetadataPdu as u8); + assert_eq!(buf[8] >> 6, false as u8); + assert_eq!(buf[8] & 0b1111, ChecksumType::Crc32 as u8); + assert_eq!(u32::from_be_bytes(buf[9..13].try_into().unwrap()), 10); + let mut current_idx = 13; + let src_name_from_raw = + Lv::from_be_bytes(&buf[current_idx..]).expect("Creating source name LV failed"); + assert_eq!(src_name_from_raw, src_filename); + current_idx += src_name_from_raw.len_full(); + let dest_name_from_raw = + Lv::from_be_bytes(&buf[current_idx..]).expect("Creating dest name LV failed"); + assert_eq!(dest_name_from_raw, dest_filename); + current_idx += dest_name_from_raw.len_full(); + // No options, so no additional data here. + assert_eq!(current_idx, written); } } diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 23f2f77..e2fd887 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -1,6 +1,7 @@ //! CFDP Packet Data Unit (PDU) support. use crate::cfdp::*; use crate::util::{UnsignedByteField, UnsignedEnum}; +use crate::CRC_CCITT_FALSE; use crate::{ByteConversionError, SizeMissmatch}; use core::fmt::{Display, Formatter}; #[cfg(feature = "std")] @@ -34,7 +35,19 @@ pub enum PduError { /// The first entry will be the source entity ID length, the second one the destination entity /// ID length. SourceDestIdLenMissmatch((usize, usize)), + /// The first tuple entry will be the found directive type, the second entry the expected entry + /// type. + WrongDirectiveType((FileDirectiveType, FileDirectiveType)), + /// The directive type field contained a value not in the range of permitted values. + /// The first tuple entry will be the found raw number, the second entry the expected entry + /// type. + InvalidDirectiveType((u8, FileDirectiveType)), + /// Invalid checksum type which is not part of the checksums listed in the + /// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/). + InvalidChecksumType(u8), FileSizeTooLarge(u64), + /// If the CRC flag for a PDU is enabled and the checksum check fails. Contains raw 16-bit CRC. + ChecksumError(u16), } impl Display for PduError { @@ -68,7 +81,23 @@ impl Display for PduError { write!(f, "{}", e) } PduError::FileSizeTooLarge(value) => { - write!(f, "file size value {} exceeds allowed 32 bit width", value) + write!(f, "file size value {value} exceeds allowed 32 bit width") + } + PduError::WrongDirectiveType((found, expected)) => { + write!(f, "found directive type {found:?}, expected {expected:?}") + } + PduError::InvalidDirectiveType((found, expected)) => { + write!( + f, + "invalid directive type value {found}, expected {expected:?} ({})", + *expected as u8 + ) + } + PduError::InvalidChecksumType(checksum_type) => { + write!(f, "invalid checksum type {checksum_type}") + } + PduError::ChecksumError(checksum) => { + write!(f, "checksum error for CRC {checksum:#04x}") } } } @@ -214,12 +243,20 @@ impl PduHeader { } } - pub fn written_len(&self) -> usize { + /// Returns only the length of the PDU header when written to a raw buffer. + pub fn header_len(&self) -> usize { FIXED_HEADER_LEN + self.pdu_conf.source_entity_id.len() + self.pdu_conf.transaction_seq_num.len() + self.pdu_conf.dest_entity_id.len() } + + /// Returns the full length of the PDU when written to a raw buffer, which is the header length + /// plus the PDU datafield length. + pub fn pdu_len(&self) -> usize { + self.header_len() + self.pdu_datafield_len as usize + } + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { // Internal note: There is currently no way to pass a PDU configuration like this, but // this check is still kept for defensive programming. @@ -270,6 +307,39 @@ impl PduHeader { Ok(current_idx) } + /// This function first verifies that the buffer can hold the full length of the PDU parsed from + /// the header. Then, it verifies the checksum as specified in the standard if the CRC flag + /// of the PDU header is set. + /// + /// This function will return the PDU length excluding the 2 CRC bytes on success. If the CRC + /// flag is not set, it will simply return the PDU length. + pub fn verify_length_and_checksum(&self, buf: &[u8]) -> Result { + if buf.len() < self.pdu_len() { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.pdu_len(), + }) + .into()); + } + if self.pdu_conf.crc_flag == CrcFlag::WithCrc { + let mut digest = CRC_CCITT_FALSE.digest(); + digest.update(&buf[..self.pdu_len()]); + if digest.finalize() != 0 { + return Err(PduError::ChecksumError(u16::from_be_bytes( + buf[self.pdu_len() - 2..self.pdu_len()].try_into().unwrap(), + ))); + } + } + Ok(self.pdu_len()) + } + + /// Please note that this function will not verify that the passed buffer can hold the full + /// PDU length. This allows recovering the header portion even if the data field length is + /// invalid. This function will also not do the CRC procedure specified in chapter 4.1.1 + /// and 4.1.2 because performing the CRC procedure requires the buffer to be large enough + /// to hold the full PDU. + /// + /// Both functions can however be performed with the [verify_length_and_checksum] function. pub fn from_be_bytes(buf: &[u8]) -> Result<(Self, usize), PduError> { if buf.len() < FIXED_HEADER_LEN { return Err(PduError::ByteConversionError( @@ -464,7 +534,7 @@ mod tests { SegmentationControl::NoRecordBoundaryPreservation ); assert_eq!(pdu_header.pdu_datafield_len, 5); - assert_eq!(pdu_header.written_len(), 7); + assert_eq!(pdu_header.header_len(), 7); } #[test] @@ -519,7 +589,7 @@ mod tests { SegmentMetadataFlag::Present, SegmentationControl::WithRecordBoundaryPreservation, ); - assert_eq!(pdu_header.written_len(), 10); + assert_eq!(pdu_header.header_len(), 10); let mut buf: [u8; 16] = [0; 16]; let res = pdu_header.write_to_be_bytes(&mut buf); assert!(res.is_ok(), "{}", format!("Result {res:?} not okay")); diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index 5d755bc..4ebf787 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -3,10 +3,9 @@ //! //! 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}; +use crate::{ByteConversionError, CcsdsPacket, CRC_CCITT_FALSE}; use core::fmt::{Debug, Display, Formatter}; use core::mem::size_of; -use crc::{Crc, CRC_16_IBM_3740}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -19,9 +18,6 @@ pub mod scheduling; pub mod verification; pub type CrcType = u16; - -/// CRC algorithm used by the PUS standard. -pub const CRC_CCITT_FALSE: Crc = Crc::::new(&CRC_16_IBM_3740); pub const CCSDS_HEADER_LEN: usize = size_of::(); #[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] @@ -261,7 +257,10 @@ pub(crate) fn user_data_from_raw( } } -pub(crate) fn verify_crc16_from_raw(raw_data: &[u8], crc16: u16) -> Result<(), PusError> { +pub(crate) fn verify_crc16_ccitt_false_from_raw( + raw_data: &[u8], + crc16: u16, +) -> Result<(), PusError> { let mut digest = CRC_CCITT_FALSE.digest(); digest.update(raw_data); if digest.finalize() == 0 { diff --git a/src/lib.rs b/src/lib.rs index 2c3dbda..5062c18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ extern crate std; use crate::ecss::CCSDS_HEADER_LEN; use core::fmt::{Debug, Display, Formatter}; +use crc::{Crc, CRC_16_IBM_3740}; use delegate::delegate; #[cfg(not(feature = "std"))] use num_traits::Unsigned; @@ -81,6 +82,9 @@ mod private { pub trait Sealed {} } +/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard. +pub const CRC_CCITT_FALSE: Crc = Crc::::new(&CRC_16_IBM_3740); + pub const MAX_APID: u16 = 2u16.pow(11) - 1; pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1; diff --git a/src/tc.rs b/src/tc.rs index 184e799..8b75f1c 100644 --- a/src/tc.rs +++ b/src/tc.rs @@ -33,12 +33,12 @@ //! ``` use crate::ecss::{ ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls, user_data_from_raw, - verify_crc16_from_raw, CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE, + verify_crc16_ccitt_false_from_raw, CrcType, PusError, PusPacket, PusVersion, }; -use crate::SpHeader; use crate::{ ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SizeMissmatch, CCSDS_HEADER_LEN, }; +use crate::{SpHeader, CRC_CCITT_FALSE}; use core::mem::size_of; use delegate::delegate; #[cfg(feature = "serde")] @@ -437,7 +437,7 @@ impl<'raw_data> PusTc<'raw_data> { calc_crc_on_serialization: false, crc16: Some(crc_from_raw_data(raw_data)?), }; - verify_crc16_from_raw(raw_data, pus_tc.crc16.expect("CRC16 invalid"))?; + verify_crc16_ccitt_false_from_raw(raw_data, pus_tc.crc16.expect("CRC16 invalid"))?; Ok((pus_tc, total_len)) } diff --git a/src/tm.rs b/src/tm.rs index 5813ccc..ed84a2e 100644 --- a/src/tm.rs +++ b/src/tm.rs @@ -2,11 +2,11 @@ //! to [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). use crate::ecss::{ ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls, user_data_from_raw, - verify_crc16_from_raw, CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE, + verify_crc16_ccitt_false_from_raw, CrcType, PusError, PusPacket, PusVersion, }; use crate::{ ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SizeMissmatch, SpHeader, - CCSDS_HEADER_LEN, + CCSDS_HEADER_LEN, CRC_CCITT_FALSE, }; use core::mem::size_of; #[cfg(feature = "serde")] @@ -439,7 +439,7 @@ impl<'raw_data> PusTm<'raw_data> { calc_crc_on_serialization: false, crc16: Some(crc_from_raw_data(raw_data)?), }; - verify_crc16_from_raw(raw_data, pus_tm.crc16.expect("CRC16 invalid"))?; + verify_crc16_ccitt_false_from_raw(raw_data, pus_tm.crc16.expect("CRC16 invalid"))?; Ok((pus_tm, total_len)) } From bf4e8414990144bae94e0e800e9f3f892687f2ce Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 29 May 2023 14:28:15 +0200 Subject: [PATCH 21/48] added correct PDU datafield length handling --- src/cfdp/pdu/metadata.rs | 45 ++++++++++++++++++++++++++++++++-------- src/cfdp/pdu/mod.rs | 34 +++++++++++++++--------------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 91f992b..37e6cac 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -1,8 +1,8 @@ use crate::cfdp::lv::Lv; use crate::cfdp::pdu::{FileDirectiveType, PduError, PduHeader}; use crate::cfdp::tlv::Tlv; -use crate::cfdp::{ChecksumType, LargeFileFlag}; -use crate::{ByteConversionError, SizeMissmatch}; +use crate::cfdp::{ChecksumType, CrcFlag, LargeFileFlag}; +use crate::{ByteConversionError, SizeMissmatch, CRC_CCITT_FALSE}; #[cfg(feature = "alloc")] use alloc::vec::Vec; #[cfg(feature = "serde")] @@ -74,12 +74,25 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { } pub fn new_with_opts( - pdu_header: PduHeader, + mut pdu_header: PduHeader, metadata_params: MetadataGenericParams, src_file_name: Lv<'src_name>, dest_file_name: Lv<'dest_name>, options: Option<&'opts [u8]>, ) -> Self { + let is_large_file = pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large; + let has_crc = pdu_header.common_pdu_conf().crc_flag == CrcFlag::WithCrc; + pdu_header.pdu_datafield_len = + 2 + 4 + src_file_name.len_full() as u16 + dest_file_name.len_full() as u16; + if is_large_file { + pdu_header.pdu_datafield_len += 4; + } + if has_crc { + pdu_header.pdu_datafield_len += 2; + } + if let Some(opts) = options { + pdu_header.pdu_datafield_len += opts.len() as u16; + } Self { pdu_header, metadata_params, @@ -114,10 +127,17 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { if let Some(opts) = self.options { len += opts.len(); } + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + len += 2; + } len } - pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + pub fn pdu_header(&self) -> &PduHeader { + &self.pdu_header + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { let expected_len = self.written_len(); if buf.len() < expected_len { return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { @@ -126,7 +146,8 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { }) .into()); } - let mut current_idx = self.pdu_header.write_to_be_bytes(buf)?; + + let mut current_idx = self.pdu_header.write_to_bytes(buf)?; buf[current_idx] = FileDirectiveType::MetadataPdu as u8; current_idx += 1; buf[current_idx] = ((self.metadata_params.closure_requested as u8) << 7) @@ -154,13 +175,19 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { buf[current_idx..current_idx + opts.len()].copy_from_slice(opts); current_idx += opts.len(); } + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + let mut digest = CRC_CCITT_FALSE.digest(); + digest.update(&buf[..current_idx]); + buf[current_idx..current_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes()); + current_idx += 2; + } Ok(current_idx) } - pub fn from_be_bytes<'longest: 'src_name + 'dest_name + 'opts>( + pub fn from_bytes<'longest: 'src_name + 'dest_name + 'opts>( buf: &'longest [u8], ) -> Result, PduError> { - let (pdu_header, mut current_idx) = PduHeader::from_be_bytes(buf)?; + let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; let is_large_file = pdu_header.pdu_conf.file_flag == LargeFileFlag::Large; // Minimal length: 1 byte + FSS (4 byte) + 2 empty LV (1 byte) @@ -272,14 +299,14 @@ pub mod tests { let metadata_pdu = MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); let mut buf: [u8; 64] = [0; 64]; - let res = metadata_pdu.write_to_be_bytes(&mut buf); + let res = metadata_pdu.write_to_bytes(&mut buf); assert!(res.is_ok()); let written = res.unwrap(); assert_eq!( written, pdu_header.header_len() + 1 + 1 + 4 + src_len + dest_len ); - verify_raw_header(&pdu_header, &buf); + verify_raw_header(metadata_pdu.pdu_header(), &buf); assert_eq!(buf[7], FileDirectiveType::MetadataPdu as u8); assert_eq!(buf[8] >> 6, false as u8); assert_eq!(buf[8] & 0b1111, ChecksumType::Crc32 as u8); diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index e2fd887..c443d3a 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -257,7 +257,7 @@ impl PduHeader { self.header_len() + self.pdu_datafield_len as usize } - pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { // Internal note: There is currently no way to pass a PDU configuration like this, but // this check is still kept for defensive programming. if self.pdu_conf.source_entity_id.len() != self.pdu_conf.dest_entity_id.len() { @@ -340,7 +340,7 @@ impl PduHeader { /// to hold the full PDU. /// /// Both functions can however be performed with the [verify_length_and_checksum] function. - pub fn from_be_bytes(buf: &[u8]) -> Result<(Self, usize), PduError> { + pub fn from_bytes(buf: &[u8]) -> Result<(Self, usize), PduError> { if buf.len() < FIXED_HEADER_LEN { return Err(PduError::ByteConversionError( ByteConversionError::FromSliceTooSmall(SizeMissmatch { @@ -546,7 +546,7 @@ mod tests { .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let mut buf: [u8; 7] = [0; 7]; - let res = pdu_header.write_to_be_bytes(&mut buf); + let res = pdu_header.write_to_bytes(&mut buf); assert!(res.is_ok()); // 4 byte fixed header plus three bytes src, dest ID and transaction ID assert_eq!(res.unwrap(), 7); @@ -562,9 +562,9 @@ mod tests { .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let mut buf: [u8; 7] = [0; 7]; - let res = pdu_header.write_to_be_bytes(&mut buf); + let res = pdu_header.write_to_bytes(&mut buf); assert!(res.is_ok()); - let deser_res = PduHeader::from_be_bytes(&buf); + let deser_res = PduHeader::from_bytes(&buf); assert!(deser_res.is_ok()); let (header_read_back, read_size) = deser_res.unwrap(); assert_eq!(read_size, 7); @@ -591,7 +591,7 @@ mod tests { ); assert_eq!(pdu_header.header_len(), 10); let mut buf: [u8; 16] = [0; 16]; - let res = pdu_header.write_to_be_bytes(&mut buf); + let res = pdu_header.write_to_bytes(&mut buf); assert!(res.is_ok(), "{}", format!("Result {res:?} not okay")); // 4 byte fixed header, 6 bytes additional fields assert_eq!(res.unwrap(), 10); @@ -617,9 +617,9 @@ mod tests { SegmentationControl::WithRecordBoundaryPreservation, ); let mut buf: [u8; 16] = [0; 16]; - let res = pdu_header.write_to_be_bytes(&mut buf); + let res = pdu_header.write_to_bytes(&mut buf); assert!(res.is_ok()); - let deser_res = PduHeader::from_be_bytes(&buf); + let deser_res = PduHeader::from_bytes(&buf); assert!(deser_res.is_ok()); let (header_read_back, read_size) = deser_res.unwrap(); assert_eq!(read_size, 10); @@ -635,11 +635,11 @@ mod tests { .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let mut buf: [u8; 7] = [0; 7]; - let res = pdu_header.write_to_be_bytes(&mut buf); + let res = pdu_header.write_to_bytes(&mut buf); assert!(res.is_ok()); buf[0] &= !0b1110_0000; buf[0] |= (CFDP_VERSION_2 + 1) << 5; - let res = PduHeader::from_be_bytes(&buf); + let res = PduHeader::from_bytes(&buf); assert!(res.is_err()); let error = res.unwrap_err(); if let PduError::CfdpVersionMissmatch(raw_version) = error { @@ -652,7 +652,7 @@ mod tests { #[test] fn test_buf_too_small_1() { let buf: [u8; 3] = [0; 3]; - let res = PduHeader::from_be_bytes(&buf); + let res = PduHeader::from_bytes(&buf); assert!(res.is_err()); let error = res.unwrap_err(); if let PduError::ByteConversionError(ByteConversionError::FromSliceTooSmall(missmatch)) = @@ -674,9 +674,9 @@ mod tests { .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let mut buf: [u8; 7] = [0; 7]; - let res = pdu_header.write_to_be_bytes(&mut buf); + let res = pdu_header.write_to_bytes(&mut buf); assert!(res.is_ok()); - let header = PduHeader::from_be_bytes(&buf[0..6]); + let header = PduHeader::from_bytes(&buf[0..6]); assert!(header.is_err()); let error = header.unwrap_err(); if let PduError::ByteConversionError(ByteConversionError::FromSliceTooSmall(missmatch)) = @@ -737,12 +737,12 @@ mod tests { .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let mut buf: [u8; 7] = [0; 7]; - let res = pdu_header.write_to_be_bytes(&mut buf); + let res = pdu_header.write_to_bytes(&mut buf); assert!(res.is_ok()); buf[3] &= !0b0111_0000; // Equivalent to the length of three buf[3] |= 0b10 << 4; - let header_res = PduHeader::from_be_bytes(&buf); + let header_res = PduHeader::from_bytes(&buf); assert!(header_res.is_err()); let error = header_res.unwrap_err(); if let PduError::InvalidEntityLen(len) = error { @@ -761,12 +761,12 @@ mod tests { .expect("common config creation failed"); let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let mut buf: [u8; 7] = [0; 7]; - let res = pdu_header.write_to_be_bytes(&mut buf); + let res = pdu_header.write_to_bytes(&mut buf); assert!(res.is_ok()); buf[3] &= !0b0000_0111; // Equivalent to the length of three buf[3] |= 0b10; - let header_res = PduHeader::from_be_bytes(&buf); + let header_res = PduHeader::from_bytes(&buf); assert!(header_res.is_err()); let error = header_res.unwrap_err(); if let PduError::InvalidTransactionSeqNumLen(len) = error { From 3166a280bcf8a14342aa0641ff084a552e0a1dab Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 29 May 2023 23:38:07 +0200 Subject: [PATCH 22/48] metadata PDU done --- Cargo.toml | 2 +- src/cfdp/lv.rs | 8 +- src/cfdp/pdu/metadata.rs | 289 ++++++++++++++++++++++++++++++++++----- src/cfdp/pdu/mod.rs | 5 +- src/cfdp/tlv.rs | 10 +- 5 files changed, 269 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index debc410..ec45f26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ crc = "3" delegate = ">=0.8, <0.10" [dependencies.num_enum] -version = "0.5" +version = "0.6" default-features = false [dependencies.serde] diff --git a/src/cfdp/lv.rs b/src/cfdp/lv.rs index 3ceb49a..23701be 100644 --- a/src/cfdp/lv.rs +++ b/src/cfdp/lv.rs @@ -103,7 +103,7 @@ impl<'data> Lv<'data> { } /// Reads a LV from a raw buffer. - pub fn from_be_bytes(buf: &'data [u8]) -> Result, ByteConversionError> { + pub fn from_bytes(buf: &'data [u8]) -> Result, ByteConversionError> { generic_len_check_deserialization(buf, MIN_LV_LEN)?; Self::from_be_bytes_no_len_check(buf) } @@ -198,7 +198,7 @@ pub mod tests { buf[2] = 2; buf[3] = 3; buf[4] = 4; - let lv = Lv::from_be_bytes(&buf); + let lv = Lv::from_bytes(&buf); assert!(lv.is_ok()); let lv = lv.unwrap(); assert!(!lv.is_empty()); @@ -215,7 +215,7 @@ pub mod tests { #[test] fn test_deserialization_empty() { let buf: [u8; 2] = [0; 2]; - let lv_empty = Lv::from_be_bytes(&buf); + let lv_empty = Lv::from_bytes(&buf); assert!(lv_empty.is_ok()); let lv_empty = lv_empty.unwrap(); assert!(lv_empty.is_empty()); @@ -255,7 +255,7 @@ pub mod tests { fn test_deserialization_buf_too_small() { let mut buf: [u8; 3] = [0; 3]; buf[0] = 4; - let res = Lv::from_be_bytes(&buf); + let res = Lv::from_bytes(&buf); assert!(res.is_err()); let error = res.unwrap_err(); if let ByteConversionError::FromSliceTooSmall(missmatch) = error { diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 37e6cac..dd6589c 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -32,7 +32,7 @@ pub fn build_metadata_opts_from_slice( ) -> Result { let mut written = 0; for tlv in tlvs { - written += tlv.write_to_be_bytes(buf)?; + written += tlv.write_to_be_bytes(&mut buf[written..])?; } Ok(written) } @@ -40,11 +40,45 @@ pub fn build_metadata_opts_from_slice( #[cfg(feature = "alloc")] pub fn build_metadata_opts_from_vec( buf: &mut [u8], - tlvs: Vec, + tlvs: &Vec, ) -> Result { build_metadata_opts_from_slice(buf, tlvs.as_slice()) } +/// Helper structure to loop through all options of a metadata PDU. It should be noted that +/// iterators in Rust are not fallible, but the TLV creation can fail, for example if the raw TLV +/// data in invalid for some reason. In that case, the iterator will yield [None] because there +/// is no way to recover from this. +/// +/// The user can accumulate the length of all TLVs yielded by the iterator and compare it against +/// the full length of the options to check whether the iterator was able to parse all TLVs +/// successfully. +pub struct OptionsIter<'opts> { + opt_buf: &'opts [u8], + current_idx: usize, +} + +impl<'opts> Iterator for OptionsIter<'opts> { + type Item = Tlv<'opts>; + + fn next(&mut self) -> Option { + if self.current_idx == self.opt_buf.len() { + return None; + } + let tlv = Tlv::from_bytes(&self.opt_buf[self.current_idx..]); + // There are not really fallible iterators so we can't continue here.. + if tlv.is_err() { + return None; + } + let tlv = tlv.unwrap(); + self.current_idx += tlv.len_full(); + Some(tlv) + } +} + +/// Metadata PDU abstraction. +/// +/// For more information, refer to CFDP chapter 5.2.5. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct MetadataPdu<'src_name, 'dest_name, 'opts> { @@ -64,7 +98,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { src_file_name: Lv<'src_name>, dest_file_name: Lv<'dest_name>, ) -> Self { - Self::new_with_opts( + Self::new_generic( pdu_header, metadata_params, src_file_name, @@ -74,6 +108,22 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { } pub fn new_with_opts( + pdu_header: PduHeader, + metadata_params: MetadataGenericParams, + src_file_name: Lv<'src_name>, + dest_file_name: Lv<'dest_name>, + options: &'opts [u8], + ) -> Self { + Self::new_generic( + pdu_header, + metadata_params, + src_file_name, + dest_file_name, + Some(options), + ) + } + + pub fn new_generic( mut pdu_header: PduHeader, metadata_params: MetadataGenericParams, src_file_name: Lv<'src_name>, @@ -114,6 +164,16 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { self.options } + /// Yield an iterator which can be used to loop through all options. Returns [None] if the + /// options field is empty. + pub fn options_iter(&self) -> Option> { + self.options?; + Some(OptionsIter { + opt_buf: self.options.unwrap(), + current_idx: 0, + }) + } + pub fn written_len(&self) -> usize { // One directive type octet, and one byte of the parameter field. let mut len = self.pdu_header.header_len() + 2; @@ -229,9 +289,9 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { if is_large_file { current_idx += 4; } - let src_file_name = Lv::from_be_bytes(&buf[current_idx..])?; + let src_file_name = Lv::from_bytes(&buf[current_idx..])?; current_idx += src_file_name.len_full(); - let dest_file_name = Lv::from_be_bytes(&buf[current_idx..])?; + let dest_file_name = Lv::from_bytes(&buf[current_idx..])?; current_idx += dest_file_name.len_full(); // All left-over bytes are options. let mut options = None; @@ -251,35 +311,70 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { #[cfg(test)] pub mod tests { use crate::cfdp::lv::Lv; - use crate::cfdp::pdu::metadata::{MetadataGenericParams, MetadataPdu}; + use crate::cfdp::pdu::metadata::{ + build_metadata_opts_from_slice, build_metadata_opts_from_vec, MetadataGenericParams, + MetadataPdu, + }; use crate::cfdp::pdu::tests::verify_raw_header; use crate::cfdp::pdu::{CommonPduConfig, FileDirectiveType, PduHeader}; - use crate::cfdp::ChecksumType; + use crate::cfdp::tlv::{Tlv, TlvType}; + use crate::cfdp::{ChecksumType, CrcFlag, LargeFileFlag}; use crate::util::UbfU8; + use std::vec; - fn common_pdu_conf() -> CommonPduConfig { + const SRC_FILENAME: &'static str = "hello-world.txt"; + const DEST_FILENAME: &'static str = "hello-world2.txt"; + + fn common_pdu_conf(crc_flag: CrcFlag, fss: LargeFileFlag) -> CommonPduConfig { let src_id = UbfU8::new(5); let dest_id = UbfU8::new(10); let transaction_seq_num = UbfU8::new(20); - CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num) - .expect("Generating common PDU config") + let mut pdu_conf = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num) + .expect("Generating common PDU config"); + pdu_conf.crc_flag = crc_flag; + pdu_conf.file_flag = fss; + pdu_conf + } + + fn generic_metadata_pdu<'opts>( + crc_flag: CrcFlag, + fss: LargeFileFlag, + opts: Option<&'opts [u8]>, + ) -> ( + Lv<'static>, + Lv<'static>, + MetadataPdu<'static, 'static, 'opts>, + ) { + let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0); + let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 10); + let src_filename = Lv::new_from_str(SRC_FILENAME).expect("Generating string LV failed"); + let dest_filename = + Lv::new_from_str(DEST_FILENAME).expect("Generating destination LV failed"); + ( + src_filename, + dest_filename, + MetadataPdu::new_generic( + pdu_header, + metadata_params, + src_filename, + dest_filename, + opts, + ), + ) } #[test] fn test_basic() { - let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(), 0); - let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 10); - let src_filename = - Lv::new_from_str("hello-world.txt").expect("Generating string LV failed"); - let src_len = src_filename.len_full(); - let dest_filename = - Lv::new_from_str("hello-world2.txt").expect("Generating destination LV failed"); - let dest_len = dest_filename.len_full(); - let metadata_pdu = - MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); + let (src_filename, dest_filename, metadata_pdu) = + generic_metadata_pdu(CrcFlag::NoCrc, LargeFileFlag::Normal, None); assert_eq!( metadata_pdu.written_len(), - pdu_header.header_len() + 1 + 1 + 4 + src_len + dest_len + metadata_pdu.pdu_header().header_len() + + 1 + + 1 + + 4 + + src_filename.len_full() + + dest_filename.len_full() ); assert_eq!(metadata_pdu.src_file_name(), src_filename); assert_eq!(metadata_pdu.dest_file_name(), dest_filename); @@ -288,23 +383,20 @@ pub mod tests { #[test] fn test_serialization() { - let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(), 0); - let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 10); - let src_filename = - Lv::new_from_str("hello-world.txt").expect("Generating string LV failed"); - let src_len = src_filename.len_full(); - let dest_filename = - Lv::new_from_str("hello-world2.txt").expect("Generating destination LV failed"); - let dest_len = dest_filename.len_full(); - let metadata_pdu = - MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); + let (src_filename, dest_filename, metadata_pdu) = + generic_metadata_pdu(CrcFlag::NoCrc, LargeFileFlag::Normal, None); let mut buf: [u8; 64] = [0; 64]; let res = metadata_pdu.write_to_bytes(&mut buf); assert!(res.is_ok()); let written = res.unwrap(); assert_eq!( written, - pdu_header.header_len() + 1 + 1 + 4 + src_len + dest_len + metadata_pdu.pdu_header.header_len() + + 1 + + 1 + + 4 + + src_filename.len_full() + + dest_filename.len_full() ); verify_raw_header(metadata_pdu.pdu_header(), &buf); assert_eq!(buf[7], FileDirectiveType::MetadataPdu as u8); @@ -313,14 +405,143 @@ pub mod tests { assert_eq!(u32::from_be_bytes(buf[9..13].try_into().unwrap()), 10); let mut current_idx = 13; let src_name_from_raw = - Lv::from_be_bytes(&buf[current_idx..]).expect("Creating source name LV failed"); + Lv::from_bytes(&buf[current_idx..]).expect("Creating source name LV failed"); assert_eq!(src_name_from_raw, src_filename); current_idx += src_name_from_raw.len_full(); let dest_name_from_raw = - Lv::from_be_bytes(&buf[current_idx..]).expect("Creating dest name LV failed"); + Lv::from_bytes(&buf[current_idx..]).expect("Creating dest name LV failed"); assert_eq!(dest_name_from_raw, dest_filename); current_idx += dest_name_from_raw.len_full(); // No options, so no additional data here. assert_eq!(current_idx, written); } + + #[test] + fn test_deserialization() { + let (_, _, metadata_pdu) = + generic_metadata_pdu(CrcFlag::NoCrc, LargeFileFlag::Normal, None); + let mut buf: [u8; 64] = [0; 64]; + metadata_pdu.write_to_bytes(&mut buf).unwrap(); + let pdu_read_back = MetadataPdu::from_bytes(&buf); + assert!(pdu_read_back.is_ok()); + let pdu_read_back = pdu_read_back.unwrap(); + assert_eq!(pdu_read_back, metadata_pdu); + } + + #[test] + fn test_with_crc_flag() { + let (src_filename, dest_filename, metadata_pdu) = + generic_metadata_pdu(CrcFlag::WithCrc, LargeFileFlag::Normal, None); + let mut buf: [u8; 64] = [0; 64]; + let write_res = metadata_pdu.write_to_bytes(&mut buf); + assert!(write_res.is_ok()); + let written = write_res.unwrap(); + assert_eq!( + written, + metadata_pdu.pdu_header().header_len() + + 1 + + 1 + + core::mem::size_of::() + + src_filename.len_full() + + dest_filename.len_full() + + 2 + ); + let pdu_read_back = MetadataPdu::from_bytes(&buf).unwrap(); + assert_eq!(pdu_read_back, metadata_pdu); + } + + #[test] + fn test_with_large_file_flag() { + let (src_filename, dest_filename, metadata_pdu) = + generic_metadata_pdu(CrcFlag::NoCrc, LargeFileFlag::Large, None); + let mut buf: [u8; 64] = [0; 64]; + let write_res = metadata_pdu.write_to_bytes(&mut buf); + assert!(write_res.is_ok()); + let written = write_res.unwrap(); + assert_eq!( + written, + metadata_pdu.pdu_header().header_len() + + 1 + + 1 + + core::mem::size_of::() + + src_filename.len_full() + + dest_filename.len_full() + ); + let pdu_read_back = MetadataPdu::from_bytes(&buf).unwrap(); + assert_eq!(pdu_read_back, metadata_pdu); + } + + #[test] + fn test_opts_builders() { + let tlv1 = Tlv::new_empty(TlvType::FlowLabel); + let msg_to_user: [u8; 4] = [1, 2, 3, 4]; + let tlv2 = Tlv::new(TlvType::MsgToUser, &msg_to_user).unwrap(); + let tlv_slice = [tlv1, tlv2]; + let mut buf: [u8; 32] = [0; 32]; + let opts = build_metadata_opts_from_slice(&mut buf, &tlv_slice); + assert!(opts.is_ok()); + let opts_len = opts.unwrap(); + assert_eq!(opts_len, tlv1.len_full() + tlv2.len_full()); + let tlv1_conv_back = Tlv::from_bytes(&buf).unwrap(); + assert_eq!(tlv1_conv_back, tlv1); + let tlv2_conv_back = Tlv::from_bytes(&buf[tlv1_conv_back.len_full()..]).unwrap(); + assert_eq!(tlv2_conv_back, tlv2); + } + + #[test] + fn test_opts_builders_from_vec() { + let tlv1 = Tlv::new_empty(TlvType::FlowLabel); + let msg_to_user: [u8; 4] = [1, 2, 3, 4]; + let tlv2 = Tlv::new(TlvType::MsgToUser, &msg_to_user).unwrap(); + let tlv_vec = vec![tlv1, tlv2]; + let mut buf: [u8; 32] = [0; 32]; + let opts = build_metadata_opts_from_vec(&mut buf, &tlv_vec); + assert!(opts.is_ok()); + let opts_len = opts.unwrap(); + assert_eq!(opts_len, tlv1.len_full() + tlv2.len_full()); + let tlv1_conv_back = Tlv::from_bytes(&buf).unwrap(); + assert_eq!(tlv1_conv_back, tlv1); + let tlv2_conv_back = Tlv::from_bytes(&buf[tlv1_conv_back.len_full()..]).unwrap(); + assert_eq!(tlv2_conv_back, tlv2); + } + + #[test] + fn test_with_opts() { + let tlv1 = Tlv::new_empty(TlvType::FlowLabel); + let msg_to_user: [u8; 4] = [1, 2, 3, 4]; + let tlv2 = Tlv::new(TlvType::MsgToUser, &msg_to_user).unwrap(); + let tlv_vec = vec![tlv1, tlv2]; + let mut opts_buf: [u8; 32] = [0; 32]; + let opts_len = build_metadata_opts_from_vec(&mut opts_buf, &tlv_vec).unwrap(); + let (src_filename, dest_filename, metadata_pdu) = generic_metadata_pdu( + CrcFlag::NoCrc, + LargeFileFlag::Normal, + Some(&opts_buf[..opts_len]), + ); + let mut buf: [u8; 128] = [0; 128]; + let write_res = metadata_pdu.write_to_bytes(&mut buf); + assert!(write_res.is_ok()); + let written = write_res.unwrap(); + assert_eq!( + written, + metadata_pdu.pdu_header.header_len() + + 1 + + 1 + + 4 + + src_filename.len_full() + + dest_filename.len_full() + + opts_len + ); + let pdu_read_back = MetadataPdu::from_bytes(&buf).unwrap(); + assert_eq!(pdu_read_back, metadata_pdu); + let opts_iter = pdu_read_back.options_iter(); + assert!(opts_iter.is_some()); + let opts_iter = opts_iter.unwrap(); + let mut accumulated_len = 0; + for (idx, opt) in opts_iter.enumerate() { + assert_eq!(tlv_vec[idx], opt); + accumulated_len += opt.len_full(); + } + assert_eq!(accumulated_len, pdu_read_back.options().unwrap().len()); + } } diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index c443d3a..1643b5d 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -206,7 +206,9 @@ impl CommonPduConfig { pub const FIXED_HEADER_LEN: usize = 4; -/// Abstraction for the PDU header common to all CFDP PDUs +/// Abstraction for the PDU header common to all CFDP PDUs. +/// +/// For detailed information, refer to chapter 5.1 of the CFDP standard. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PduHeader { @@ -329,6 +331,7 @@ impl PduHeader { buf[self.pdu_len() - 2..self.pdu_len()].try_into().unwrap(), ))); } + return Ok(self.pdu_len() - 2); } Ok(self.pdu_len()) } diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 3f91af1..503b478 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -111,11 +111,11 @@ impl<'data> Tlv<'data> { /// bytestream with the exact size of the expected TLV. This function will take care /// of parsing the length byte, and the length of the parsed TLV can be retrieved using /// [len_full]. - pub fn from_be_bytes(buf: &'data [u8]) -> Result, TlvLvError> { + pub fn from_bytes(buf: &'data [u8]) -> Result, TlvLvError> { generic_len_check_deserialization(buf, MIN_TLV_LEN)?; Ok(Self { tlv_type_field: TlvTypeField::from(buf[0]), - lv: Lv::from_be_bytes(&buf[MIN_LV_LEN..])?, + lv: Lv::from_bytes(&buf[MIN_LV_LEN..])?, }) } } @@ -167,7 +167,7 @@ mod tests { assert!(entity_id.write_to_be_bytes(&mut buf[2..]).is_ok()); buf[0] = TlvType::EntityId as u8; buf[1] = 1; - let tlv_from_raw = Tlv::from_be_bytes(&mut buf); + let tlv_from_raw = Tlv::from_bytes(&mut buf); assert!(tlv_from_raw.is_ok()); let tlv_from_raw = tlv_from_raw.unwrap(); assert_eq!( @@ -207,7 +207,7 @@ mod tests { let mut buf: [u8; 4] = [0; 4]; buf[0] = TlvType::MsgToUser as u8; buf[1] = 0; - let tlv_empty = Tlv::from_be_bytes(&mut buf); + let tlv_empty = Tlv::from_bytes(&mut buf); assert!(tlv_empty.is_ok()); let tlv_empty = tlv_empty.unwrap(); assert!(tlv_empty.is_empty()); @@ -239,7 +239,7 @@ mod tests { buf[0] = 3; buf[1] = 1; buf[2] = 5; - let tlv = Tlv::from_be_bytes(&mut buf); + let tlv = Tlv::from_bytes(&mut buf); assert!(tlv.is_ok()); let tlv = tlv.unwrap(); assert_eq!(tlv.tlv_type_field(), TlvTypeField::Custom(3)); From 9d758cce455728a7a5de7e4ee1a3dea6d41c1d1b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 30 May 2023 00:16:16 +0200 Subject: [PATCH 23/48] first basic fd PDU impl --- src/cfdp/pdu/file_data.rs | 129 ++++++++++++++++++++++++++++++++++++++ src/cfdp/pdu/metadata.rs | 44 ++++++++----- src/cfdp/pdu/mod.rs | 22 +++++++ 3 files changed, 180 insertions(+), 15 deletions(-) create mode 100644 src/cfdp/pdu/file_data.rs diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs new file mode 100644 index 0000000..59a53ea --- /dev/null +++ b/src/cfdp/pdu/file_data.rs @@ -0,0 +1,129 @@ +use crate::cfdp::pdu::{write_file_size, PduError, PduHeader}; +use crate::cfdp::{CrcFlag, LargeFileFlag, PduType, SegmentMetadataFlag}; +use crate::{ByteConversionError, SizeMissmatch}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum RecordContinuationState { + NoStartNoEnd = 0b00, + StartWithoutEnd = 0b01, + EndWithoutStart = 0b10, + StartAndEnd = 0b11, +} + +pub struct SegmentMetadata<'seg_meta> { + record_continuation_state: RecordContinuationState, + seg_metadata_len: u8, + metadata: Option<&'seg_meta [u8]>, +} + +impl SegmentMetadata<'_> { + pub fn written_len(&self) -> usize { + 1 + self.seg_metadata_len as usize + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.written_len() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.written_len(), + })); + } + buf[0] = ((self.record_continuation_state as u8) << 6) | self.seg_metadata_len; + if self.metadata.is_some() { + buf[1..].copy_from_slice(self.metadata.unwrap()) + } + Ok(self.written_len()) + } +} + +pub struct FileDataPdu<'seg_meta, 'file_data> { + pdu_header: PduHeader, + segment_metadata: Option>, + offset: u64, + file_data: &'file_data [u8], +} + +impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { + pub fn new_with_seg_metadata( + pdu_header: PduHeader, + segment_metadata: SegmentMetadata<'seg_meta>, + offset: u64, + file_data: &'file_data [u8], + ) -> Self { + Self::new_generic(pdu_header, Some(segment_metadata), offset, file_data) + } + + pub fn new_no_seg_metadata( + pdu_header: PduHeader, + offset: u64, + file_data: &'file_data [u8], + ) -> Self { + Self::new_generic(pdu_header, None, offset, file_data) + } + + pub fn new_generic( + mut pdu_header: PduHeader, + segment_metadata: Option>, + offset: u64, + file_data: &'file_data [u8], + ) -> Self { + pdu_header.pdu_type = PduType::FileData; + if segment_metadata.is_some() { + pdu_header.seg_metadata_flag = SegmentMetadataFlag::Present; + } + Self { + pdu_header, + segment_metadata, + offset, + file_data, + } + } + + pub fn written_len(&self) -> usize { + let mut len = self.pdu_header.header_len(); + if self.segment_metadata.is_some() { + len += self.segment_metadata.as_ref().unwrap().written_len() + } + // Regular file size + len += core::mem::size_of::(); + if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { + len += core::mem::size_of::(); + } + len += self.file_data.len(); + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + len += 2; + } + len + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.written_len() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.written_len(), + }) + .into()); + } + let mut current_idx = self.pdu_header.write_to_bytes(buf)?; + if self.segment_metadata.is_some() { + current_idx += self + .segment_metadata + .as_ref() + .unwrap() + .write_to_bytes(&mut buf[current_idx..])?; + } + write_file_size( + &mut current_idx, + self.pdu_header.common_pdu_conf().file_flag, + self.offset, + buf, + )?; + buf[current_idx..current_idx + self.file_data.len()].copy_from_slice(self.file_data); + Ok(current_idx) + } +} diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index dd6589c..ce0a70a 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -1,7 +1,7 @@ use crate::cfdp::lv::Lv; -use crate::cfdp::pdu::{FileDirectiveType, PduError, PduHeader}; +use crate::cfdp::pdu::{write_file_size, FileDirectiveType, PduError, PduHeader}; use crate::cfdp::tlv::Tlv; -use crate::cfdp::{ChecksumType, CrcFlag, LargeFileFlag}; +use crate::cfdp::{ChecksumType, CrcFlag, LargeFileFlag, PduType}; use crate::{ByteConversionError, SizeMissmatch, CRC_CCITT_FALSE}; #[cfg(feature = "alloc")] use alloc::vec::Vec; @@ -130,6 +130,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { dest_file_name: Lv<'dest_name>, options: Option<&'opts [u8]>, ) -> Self { + pdu_header.pdu_type = PduType::FileDirective; let is_large_file = pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large; let has_crc = pdu_header.common_pdu_conf().crc_flag == CrcFlag::WithCrc; pdu_header.pdu_datafield_len = @@ -213,18 +214,12 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { buf[current_idx] = ((self.metadata_params.closure_requested as u8) << 7) | (self.metadata_params.checksum_type as u8); current_idx += 1; - if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { - buf[current_idx..current_idx + core::mem::size_of::()] - .copy_from_slice(&self.metadata_params.file_size.to_be_bytes()); - current_idx += core::mem::size_of::() - } else { - if self.metadata_params.file_size > u32::MAX as u64 { - return Err(PduError::FileSizeTooLarge(self.metadata_params.file_size)); - } - buf[current_idx..current_idx + core::mem::size_of::()] - .copy_from_slice(&(self.metadata_params.file_size as u32).to_be_bytes()); - current_idx += core::mem::size_of::() - } + write_file_size( + &mut current_idx, + self.pdu_header.common_pdu_conf().file_flag, + self.metadata_params.file_size, + buf, + )?; current_idx += self .src_file_name .write_to_be_bytes(&mut buf[current_idx..])?; @@ -318,7 +313,9 @@ pub mod tests { use crate::cfdp::pdu::tests::verify_raw_header; use crate::cfdp::pdu::{CommonPduConfig, FileDirectiveType, PduHeader}; use crate::cfdp::tlv::{Tlv, TlvType}; - use crate::cfdp::{ChecksumType, CrcFlag, LargeFileFlag}; + use crate::cfdp::{ + ChecksumType, CrcFlag, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, + }; use crate::util::UbfU8; use std::vec; @@ -544,4 +541,21 @@ pub mod tests { } assert_eq!(accumulated_len, pdu_read_back.options().unwrap().len()); } + + #[test] + fn test_corrects_pdu_header() { + let pdu_header = PduHeader::new_for_file_data( + common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal), + 0, + SegmentMetadataFlag::NotPresent, + SegmentationControl::NoRecordBoundaryPreservation, + ); + let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 10); + let src_filename = Lv::new_from_str(SRC_FILENAME).expect("Generating string LV failed"); + let dest_filename = + Lv::new_from_str(DEST_FILENAME).expect("Generating destination LV failed"); + let metadata_pdu = + MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); + assert_eq!(metadata_pdu.pdu_header().pdu_type(), PduType::FileDirective); + } } diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 1643b5d..31fafb4 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -7,6 +7,7 @@ use core::fmt::{Display, Formatter}; #[cfg(feature = "std")] use std::error::Error; +pub mod file_data; pub mod metadata; #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] @@ -442,6 +443,27 @@ impl PduHeader { } } +pub(crate) fn write_file_size( + current_idx: &mut usize, + fss: LargeFileFlag, + file_size: u64, + buf: &mut [u8], +) -> Result<(), PduError> { + if fss == LargeFileFlag::Large { + buf[*current_idx..*current_idx + core::mem::size_of::()] + .copy_from_slice(&file_size.to_be_bytes()); + *current_idx += core::mem::size_of::() + } else { + if file_size > u32::MAX as u64 { + return Err(PduError::FileSizeTooLarge(file_size)); + } + buf[*current_idx..*current_idx + core::mem::size_of::()] + .copy_from_slice(&(file_size as u32).to_be_bytes()); + *current_idx += core::mem::size_of::() + } + Ok(()) +} + #[cfg(test)] mod tests { use crate::cfdp::pdu::{CommonPduConfig, PduError, PduHeader, FIXED_HEADER_LEN}; From 44223c1c0f602b8ac8609a9985d558c55b4a7f5d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 30 May 2023 08:33:03 +0200 Subject: [PATCH 24/48] add file data test harness --- src/cfdp/pdu/file_data.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index 59a53ea..8839375 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -127,3 +127,11 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { Ok(current_idx) } } + +#[cfg(test)] +mod tests { + #[test] + fn test_basic() { + + } +} \ No newline at end of file From 5c3c9a9bde67223ba2a26af07dd1dcf98b08d330 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 30 May 2023 11:09:41 +0200 Subject: [PATCH 25/48] add test run config --- .idea/runConfigurations/Test.xml | 19 +++++++++++++ src/cfdp/pdu/file_data.rs | 34 ++++++++++++++++++++-- src/cfdp/pdu/metadata.rs | 2 +- src/cfdp/pdu/mod.rs | 49 ++++++++++++++++++++++++-------- 4 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 .idea/runConfigurations/Test.xml diff --git a/.idea/runConfigurations/Test.xml b/.idea/runConfigurations/Test.xml new file mode 100644 index 0000000..d051ff1 --- /dev/null +++ b/.idea/runConfigurations/Test.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index 8839375..cece350 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -15,6 +15,8 @@ pub enum RecordContinuationState { StartAndEnd = 0b11, } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct SegmentMetadata<'seg_meta> { record_continuation_state: RecordContinuationState, seg_metadata_len: u8, @@ -41,8 +43,11 @@ impl SegmentMetadata<'_> { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct FileDataPdu<'seg_meta, 'file_data> { pdu_header: PduHeader, + #[cfg_attr(feature = "serde", serde(borrow))] segment_metadata: Option>, offset: u64, file_data: &'file_data [u8], @@ -101,6 +106,18 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { len } + pub fn offset(&self) -> u64 { + self.offset + } + + pub fn file_data(&self) -> &'file_data [u8] { + self.file_data + } + + pub fn seg_metadata(&self) -> Option<&SegmentMetadata> { + self.segment_metadata.as_ref() + } + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { if buf.len() < self.written_len() { return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { @@ -130,8 +147,21 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { #[cfg(test)] mod tests { + use crate::cfdp::pdu::file_data::FileDataPdu; + use crate::cfdp::pdu::{CommonPduConfig, PduHeader}; + use crate::util::UbfU8; + #[test] fn test_basic() { - + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_num = UbfU8::new(3); + let common_conf = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0); + let file_data: [u8; 4] = [1, 2, 3, 4]; + let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data); + assert_eq!(fd_pdu.file_data(), file_data); + assert_eq!(fd_pdu.offset(), 10); } -} \ No newline at end of file +} diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index ce0a70a..6278335 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -47,7 +47,7 @@ pub fn build_metadata_opts_from_vec( /// Helper structure to loop through all options of a metadata PDU. It should be noted that /// iterators in Rust are not fallible, but the TLV creation can fail, for example if the raw TLV -/// data in invalid for some reason. In that case, the iterator will yield [None] because there +/// data is invalid for some reason. In that case, the iterator will yield [None] because there /// is no way to recover from this. /// /// The user can accumulate the length of all TLVs yielded by the iterator and compare it against diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 31fafb4..efca589 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -227,8 +227,43 @@ impl PduHeader { seg_metadata_flag: SegmentMetadataFlag, seg_ctrl: SegmentationControl, ) -> Self { - PduHeader { - pdu_type: PduType::FileData, + Self::new_generic( + PduType::FileData, + pdu_conf, + pdu_datafield_len, + seg_metadata_flag, + seg_ctrl, + ) + } + + pub fn new_for_file_data_default(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self { + Self::new_generic( + PduType::FileData, + pdu_conf, + pdu_datafield_len, + SegmentMetadataFlag::NotPresent, + SegmentationControl::NoRecordBoundaryPreservation, + ) + } + pub fn new_no_file_data(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self { + Self::new_generic( + PduType::FileDirective, + pdu_conf, + pdu_datafield_len, + SegmentMetadataFlag::NotPresent, + SegmentationControl::NoRecordBoundaryPreservation, + ) + } + + pub fn new_generic( + pdu_type: PduType, + pdu_conf: CommonPduConfig, + pdu_datafield_len: u16, + seg_metadata_flag: SegmentMetadataFlag, + seg_ctrl: SegmentationControl, + ) -> Self { + Self { + pdu_type, pdu_conf, seg_metadata_flag, seg_ctrl, @@ -236,16 +271,6 @@ impl PduHeader { } } - pub fn new_no_file_data(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self { - PduHeader { - pdu_type: PduType::FileDirective, - pdu_conf, - seg_metadata_flag: SegmentMetadataFlag::NotPresent, - seg_ctrl: SegmentationControl::NoRecordBoundaryPreservation, - pdu_datafield_len, - } - } - /// Returns only the length of the PDU header when written to a raw buffer. pub fn header_len(&self) -> usize { FIXED_HEADER_LEN From 81eb8e7887e6c2030f0668778b0a8447a602c749 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 30 May 2023 15:36:02 +0200 Subject: [PATCH 26/48] continue file data PDU --- .idea/runConfigurations/Check.xml | 19 ++++ src/cfdp/pdu/file_data.rs | 150 ++++++++++++++++++++++++++---- src/cfdp/pdu/metadata.rs | 52 ++++------- src/cfdp/pdu/mod.rs | 34 ++++--- 4 files changed, 194 insertions(+), 61 deletions(-) create mode 100644 .idea/runConfigurations/Check.xml diff --git a/.idea/runConfigurations/Check.xml b/.idea/runConfigurations/Check.xml new file mode 100644 index 0000000..502aa84 --- /dev/null +++ b/.idea/runConfigurations/Check.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index cece350..1dd7062 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -1,4 +1,4 @@ -use crate::cfdp::pdu::{write_file_size, PduError, PduHeader}; +use crate::cfdp::pdu::{read_fss_field, write_fss_field, PduError, PduHeader}; use crate::cfdp::{CrcFlag, LargeFileFlag, PduType, SegmentMetadataFlag}; use crate::{ByteConversionError, SizeMissmatch}; use num_enum::{IntoPrimitive, TryFromPrimitive}; @@ -23,12 +23,12 @@ pub struct SegmentMetadata<'seg_meta> { metadata: Option<&'seg_meta [u8]>, } -impl SegmentMetadata<'_> { +impl<'seg_meta> SegmentMetadata<'seg_meta> { pub fn written_len(&self) -> usize { 1 + self.seg_metadata_len as usize } - pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + pub(crate) fn write_to_bytes(&self, buf: &mut [u8]) -> Result { if buf.len() < self.written_len() { return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { found: buf.len(), @@ -41,6 +41,27 @@ impl SegmentMetadata<'_> { } Ok(self.written_len()) } + + pub(crate) fn from_bytes(buf: &'seg_meta [u8]) -> Result { + if buf.is_empty() { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 2, + })); + } + let mut metadata = None; + let seg_metadata_len = (buf[0] & 0b111111) as usize; + if seg_metadata_len > 0 { + metadata = Some(&buf[1..1 + seg_metadata_len]); + } + Ok(Self { + // Can't fail, only 2 bits + record_continuation_state: RecordContinuationState::try_from((buf[0] >> 6) & 0b11) + .unwrap(), + seg_metadata_len: seg_metadata_len as u8, + metadata, + }) + } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -81,30 +102,33 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { if segment_metadata.is_some() { pdu_header.seg_metadata_flag = SegmentMetadataFlag::Present; } - Self { + let mut pdu = Self { pdu_header, segment_metadata, offset, file_data, - } + }; + pdu.pdu_header.pdu_datafield_len = pdu.calc_pdu_datafield_len() as u16; + pdu } - pub fn written_len(&self) -> usize { - let mut len = self.pdu_header.header_len(); - if self.segment_metadata.is_some() { - len += self.segment_metadata.as_ref().unwrap().written_len() - } - // Regular file size - len += core::mem::size_of::(); + fn calc_pdu_datafield_len(&self) -> usize { + let mut len = core::mem::size_of::(); if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { len += core::mem::size_of::(); } + if self.segment_metadata.is_some() { + len += self.segment_metadata.as_ref().unwrap().written_len() + } len += self.file_data.len(); if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { len += 2; } len } + pub fn written_len(&self) -> usize { + self.pdu_header.header_len() + self.calc_pdu_datafield_len() + } pub fn offset(&self) -> u64 { self.offset @@ -114,7 +138,7 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { self.file_data } - pub fn seg_metadata(&self) -> Option<&SegmentMetadata> { + pub fn segment_metadata(&self) -> Option<&SegmentMetadata> { self.segment_metadata.as_ref() } @@ -134,15 +158,51 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { .unwrap() .write_to_bytes(&mut buf[current_idx..])?; } - write_file_size( - &mut current_idx, + current_idx += write_fss_field( self.pdu_header.common_pdu_conf().file_flag, self.offset, - buf, + &mut buf[current_idx..], )?; buf[current_idx..current_idx + self.file_data.len()].copy_from_slice(self.file_data); + current_idx += self.file_data.len(); Ok(current_idx) } + + pub fn from_bytes<'longest: 'seg_meta + 'file_data>( + buf: &'longest [u8], + ) -> Result { + let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; + let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; + let mut min_expected_len = current_idx + core::mem::size_of::(); + min_expected_len = core::cmp::max(min_expected_len, pdu_header.pdu_len()); + if buf.len() < min_expected_len { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: min_expected_len, + }) + .into()); + } + let mut segment_metadata = None; + if pdu_header.seg_metadata_flag == SegmentMetadataFlag::Present { + segment_metadata = Some(SegmentMetadata::from_bytes(&buf[current_idx..])?); + current_idx += segment_metadata.as_ref().unwrap().written_len(); + } + let (fss, offset) = read_fss_field(pdu_header.pdu_conf.file_flag, &buf[current_idx..]); + current_idx += fss; + if current_idx > full_len_without_crc { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: current_idx, + expected: full_len_without_crc, + }) + .into()); + } + Ok(Self { + pdu_header, + segment_metadata, + offset, + file_data: &buf[current_idx..full_len_without_crc], + }) + } } #[cfg(test)] @@ -163,5 +223,63 @@ mod tests { let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data); assert_eq!(fd_pdu.file_data(), file_data); assert_eq!(fd_pdu.offset(), 10); + assert!(fd_pdu.segment_metadata().is_none()); + assert_eq!( + fd_pdu.written_len(), + fd_pdu.pdu_header.header_len() + core::mem::size_of::() + 4 + ); + } + + #[test] + fn test_serialization() { + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_num = UbfU8::new(3); + let common_conf = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0); + let file_data: [u8; 4] = [1, 2, 3, 4]; + let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data); + let mut buf: [u8; 32] = [0; 32]; + let res = fd_pdu.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!( + written, + fd_pdu.pdu_header.header_len() + core::mem::size_of::() + 4 + ); + let mut current_idx = fd_pdu.pdu_header.header_len(); + let file_size = u32::from_be_bytes( + buf[fd_pdu.pdu_header.header_len()..fd_pdu.pdu_header.header_len() + 4] + .try_into() + .unwrap(), + ); + current_idx += 4; + assert_eq!(file_size, 10); + assert_eq!(buf[current_idx], 1); + current_idx += 1; + assert_eq!(buf[current_idx], 2); + current_idx += 1; + assert_eq!(buf[current_idx], 3); + current_idx += 1; + assert_eq!(buf[current_idx], 4); + } + + #[test] + fn test_deserialization() { + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_num = UbfU8::new(3); + let common_conf = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0); + let file_data: [u8; 4] = [1, 2, 3, 4]; + let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data); + let mut buf: [u8; 32] = [0; 32]; + fd_pdu.write_to_bytes(&mut buf).unwrap(); + let fd_pdu_read_back = FileDataPdu::from_bytes(&buf); + assert!(fd_pdu_read_back.is_ok()); + let fd_pdu_read_back = fd_pdu_read_back.unwrap(); + assert_eq!(fd_pdu_read_back, fd_pdu); } } diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 6278335..5bfee82 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -1,5 +1,5 @@ use crate::cfdp::lv::Lv; -use crate::cfdp::pdu::{write_file_size, FileDirectiveType, PduError, PduHeader}; +use crate::cfdp::pdu::{read_fss_field, write_fss_field, FileDirectiveType, PduError, PduHeader}; use crate::cfdp::tlv::Tlv; use crate::cfdp::{ChecksumType, CrcFlag, LargeFileFlag, PduType}; use crate::{ByteConversionError, SizeMissmatch, CRC_CCITT_FALSE}; @@ -131,26 +131,15 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { options: Option<&'opts [u8]>, ) -> Self { pdu_header.pdu_type = PduType::FileDirective; - let is_large_file = pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large; - let has_crc = pdu_header.common_pdu_conf().crc_flag == CrcFlag::WithCrc; - pdu_header.pdu_datafield_len = - 2 + 4 + src_file_name.len_full() as u16 + dest_file_name.len_full() as u16; - if is_large_file { - pdu_header.pdu_datafield_len += 4; - } - if has_crc { - pdu_header.pdu_datafield_len += 2; - } - if let Some(opts) = options { - pdu_header.pdu_datafield_len += opts.len() as u16; - } - Self { + let mut pdu = Self { pdu_header, metadata_params, src_file_name, dest_file_name, options, - } + }; + pdu.pdu_header.pdu_datafield_len = pdu.calc_pdu_datafield_len() as u16; + pdu } pub fn src_file_name(&self) -> Lv<'src_name> { @@ -177,7 +166,12 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { pub fn written_len(&self) -> usize { // One directive type octet, and one byte of the parameter field. - let mut len = self.pdu_header.header_len() + 2; + self.pdu_header.header_len() + self.calc_pdu_datafield_len() + } + + fn calc_pdu_datafield_len(&self) -> usize { + // One directve type octet and one byte of the directive parameter field. + let mut len = 2; if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { len += 8; } else { @@ -214,11 +208,10 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { buf[current_idx] = ((self.metadata_params.closure_requested as u8) << 7) | (self.metadata_params.checksum_type as u8); current_idx += 1; - write_file_size( - &mut current_idx, + current_idx += write_fss_field( self.pdu_header.common_pdu_conf().file_flag, self.metadata_params.file_size, - buf, + &mut buf[current_idx..], )?; current_idx += self .src_file_name @@ -241,7 +234,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { pub fn from_bytes<'longest: 'src_name + 'dest_name + 'opts>( buf: &'longest [u8], - ) -> Result, PduError> { + ) -> Result { let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; let is_large_file = pdu_header.pdu_conf.file_flag == LargeFileFlag::Large; @@ -268,22 +261,15 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { ))); } current_idx += 1; - - let file_size = if pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { - u64::from_be_bytes(buf[current_idx + 1..current_idx + 9].try_into().unwrap()) - } else { - u32::from_be_bytes(buf[current_idx + 1..current_idx + 5].try_into().unwrap()) as u64 - }; + let (fss_len, file_size) = + read_fss_field(pdu_header.pdu_conf.file_flag, &buf[current_idx + 1..]); let metadata_params = MetadataGenericParams { closure_requested: ((buf[current_idx] >> 6) & 0b1) != 0, checksum_type: ChecksumType::try_from(buf[current_idx] & 0b1111) .map_err(|_| PduError::InvalidChecksumType(buf[current_idx] & 0b1111))?, file_size, }; - current_idx += 5; - if is_large_file { - current_idx += 4; - } + current_idx += 1 + fss_len; let src_file_name = Lv::from_bytes(&buf[current_idx..])?; current_idx += src_file_name.len_full(); let dest_file_name = Lv::from_bytes(&buf[current_idx..])?; @@ -343,7 +329,7 @@ pub mod tests { MetadataPdu<'static, 'static, 'opts>, ) { let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0); - let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 10); + let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 0x1010); let src_filename = Lv::new_from_str(SRC_FILENAME).expect("Generating string LV failed"); let dest_filename = Lv::new_from_str(DEST_FILENAME).expect("Generating destination LV failed"); @@ -399,7 +385,7 @@ pub mod tests { assert_eq!(buf[7], FileDirectiveType::MetadataPdu as u8); assert_eq!(buf[8] >> 6, false as u8); assert_eq!(buf[8] & 0b1111, ChecksumType::Crc32 as u8); - assert_eq!(u32::from_be_bytes(buf[9..13].try_into().unwrap()), 10); + assert_eq!(u32::from_be_bytes(buf[9..13].try_into().unwrap()), 0x1010); let mut current_idx = 13; let src_name_from_raw = Lv::from_bytes(&buf[current_idx..]).expect("Creating source name LV failed"); diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index efca589..83c043a 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -468,25 +468,35 @@ impl PduHeader { } } -pub(crate) fn write_file_size( - current_idx: &mut usize, - fss: LargeFileFlag, +pub(crate) fn write_fss_field( + file_flag: LargeFileFlag, file_size: u64, buf: &mut [u8], -) -> Result<(), PduError> { - if fss == LargeFileFlag::Large { - buf[*current_idx..*current_idx + core::mem::size_of::()] - .copy_from_slice(&file_size.to_be_bytes()); - *current_idx += core::mem::size_of::() +) -> Result { + Ok(if file_flag == LargeFileFlag::Large { + buf[..core::mem::size_of::()].copy_from_slice(&file_size.to_be_bytes()); + core::mem::size_of::() } else { if file_size > u32::MAX as u64 { return Err(PduError::FileSizeTooLarge(file_size)); } - buf[*current_idx..*current_idx + core::mem::size_of::()] - .copy_from_slice(&(file_size as u32).to_be_bytes()); - *current_idx += core::mem::size_of::() + buf[..core::mem::size_of::()].copy_from_slice(&(file_size as u32).to_be_bytes()); + core::mem::size_of::() + }) +} + +pub(crate) fn read_fss_field(file_flag: LargeFileFlag, buf: &[u8]) -> (usize, u64) { + if file_flag == LargeFileFlag::Large { + ( + core::mem::size_of::(), + u64::from_be_bytes(buf[..core::mem::size_of::()].try_into().unwrap()), + ) + } else { + ( + core::mem::size_of::(), + u32::from_be_bytes(buf[..core::mem::size_of::()].try_into().unwrap()).into(), + ) } - Ok(()) } #[cfg(test)] From 0ad8dd6eef26f0c17035d5fe284781b2df23a923 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 30 May 2023 17:39:33 +0200 Subject: [PATCH 27/48] add test with segment metadata --- src/cfdp/pdu/file_data.rs | 49 +++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index 1dd7062..bff5c1a 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -19,13 +19,28 @@ pub enum RecordContinuationState { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct SegmentMetadata<'seg_meta> { record_continuation_state: RecordContinuationState, - seg_metadata_len: u8, metadata: Option<&'seg_meta [u8]>, } impl<'seg_meta> SegmentMetadata<'seg_meta> { + pub fn new( + record_continuation_state: RecordContinuationState, + metadata: Option<&'seg_meta [u8]>, + ) -> Option { + if let Some(metadata) = metadata { + if metadata.len() > 2_usize.pow(6) - 1 { + return None; + } + } + Some(Self { + record_continuation_state, + metadata, + }) + } + pub fn written_len(&self) -> usize { - 1 + self.seg_metadata_len as usize + // Map empty metadata to 0 and slice to its length. + 1 + self.metadata.map_or(0, |meta| meta.len()) } pub(crate) fn write_to_bytes(&self, buf: &mut [u8]) -> Result { @@ -35,7 +50,8 @@ impl<'seg_meta> SegmentMetadata<'seg_meta> { expected: self.written_len(), })); } - buf[0] = ((self.record_continuation_state as u8) << 6) | self.seg_metadata_len; + buf[0] = ((self.record_continuation_state as u8) << 6) + | self.metadata.map_or(0, |meta| meta.len() as u8); if self.metadata.is_some() { buf[1..].copy_from_slice(self.metadata.unwrap()) } @@ -58,7 +74,6 @@ impl<'seg_meta> SegmentMetadata<'seg_meta> { // Can't fail, only 2 bits record_continuation_state: RecordContinuationState::try_from((buf[0] >> 6) & 0b11) .unwrap(), - seg_metadata_len: seg_metadata_len as u8, metadata, }) } @@ -207,8 +222,9 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { #[cfg(test)] mod tests { - use crate::cfdp::pdu::file_data::FileDataPdu; + use crate::cfdp::pdu::file_data::{FileDataPdu, RecordContinuationState, SegmentMetadata}; use crate::cfdp::pdu::{CommonPduConfig, PduHeader}; + use crate::cfdp::{SegmentMetadataFlag, SegmentationControl}; use crate::util::UbfU8; #[test] @@ -282,4 +298,27 @@ mod tests { let fd_pdu_read_back = fd_pdu_read_back.unwrap(); assert_eq!(fd_pdu_read_back, fd_pdu); } + + #[test] + fn test_with_seg_metadata() { + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_num = UbfU8::new(3); + let common_conf = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + let pdu_header = PduHeader::new_for_file_data( + common_conf, + 0, + SegmentMetadataFlag::Present, + SegmentationControl::WithRecordBoundaryPreservation, + ); + let file_data: [u8; 4] = [1, 2, 3, 4]; + let seg_metadata: [u8; 4] = [4, 3, 2, 1]; + let segment_meta = + SegmentMetadata::new(RecordContinuationState::StartAndEnd, Some(&seg_metadata)) + .unwrap(); + let fd_pdu = FileDataPdu::new_with_seg_metadata(pdu_header, segment_meta, 10, &file_data); + assert!(fd_pdu.segment_metadata().is_some()); + assert_eq!(*fd_pdu.segment_metadata().unwrap(), segment_meta); + } } From 9f574ff443ce302d76e67053340542ce9b2c4942 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 30 May 2023 19:35:38 +0200 Subject: [PATCH 28/48] continue file data test --- src/cfdp/pdu/file_data.rs | 43 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index bff5c1a..de4acae 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -52,8 +52,8 @@ impl<'seg_meta> SegmentMetadata<'seg_meta> { } buf[0] = ((self.record_continuation_state as u8) << 6) | self.metadata.map_or(0, |meta| meta.len() as u8); - if self.metadata.is_some() { - buf[1..].copy_from_slice(self.metadata.unwrap()) + if let Some(metadata) = self.metadata { + buf[1..1 + metadata.len()].copy_from_slice(metadata) } Ok(self.written_len()) } @@ -320,5 +320,44 @@ mod tests { let fd_pdu = FileDataPdu::new_with_seg_metadata(pdu_header, segment_meta, 10, &file_data); assert!(fd_pdu.segment_metadata().is_some()); assert_eq!(*fd_pdu.segment_metadata().unwrap(), segment_meta); + assert_eq!( + fd_pdu.written_len(), + fd_pdu.pdu_header.header_len() + + 1 + + seg_metadata.len() + + core::mem::size_of::() + + 4 + ); + let mut buf: [u8; 32] = [0; 32]; + fd_pdu + .write_to_bytes(&mut buf) + .expect("writing FD PDU failed"); + let mut current_idx = fd_pdu.pdu_header.header_len(); + assert_eq!( + RecordContinuationState::try_from((buf[current_idx] >> 6) & 0b11).unwrap(), + RecordContinuationState::StartAndEnd + ); + assert_eq!((buf[current_idx] & 0b111111) as usize, seg_metadata.len()); + current_idx += 1; + assert_eq!(buf[current_idx], 4); + current_idx += 1; + assert_eq!(buf[current_idx], 3); + current_idx += 1; + assert_eq!(buf[current_idx], 2); + current_idx += 1; + assert_eq!(buf[current_idx], 1); + current_idx += 1; + // Still verify that the rest is written correctly. + assert_eq!(u32::from_be_bytes(buf[current_idx..current_idx+4].try_into().unwrap()), 10); + current_idx+=4; + assert_eq!(buf[current_idx], 1); + current_idx += 1; + assert_eq!(buf[current_idx], 2); + current_idx += 1; + assert_eq!(buf[current_idx], 3); + current_idx += 1; + assert_eq!(buf[current_idx], 4); + current_idx +=1; + assert_eq!(current_idx, fd_pdu.written_len()); } } From bb1ecb29b68bcdd8b635ce5d7b22ce0ca8691d9d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 30 May 2023 19:35:59 +0200 Subject: [PATCH 29/48] fmt and clippy --- src/cfdp/pdu/file_data.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index de4acae..f616adc 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -348,8 +348,11 @@ mod tests { assert_eq!(buf[current_idx], 1); current_idx += 1; // Still verify that the rest is written correctly. - assert_eq!(u32::from_be_bytes(buf[current_idx..current_idx+4].try_into().unwrap()), 10); - current_idx+=4; + assert_eq!( + u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()), + 10 + ); + current_idx += 4; assert_eq!(buf[current_idx], 1); current_idx += 1; assert_eq!(buf[current_idx], 2); @@ -357,7 +360,7 @@ mod tests { assert_eq!(buf[current_idx], 3); current_idx += 1; assert_eq!(buf[current_idx], 4); - current_idx +=1; + current_idx += 1; assert_eq!(current_idx, fd_pdu.written_len()); } } From 0b714b7426a2dd6f1e7526d22af83c57eee76b82 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 6 Jun 2023 08:59:18 +0200 Subject: [PATCH 30/48] start EOF --- src/cfdp/pdu/eof.rs | 0 src/cfdp/pdu/file_data.rs | 33 +++++++++++++++++++++++++++++++-- src/cfdp/pdu/metadata.rs | 12 ++++++------ 3 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 src/cfdp/pdu/eof.rs diff --git a/src/cfdp/pdu/eof.rs b/src/cfdp/pdu/eof.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index f616adc..13db1af 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -130,7 +130,7 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { fn calc_pdu_datafield_len(&self) -> usize { let mut len = core::mem::size_of::(); if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { - len += core::mem::size_of::(); + len += 4; } if self.segment_metadata.is_some() { len += self.segment_metadata.as_ref().unwrap().written_len() @@ -300,7 +300,7 @@ mod tests { } #[test] - fn test_with_seg_metadata() { + fn test_with_seg_metadata_serialization() { let src_id = UbfU8::new(1); let dest_id = UbfU8::new(2); let transaction_seq_num = UbfU8::new(3); @@ -363,4 +363,33 @@ mod tests { current_idx += 1; assert_eq!(current_idx, fd_pdu.written_len()); } + + #[test] + fn test_with_seg_metadata_deserialization() { + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_num = UbfU8::new(3); + let common_conf = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + let pdu_header = PduHeader::new_for_file_data( + common_conf, + 0, + SegmentMetadataFlag::Present, + SegmentationControl::WithRecordBoundaryPreservation, + ); + let file_data: [u8; 4] = [1, 2, 3, 4]; + let seg_metadata: [u8; 4] = [4, 3, 2, 1]; + let segment_meta = + SegmentMetadata::new(RecordContinuationState::StartAndEnd, Some(&seg_metadata)) + .unwrap(); + let fd_pdu = FileDataPdu::new_with_seg_metadata(pdu_header, segment_meta, 10, &file_data); + let mut buf: [u8; 32] = [0; 32]; + fd_pdu + .write_to_bytes(&mut buf) + .expect("writing FD PDU failed"); + let fd_pdu_read_back = FileDataPdu::from_bytes(&buf); + assert!(fd_pdu_read_back.is_ok()); + let fd_pdu_read_back = fd_pdu_read_back.unwrap(); + assert_eq!(fd_pdu_read_back, fd_pdu); + } } diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 5bfee82..123d2e8 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -92,13 +92,13 @@ pub struct MetadataPdu<'src_name, 'dest_name, 'opts> { } impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { - pub fn new( + pub fn new_no_opts( pdu_header: PduHeader, metadata_params: MetadataGenericParams, src_file_name: Lv<'src_name>, dest_file_name: Lv<'dest_name>, ) -> Self { - Self::new_generic( + Self::new( pdu_header, metadata_params, src_file_name, @@ -114,7 +114,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { dest_file_name: Lv<'dest_name>, options: &'opts [u8], ) -> Self { - Self::new_generic( + Self::new( pdu_header, metadata_params, src_file_name, @@ -123,7 +123,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { ) } - pub fn new_generic( + pub fn new( mut pdu_header: PduHeader, metadata_params: MetadataGenericParams, src_file_name: Lv<'src_name>, @@ -336,7 +336,7 @@ pub mod tests { ( src_filename, dest_filename, - MetadataPdu::new_generic( + MetadataPdu::new( pdu_header, metadata_params, src_filename, @@ -541,7 +541,7 @@ pub mod tests { let dest_filename = Lv::new_from_str(DEST_FILENAME).expect("Generating destination LV failed"); let metadata_pdu = - MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename); + MetadataPdu::new_no_opts(pdu_header, metadata_params, src_filename, dest_filename); assert_eq!(metadata_pdu.pdu_header().pdu_type(), PduType::FileDirective); } } From 912c03b5c7ccd9ca3142bf12728146ddf8e04178 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 7 Jun 2023 01:12:07 +0200 Subject: [PATCH 31/48] base line EOF model --- src/cfdp/mod.rs | 13 ++++++ src/cfdp/pdu/eof.rs | 13 ++++++ src/cfdp/pdu/metadata.rs | 2 +- src/cfdp/pdu/mod.rs | 1 + src/cfdp/tlv.rs | 88 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 112 insertions(+), 5 deletions(-) diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index d501b5d..7067056 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -140,6 +140,10 @@ pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4]; pub enum TlvLvError { DataTooLarge(usize), ByteConversionError(ByteConversionError), + /// First value: Found value. Second value: Expected value. + InvalidTlvTypeField((u8, u8)), + /// Logically invalid value length detected. + InvalidValueLength(u8), } impl From for TlvLvError { @@ -162,6 +166,15 @@ impl Display for TlvLvError { TlvLvError::ByteConversionError(e) => { write!(f, "{}", e) } + TlvLvError::InvalidTlvTypeField((found, expected)) => { + write!( + f, + "invalid TLV type field, found {found}, expected {expected}" + ) + } + TlvLvError::InvalidValueLength(len) => { + write!(f, "invalid value length {len} detected") + } } } } diff --git a/src/cfdp/pdu/eof.rs b/src/cfdp/pdu/eof.rs index e69de29..3c338b0 100644 --- a/src/cfdp/pdu/eof.rs +++ b/src/cfdp/pdu/eof.rs @@ -0,0 +1,13 @@ +use crate::cfdp::tlv::EntityIdTlv; +use crate::cfdp::ConditionCode; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct EofPdu { + condition_code: ConditionCode, + file_checksum: u32, + file_size: u64, + fault_location: Option, +} diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 123d2e8..a5544b9 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -32,7 +32,7 @@ pub fn build_metadata_opts_from_slice( ) -> Result { let mut written = 0; for tlv in tlvs { - written += tlv.write_to_be_bytes(&mut buf[written..])?; + written += tlv.write_to_bytes(&mut buf[written..])?; } Ok(written) } diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 83c043a..bcdc189 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -7,6 +7,7 @@ use core::fmt::{Display, Formatter}; #[cfg(feature = "std")] use std::error::Error; +pub mod eof; pub mod file_data; pub mod metadata; diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 503b478..4898669 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -3,7 +3,8 @@ use crate::cfdp::lv::{ generic_len_check_data_serialization, generic_len_check_deserialization, Lv, MIN_LV_LEN, }; use crate::cfdp::TlvLvError; -use crate::ByteConversionError; +use crate::util::{UnsignedByteField, UnsignedByteFieldError, UnsignedEnum}; +use crate::{ByteConversionError, SizeMissmatch}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -81,7 +82,7 @@ impl<'data> Tlv<'data> { self.tlv_type_field } - pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { generic_len_check_data_serialization(buf, self.len_value(), MIN_TLV_LEN)?; buf[0] = self.tlv_type_field.into(); self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]); @@ -120,6 +121,85 @@ impl<'data> Tlv<'data> { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct EntityIdTlv { + entity_id: UnsignedByteField, +} + +impl EntityIdTlv { + pub fn new(entity_id: UnsignedByteField) -> Self { + Self { entity_id } + } + + pub fn len_check(buf: &mut [u8]) -> Result<(), ByteConversionError> { + if buf.len() < 2 { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 2, + })); + } + Ok(()) + } + + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { + Self::len_check(buf)?; + buf[0] = TlvType::EntityId as u8; + buf[1] = self.entity_id.len() as u8; + self.entity_id.write_to_be_bytes(&mut buf[2..]) + } + + pub fn to_tlv(self, buf: &mut [u8]) -> Result { + Self::len_check(buf)?; + self.entity_id + .write_to_be_bytes(&mut buf[2..2 + self.entity_id.len()])?; + Tlv::new(TlvType::EntityId, &buf[2..2 + self.entity_id.len()]).map_err(|e| match e { + TlvLvError::ByteConversionError(e) => e, + // All other errors are impossible. + _ => panic!("unexpected TLV error"), + }) + } +} + +impl<'data> TryFrom> for EntityIdTlv { + type Error = TlvLvError; + + fn try_from(value: Tlv) -> Result { + match value.tlv_type_field { + TlvTypeField::Standard(tlv_type) => { + if tlv_type != TlvType::EntityId { + return Err(TlvLvError::InvalidTlvTypeField(( + tlv_type as u8, + TlvType::EntityId as u8, + ))); + } + } + TlvTypeField::Custom(val) => { + return Err(TlvLvError::InvalidTlvTypeField(( + val, + TlvType::EntityId as u8, + ))); + } + } + if value.len_value() != 1 + && value.len_value() != 2 + && value.len_value() != 4 + && value.len_value() != 8 + { + return Err(TlvLvError::InvalidValueLength(value.len_value() as u8)); + } + Ok(Self::new( + UnsignedByteField::new_from_be_bytes(value.len_value(), value.value().unwrap()) + .map_err(|e| match e { + UnsignedByteFieldError::ByteConversionError(e) => e, + // This can not happen, we checked for the length validity, and the data is always smaller than + // 255 bytes. + _ => panic!("unexpected error"), + })?, + )) + } +} + #[cfg(test)] mod tests { use crate::cfdp::tlv::{Tlv, TlvType, TlvTypeField}; @@ -154,7 +234,7 @@ mod tests { assert!(tlv_res.is_ok()); let tlv_res = tlv_res.unwrap(); let mut ser_buf: [u8; 4] = [0; 4]; - assert!(tlv_res.write_to_be_bytes(&mut ser_buf).is_ok()); + assert!(tlv_res.write_to_bytes(&mut ser_buf).is_ok()); assert_eq!(ser_buf[0], TlvType::EntityId as u8); assert_eq!(ser_buf[1], 1); assert_eq!(ser_buf[2], 5); @@ -197,7 +277,7 @@ mod tests { fn test_empty_serialization() { let tlv_empty = Tlv::new_empty(TlvType::MsgToUser); let mut buf: [u8; 4] = [0; 4]; - assert!(tlv_empty.write_to_be_bytes(&mut buf).is_ok()); + assert!(tlv_empty.write_to_bytes(&mut buf).is_ok()); assert_eq!(buf[0], TlvType::MsgToUser as u8); assert_eq!(buf[1], 0); } From ab5c28d304b122872e7415dcb312ae189166d84d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 8 Jun 2023 16:51:22 +0200 Subject: [PATCH 32/48] finish PDU --- src/cfdp/pdu/finished.rs | 34 ++++++++++++++++++++++++++++++++++ src/cfdp/pdu/mod.rs | 1 + 2 files changed, 35 insertions(+) create mode 100644 src/cfdp/pdu/finished.rs diff --git a/src/cfdp/pdu/finished.rs b/src/cfdp/pdu/finished.rs new file mode 100644 index 0000000..decb0ea --- /dev/null +++ b/src/cfdp/pdu/finished.rs @@ -0,0 +1,34 @@ +use crate::cfdp::pdu::PduHeader; +use crate::cfdp::ConditionCode; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum DeliveryCode { + Complete = 0, + Incomplete = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum FileStatus { + DiscardDeliberately = 0b00, + DiscardedFsRejection = 0b01, + Retained = 0b10, + Unreported = 0b11, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct FinishPdu<'fs_responses> { + pdu_header: PduHeader, + condition_code: ConditionCode, + delivery_code: DeliveryCode, + file_status: FileStatus, + fs_responses: Optional<&'fs_responses [u8]>, + // fault_location: +} diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index bcdc189..638d325 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -9,6 +9,7 @@ use std::error::Error; pub mod eof; pub mod file_data; +pub mod finished; pub mod metadata; #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] From 84b909b7220f992243539290f4a2f5b0ed2070aa Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 8 Jun 2023 19:24:55 +0200 Subject: [PATCH 33/48] continue EOF --- src/cfdp/pdu/eof.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/cfdp/pdu/eof.rs b/src/cfdp/pdu/eof.rs index 3c338b0..02cdb42 100644 --- a/src/cfdp/pdu/eof.rs +++ b/src/cfdp/pdu/eof.rs @@ -2,12 +2,26 @@ use crate::cfdp::tlv::EntityIdTlv; use crate::cfdp::ConditionCode; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use crate::cfdp::pdu::PduHeader; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct EofPdu { + pdu_header: PduHeader, condition_code: ConditionCode, file_checksum: u32, file_size: u64, fault_location: Option, } + +impl EofPdu { + pub fn new_no_error(pdu_header: PduHeader, file_checksum: u32, file_size: u64) -> Self { + Self { + pdu_header, + condition_code: ConditionCode::NoError, + file_checksum, + file_size, + fault_location: None + } + } +} \ No newline at end of file From 006bc39ff6a770c4e2ea73cf096f8d488ef22bfd Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 8 Jun 2023 20:50:18 +0200 Subject: [PATCH 34/48] finished basic EOF unittests --- src/cfdp/pdu/eof.rs | 196 +++++++++++++++++++++++++++++++++++++-- src/cfdp/pdu/finished.rs | 5 +- src/cfdp/pdu/metadata.rs | 17 +--- src/cfdp/pdu/mod.rs | 28 ++++++ src/cfdp/tlv.rs | 25 ++++- src/ecss/mod.rs | 2 +- src/util.rs | 27 ++++-- 7 files changed, 261 insertions(+), 39 deletions(-) diff --git a/src/cfdp/pdu/eof.rs b/src/cfdp/pdu/eof.rs index 02cdb42..79d8645 100644 --- a/src/cfdp/pdu/eof.rs +++ b/src/cfdp/pdu/eof.rs @@ -1,8 +1,9 @@ +use crate::cfdp::pdu::{read_fss_field, write_fss_field, FileDirectiveType, PduError, PduHeader}; use crate::cfdp::tlv::EntityIdTlv; -use crate::cfdp::ConditionCode; +use crate::cfdp::{ConditionCode, LargeFileFlag}; +use crate::{ByteConversionError, SizeMissmatch}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crate::cfdp::pdu::PduHeader; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -16,12 +17,187 @@ pub struct EofPdu { impl EofPdu { pub fn new_no_error(pdu_header: PduHeader, file_checksum: u32, file_size: u64) -> Self { - Self { - pdu_header, - condition_code: ConditionCode::NoError, - file_checksum, - file_size, - fault_location: None - } + let mut eof_pdu = Self { + pdu_header, + condition_code: ConditionCode::NoError, + file_checksum, + file_size, + fault_location: None, + }; + eof_pdu.pdu_header.pdu_datafield_len = eof_pdu.calc_pdu_datafield_len() as u16; + eof_pdu } -} \ No newline at end of file + + pub fn pdu_header(&self) -> &PduHeader { + &self.pdu_header + } + + pub fn written_len(&self) -> usize { + self.pdu_header.header_len() + self.calc_pdu_datafield_len() + } + + pub fn condition_code(&self) -> ConditionCode { + self.condition_code + } + + pub fn file_checksum(&self) -> u32 { + self.file_checksum + } + + pub fn file_size(&self) -> u64 { + self.file_size + } + + fn calc_pdu_datafield_len(&self) -> usize { + // One directive type octet, 4 bits condition code, 4 spare bits. + let mut len = 2 + core::mem::size_of::() + 4; + if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { + len += 4; + } + if let Some(fault_location) = self.fault_location { + len += fault_location.len_full(); + } + len + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + let expected_len = self.written_len(); + if buf.len() < expected_len { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: expected_len, + }) + .into()); + } + let mut current_idx = self.pdu_header.write_to_bytes(buf)?; + buf[current_idx] = FileDirectiveType::EofPdu as u8; + current_idx += 1; + buf[current_idx] = (self.condition_code as u8) << 4; + current_idx += 1; + buf[current_idx..current_idx + 4].copy_from_slice(&self.file_checksum.to_be_bytes()); + current_idx += 4; + current_idx += write_fss_field( + self.pdu_header.pdu_conf.file_flag, + self.file_size, + &mut buf[current_idx..], + )?; + if let Some(fault_location) = self.fault_location { + current_idx += fault_location.write_to_be_bytes(buf)?; + } + Ok(current_idx) + } + + pub fn from_bytes(buf: &[u8]) -> Result { + let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; + let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; + let is_large_file = pdu_header.pdu_conf.file_flag == LargeFileFlag::Large; + let mut min_expected_len = 2 + 4 + 4; + if is_large_file { + min_expected_len += 4; + } + if pdu_header.header_len() + min_expected_len > buf.len() { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: pdu_header.header_len() + min_expected_len, + }) + .into()); + } + let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { + PduError::InvalidDirectiveType((buf[current_idx], FileDirectiveType::EofPdu)) + })?; + if directive_type != FileDirectiveType::EofPdu { + return Err(PduError::WrongDirectiveType(( + directive_type, + FileDirectiveType::EofPdu, + ))); + } + current_idx += 1; + let condition_code = ConditionCode::try_from((buf[current_idx] >> 4) & 0b1111) + .map_err(|_| PduError::InvalidConditionCode((buf[current_idx] >> 4) & 0b1111))?; + current_idx += 1; + let file_checksum = + u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); + current_idx += 4; + let (fss_field_len, file_size) = + read_fss_field(pdu_header.pdu_conf.file_flag, &buf[current_idx..]); + current_idx += fss_field_len; + let mut fault_location = None; + if condition_code != ConditionCode::NoError && current_idx < full_len_without_crc { + fault_location = Some(EntityIdTlv::from_bytes(&buf[current_idx..])?); + } + Ok(Self { + pdu_header, + condition_code, + file_checksum, + file_size, + fault_location, + }) + } +} + +#[cfg(test)] +mod tests { + use crate::cfdp::pdu::eof::EofPdu; + use crate::cfdp::pdu::tests::{common_pdu_conf, verify_raw_header}; + use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; + use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag}; + + #[test] + fn test_basic() { + let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); + let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); + let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); + assert_eq!(eof_pdu.written_len(), pdu_header.header_len() + 2 + 4 + 4); + assert_eq!(eof_pdu.file_checksum(), 0x01020304); + assert_eq!(eof_pdu.file_size(), 12); + assert_eq!(eof_pdu.condition_code(), ConditionCode::NoError); + } + + #[test] + fn test_serialization() { + let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); + let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); + let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); + let mut buf: [u8; 64] = [0; 64]; + let res = eof_pdu.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, eof_pdu.written_len()); + verify_raw_header(eof_pdu.pdu_header(), &buf); + let mut current_idx = eof_pdu.pdu_header().header_len(); + buf[current_idx] = FileDirectiveType::EofPdu as u8; + current_idx += 1; + assert_eq!( + (buf[current_idx] >> 4) & 0b1111, + ConditionCode::NoError as u8 + ); + current_idx += 1; + assert_eq!( + u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()), + 0x01020304 + ); + current_idx += 4; + assert_eq!( + u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()), + 12 + ); + current_idx += 4; + assert_eq!(current_idx, written); + } + + #[test] + fn test_deserialization() { + let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); + let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); + let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); + let mut buf: [u8; 64] = [0; 64]; + eof_pdu.write_to_bytes(&mut buf).unwrap(); + let eof_read_back = EofPdu::from_bytes(&buf); + if !eof_read_back.is_ok() { + let e = eof_read_back.unwrap_err(); + panic!("deserialization failed with: {e}") + } + let eof_read_back = eof_read_back.unwrap(); + assert_eq!(eof_read_back, eof_pdu); + } +} diff --git a/src/cfdp/pdu/finished.rs b/src/cfdp/pdu/finished.rs index decb0ea..6a2220c 100644 --- a/src/cfdp/pdu/finished.rs +++ b/src/cfdp/pdu/finished.rs @@ -1,4 +1,5 @@ use crate::cfdp::pdu::PduHeader; +use crate::cfdp::tlv::EntityIdTlv; use crate::cfdp::ConditionCode; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] @@ -29,6 +30,6 @@ pub struct FinishPdu<'fs_responses> { condition_code: ConditionCode, delivery_code: DeliveryCode, file_status: FileStatus, - fs_responses: Optional<&'fs_responses [u8]>, - // fault_location: + fs_responses: Option<&'fs_responses [u8]>, + fault_location: Option, } diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index a5544b9..b9d912a 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -165,7 +165,6 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { } pub fn written_len(&self) -> usize { - // One directive type octet, and one byte of the parameter field. self.pdu_header.header_len() + self.calc_pdu_datafield_len() } @@ -296,29 +295,17 @@ pub mod tests { build_metadata_opts_from_slice, build_metadata_opts_from_vec, MetadataGenericParams, MetadataPdu, }; - use crate::cfdp::pdu::tests::verify_raw_header; - use crate::cfdp::pdu::{CommonPduConfig, FileDirectiveType, PduHeader}; + use crate::cfdp::pdu::tests::{common_pdu_conf, verify_raw_header}; + use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; use crate::cfdp::tlv::{Tlv, TlvType}; use crate::cfdp::{ ChecksumType, CrcFlag, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, }; - use crate::util::UbfU8; use std::vec; const SRC_FILENAME: &'static str = "hello-world.txt"; const DEST_FILENAME: &'static str = "hello-world2.txt"; - fn common_pdu_conf(crc_flag: CrcFlag, fss: LargeFileFlag) -> CommonPduConfig { - let src_id = UbfU8::new(5); - let dest_id = UbfU8::new(10); - let transaction_seq_num = UbfU8::new(20); - let mut pdu_conf = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num) - .expect("Generating common PDU config"); - pdu_conf.crc_flag = crc_flag; - pdu_conf.file_flag = fss; - pdu_conf - } - fn generic_metadata_pdu<'opts>( crc_flag: CrcFlag, fss: LargeFileFlag, diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 638d325..e786963 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -45,12 +45,16 @@ pub enum PduError { /// The first tuple entry will be the found raw number, the second entry the expected entry /// type. InvalidDirectiveType((u8, FileDirectiveType)), + /// Invalid condition code. Contains the raw detected value. + InvalidConditionCode(u8), /// Invalid checksum type which is not part of the checksums listed in the /// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/). InvalidChecksumType(u8), FileSizeTooLarge(u64), /// If the CRC flag for a PDU is enabled and the checksum check fails. Contains raw 16-bit CRC. ChecksumError(u16), + /// Error handling a TLV field. + TlvLvError(TlvLvError), } impl Display for PduError { @@ -89,6 +93,9 @@ impl Display for PduError { PduError::WrongDirectiveType((found, expected)) => { write!(f, "found directive type {found:?}, expected {expected:?}") } + PduError::InvalidConditionCode(raw_code) => { + write!(f, "found invalid condition code with raw value {raw_code}") + } PduError::InvalidDirectiveType((found, expected)) => { write!( f, @@ -102,6 +109,9 @@ impl Display for PduError { PduError::ChecksumError(checksum) => { write!(f, "checksum error for CRC {checksum:#04x}") } + PduError::TlvLvError(error) => { + write!(f, "pdu tlv error: {error}") + } } } } @@ -111,6 +121,7 @@ impl Error for PduError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { PduError::ByteConversionError(e) => Some(e), + PduError::TlvLvError(e) => Some(e), _ => None, } } @@ -122,6 +133,12 @@ impl From for PduError { } } +impl From for PduError { + fn from(e: TlvLvError) -> Self { + Self::TlvLvError(e) + } +} + /// Common configuration fields for a PDU. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -514,6 +531,17 @@ mod tests { use crate::ByteConversionError; use std::format; + pub(crate) fn common_pdu_conf(crc_flag: CrcFlag, fss: LargeFileFlag) -> CommonPduConfig { + let src_id = UbfU8::new(5); + let dest_id = UbfU8::new(10); + let transaction_seq_num = UbfU8::new(20); + let mut pdu_conf = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num) + .expect("Generating common PDU config"); + pdu_conf.crc_flag = crc_flag; + pdu_conf.file_flag = fss; + pdu_conf + } + pub(crate) fn verify_raw_header(pdu_conf: &PduHeader, buf: &[u8]) { assert_eq!((buf[0] >> 5) & 0b111, CFDP_VERSION_2); // File directive diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 4898669..5a4e279 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -132,7 +132,7 @@ impl EntityIdTlv { Self { entity_id } } - pub fn len_check(buf: &mut [u8]) -> Result<(), ByteConversionError> { + fn len_check(buf: &[u8]) -> Result<(), ByteConversionError> { if buf.len() < 2 { return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { found: buf.len(), @@ -142,13 +142,34 @@ impl EntityIdTlv { Ok(()) } - pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { + pub fn len_value(&self) -> usize { + self.entity_id.len() + } + + pub fn len_full(&self) -> usize { + 2 + self.entity_id.len() + } + + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { Self::len_check(buf)?; buf[0] = TlvType::EntityId as u8; buf[1] = self.entity_id.len() as u8; self.entity_id.write_to_be_bytes(&mut buf[2..]) } + pub fn from_bytes(buf: &[u8]) -> Result { + Self::len_check(buf)?; + TlvType::try_from(buf[0]) + .map_err(|_| TlvLvError::InvalidTlvTypeField((buf[0], TlvType::EntityId as u8)))?; + let len = buf[1]; + if len != 1 && len != 2 && len != 4 && len != 8 { + return Err(TlvLvError::InvalidValueLength(len)); + } + // Okay to unwrap here. The checks before make sure that the deserialization never fails + let entity_id = UnsignedByteField::new_from_be_bytes(len as usize, &buf[2..]).unwrap(); + Ok(Self { entity_id }) + } + pub fn to_tlv(self, buf: &mut [u8]) -> Result { Self::len_check(buf)?; self.entity_id diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index 4ebf787..920632f 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -328,7 +328,7 @@ impl UnsignedEnum for GenericEcssEnumWrapper { (self.pfc() / 8) as usize } - fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { self.field.write_to_be_bytes(buf) } } diff --git a/src/util.rs b/src/util.rs index f37d5b6..b5d34b1 100644 --- a/src/util.rs +++ b/src/util.rs @@ -71,7 +71,8 @@ impl ToBeBytes for u64 { #[allow(clippy::len_without_is_empty)] pub trait UnsignedEnum { fn len(&self) -> usize; - fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError>; + /// Write the unsigned enumeration to a raw buffer. Returns the written size on success. + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result; } pub trait UnsignedEnumExt: UnsignedEnum + Debug + Copy + Clone + PartialEq + Eq {} @@ -163,7 +164,7 @@ impl UnsignedEnum for UnsignedByteField { self.width } - fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { if buf.len() < self.len() { return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { expected: self.len(), @@ -171,7 +172,7 @@ impl UnsignedEnum for UnsignedByteField { })); } match self.len() { - 0 => Ok(()), + 0 => Ok(0), 1 => { let u8 = UnsignedByteFieldU8::try_from(*self).unwrap(); u8.write_to_be_bytes(buf) @@ -213,7 +214,7 @@ impl UnsignedEnum for GenericUnsignedByteField { self.value.written_len() } - fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { if buf.len() < self.len() { return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { found: buf.len(), @@ -221,7 +222,7 @@ impl UnsignedEnum for GenericUnsignedByteField { })); } buf[0..self.len()].copy_from_slice(self.value.to_be_bytes().as_ref()); - Ok(()) + Ok(self.value.written_len()) } } @@ -318,8 +319,10 @@ pub mod tests { let u8 = UnsignedByteFieldU8::new(5); assert_eq!(u8.len(), 1); let mut buf: [u8; 8] = [0; 8]; - u8.write_to_be_bytes(&mut buf) + let len = u8 + .write_to_be_bytes(&mut buf) .expect("writing to raw buffer failed"); + assert_eq!(len, 1); assert_eq!(buf[0], 5); for i in 1..8 { assert_eq!(buf[i], 0); @@ -331,8 +334,10 @@ pub mod tests { let u16 = UnsignedByteFieldU16::new(3823); assert_eq!(u16.len(), 2); let mut buf: [u8; 8] = [0; 8]; - u16.write_to_be_bytes(&mut buf) + let len = u16 + .write_to_be_bytes(&mut buf) .expect("writing to raw buffer failed"); + assert_eq!(len, 2); let raw_val = u16::from_be_bytes(buf[0..2].try_into().unwrap()); assert_eq!(raw_val, 3823); for i in 2..8 { @@ -345,8 +350,10 @@ pub mod tests { let u32 = UnsignedByteFieldU32::new(80932); assert_eq!(u32.len(), 4); let mut buf: [u8; 8] = [0; 8]; - u32.write_to_be_bytes(&mut buf) + let len = u32 + .write_to_be_bytes(&mut buf) .expect("writing to raw buffer failed"); + assert_eq!(len, 4); let raw_val = u32::from_be_bytes(buf[0..4].try_into().unwrap()); assert_eq!(raw_val, 80932); for i in 4..8 { @@ -359,8 +366,10 @@ pub mod tests { let u64 = UnsignedByteFieldU64::new(5999999); assert_eq!(u64.len(), 8); let mut buf: [u8; 8] = [0; 8]; - u64.write_to_be_bytes(&mut buf) + let len = u64 + .write_to_be_bytes(&mut buf) .expect("writing to raw buffer failed"); + assert_eq!(len, 8); let raw_val = u64::from_be_bytes(buf[0..8].try_into().unwrap()); assert_eq!(raw_val, 5999999); } From eb6bc4b8a89c9e63953533e96eb331d1d8aaadb9 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 8 Jun 2023 20:55:46 +0200 Subject: [PATCH 35/48] added some basic API for finished PDU --- src/cfdp/pdu/finished.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/cfdp/pdu/finished.rs b/src/cfdp/pdu/finished.rs index 6a2220c..0f8cb42 100644 --- a/src/cfdp/pdu/finished.rs +++ b/src/cfdp/pdu/finished.rs @@ -25,7 +25,7 @@ pub enum FileStatus { #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct FinishPdu<'fs_responses> { +pub struct FinishedPdu<'fs_responses> { pdu_header: PduHeader, condition_code: ConditionCode, delivery_code: DeliveryCode, @@ -33,3 +33,27 @@ pub struct FinishPdu<'fs_responses> { fs_responses: Option<&'fs_responses [u8]>, fault_location: Option, } + +impl FinishedPdu<'_> { + /// Default finished PDU: No error (no fault location field) and no filestore responses. + pub fn new_default( + pdu_header: PduHeader, + delivery_code: DeliveryCode, + file_status: FileStatus, + ) -> Self { + Self { + pdu_header, + condition_code: ConditionCode::NoError, + delivery_code, + file_status, + fs_responses: None, + fault_location: None, + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_basic() {} +} From 02675ba0865828e7619cf2fcb5ee6ec0d5908019 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 12 Jun 2023 03:57:38 +0200 Subject: [PATCH 36/48] first basic Finished PDU impl --- src/cfdp/mod.rs | 6 +- src/cfdp/pdu/eof.rs | 13 +- src/cfdp/pdu/file_data.rs | 15 +- src/cfdp/pdu/finished.rs | 289 ++++++++++++++++++++++++++++++++++++-- src/cfdp/pdu/metadata.rs | 14 +- src/cfdp/pdu/mod.rs | 30 ++++ src/cfdp/tlv.rs | 9 +- 7 files changed, 333 insertions(+), 43 deletions(-) diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index 7067056..9add59e 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -140,8 +140,8 @@ pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4]; pub enum TlvLvError { DataTooLarge(usize), ByteConversionError(ByteConversionError), - /// First value: Found value. Second value: Expected value. - InvalidTlvTypeField((u8, u8)), + /// First value: Found value. Second value: Expected value if there is one. + InvalidTlvTypeField((u8, Option)), /// Logically invalid value length detected. InvalidValueLength(u8), } @@ -169,7 +169,7 @@ impl Display for TlvLvError { TlvLvError::InvalidTlvTypeField((found, expected)) => { write!( f, - "invalid TLV type field, found {found}, expected {expected}" + "invalid TLV type field, found {found}, possibly expected {expected:?}" ) } TlvLvError::InvalidValueLength(len) => { diff --git a/src/cfdp/pdu/eof.rs b/src/cfdp/pdu/eof.rs index 79d8645..bfbc65e 100644 --- a/src/cfdp/pdu/eof.rs +++ b/src/cfdp/pdu/eof.rs @@ -1,4 +1,7 @@ -use crate::cfdp::pdu::{read_fss_field, write_fss_field, FileDirectiveType, PduError, PduHeader}; +use crate::cfdp::pdu::{ + generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, FileDirectiveType, + PduError, PduHeader, +}; use crate::cfdp::tlv::EntityIdTlv; use crate::cfdp::{ConditionCode, LargeFileFlag}; use crate::{ByteConversionError, SizeMissmatch}; @@ -95,13 +98,7 @@ impl EofPdu { if is_large_file { min_expected_len += 4; } - if pdu_header.header_len() + min_expected_len > buf.len() { - return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { - found: buf.len(), - expected: pdu_header.header_len() + min_expected_len, - }) - .into()); - } + generic_length_checks_pdu_deserialization(buf, min_expected_len, full_len_without_crc)?; let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { PduError::InvalidDirectiveType((buf[current_idx], FileDirectiveType::EofPdu)) })?; diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index 13db1af..072ba2e 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -1,4 +1,6 @@ -use crate::cfdp::pdu::{read_fss_field, write_fss_field, PduError, PduHeader}; +use crate::cfdp::pdu::{ + generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, PduError, PduHeader, +}; use crate::cfdp::{CrcFlag, LargeFileFlag, PduType, SegmentMetadataFlag}; use crate::{ByteConversionError, SizeMissmatch}; use num_enum::{IntoPrimitive, TryFromPrimitive}; @@ -188,15 +190,8 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { ) -> Result { let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; - let mut min_expected_len = current_idx + core::mem::size_of::(); - min_expected_len = core::cmp::max(min_expected_len, pdu_header.pdu_len()); - if buf.len() < min_expected_len { - return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { - found: buf.len(), - expected: min_expected_len, - }) - .into()); - } + let min_expected_len = current_idx + core::mem::size_of::(); + generic_length_checks_pdu_deserialization(buf, min_expected_len, full_len_without_crc)?; let mut segment_metadata = None; if pdu_header.seg_metadata_flag == SegmentMetadataFlag::Present { segment_metadata = Some(SegmentMetadata::from_bytes(&buf[current_idx..])?); diff --git a/src/cfdp/pdu/finished.rs b/src/cfdp/pdu/finished.rs index 0f8cb42..46e6db4 100644 --- a/src/cfdp/pdu/finished.rs +++ b/src/cfdp/pdu/finished.rs @@ -1,6 +1,9 @@ -use crate::cfdp::pdu::PduHeader; -use crate::cfdp::tlv::EntityIdTlv; -use crate::cfdp::ConditionCode; +use crate::cfdp::pdu::{ + generic_length_checks_pdu_deserialization, FileDirectiveType, PduError, PduHeader, +}; +use crate::cfdp::tlv::{EntityIdTlv, Tlv, TlvType, TlvTypeField}; +use crate::cfdp::{ConditionCode, PduType, TlvLvError}; +use crate::{ByteConversionError, SizeMissmatch}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -34,26 +37,294 @@ pub struct FinishedPdu<'fs_responses> { fault_location: Option, } -impl FinishedPdu<'_> { +impl<'fs_responses> FinishedPdu<'fs_responses> { /// Default finished PDU: No error (no fault location field) and no filestore responses. pub fn new_default( pdu_header: PduHeader, delivery_code: DeliveryCode, file_status: FileStatus, ) -> Self { - Self { + Self::new_generic( pdu_header, - condition_code: ConditionCode::NoError, + ConditionCode::NoError, delivery_code, file_status, - fs_responses: None, - fault_location: None, + None, + None, + ) + } + + pub fn new_with_error( + pdu_header: PduHeader, + condition_code: ConditionCode, + delivery_code: DeliveryCode, + file_status: FileStatus, + fault_location: EntityIdTlv, + ) -> Self { + Self::new_generic( + pdu_header, + condition_code, + delivery_code, + file_status, + None, + Some(fault_location), + ) + } + + pub fn new_generic( + mut pdu_header: PduHeader, + condition_code: ConditionCode, + delivery_code: DeliveryCode, + file_status: FileStatus, + fs_responses: Option<&'fs_responses [u8]>, + fault_location: Option, + ) -> Self { + pdu_header.pdu_type = PduType::FileDirective; + let mut finished_pdu = Self { + pdu_header, + condition_code, + delivery_code, + file_status, + fs_responses, + fault_location, + }; + finished_pdu.pdu_header.pdu_datafield_len = finished_pdu.calc_pdu_datafield_len() as u16; + finished_pdu + } + pub fn pdu_header(&self) -> &PduHeader { + &self.pdu_header + } + + pub fn written_len(&self) -> usize { + self.pdu_header.header_len() + self.calc_pdu_datafield_len() + } + + pub fn condition_code(&self) -> ConditionCode { + self.condition_code + } + + pub fn delivery_code(&self) -> DeliveryCode { + self.delivery_code + } + + pub fn file_status(&self) -> FileStatus { + self.file_status + } + + pub fn filestore_responses(&self) -> Option<&'fs_responses [u8]> { + self.fs_responses + } + + pub fn fault_location(&self) -> Option { + self.fault_location + } + + fn calc_pdu_datafield_len(&self) -> usize { + let mut base_len = 2; + if let Some(fs_responses) = self.fs_responses { + base_len += fs_responses.len(); } + if let Some(fault_location) = self.fault_location { + base_len += fault_location.len_full(); + } + base_len + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + let expected_len = self.written_len(); + if buf.len() < expected_len { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: expected_len, + }) + .into()); + } + + let mut current_idx = self.pdu_header.write_to_bytes(buf)?; + buf[current_idx] = FileDirectiveType::FinishedPdu as u8; + current_idx += 1; + buf[current_idx] = ((self.condition_code as u8) << 4) + | ((self.delivery_code as u8) << 2) + | self.file_status as u8; + current_idx += 1; + if let Some(fs_responses) = self.fs_responses { + buf[current_idx..current_idx + fs_responses.len()].copy_from_slice(fs_responses); + current_idx += fs_responses.len(); + } + if let Some(fault_location) = self.fault_location { + current_idx += fault_location.write_to_be_bytes(&mut buf[current_idx..])?; + } + Ok(current_idx) + } + + /// Generates [Self] from a raw bytestream. + pub fn from_bytes(buf: &'fs_responses [u8]) -> Result { + let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; + let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; + let min_expected_len = current_idx + 2; + generic_length_checks_pdu_deserialization(buf, min_expected_len, full_len_without_crc)?; + let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { + PduError::InvalidDirectiveType((buf[current_idx], FileDirectiveType::FinishedPdu)) + })?; + if directive_type != FileDirectiveType::FinishedPdu { + return Err(PduError::WrongDirectiveType(( + directive_type, + FileDirectiveType::FinishedPdu, + ))); + } + current_idx += 1; + let condition_code = ConditionCode::try_from((buf[current_idx] >> 4) & 0b1111) + .map_err(|_| PduError::InvalidConditionCode((buf[current_idx] >> 4) & 0b1111))?; + // Unwrap is okay here for both of the following operations which can not fail. + let delivery_code = DeliveryCode::try_from((buf[current_idx] >> 2) & 0b1).unwrap(); + let file_status = FileStatus::try_from(buf[current_idx] & 0b11).unwrap(); + current_idx += 1; + let (fs_responses, fault_location) = + Self::parse_tlv_fields(current_idx, full_len_without_crc, buf)?; + Ok(Self { + pdu_header, + condition_code, + delivery_code, + file_status, + fs_responses, + fault_location, + }) + } + + fn parse_tlv_fields( + mut current_idx: usize, + full_len_without_crc: usize, + buf: &'fs_responses [u8], + ) -> Result<(Option<&'fs_responses [u8]>, Option), PduError> { + let mut fs_responses = None; + let mut fault_location = None; + let start_of_fs_responses = current_idx; + // There are leftover filestore response(s) and/or a fault location field. + while current_idx < full_len_without_crc { + let next_tlv = Tlv::from_bytes(&buf[current_idx..])?; + match next_tlv.tlv_type_field() { + TlvTypeField::Standard(tlv_type) => { + if tlv_type == TlvType::FilestoreResponse { + current_idx += next_tlv.len_full(); + if current_idx == full_len_without_crc { + fs_responses = Some(&buf[start_of_fs_responses..current_idx]); + } + } else if tlv_type == TlvType::EntityId { + // At least one FS response is included. + if current_idx > full_len_without_crc { + fs_responses = Some(&buf[start_of_fs_responses..current_idx]); + } + fault_location = Some(EntityIdTlv::from_bytes(&buf[current_idx..])?); + current_idx += fault_location.as_ref().unwrap().len_full(); + // This is considered a configuration error: The entity ID has to be the + // last TLV, everything else would break the whole handling of the packet + // TLVs. + if current_idx != full_len_without_crc { + return Err(PduError::FormatError); + } + } else { + return Err(TlvLvError::InvalidTlvTypeField((tlv_type as u8, None)).into()); + } + } + TlvTypeField::Custom(raw) => { + return Err(TlvLvError::InvalidTlvTypeField((raw, None)).into()); + } + } + } + Ok((fs_responses, fault_location)) } } #[cfg(test)] mod tests { + use crate::cfdp::pdu::finished::{DeliveryCode, FileStatus, FinishedPdu}; + use crate::cfdp::pdu::tests::{common_pdu_conf, verify_raw_header}; + use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; + use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag}; + + fn generic_finished_pdu( + crc_flag: CrcFlag, + fss: LargeFileFlag, + delivery_code: DeliveryCode, + file_status: FileStatus, + ) -> FinishedPdu<'static> { + let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0); + FinishedPdu::new_default(pdu_header, delivery_code, file_status) + } + #[test] - fn test_basic() {} + fn test_basic() { + let finished_pdu = generic_finished_pdu( + CrcFlag::NoCrc, + LargeFileFlag::Normal, + DeliveryCode::Complete, + FileStatus::Retained, + ); + assert_eq!(finished_pdu.condition_code(), ConditionCode::NoError); + assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Complete); + assert_eq!(finished_pdu.file_status(), FileStatus::Retained); + assert_eq!(finished_pdu.filestore_responses(), None); + assert_eq!(finished_pdu.fault_location(), None); + assert_eq!(finished_pdu.pdu_header().pdu_datafield_len, 2); + } + + fn generic_serialization_test_no_error(delivery_code: DeliveryCode, file_status: FileStatus) { + let finished_pdu = generic_finished_pdu( + CrcFlag::NoCrc, + LargeFileFlag::Normal, + delivery_code, + file_status, + ); + let mut buf: [u8; 64] = [0; 64]; + let written = finished_pdu.write_to_bytes(&mut buf); + assert!(written.is_ok()); + let written = written.unwrap(); + assert_eq!(written, finished_pdu.written_len()); + assert_eq!(written, finished_pdu.pdu_header().header_len() + 2); + verify_raw_header(finished_pdu.pdu_header(), &buf); + let mut current_idx = finished_pdu.pdu_header().header_len(); + assert_eq!(buf[current_idx], FileDirectiveType::FinishedPdu as u8); + current_idx += 1; + assert_eq!( + (buf[current_idx] >> 4) & 0b1111, + ConditionCode::NoError as u8 + ); + assert_eq!((buf[current_idx] >> 2) & 0b1, delivery_code as u8); + assert_eq!(buf[current_idx] & 0b11, file_status as u8); + assert_eq!(current_idx + 1, written); + } + + #[test] + fn test_serialization_simple() { + generic_serialization_test_no_error(DeliveryCode::Complete, FileStatus::Retained); + } + + #[test] + fn test_serialization_simple_2() { + generic_serialization_test_no_error( + DeliveryCode::Incomplete, + FileStatus::DiscardDeliberately, + ); + } + + #[test] + fn test_serialization_simple_3() { + generic_serialization_test_no_error(DeliveryCode::Incomplete, FileStatus::Unreported); + } + + #[test] + fn test_deserialization_simple() { + let finished_pdu = generic_finished_pdu( + CrcFlag::NoCrc, + LargeFileFlag::Normal, + DeliveryCode::Complete, + FileStatus::Retained, + ); + let mut buf: [u8; 64] = [0; 64]; + finished_pdu.write_to_bytes(&mut buf).unwrap(); + let read_back = FinishedPdu::from_bytes(&buf); + assert!(read_back.is_ok()); + let read_back = read_back.unwrap(); + assert_eq!(finished_pdu, read_back); + } } diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index b9d912a..382cd25 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -1,5 +1,8 @@ use crate::cfdp::lv::Lv; -use crate::cfdp::pdu::{read_fss_field, write_fss_field, FileDirectiveType, PduError, PduHeader}; +use crate::cfdp::pdu::{ + generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, FileDirectiveType, + PduError, PduHeader, +}; use crate::cfdp::tlv::Tlv; use crate::cfdp::{ChecksumType, CrcFlag, LargeFileFlag, PduType}; use crate::{ByteConversionError, SizeMissmatch, CRC_CCITT_FALSE}; @@ -242,14 +245,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { if is_large_file { min_expected_len += 4; } - min_expected_len = core::cmp::max(min_expected_len, pdu_header.pdu_len()); - if buf.len() < min_expected_len { - return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { - found: buf.len(), - expected: min_expected_len, - }) - .into()); - } + generic_length_checks_pdu_deserialization(buf, min_expected_len, full_len_without_crc)?; let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { PduError::InvalidDirectiveType((buf[current_idx], FileDirectiveType::MetadataPdu)) })?; diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index e786963..225874a 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -53,6 +53,8 @@ pub enum PduError { FileSizeTooLarge(u64), /// If the CRC flag for a PDU is enabled and the checksum check fails. Contains raw 16-bit CRC. ChecksumError(u16), + /// Generic error for invalid PDU formats. + FormatError, /// Error handling a TLV field. TlvLvError(TlvLvError), } @@ -112,6 +114,9 @@ impl Display for PduError { PduError::TlvLvError(error) => { write!(f, "pdu tlv error: {error}") } + PduError::FormatError => { + write!(f, "generic PDU format error") + } } } } @@ -518,6 +523,31 @@ pub(crate) fn read_fss_field(file_flag: LargeFileFlag, buf: &[u8]) -> (usize, u6 } } +// This is a generic length check applicable to most PDU deserializations. It first checks whether +// a given buffer can hold an expected minimum size, and then it checks whether the PDU datafield +// length is larger than that expected minimum size. +pub(crate) fn generic_length_checks_pdu_deserialization( + buf: &[u8], + min_expected_len: usize, + full_len_without_crc: usize, +) -> Result<(), ByteConversionError> { + // Buffer too short to hold additional expected minimum datasize. + if buf.len() < min_expected_len { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: min_expected_len, + })); + } + // This can happen if the PDU datafield length value is invalid. + if full_len_without_crc < min_expected_len { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: full_len_without_crc, + expected: min_expected_len, + })); + } + Ok(()) +} + #[cfg(test)] mod tests { use crate::cfdp::pdu::{CommonPduConfig, PduError, PduHeader, FIXED_HEADER_LEN}; diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 5a4e279..d7a7376 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -159,8 +159,9 @@ impl EntityIdTlv { pub fn from_bytes(buf: &[u8]) -> Result { Self::len_check(buf)?; - TlvType::try_from(buf[0]) - .map_err(|_| TlvLvError::InvalidTlvTypeField((buf[0], TlvType::EntityId as u8)))?; + TlvType::try_from(buf[0]).map_err(|_| { + TlvLvError::InvalidTlvTypeField((buf[0], Some(TlvType::EntityId as u8))) + })?; let len = buf[1]; if len != 1 && len != 2 && len != 4 && len != 8 { return Err(TlvLvError::InvalidValueLength(len)); @@ -191,14 +192,14 @@ impl<'data> TryFrom> for EntityIdTlv { if tlv_type != TlvType::EntityId { return Err(TlvLvError::InvalidTlvTypeField(( tlv_type as u8, - TlvType::EntityId as u8, + Some(TlvType::EntityId as u8), ))); } } TlvTypeField::Custom(val) => { return Err(TlvLvError::InvalidTlvTypeField(( val, - TlvType::EntityId as u8, + Some(TlvType::EntityId as u8), ))); } } From 15bc12aede5210b695c08b4c82650600b372ac4a Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 12 Jun 2023 04:02:18 +0200 Subject: [PATCH 37/48] that should be sufficient for the first FSM approach --- src/cfdp/tlv.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index d7a7376..3a44b0b 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -30,6 +30,21 @@ pub enum TlvTypeField { Custom(u8), } +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum FsRequestActionCode { + CreateFile = 0b0000, + DeleteFile = 0b0001, + RenameFile = 0b0010, + AppendFile = 0b0011, + ReplaceFile = 0b0100, + CreateDirectory = 0b0101, + RemoveDirectory = 0b0110, + DenyFile = 0b0111, + DenyDirectory = 0b1000, +} + impl From for TlvTypeField { fn from(value: u8) -> Self { match TlvType::try_from(value) { From d217a669b21f5f33432f4cc139d088da5e953300 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 12 Jun 2023 04:04:52 +0200 Subject: [PATCH 38/48] add more docs --- src/cfdp/pdu/file_data.rs | 3 +++ src/cfdp/pdu/finished.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index 072ba2e..1207a04 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -81,6 +81,9 @@ impl<'seg_meta> SegmentMetadata<'seg_meta> { } } +/// File Data PDU abstraction. +/// +/// For more information, refer to CFDP chapter 5.3. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct FileDataPdu<'seg_meta, 'file_data> { diff --git a/src/cfdp/pdu/finished.rs b/src/cfdp/pdu/finished.rs index 46e6db4..5b14a72 100644 --- a/src/cfdp/pdu/finished.rs +++ b/src/cfdp/pdu/finished.rs @@ -26,6 +26,9 @@ pub enum FileStatus { Unreported = 0b11, } +/// Finished PDU abstraction. +/// +/// For more information, refer to CFDP chapter 5.2.3. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct FinishedPdu<'fs_responses> { From e48c2fe36840048aa75eefe3bad62d36af27fb53 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 12 Jun 2023 04:13:41 +0200 Subject: [PATCH 39/48] some docs --- README.md | 2 ++ src/cfdp/mod.rs | 1 + src/cfdp/pdu/mod.rs | 3 ++- src/cfdp/tlv.rs | 2 +- src/lib.rs | 2 ++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ca85e6c..a440074 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Currently, this includes the following components: - Space Packet implementation according to [CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf) +- CCSDS File Delivery Protocol (CFDP) packet implementations according to + [CCSDS Blue Book 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf) - PUS Telecommand and PUS Telemetry implementation according to the [ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). - CUC (CCSDS Unsegmented Time Code) implementation according to diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index 9add59e..bd1d918 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -1,3 +1,4 @@ +//! Low-level CCSDS File Delivery Protocol (CFDP) support according to [CCSDS 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf). use crate::ByteConversionError; use core::fmt::{Display, Formatter}; use num_enum::{IntoPrimitive, TryFromPrimitive}; diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 225874a..148e8d4 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -392,7 +392,8 @@ impl PduHeader { /// and 4.1.2 because performing the CRC procedure requires the buffer to be large enough /// to hold the full PDU. /// - /// Both functions can however be performed with the [verify_length_and_checksum] function. + /// Both functions can however be performed with the [Self::verify_length_and_checksum] + /// function. pub fn from_bytes(buf: &[u8]) -> Result<(Self, usize), PduError> { if buf.len() < FIXED_HEADER_LEN { return Err(PduError::ByteConversionError( diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 3a44b0b..0efed46 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -126,7 +126,7 @@ impl<'data> Tlv<'data> { /// Creates a TLV give a raw bytestream. Please note that is is not necessary to pass the /// bytestream with the exact size of the expected TLV. This function will take care /// of parsing the length byte, and the length of the parsed TLV can be retrieved using - /// [len_full]. + /// [Self::len_full]. pub fn from_bytes(buf: &'data [u8]) -> Result, TlvLvError> { generic_len_check_deserialization(buf, MIN_TLV_LEN)?; Ok(Self { diff --git a/src/lib.rs b/src/lib.rs index 5062c18..094322d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,8 @@ //! //! - Space Packet implementation according to //! [CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf) +//! - CCSDS File Delivery Protocol (CFDP) packet implementations according to +//! [CCSDS Blue Book 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf) //! - PUS Telecommand and PUS Telemetry implementation according to the //! [ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). //! - CUC (CCSDS Unsegmented Time Code) implementation according to From 895080bbc001ee82efb2d237f185d72397a6d541 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 12 Jun 2023 13:00:58 +0200 Subject: [PATCH 40/48] add crc serialization for all packets --- src/cfdp/pdu/eof.rs | 9 ++++++--- src/cfdp/pdu/file_data.rs | 6 +++++- src/cfdp/pdu/finished.rs | 7 +++++-- src/cfdp/pdu/metadata.rs | 11 ++++------- src/cfdp/pdu/mod.rs | 8 ++++++++ 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/cfdp/pdu/eof.rs b/src/cfdp/pdu/eof.rs index bfbc65e..8451533 100644 --- a/src/cfdp/pdu/eof.rs +++ b/src/cfdp/pdu/eof.rs @@ -1,9 +1,9 @@ use crate::cfdp::pdu::{ - generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, FileDirectiveType, - PduError, PduHeader, + add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, + FileDirectiveType, PduError, PduHeader, }; use crate::cfdp::tlv::EntityIdTlv; -use crate::cfdp::{ConditionCode, LargeFileFlag}; +use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag}; use crate::{ByteConversionError, SizeMissmatch}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -87,6 +87,9 @@ impl EofPdu { if let Some(fault_location) = self.fault_location { current_idx += fault_location.write_to_be_bytes(buf)?; } + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + current_idx = add_pdu_crc(buf, current_idx); + } Ok(current_idx) } diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs index 1207a04..ee6846c 100644 --- a/src/cfdp/pdu/file_data.rs +++ b/src/cfdp/pdu/file_data.rs @@ -1,5 +1,6 @@ use crate::cfdp::pdu::{ - generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, PduError, PduHeader, + add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, + PduError, PduHeader, }; use crate::cfdp::{CrcFlag, LargeFileFlag, PduType, SegmentMetadataFlag}; use crate::{ByteConversionError, SizeMissmatch}; @@ -185,6 +186,9 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { )?; buf[current_idx..current_idx + self.file_data.len()].copy_from_slice(self.file_data); current_idx += self.file_data.len(); + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + current_idx = add_pdu_crc(buf, current_idx); + } Ok(current_idx) } diff --git a/src/cfdp/pdu/finished.rs b/src/cfdp/pdu/finished.rs index 5b14a72..29cf747 100644 --- a/src/cfdp/pdu/finished.rs +++ b/src/cfdp/pdu/finished.rs @@ -1,8 +1,8 @@ use crate::cfdp::pdu::{ - generic_length_checks_pdu_deserialization, FileDirectiveType, PduError, PduHeader, + add_pdu_crc, generic_length_checks_pdu_deserialization, FileDirectiveType, PduError, PduHeader, }; use crate::cfdp::tlv::{EntityIdTlv, Tlv, TlvType, TlvTypeField}; -use crate::cfdp::{ConditionCode, PduType, TlvLvError}; +use crate::cfdp::{ConditionCode, CrcFlag, PduType, TlvLvError}; use crate::{ByteConversionError, SizeMissmatch}; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] @@ -157,6 +157,9 @@ impl<'fs_responses> FinishedPdu<'fs_responses> { if let Some(fault_location) = self.fault_location { current_idx += fault_location.write_to_be_bytes(&mut buf[current_idx..])?; } + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + current_idx = add_pdu_crc(buf, current_idx); + } Ok(current_idx) } diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 382cd25..fbdb982 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -1,11 +1,11 @@ use crate::cfdp::lv::Lv; use crate::cfdp::pdu::{ - generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, FileDirectiveType, - PduError, PduHeader, + add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, + FileDirectiveType, PduError, PduHeader, }; use crate::cfdp::tlv::Tlv; use crate::cfdp::{ChecksumType, CrcFlag, LargeFileFlag, PduType}; -use crate::{ByteConversionError, SizeMissmatch, CRC_CCITT_FALSE}; +use crate::{ByteConversionError, SizeMissmatch}; #[cfg(feature = "alloc")] use alloc::vec::Vec; #[cfg(feature = "serde")] @@ -226,10 +226,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { current_idx += opts.len(); } if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { - let mut digest = CRC_CCITT_FALSE.digest(); - digest.update(&buf[..current_idx]); - buf[current_idx..current_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes()); - current_idx += 2; + current_idx = add_pdu_crc(buf, current_idx); } Ok(current_idx) } diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 148e8d4..2ef5771 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -549,6 +549,14 @@ pub(crate) fn generic_length_checks_pdu_deserialization( Ok(()) } +pub(crate) fn add_pdu_crc(buf: &mut [u8], mut current_idx: usize) -> usize { + let mut digest = CRC_CCITT_FALSE.digest(); + digest.update(&buf[..current_idx]); + buf[current_idx..current_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes()); + current_idx += 2; + current_idx +} + #[cfg(test)] mod tests { use crate::cfdp::pdu::{CommonPduConfig, PduError, PduHeader, FIXED_HEADER_LEN}; From 99dbf9dc8585f42eac9c8dfc208757544e750f11 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 12 Jun 2023 14:26:40 +0200 Subject: [PATCH 41/48] finished basic FS request impl --- src/cfdp/mod.rs | 16 +++- src/cfdp/tlv.rs | 209 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 217 insertions(+), 8 deletions(-) diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index bd1d918..32480d3 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -143,8 +143,14 @@ pub enum TlvLvError { ByteConversionError(ByteConversionError), /// First value: Found value. Second value: Expected value if there is one. InvalidTlvTypeField((u8, Option)), - /// Logically invalid value length detected. - InvalidValueLength(u8), + /// Logically invalid value length detected. The value length may not exceed 255 bytes. + /// Depending on the concrete TLV type, the value length may also be logically invalid. + InvalidValueLength(usize), + /// Only applies to filestore requests and responses. Second name was missing where one is + /// expected. + SecondNameMissing, + /// Invalid action code for filestore requests or responses. + InvalidFilestoreActionCode(u8), } impl From for TlvLvError { @@ -176,6 +182,12 @@ impl Display for TlvLvError { TlvLvError::InvalidValueLength(len) => { write!(f, "invalid value length {len} detected") } + TlvLvError::SecondNameMissing => { + write!(f, "second name missing for filestore request or response") + } + TlvLvError::InvalidFilestoreActionCode(raw) => { + write!(f, "invalid filestore action code with raw value {raw}") + } } } } diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 0efed46..d4ae3fa 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -33,11 +33,16 @@ pub enum TlvTypeField { #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(u8)] -pub enum FsRequestActionCode { +pub enum FilestoreActionCode { CreateFile = 0b0000, DeleteFile = 0b0001, RenameFile = 0b0010, + /// This operation appends one file to another. The first specified name will form the first + /// part of the new file and the name of the new file. This function can be used to get + /// similar functionality to the UNIX cat utility (albeit for only two files). AppendFile = 0b0011, + /// This operation replaces the content of the first specified file with the content of + /// the secondly specified file. ReplaceFile = 0b0100, CreateDirectory = 0b0101, RemoveDirectory = 0b0110, @@ -136,6 +141,18 @@ impl<'data> Tlv<'data> { } } +pub(crate) fn verify_tlv_type(raw_type: u8, expected_tlv_type: TlvType) -> Result<(), TlvLvError> { + let tlv_type = TlvType::try_from(raw_type) + .map_err(|_| TlvLvError::InvalidTlvTypeField((raw_type, Some(expected_tlv_type as u8))))?; + if tlv_type != expected_tlv_type { + return Err(TlvLvError::InvalidTlvTypeField(( + tlv_type as u8, + Some(expected_tlv_type as u8), + ))); + } + Ok(()) +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct EntityIdTlv { @@ -174,18 +191,17 @@ impl EntityIdTlv { pub fn from_bytes(buf: &[u8]) -> Result { Self::len_check(buf)?; - TlvType::try_from(buf[0]).map_err(|_| { - TlvLvError::InvalidTlvTypeField((buf[0], Some(TlvType::EntityId as u8))) - })?; + verify_tlv_type(buf[0], TlvType::EntityId)?; let len = buf[1]; if len != 1 && len != 2 && len != 4 && len != 8 { - return Err(TlvLvError::InvalidValueLength(len)); + return Err(TlvLvError::InvalidValueLength(len as usize)); } // Okay to unwrap here. The checks before make sure that the deserialization never fails let entity_id = UnsignedByteField::new_from_be_bytes(len as usize, &buf[2..]).unwrap(); Ok(Self { entity_id }) } + /// Convert to a generic [Tlv], which also erases the programmatic type information. pub fn to_tlv(self, buf: &mut [u8]) -> Result { Self::len_check(buf)?; self.entity_id @@ -223,7 +239,7 @@ impl<'data> TryFrom> for EntityIdTlv { && value.len_value() != 4 && value.len_value() != 8 { - return Err(TlvLvError::InvalidValueLength(value.len_value() as u8)); + return Err(TlvLvError::InvalidValueLength(value.len_value())); } Ok(Self::new( UnsignedByteField::new_from_be_bytes(value.len_value(), value.value().unwrap()) @@ -237,6 +253,187 @@ impl<'data> TryFrom> for EntityIdTlv { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct FilestoreRequestTlv<'first_name, 'second_name> { + action_code: FilestoreActionCode, + #[cfg_attr(feature = "serde", serde(borrow))] + first_name: Lv<'first_name>, + #[cfg_attr(feature = "serde", serde(borrow))] + second_name: Option>, +} + +impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> { + pub fn new_create_file(first_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::CreateFile, first_name, None) + } + + pub fn new_delete_file(first_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::DeleteFile, first_name, None) + } + + pub fn new_rename_file( + source_name: Lv<'first_name>, + target_name: Lv<'second_name>, + ) -> Result { + Self::new( + FilestoreActionCode::RenameFile, + source_name, + Some(target_name), + ) + } + + /// This operation appends one file to another. The first specified name will form the first + /// part of the new file and the name of the new file. This function can be used to get + /// similar functionality to the UNIX cat utility (albeit for only two files). + pub fn new_append_file( + first_file: Lv<'first_name>, + second_file: Lv<'second_name>, + ) -> Result { + Self::new( + FilestoreActionCode::AppendFile, + first_file, + Some(second_file), + ) + } + + /// This operation replaces the content of the first specified file with the content of + /// the secondly specified file. This function can be used to get similar functionality to + /// the UNIX copy (cp) utility if the target file already exists. + pub fn new_replace_file( + replaced_file: Lv<'first_name>, + new_file: Lv<'second_name>, + ) -> Result { + Self::new( + FilestoreActionCode::ReplaceFile, + replaced_file, + Some(new_file), + ) + } + + pub fn new_create_directory(dir_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::CreateDirectory, dir_name, None) + } + + pub fn new_remove_directory(dir_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::RemoveDirectory, dir_name, None) + } + + pub fn new_deny_file(file_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::DenyFile, file_name, None) + } + + pub fn new_deny_directory(dir_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::DenyFile, dir_name, None) + } + + /// This function will return [None] if the respective action code requires two names but + /// only one is passed. It will also returns [None] if the cumulative length of the first + /// name and the second name exceeds 255 bytes. + /// + /// This is the case for the rename, append and replace filestore request. + pub fn new( + action_code: FilestoreActionCode, + first_name: Lv<'first_name>, + second_name: Option>, + ) -> Result { + let mut base_value_len = first_name.len_full(); + if Self::has_second_filename(action_code) { + if second_name.is_none() { + return Err(TlvLvError::SecondNameMissing); + } + base_value_len += second_name.as_ref().unwrap().len_full(); + } + if base_value_len > u8::MAX as usize { + return Err(TlvLvError::InvalidValueLength(base_value_len)); + } + Ok(Self { + action_code, + first_name, + second_name, + }) + } + + pub fn has_second_filename(action_code: FilestoreActionCode) -> bool { + if action_code == FilestoreActionCode::RenameFile + || action_code == FilestoreActionCode::AppendFile + || action_code == FilestoreActionCode::ReplaceFile + { + return true; + } + false + } + + pub fn len_value(&self) -> usize { + let mut len = 1 + self.first_name.len_full(); + if let Some(second_name) = self.second_name { + len += second_name.len_full(); + } + len + } + + pub fn len_full(&self) -> usize { + 2 + self.len_value() + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.len_full() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.len_full(), + })); + } + buf[0] = TlvType::FilestoreRequest as u8; + buf[1] = self.len_value() as u8; + buf[2] = (self.action_code as u8) << 4; + let mut current_idx = 2; + // Length checks were already performed. + self.first_name.write_to_be_bytes_no_len_check( + &mut buf[current_idx..current_idx + self.first_name.len_full()], + ); + current_idx += self.first_name.len_full(); + if let Some(second_name) = self.second_name { + second_name.write_to_be_bytes_no_len_check( + &mut buf[current_idx..current_idx + second_name.len_full()], + ); + current_idx += second_name.len_full(); + } + Ok(current_idx) + } + + pub fn from_bytes<'longest: 'first_name + 'second_name>( + buf: &'longest [u8], + ) -> Result { + if buf.len() < 2 { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 2, + }) + .into()); + } + verify_tlv_type(buf[0], TlvType::FilestoreRequest)?; + let len = buf[1] as usize; + let mut current_idx = 2; + let action_code = FilestoreActionCode::try_from((buf[2] >> 4) & 0b1111) + .map_err(|_| TlvLvError::InvalidFilestoreActionCode((buf[2] >> 4) & 0b1111))?; + let first_name = Lv::from_bytes(&buf[current_idx..])?; + let mut second_name = None; + + current_idx += first_name.len_full(); + if Self::has_second_filename(action_code) { + if current_idx >= 2 + len { + return Err(TlvLvError::SecondNameMissing); + } + second_name = Some(Lv::from_bytes(&buf[current_idx..])?); + } + Ok(Self { + action_code, + first_name, + second_name, + }) + } +} + #[cfg(test)] mod tests { use crate::cfdp::tlv::{Tlv, TlvType, TlvTypeField}; From d9028d21dab79733db437140469e8abfcf6d37bc Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 12 Jun 2023 15:52:10 +0200 Subject: [PATCH 42/48] start adding tests --- src/cfdp/tlv.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index d4ae3fa..71830b8 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -436,7 +436,8 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> { #[cfg(test)] mod tests { - use crate::cfdp::tlv::{Tlv, TlvType, TlvTypeField}; + use crate::cfdp::lv::Lv; + use crate::cfdp::tlv::{FilestoreRequestTlv, Tlv, TlvType, TlvTypeField}; use crate::cfdp::TlvLvError; use crate::util::{UbfU8, UnsignedEnum}; @@ -560,4 +561,12 @@ mod tests { assert_eq!(tlv.len_value(), 1); assert_eq!(tlv.len_full(), 3); } + + #[test] + fn test_fs_request_basic() { + let first_name = Lv::new_from_str("hello.txt").unwrap(); + let fs_request = FilestoreRequestTlv::new_create_file(first_name); + assert!(fs_request.is_ok()); + let fs_request = fs_request.unwrap(); + } } From 0e87039010f0c2fd434c06978a042eb412166588 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 12 Jun 2023 16:04:32 +0200 Subject: [PATCH 43/48] added first basic state test --- src/cfdp/tlv.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 71830b8..ec25655 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -364,6 +364,18 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> { false } + pub fn action_code(&self) -> FilestoreActionCode { + self.action_code + } + + pub fn first_name(&self) -> Lv<'first_name> { + self.first_name + } + + pub fn second_name(&self) -> Option> { + self.second_name + } + pub fn len_value(&self) -> usize { let mut len = 1 + self.first_name.len_full(); if let Some(second_name) = self.second_name { @@ -437,7 +449,7 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> { #[cfg(test)] mod tests { use crate::cfdp::lv::Lv; - use crate::cfdp::tlv::{FilestoreRequestTlv, Tlv, TlvType, TlvTypeField}; + use crate::cfdp::tlv::{FilestoreActionCode, FilestoreRequestTlv, Tlv, TlvType, TlvTypeField}; use crate::cfdp::TlvLvError; use crate::util::{UbfU8, UnsignedEnum}; @@ -568,5 +580,8 @@ mod tests { let fs_request = FilestoreRequestTlv::new_create_file(first_name); assert!(fs_request.is_ok()); let fs_request = fs_request.unwrap(); + assert_eq!(fs_request.action_code(), FilestoreActionCode::CreateFile); + assert_eq!(fs_request.first_name(), first_name); + assert_eq!(fs_request.second_name(), None); } } From 3727e7e6688d7ba397a45bf7cbd26766ff588d7a Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 12 Jun 2023 16:19:20 +0200 Subject: [PATCH 44/48] added more FS request tests --- src/cfdp/tlv.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index ec25655..981f6e0 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -324,7 +324,7 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> { } pub fn new_deny_directory(dir_name: Lv<'first_name>) -> Result { - Self::new(FilestoreActionCode::DenyFile, dir_name, None) + Self::new(FilestoreActionCode::DenyDirectory, dir_name, None) } /// This function will return [None] if the respective action code requires two names but @@ -574,14 +574,98 @@ mod tests { assert_eq!(tlv.len_full(), 3); } - #[test] - fn test_fs_request_basic() { + fn generic_fs_request_test_one_file(action_code: FilestoreActionCode) { + assert!(!FilestoreRequestTlv::has_second_filename(action_code)); let first_name = Lv::new_from_str("hello.txt").unwrap(); - let fs_request = FilestoreRequestTlv::new_create_file(first_name); + let fs_request = match action_code { + FilestoreActionCode::CreateFile => FilestoreRequestTlv::new_create_file(first_name), + FilestoreActionCode::DeleteFile => FilestoreRequestTlv::new_delete_file(first_name), + FilestoreActionCode::CreateDirectory => { + FilestoreRequestTlv::new_create_directory(first_name) + } + FilestoreActionCode::RemoveDirectory => { + FilestoreRequestTlv::new_remove_directory(first_name) + } + FilestoreActionCode::DenyFile => FilestoreRequestTlv::new_deny_file(first_name), + FilestoreActionCode::DenyDirectory => { + FilestoreRequestTlv::new_deny_directory(first_name) + } + _ => panic!("invalid action code"), + }; assert!(fs_request.is_ok()); let fs_request = fs_request.unwrap(); - assert_eq!(fs_request.action_code(), FilestoreActionCode::CreateFile); + assert_eq!(fs_request.len_value(), 1 + first_name.len_full()); + assert_eq!(fs_request.len_full(), fs_request.len_value() + 2); + assert_eq!(fs_request.action_code(), action_code); assert_eq!(fs_request.first_name(), first_name); assert_eq!(fs_request.second_name(), None); } + + fn generic_fs_request_test_two_files(action_code: FilestoreActionCode) { + assert!(FilestoreRequestTlv::has_second_filename(action_code)); + let first_name = Lv::new_from_str("hello.txt").unwrap(); + let second_name = Lv::new_from_str("hello2.txt").unwrap(); + let fs_request = match action_code { + FilestoreActionCode::ReplaceFile => { + FilestoreRequestTlv::new_replace_file(first_name, second_name) + } + FilestoreActionCode::AppendFile => { + FilestoreRequestTlv::new_append_file(first_name, second_name) + } + FilestoreActionCode::RenameFile => { + FilestoreRequestTlv::new_rename_file(first_name, second_name) + } + _ => panic!("invalid action code"), + }; + assert!(fs_request.is_ok()); + let fs_request = fs_request.unwrap(); + assert_eq!( + fs_request.len_value(), + 1 + first_name.len_full() + second_name.len_full() + ); + assert_eq!(fs_request.len_full(), fs_request.len_value() + 2); + assert_eq!(fs_request.action_code(), action_code); + assert_eq!(fs_request.first_name(), first_name); + assert!(fs_request.second_name().is_some()); + assert_eq!(fs_request.second_name().unwrap(), second_name); + } + + #[test] + fn test_fs_request_basic_create_file() { + generic_fs_request_test_one_file(FilestoreActionCode::CreateFile); + } + #[test] + fn test_fs_request_basic_delete() { + generic_fs_request_test_one_file(FilestoreActionCode::DeleteFile); + } + #[test] + fn test_fs_request_basic_create_dir() { + generic_fs_request_test_one_file(FilestoreActionCode::CreateDirectory); + } + #[test] + fn test_fs_request_basic_remove_dir() { + generic_fs_request_test_one_file(FilestoreActionCode::RemoveDirectory); + } + #[test] + fn test_fs_request_basic_deny_file() { + generic_fs_request_test_one_file(FilestoreActionCode::DenyFile); + } + #[test] + fn test_fs_request_basic_deny_dir() { + generic_fs_request_test_one_file(FilestoreActionCode::DenyDirectory); + } + #[test] + fn test_fs_request_basic_append_file() { + generic_fs_request_test_two_files(FilestoreActionCode::AppendFile); + } + #[test] + fn test_fs_request_basic_rename_file() { + generic_fs_request_test_two_files(FilestoreActionCode::RenameFile); + } + #[test] + fn test_fs_request_basic_replace_file() { + generic_fs_request_test_two_files(FilestoreActionCode::ReplaceFile); + } + #[test] + fn test_fs_request_serialization() {} } From a313a784ffcd613553641c5ad958d72568f0832c Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 2 Jul 2023 17:18:33 +0200 Subject: [PATCH 45/48] finish FS request unittests --- src/cfdp/lv.rs | 8 ++++ src/cfdp/tlv.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 109 insertions(+), 7 deletions(-) diff --git a/src/cfdp/lv.rs b/src/cfdp/lv.rs index 23701be..cf6f209 100644 --- a/src/cfdp/lv.rs +++ b/src/cfdp/lv.rs @@ -1,6 +1,7 @@ //! Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. use crate::cfdp::TlvLvError; use crate::{ByteConversionError, SizeMissmatch}; +use core::str::Utf8Error; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] @@ -95,6 +96,13 @@ impl<'data> Lv<'data> { self.data } + /// Convenience function to extract the value as a [str]. This is useful if the LV is + /// known to contain a [str], for example being a file name. + pub fn value_as_str(&self) -> Option> { + self.data?; + Some(std::str::from_utf8(self.data.unwrap())) + } + /// Writes the LV to a raw buffer. Please note that the first byte will contain the length /// of the value, but the values may not exceed a length of [u8::MAX]. pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs index 981f6e0..2bb3e9d 100644 --- a/src/cfdp/tlv.rs +++ b/src/cfdp/tlv.rs @@ -398,7 +398,7 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> { buf[0] = TlvType::FilestoreRequest as u8; buf[1] = self.len_value() as u8; buf[2] = (self.action_code as u8) << 4; - let mut current_idx = 2; + let mut current_idx = 3; // Length checks were already performed. self.first_name.write_to_be_bytes_no_len_check( &mut buf[current_idx..current_idx + self.first_name.len_full()], @@ -428,6 +428,7 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> { let mut current_idx = 2; let action_code = FilestoreActionCode::try_from((buf[2] >> 4) & 0b1111) .map_err(|_| TlvLvError::InvalidFilestoreActionCode((buf[2] >> 4) & 0b1111))?; + current_idx += 1; let first_name = Lv::from_bytes(&buf[current_idx..])?; let mut second_name = None; @@ -453,6 +454,9 @@ mod tests { use crate::cfdp::TlvLvError; use crate::util::{UbfU8, UnsignedEnum}; + const TLV_TEST_STR_0: &'static str = "hello.txt"; + const TLV_TEST_STR_1: &'static str = "hello2.txt"; + #[test] fn test_basic() { let entity_id = UbfU8::new(5); @@ -574,9 +578,11 @@ mod tests { assert_eq!(tlv.len_full(), 3); } - fn generic_fs_request_test_one_file(action_code: FilestoreActionCode) { + fn generic_fs_request_test_one_file( + action_code: FilestoreActionCode, + ) -> FilestoreRequestTlv<'static, 'static> { assert!(!FilestoreRequestTlv::has_second_filename(action_code)); - let first_name = Lv::new_from_str("hello.txt").unwrap(); + let first_name = Lv::new_from_str(TLV_TEST_STR_0).unwrap(); let fs_request = match action_code { FilestoreActionCode::CreateFile => FilestoreRequestTlv::new_create_file(first_name), FilestoreActionCode::DeleteFile => FilestoreRequestTlv::new_delete_file(first_name), @@ -599,12 +605,15 @@ mod tests { assert_eq!(fs_request.action_code(), action_code); assert_eq!(fs_request.first_name(), first_name); assert_eq!(fs_request.second_name(), None); + fs_request } - fn generic_fs_request_test_two_files(action_code: FilestoreActionCode) { + fn generic_fs_request_test_two_files( + action_code: FilestoreActionCode, + ) -> FilestoreRequestTlv<'static, 'static> { assert!(FilestoreRequestTlv::has_second_filename(action_code)); - let first_name = Lv::new_from_str("hello.txt").unwrap(); - let second_name = Lv::new_from_str("hello2.txt").unwrap(); + let first_name = Lv::new_from_str(TLV_TEST_STR_0).unwrap(); + let second_name = Lv::new_from_str(TLV_TEST_STR_1).unwrap(); let fs_request = match action_code { FilestoreActionCode::ReplaceFile => { FilestoreRequestTlv::new_replace_file(first_name, second_name) @@ -628,44 +637,129 @@ mod tests { assert_eq!(fs_request.first_name(), first_name); assert!(fs_request.second_name().is_some()); assert_eq!(fs_request.second_name().unwrap(), second_name); + fs_request } #[test] fn test_fs_request_basic_create_file() { generic_fs_request_test_one_file(FilestoreActionCode::CreateFile); } + #[test] fn test_fs_request_basic_delete() { generic_fs_request_test_one_file(FilestoreActionCode::DeleteFile); } + #[test] fn test_fs_request_basic_create_dir() { generic_fs_request_test_one_file(FilestoreActionCode::CreateDirectory); } + #[test] fn test_fs_request_basic_remove_dir() { generic_fs_request_test_one_file(FilestoreActionCode::RemoveDirectory); } + #[test] fn test_fs_request_basic_deny_file() { generic_fs_request_test_one_file(FilestoreActionCode::DenyFile); } + #[test] fn test_fs_request_basic_deny_dir() { generic_fs_request_test_one_file(FilestoreActionCode::DenyDirectory); } + #[test] fn test_fs_request_basic_append_file() { generic_fs_request_test_two_files(FilestoreActionCode::AppendFile); } + #[test] fn test_fs_request_basic_rename_file() { generic_fs_request_test_two_files(FilestoreActionCode::RenameFile); } + #[test] fn test_fs_request_basic_replace_file() { generic_fs_request_test_two_files(FilestoreActionCode::ReplaceFile); } + + fn check_fs_request_first_part( + buf: &[u8], + action_code: FilestoreActionCode, + expected_val_len: u8, + ) -> usize { + assert_eq!(buf[0], TlvType::FilestoreRequest as u8); + assert_eq!(buf[1], expected_val_len); + assert_eq!((buf[2] >> 4) & 0b1111, action_code as u8); + let lv = Lv::from_bytes(&buf[3..]); + assert!(lv.is_ok()); + let lv = lv.unwrap(); + assert_eq!(lv.value_as_str().unwrap().unwrap(), TLV_TEST_STR_0); + 3 + lv.len_full() + } + #[test] - fn test_fs_request_serialization() {} + fn test_fs_request_serialization_one_file() { + let req = generic_fs_request_test_one_file(FilestoreActionCode::CreateFile); + let mut buf: [u8; 64] = [0; 64]; + let res = req.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, 3 + 1 + TLV_TEST_STR_0.len()); + assert_eq!(written, req.len_full()); + check_fs_request_first_part( + &buf, + FilestoreActionCode::CreateFile, + 1 + 1 + TLV_TEST_STR_0.len() as u8, + ); + } + + #[test] + fn test_fs_request_deserialization_one_file() { + let req = generic_fs_request_test_one_file(FilestoreActionCode::CreateFile); + let mut buf: [u8; 64] = [0; 64]; + let res = req.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let req_conv_back = FilestoreRequestTlv::from_bytes(&buf); + assert!(req_conv_back.is_ok()); + let req_conv_back = req_conv_back.unwrap(); + assert_eq!(req_conv_back, req); + } + + #[test] + fn test_fs_request_serialization_two_files() { + let req = generic_fs_request_test_two_files(FilestoreActionCode::RenameFile); + let mut buf: [u8; 64] = [0; 64]; + let res = req.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, req.len_full()); + assert_eq!( + written, + 3 + 1 + TLV_TEST_STR_0.len() + 1 + TLV_TEST_STR_1.len() + ); + let current_idx = check_fs_request_first_part( + &buf, + FilestoreActionCode::RenameFile, + 1 + 1 + TLV_TEST_STR_0.len() as u8 + 1 + TLV_TEST_STR_1.len() as u8, + ); + let second_lv = Lv::from_bytes(&buf[current_idx..]); + assert!(second_lv.is_ok()); + let second_lv = second_lv.unwrap(); + assert_eq!(second_lv.value_as_str().unwrap().unwrap(), TLV_TEST_STR_1); + assert_eq!(current_idx + second_lv.len_full(), req.len_full()); + } + + #[test] + fn test_fs_request_deserialization_two_files() { + let req = generic_fs_request_test_two_files(FilestoreActionCode::RenameFile); + let mut buf: [u8; 64] = [0; 64]; + req.write_to_bytes(&mut buf).unwrap(); + let req_conv_back = FilestoreRequestTlv::from_bytes(&buf); + assert!(req_conv_back.is_ok()); + let req_conv_back = req_conv_back.unwrap(); + assert_eq!(req_conv_back, req); + } } From ad6495734299739fb653aae0f188074ed0dca0d2 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 2 Jul 2023 17:20:57 +0200 Subject: [PATCH 46/48] some documentation --- src/cfdp/pdu/eof.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cfdp/pdu/eof.rs b/src/cfdp/pdu/eof.rs index 8451533..71f977b 100644 --- a/src/cfdp/pdu/eof.rs +++ b/src/cfdp/pdu/eof.rs @@ -8,6 +8,9 @@ use crate::{ByteConversionError, SizeMissmatch}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +/// Finished PDU abstraction. +/// +/// For more information, refer to CFDP chapter 5.2.2. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct EofPdu { From 188651c4a42c59f985b83adc0947b14e50aaa0fd Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 2 Jul 2023 17:22:07 +0200 Subject: [PATCH 47/48] typos --- src/cfdp/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs index 32480d3..b6c082f 100644 --- a/src/cfdp/mod.rs +++ b/src/cfdp/mod.rs @@ -48,7 +48,7 @@ pub enum CrcFlag { WithCrc = 1, } -/// Always 0 and ignores for File Directive PDUs (CCSDS 727.0-B-5 P.75) +/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75) #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(u8)] @@ -57,7 +57,7 @@ pub enum SegmentMetadataFlag { Present = 1, } -/// Always 0 and ignores for File Directive PDUs (CCSDS 727.0-B-5 P.75) +/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75) #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(u8)] From ae8fb8ee14ef3111903a799d8be68019df6ee9a1 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 2 Jul 2023 17:25:10 +0200 Subject: [PATCH 48/48] core compatibility fix --- src/cfdp/lv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfdp/lv.rs b/src/cfdp/lv.rs index cf6f209..96f932d 100644 --- a/src/cfdp/lv.rs +++ b/src/cfdp/lv.rs @@ -100,7 +100,7 @@ impl<'data> Lv<'data> { /// known to contain a [str], for example being a file name. pub fn value_as_str(&self) -> Option> { self.data?; - Some(std::str::from_utf8(self.data.unwrap())) + Some(core::str::from_utf8(self.data.unwrap())) } /// Writes the LV to a raw buffer. Please note that the first byte will contain the length