use crate::cfdp::pdu::{ add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, FileDirectiveType, PduError, PduHeader, }; use crate::cfdp::tlv::{EntityIdTlv, WritableTlv}; use crate::cfdp::{ConditionCode, CrcFlag, Direction, LargeFileFlag}; use crate::ByteConversionError; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::{CfdpPdu, WritablePduPacket}; /// 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 { pdu_header: PduHeader, condition_code: ConditionCode, file_checksum: u32, file_size: u64, fault_location: Option, } impl EofPdu { pub fn new_no_error(mut pdu_header: PduHeader, file_checksum: u32, file_size: u64) -> Self { // Force correct direction flag. pdu_header.pdu_conf.direction = Direction::TowardsReceiver; 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 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(); } if self.crc_flag() == CrcFlag::WithCrc { len += 2; } len } 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; } 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 { found: buf[current_idx], expected: Some(FileDirectiveType::EofPdu), } })?; if directive_type != FileDirectiveType::EofPdu { return Err(PduError::WrongDirectiveType { found: directive_type, expected: 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, }) } } impl CfdpPdu for EofPdu { fn pdu_header(&self) -> &PduHeader { &self.pdu_header } fn file_directive_type(&self) -> Option { Some(FileDirectiveType::EofPdu) } } impl WritablePduPacket for EofPdu { fn write_to_bytes(&self, buf: &mut [u8]) -> Result { let expected_len = self.len_written(); if buf.len() < expected_len { return Err(ByteConversionError::ToSliceTooSmall { 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_bytes(buf)?; } if self.crc_flag() == CrcFlag::WithCrc { current_idx = add_pdu_crc(buf, current_idx); } Ok(current_idx) } fn len_written(&self) -> usize { self.pdu_header.header_len() + self.calc_pdu_datafield_len() } } #[cfg(test)] mod tests { use super::*; use crate::cfdp::pdu::tests::{ common_pdu_conf, verify_raw_header, TEST_DEST_ID, TEST_SEQ_NUM, TEST_SRC_ID, }; use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag, PduType, TransmissionMode}; #[cfg(feature = "serde")] use crate::tests::generic_serde_test; fn verify_state(&eof_pdu: &EofPdu, file_flag: LargeFileFlag) { assert_eq!(eof_pdu.file_checksum(), 0x01020304); assert_eq!(eof_pdu.file_size(), 12); assert_eq!(eof_pdu.condition_code(), ConditionCode::NoError); assert_eq!(eof_pdu.crc_flag(), CrcFlag::NoCrc); assert_eq!(eof_pdu.file_flag(), file_flag); assert_eq!(eof_pdu.pdu_type(), PduType::FileDirective); assert_eq!( eof_pdu.file_directive_type(), Some(FileDirectiveType::EofPdu) ); assert_eq!(eof_pdu.transmission_mode(), TransmissionMode::Acknowledged); assert_eq!(eof_pdu.direction(), Direction::TowardsReceiver); assert_eq!(eof_pdu.source_id(), TEST_SRC_ID.into()); assert_eq!(eof_pdu.dest_id(), TEST_DEST_ID.into()); assert_eq!(eof_pdu.transaction_seq_num(), TEST_SEQ_NUM.into()); } #[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.len_written(), pdu_header.header_len() + 2 + 4 + 4); verify_state(&eof_pdu, LargeFileFlag::Normal); } #[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.len_written()); 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 let Err(e) = eof_read_back { panic!("deserialization failed with: {e}") } let eof_read_back = eof_read_back.unwrap(); assert_eq!(eof_read_back, eof_pdu); } #[test] fn test_write_to_vec() { 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 written = eof_pdu.write_to_bytes(&mut buf).unwrap(); let pdu_vec = eof_pdu.to_vec().unwrap(); assert_eq!(buf[0..written], pdu_vec); } #[test] fn test_with_crc() { let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, 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 written = eof_pdu.write_to_bytes(&mut buf).unwrap(); assert_eq!(written, eof_pdu.len_written()); let eof_from_raw = EofPdu::from_bytes(&buf).expect("creating EOF PDU failed"); assert_eq!(eof_from_raw, eof_pdu); buf[written - 1] -= 1; let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16; let error = EofPdu::from_bytes(&buf).unwrap_err(); if let PduError::ChecksumError(e) = error { assert_eq!(e, crc); } else { panic!("expected crc error"); } } #[test] fn test_with_large_file_flag() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large); let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); verify_state(&eof_pdu, LargeFileFlag::Large); assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 8 + 4); } #[test] #[cfg(feature = "serde")] fn test_eof_serde() { 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); generic_serde_test(eof_pdu); } }