From 006bc39ff6a770c4e2ea73cf096f8d488ef22bfd Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 8 Jun 2023 20:50:18 +0200 Subject: [PATCH] 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); }