//! # Acknowledgement (ACK) PDU packet implementation. use crate::{ cfdp::{ConditionCode, CrcFlag, Direction, TransactionStatus}, ByteConversionError, }; use super::{ add_pdu_crc, generic_length_checks_pdu_deserialization, CfdpPdu, FileDirectiveType, PduError, PduHeader, WritablePduPacket, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Invalid [FileDirectiveType] of the acknowledged PDU error. #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] #[error("invalid directive code of acknowledged PDU")] pub struct InvalidAckedDirectiveCodeError(pub FileDirectiveType); /// ACK PDU abstraction. /// /// For more information, refer to CFDP chapter 5.2.4. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct AckPdu { pdu_header: PduHeader, directive_code_of_acked_pdu: FileDirectiveType, condition_code: ConditionCode, transaction_status: TransactionStatus, } impl AckPdu { /// Constructor. pub fn new( mut pdu_header: PduHeader, directive_code_of_acked_pdu: FileDirectiveType, condition_code: ConditionCode, transaction_status: TransactionStatus, ) -> Result { if directive_code_of_acked_pdu == FileDirectiveType::Eof { pdu_header.pdu_conf.direction = Direction::TowardsSender; } else if directive_code_of_acked_pdu == FileDirectiveType::Finished { pdu_header.pdu_conf.direction = Direction::TowardsReceiver; } else { return Err(InvalidAckedDirectiveCodeError(directive_code_of_acked_pdu)); } // Force correct direction flag. let mut ack_pdu = Self { pdu_header, directive_code_of_acked_pdu, condition_code, transaction_status, }; ack_pdu.pdu_header.pdu_datafield_len = ack_pdu.calc_pdu_datafield_len() as u16; Ok(ack_pdu) } /// Constructor for an ACK PDU acknowledging an EOF PDU. /// /// Relevant for the file receiver. pub fn new_for_eof_pdu( pdu_header: PduHeader, condition_code: ConditionCode, transaction_status: TransactionStatus, ) -> Self { // Unwrap okay here, [new] can only fail on invalid directive codes. Self::new( pdu_header, FileDirectiveType::Eof, condition_code, transaction_status, ) .unwrap() } /// Constructor for an ACK PDU acknowledging a Finished PDU. /// /// Relevant for the file sender. pub fn new_for_finished_pdu( pdu_header: PduHeader, condition_code: ConditionCode, transaction_status: TransactionStatus, ) -> Self { // Unwrap okay here, [new] can only fail on invalid directive codes. Self::new( pdu_header, FileDirectiveType::Finished, condition_code, transaction_status, ) .unwrap() } /// PDU header. #[inline] pub fn pdu_header(&self) -> &PduHeader { &self.pdu_header } /// Directive code of the acknowledged PDU. #[inline] pub fn directive_code_of_acked_pdu(&self) -> FileDirectiveType { self.directive_code_of_acked_pdu } /// Condition code. #[inline] pub fn condition_code(&self) -> ConditionCode { self.condition_code } /// Transaction status. #[inline] pub fn transaction_status(&self) -> TransactionStatus { self.transaction_status } #[inline] fn calc_pdu_datafield_len(&self) -> usize { if self.crc_flag() == CrcFlag::WithCrc { return 5; } 3 } /// Construct [Self] from the provided byte slice. 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)?; generic_length_checks_pdu_deserialization(buf, current_idx + 3, full_len_without_crc)?; let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { PduError::InvalidDirectiveType { found: buf[current_idx], expected: Some(FileDirectiveType::Ack), } })?; if directive_type != FileDirectiveType::Ack { return Err(PduError::WrongDirectiveType { found: directive_type, expected: FileDirectiveType::Ack, }); } current_idx += 1; let acked_directive_type = FileDirectiveType::try_from(buf[current_idx] >> 4).map_err(|_| { PduError::InvalidDirectiveType { found: buf[current_idx], expected: None, } })?; if acked_directive_type != FileDirectiveType::Eof && acked_directive_type != FileDirectiveType::Finished { return Err(PduError::InvalidDirectiveType { found: acked_directive_type as u8, expected: None, }); } current_idx += 1; let condition_code = ConditionCode::try_from((buf[current_idx] >> 4) & 0b1111) .map_err(|_| PduError::InvalidConditionCode((buf[current_idx] >> 4) & 0b1111))?; let transaction_status = TransactionStatus::try_from(buf[current_idx] & 0b11).unwrap(); // Unwrap okay, validity of acked directive code was checked. Ok(Self::new( pdu_header, acked_directive_type, condition_code, transaction_status, ) .unwrap()) } /// Write [Self] to the provided buffer and returns the written size. pub 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::Ack as u8; current_idx += 1; buf[current_idx] = (self.directive_code_of_acked_pdu as u8) << 4; if self.directive_code_of_acked_pdu == FileDirectiveType::Finished { // This is the directive subtype code. It needs to be set to 0b0001 if the ACK PDU // acknowledges a Finished PDU, and to 0b0000 otherwise. buf[current_idx] |= 0b0001; } current_idx += 1; buf[current_idx] = ((self.condition_code as u8) << 4) | (self.transaction_status as u8); current_idx += 1; if self.crc_flag() == CrcFlag::WithCrc { current_idx = add_pdu_crc(buf, current_idx); } Ok(current_idx) } /// Length of the written PDU in bytes. pub fn len_written(&self) -> usize { self.pdu_header.header_len() + self.calc_pdu_datafield_len() } } impl CfdpPdu for AckPdu { #[inline] fn pdu_header(&self) -> &PduHeader { self.pdu_header() } #[inline] fn file_directive_type(&self) -> Option { Some(FileDirectiveType::Ack) } } impl WritablePduPacket for AckPdu { fn write_to_bytes(&self, buf: &mut [u8]) -> Result { self.write_to_bytes(buf) } fn len_written(&self) -> usize { self.len_written() } } #[cfg(test)] mod tests { use crate::cfdp::{ pdu::tests::{common_pdu_conf, verify_raw_header, TEST_DEST_ID, TEST_SEQ_NUM, TEST_SRC_ID}, LargeFileFlag, PduType, TransmissionMode, }; use super::*; #[cfg(feature = "serde")] use crate::tests::generic_serde_test; fn verify_state(ack_pdu: &AckPdu, expected_crc_flag: CrcFlag, expected_dir: Direction) { assert_eq!(ack_pdu.condition_code(), ConditionCode::NoError); assert_eq!(ack_pdu.transaction_status(), TransactionStatus::Active); assert_eq!(ack_pdu.crc_flag(), expected_crc_flag); assert_eq!(ack_pdu.file_flag(), LargeFileFlag::Normal); assert_eq!(ack_pdu.pdu_type(), PduType::FileDirective); assert_eq!(ack_pdu.file_directive_type(), Some(FileDirectiveType::Ack)); assert_eq!(ack_pdu.transmission_mode(), TransmissionMode::Acknowledged); assert_eq!(ack_pdu.direction(), expected_dir); assert_eq!(ack_pdu.source_id(), TEST_SRC_ID.into()); assert_eq!(ack_pdu.dest_id(), TEST_DEST_ID.into()); assert_eq!(ack_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_for_file_directive(pdu_conf, 0); let ack_pdu = AckPdu::new( pdu_header, FileDirectiveType::Finished, ConditionCode::NoError, TransactionStatus::Active, ) .expect("creating ACK PDU failed"); assert_eq!( ack_pdu.directive_code_of_acked_pdu(), FileDirectiveType::Finished ); verify_state(&ack_pdu, CrcFlag::NoCrc, Direction::TowardsReceiver); } fn generic_serialization_test( condition_code: ConditionCode, transaction_status: TransactionStatus, ) { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let ack_pdu = AckPdu::new_for_finished_pdu(pdu_header, condition_code, transaction_status); let mut buf: [u8; 64] = [0; 64]; let res = ack_pdu.write_to_bytes(&mut buf); assert!(res.is_ok()); let written = res.unwrap(); assert_eq!(written, ack_pdu.len_written()); verify_raw_header(ack_pdu.pdu_header(), &buf); assert_eq!(buf[7], FileDirectiveType::Ack as u8); assert_eq!((buf[8] >> 4) & 0b1111, FileDirectiveType::Finished as u8); assert_eq!(buf[8] & 0b1111, 0b0001); assert_eq!(buf[9] >> 4 & 0b1111, condition_code as u8); assert_eq!(buf[9] & 0b11, transaction_status as u8); assert_eq!(written, 10); } #[test] fn test_serialization_no_error() { generic_serialization_test(ConditionCode::NoError, TransactionStatus::Active); } #[test] fn test_serialization_too_small() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let ack_pdu = AckPdu::new( pdu_header, FileDirectiveType::Finished, ConditionCode::NoError, TransactionStatus::Active, ) .expect("creating ACK PDU failed"); if let Err(PduError::ByteConversion(ByteConversionError::ToSliceTooSmall { found, expected, })) = ack_pdu.write_to_bytes(&mut [0; 5]) { assert_eq!(found, 5); assert_eq!(expected, ack_pdu.len_written()); } else { panic!("serialization should have failed"); } } #[test] fn test_serialization_fs_error() { generic_serialization_test(ConditionCode::FileSizeError, TransactionStatus::Terminated); } #[test] fn test_invalid_directive_code_of_acked_pdu() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); assert_eq!( AckPdu::new( pdu_header, FileDirectiveType::Metadata, ConditionCode::NoError, TransactionStatus::Active, ) .unwrap_err(), InvalidAckedDirectiveCodeError(FileDirectiveType::Metadata) ); } #[test] fn test_deserialization() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let ack_pdu = AckPdu::new_for_finished_pdu( pdu_header, ConditionCode::NoError, TransactionStatus::Active, ); let ack_vec = ack_pdu.to_vec().unwrap(); let ack_deserialized = AckPdu::from_bytes(&ack_vec).expect("ACK PDU deserialization failed"); assert_eq!(ack_deserialized, ack_pdu); } #[test] fn test_with_crc() { let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let ack_pdu = AckPdu::new_for_finished_pdu( pdu_header, ConditionCode::NoError, TransactionStatus::Active, ); let ack_vec = ack_pdu.to_vec().unwrap(); assert_eq!(ack_vec.len(), ack_pdu.len_written()); assert_eq!(ack_vec.len(), 12); let ack_deserialized = AckPdu::from_bytes(&ack_vec).expect("ACK PDU deserialization failed"); assert_eq!(ack_deserialized, ack_pdu); } #[test] fn test_for_eof_pdu() { let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let ack_pdu = AckPdu::new_for_eof_pdu( pdu_header, ConditionCode::NoError, TransactionStatus::Active, ); assert_eq!( ack_pdu.directive_code_of_acked_pdu(), FileDirectiveType::Eof ); verify_state(&ack_pdu, CrcFlag::WithCrc, Direction::TowardsSender); } #[test] #[cfg(feature = "serde")] fn test_ack_pdu_serialization() { let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let ack_pdu = AckPdu::new_for_eof_pdu( pdu_header, ConditionCode::NoError, TransactionStatus::Active, ); generic_serde_test(ack_pdu); } }