Lines
90.04 %
Functions
36.51 %
Branches
100 %
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};
/// 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 {
pub fn new(
mut pdu_header: PduHeader,
) -> Result<Self, PduError> {
if directive_code_of_acked_pdu == FileDirectiveType::EofPdu {
pdu_header.pdu_conf.direction = Direction::TowardsSender;
} else if directive_code_of_acked_pdu == FileDirectiveType::FinishedPdu {
pdu_header.pdu_conf.direction = Direction::TowardsReceiver;
} else {
return Err(PduError::InvalidDirectiveType {
found: directive_code_of_acked_pdu as u8,
expected: None,
});
// 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)
pub fn new_for_eof_pdu(
) -> Self {
// Unwrap okay here, [new] can only fail on invalid directive codes.
Self::new(
FileDirectiveType::EofPdu,
)
.unwrap()
pub fn new_for_finished_pdu(
FileDirectiveType::FinishedPdu,
pub fn pdu_header(&self) -> &PduHeader {
&self.pdu_header
pub fn directive_code_of_acked_pdu(&self) -> FileDirectiveType {
self.directive_code_of_acked_pdu
pub fn condition_code(&self) -> ConditionCode {
self.condition_code
pub fn transaction_status(&self) -> TransactionStatus {
self.transaction_status
fn calc_pdu_datafield_len(&self) -> usize {
if self.crc_flag() == CrcFlag::WithCrc {
return 5;
3
pub fn from_bytes(buf: &[u8]) -> Result<AckPdu, PduError> {
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::AckPdu),
})?;
if directive_type != FileDirectiveType::AckPdu {
return Err(PduError::WrongDirectiveType {
found: directive_type,
expected: FileDirectiveType::AckPdu,
current_idx += 1;
let acked_directive_type =
FileDirectiveType::try_from(buf[current_idx] >> 4).map_err(|_| {
if acked_directive_type != FileDirectiveType::EofPdu
&& acked_directive_type != FileDirectiveType::FinishedPdu
{
found: acked_directive_type as u8,
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();
acked_directive_type,
impl CfdpPdu for AckPdu {
fn pdu_header(&self) -> &PduHeader {
fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::AckPdu)
impl WritablePduPacket for AckPdu {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
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::AckPdu as u8;
buf[current_idx] = (self.directive_code_of_acked_pdu as u8) << 4;
if self.directive_code_of_acked_pdu == FileDirectiveType::FinishedPdu {
// 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;
buf[current_idx] = ((self.condition_code as u8) << 4) | (self.transaction_status as u8);
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 crate::cfdp::{
pdu::tests::{common_pdu_conf, verify_raw_header, TEST_DEST_ID, TEST_SEQ_NUM, TEST_SRC_ID},
LargeFileFlag, PduType, TransmissionMode,
use super::*;
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(),
);
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_no_file_data(pdu_conf, 0);
let ack_pdu = AckPdu::new(
ConditionCode::NoError,
TransactionStatus::Active,
.expect("creating ACK PDU failed");
ack_pdu.directive_code_of_acked_pdu(),
FileDirectiveType::FinishedPdu
verify_state(&ack_pdu, CrcFlag::NoCrc, Direction::TowardsReceiver);
fn generic_serialization_test(
) {
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::AckPdu as u8);
assert_eq!((buf[8] >> 4) & 0b1111, FileDirectiveType::FinishedPdu 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);
fn test_serialization_no_error() {
generic_serialization_test(ConditionCode::NoError, TransactionStatus::Active);
fn test_serialization_fs_error() {
generic_serialization_test(ConditionCode::FileSizeError, TransactionStatus::Terminated);
fn test_deserialization() {
let ack_pdu = AckPdu::new_for_finished_pdu(
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);
fn test_with_crc() {
let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal);
assert_eq!(ack_vec.len(), ack_pdu.len_written());
assert_eq!(ack_vec.len(), 12);
fn test_for_eof_pdu() {
let ack_pdu = AckPdu::new_for_eof_pdu(
FileDirectiveType::EofPdu
verify_state(&ack_pdu, CrcFlag::WithCrc, Direction::TowardsSender);
fn test_ack_pdu_serialization() {
generic_serde_test(ack_pdu);