finished basic EOF unittests
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good

This commit is contained in:
Robin Müller 2023-06-08 20:50:18 +02:00
parent bce16a6018
commit 006bc39ff6
Signed by: muellerr
GPG Key ID: A649FB78196E3849
7 changed files with 261 additions and 39 deletions

View File

@ -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
}
}
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::<u32>() + 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<usize, PduError> {
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<EofPdu, PduError> {
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);
}
}

View File

@ -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<EntityIdTlv>,
}

View File

@ -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,

View File

@ -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<ByteConversionError> for PduError {
}
}
impl From<TlvLvError> 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

View File

@ -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<usize, 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 from_bytes(buf: &[u8]) -> Result<Self, TlvLvError> {
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<Tlv, ByteConversionError> {
Self::len_check(buf)?;
self.entity_id

View File

@ -328,7 +328,7 @@ impl<TYPE: ToBeBytes> UnsignedEnum for GenericEcssEnumWrapper<TYPE> {
(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<usize, ByteConversionError> {
self.field.write_to_be_bytes(buf)
}
}

View File

@ -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<usize, ByteConversionError>;
}
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<usize, ByteConversionError> {
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<TYPE: ToBeBytes> UnsignedEnum for GenericUnsignedByteField<TYPE> {
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<usize, ByteConversionError> {
if buf.len() < self.len() {
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
found: buf.len(),
@ -221,7 +222,7 @@ impl<TYPE: ToBeBytes> UnsignedEnum for GenericUnsignedByteField<TYPE> {
}));
}
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);
}