Add ACK and NAK PDU abstractions #41
12
README.md
12
README.md
@ -47,3 +47,15 @@ deserializing them with an appropriate `serde` provider like
|
|||||||
|
|
||||||
You can check the [documentation](https://docs.rs/spacepackets) of individual modules for various
|
You can check the [documentation](https://docs.rs/spacepackets) of individual modules for various
|
||||||
usage examples.
|
usage examples.
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
|
||||||
|
Coverage was generated using [`grcov`](https://github.com/mozilla/grcov). If you have not done so
|
||||||
|
already, install the `llvm-tools-preview`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rustup component add llvm-tools-preview
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, you can simply run `coverage.py` to test the project with coverage. You can optionally
|
||||||
|
supply the `--open` flag to open the coverage report in your webbrowser.
|
||||||
|
28
coverage.py
Executable file
28
coverage.py
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cov_report(open_report: bool):
|
||||||
|
os.environ["RUSTFLAGS"] = "-Cinstrument-coverage"
|
||||||
|
os.environ["LLVM_PROFILE_FILE"] = "target/coverage/%p-%m.profraw"
|
||||||
|
os.system("cargo test")
|
||||||
|
os.system(
|
||||||
|
"grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing "
|
||||||
|
"-o ./target/debug/coverage/"
|
||||||
|
)
|
||||||
|
if open_report:
|
||||||
|
coverage_report_path = os.path.abspath("./target/debug/coverage/index.html")
|
||||||
|
webbrowser.open_new_tab(coverage_report_path)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Generate coverage report and optionally open it in a browser")
|
||||||
|
parser.add_argument("--open", action="store_true", help="Open the coverage report in a browser")
|
||||||
|
args = parser.parse_args()
|
||||||
|
generate_cov_report(args.open)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -48,6 +48,24 @@ pub enum CrcFlag {
|
|||||||
WithCrc = 1,
|
WithCrc = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<bool> for CrcFlag {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
if value {
|
||||||
|
return CrcFlag::WithCrc;
|
||||||
|
}
|
||||||
|
CrcFlag::NoCrc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CrcFlag> for bool {
|
||||||
|
fn from(value: CrcFlag) -> Self {
|
||||||
|
if value == CrcFlag::WithCrc {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75)
|
/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75)
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
@ -119,6 +137,22 @@ pub enum LargeFileFlag {
|
|||||||
Large = 1,
|
Large = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transaction status for the ACK PDU field according to chapter 5.2.4 of the CFDP standard.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum TransactionStatus {
|
||||||
|
/// Transaction is not currently active and the CFDP implementation does not retain a
|
||||||
|
/// transaction history.
|
||||||
|
Undefined = 0b00,
|
||||||
|
Active = 0b01,
|
||||||
|
/// Transaction was active in the past and was terminated.
|
||||||
|
Terminated = 0b10,
|
||||||
|
/// The CFDP implementation does retain a tranaction history, and the transaction is not and
|
||||||
|
/// never was active at this entity.
|
||||||
|
Unrecognized = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
/// Checksum types according to the
|
/// Checksum types according to the
|
||||||
/// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/)
|
/// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/)
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
||||||
|
299
src/cfdp/pdu/ack.rs
Normal file
299
src/cfdp/pdu/ack.rs
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
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))]
|
||||||
|
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,
|
||||||
|
directive_code_of_acked_pdu: FileDirectiveType,
|
||||||
|
condition_code: ConditionCode,
|
||||||
|
transaction_status: TransactionStatus,
|
||||||
|
) -> 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(
|
||||||
|
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::EofPdu,
|
||||||
|
condition_code,
|
||||||
|
transaction_status,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
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::FinishedPdu,
|
||||||
|
condition_code,
|
||||||
|
transaction_status,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
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(|_| {
|
||||||
|
PduError::InvalidDirectiveType {
|
||||||
|
found: buf[current_idx],
|
||||||
|
expected: None,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
if acked_directive_type != FileDirectiveType::EofPdu
|
||||||
|
&& acked_directive_type != FileDirectiveType::FinishedPdu
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
Self::new(
|
||||||
|
pdu_header,
|
||||||
|
acked_directive_type,
|
||||||
|
condition_code,
|
||||||
|
transaction_status,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CfdpPdu for AckPdu {
|
||||||
|
fn pdu_header(&self) -> &PduHeader {
|
||||||
|
&self.pdu_header
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
current_idx += 1;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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::*;
|
||||||
|
|
||||||
|
#[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(
|
||||||
|
pdu_header,
|
||||||
|
FileDirectiveType::FinishedPdu,
|
||||||
|
ConditionCode::NoError,
|
||||||
|
TransactionStatus::Active,
|
||||||
|
)
|
||||||
|
.expect("creating ACK PDU failed");
|
||||||
|
assert_eq!(
|
||||||
|
ack_pdu.directive_code_of_acked_pdu(),
|
||||||
|
FileDirectiveType::FinishedPdu
|
||||||
|
);
|
||||||
|
assert_eq!(ack_pdu.condition_code(), ConditionCode::NoError);
|
||||||
|
assert_eq!(ack_pdu.transaction_status(), TransactionStatus::Active);
|
||||||
|
|
||||||
|
assert_eq!(ack_pdu.crc_flag(), CrcFlag::NoCrc);
|
||||||
|
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::AckPdu)
|
||||||
|
);
|
||||||
|
assert_eq!(ack_pdu.transmission_mode(), TransmissionMode::Acknowledged);
|
||||||
|
assert_eq!(ack_pdu.direction(), Direction::TowardsReceiver);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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_no_file_data(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::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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization_no_error() {
|
||||||
|
generic_serialization_test(ConditionCode::NoError, TransactionStatus::Active);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization_fs_error() {
|
||||||
|
generic_serialization_test(ConditionCode::FileSizeError, TransactionStatus::Terminated);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 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_no_file_data(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);
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,9 @@ impl EofPdu {
|
|||||||
if let Some(fault_location) = self.fault_location {
|
if let Some(fault_location) = self.fault_location {
|
||||||
len += fault_location.len_full();
|
len += fault_location.len_full();
|
||||||
}
|
}
|
||||||
|
if self.crc_flag() == CrcFlag::WithCrc {
|
||||||
|
len += 2;
|
||||||
|
}
|
||||||
len
|
len
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +149,7 @@ impl WritablePduPacket for EofPdu {
|
|||||||
if let Some(fault_location) = self.fault_location {
|
if let Some(fault_location) = self.fault_location {
|
||||||
current_idx += fault_location.write_to_be_bytes(buf)?;
|
current_idx += fault_location.write_to_be_bytes(buf)?;
|
||||||
}
|
}
|
||||||
if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc {
|
if self.crc_flag() == CrcFlag::WithCrc {
|
||||||
current_idx = add_pdu_crc(buf, current_idx);
|
current_idx = add_pdu_crc(buf, current_idx);
|
||||||
}
|
}
|
||||||
Ok(current_idx)
|
Ok(current_idx)
|
||||||
@ -160,9 +163,11 @@ impl WritablePduPacket for EofPdu {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::cfdp::pdu::tests::{common_pdu_conf, verify_raw_header};
|
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::pdu::{FileDirectiveType, PduHeader};
|
||||||
use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag};
|
use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag, PduType, TransmissionMode};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic() {
|
fn test_basic() {
|
||||||
@ -173,6 +178,19 @@ mod tests {
|
|||||||
assert_eq!(eof_pdu.file_checksum(), 0x01020304);
|
assert_eq!(eof_pdu.file_checksum(), 0x01020304);
|
||||||
assert_eq!(eof_pdu.file_size(), 12);
|
assert_eq!(eof_pdu.file_size(), 12);
|
||||||
assert_eq!(eof_pdu.condition_code(), ConditionCode::NoError);
|
assert_eq!(eof_pdu.condition_code(), ConditionCode::NoError);
|
||||||
|
|
||||||
|
assert_eq!(eof_pdu.crc_flag(), CrcFlag::NoCrc);
|
||||||
|
assert_eq!(eof_pdu.file_flag(), LargeFileFlag::Normal);
|
||||||
|
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]
|
#[test]
|
||||||
@ -215,8 +233,7 @@ mod tests {
|
|||||||
let mut buf: [u8; 64] = [0; 64];
|
let mut buf: [u8; 64] = [0; 64];
|
||||||
eof_pdu.write_to_bytes(&mut buf).unwrap();
|
eof_pdu.write_to_bytes(&mut buf).unwrap();
|
||||||
let eof_read_back = EofPdu::from_bytes(&buf);
|
let eof_read_back = EofPdu::from_bytes(&buf);
|
||||||
if eof_read_back.is_err() {
|
if let Err(e) = eof_read_back {
|
||||||
let e = eof_read_back.unwrap_err();
|
|
||||||
panic!("deserialization failed with: {e}")
|
panic!("deserialization failed with: {e}")
|
||||||
}
|
}
|
||||||
let eof_read_back = eof_read_back.unwrap();
|
let eof_read_back = eof_read_back.unwrap();
|
||||||
@ -233,4 +250,24 @@ mod tests {
|
|||||||
let pdu_vec = eof_pdu.to_vec().unwrap();
|
let pdu_vec = eof_pdu.to_vec().unwrap();
|
||||||
assert_eq!(buf[0..written], pdu_vec);
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> {
|
|||||||
len += self.segment_metadata.as_ref().unwrap().written_len()
|
len += self.segment_metadata.as_ref().unwrap().written_len()
|
||||||
}
|
}
|
||||||
len += self.file_data.len();
|
len += self.file_data.len();
|
||||||
if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc {
|
if self.crc_flag() == CrcFlag::WithCrc {
|
||||||
len += 2;
|
len += 2;
|
||||||
}
|
}
|
||||||
len
|
len
|
||||||
@ -225,7 +225,7 @@ impl WritablePduPacket for FileDataPdu<'_, '_> {
|
|||||||
)?;
|
)?;
|
||||||
buf[current_idx..current_idx + self.file_data.len()].copy_from_slice(self.file_data);
|
buf[current_idx..current_idx + self.file_data.len()].copy_from_slice(self.file_data);
|
||||||
current_idx += self.file_data.len();
|
current_idx += self.file_data.len();
|
||||||
if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc {
|
if self.crc_flag() == CrcFlag::WithCrc {
|
||||||
current_idx = add_pdu_crc(buf, current_idx);
|
current_idx = add_pdu_crc(buf, current_idx);
|
||||||
}
|
}
|
||||||
Ok(current_idx)
|
Ok(current_idx)
|
||||||
@ -239,17 +239,14 @@ impl WritablePduPacket for FileDataPdu<'_, '_> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::cfdp::pdu::tests::{TEST_DEST_ID, TEST_SEQ_NUM, TEST_SRC_ID};
|
||||||
use crate::cfdp::pdu::{CommonPduConfig, PduHeader};
|
use crate::cfdp::pdu::{CommonPduConfig, PduHeader};
|
||||||
use crate::cfdp::{SegmentMetadataFlag, SegmentationControl};
|
use crate::cfdp::{Direction, SegmentMetadataFlag, SegmentationControl, TransmissionMode};
|
||||||
use crate::util::UbfU8;
|
|
||||||
|
|
||||||
const SRC_ID: UbfU8 = UbfU8::new(1);
|
|
||||||
const DEST_ID: UbfU8 = UbfU8::new(2);
|
|
||||||
const SEQ_NUM: UbfU8 = UbfU8::new(3);
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic() {
|
fn test_basic() {
|
||||||
let common_conf = CommonPduConfig::new_with_byte_fields(SRC_ID, DEST_ID, SEQ_NUM).unwrap();
|
let common_conf =
|
||||||
|
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
|
||||||
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
|
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
|
||||||
let file_data: [u8; 4] = [1, 2, 3, 4];
|
let file_data: [u8; 4] = [1, 2, 3, 4];
|
||||||
let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data);
|
let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data);
|
||||||
@ -260,11 +257,22 @@ mod tests {
|
|||||||
fd_pdu.len_written(),
|
fd_pdu.len_written(),
|
||||||
fd_pdu.pdu_header.header_len() + core::mem::size_of::<u32>() + 4
|
fd_pdu.pdu_header.header_len() + core::mem::size_of::<u32>() + 4
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert_eq!(fd_pdu.crc_flag(), CrcFlag::NoCrc);
|
||||||
|
assert_eq!(fd_pdu.file_flag(), LargeFileFlag::Normal);
|
||||||
|
assert_eq!(fd_pdu.pdu_type(), PduType::FileData);
|
||||||
|
assert_eq!(fd_pdu.file_directive_type(), None);
|
||||||
|
assert_eq!(fd_pdu.transmission_mode(), TransmissionMode::Acknowledged);
|
||||||
|
assert_eq!(fd_pdu.direction(), Direction::TowardsReceiver);
|
||||||
|
assert_eq!(fd_pdu.source_id(), TEST_SRC_ID.into());
|
||||||
|
assert_eq!(fd_pdu.dest_id(), TEST_DEST_ID.into());
|
||||||
|
assert_eq!(fd_pdu.transaction_seq_num(), TEST_SEQ_NUM.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialization() {
|
fn test_serialization() {
|
||||||
let common_conf = CommonPduConfig::new_with_byte_fields(SRC_ID, DEST_ID, SEQ_NUM).unwrap();
|
let common_conf =
|
||||||
|
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
|
||||||
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
|
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
|
||||||
let file_data: [u8; 4] = [1, 2, 3, 4];
|
let file_data: [u8; 4] = [1, 2, 3, 4];
|
||||||
let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data);
|
let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data);
|
||||||
@ -295,7 +303,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_write_to_vec() {
|
fn test_write_to_vec() {
|
||||||
let common_conf = CommonPduConfig::new_with_byte_fields(SRC_ID, DEST_ID, SEQ_NUM).unwrap();
|
let common_conf =
|
||||||
|
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
|
||||||
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
|
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
|
||||||
let file_data: [u8; 4] = [1, 2, 3, 4];
|
let file_data: [u8; 4] = [1, 2, 3, 4];
|
||||||
let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data);
|
let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data);
|
||||||
@ -307,7 +316,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialization() {
|
fn test_deserialization() {
|
||||||
let common_conf = CommonPduConfig::new_with_byte_fields(SRC_ID, DEST_ID, SEQ_NUM).unwrap();
|
let common_conf =
|
||||||
|
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
|
||||||
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
|
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
|
||||||
let file_data: [u8; 4] = [1, 2, 3, 4];
|
let file_data: [u8; 4] = [1, 2, 3, 4];
|
||||||
let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data);
|
let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data);
|
||||||
@ -319,13 +329,33 @@ mod tests {
|
|||||||
assert_eq!(fd_pdu_read_back, fd_pdu);
|
assert_eq!(fd_pdu_read_back, fd_pdu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_crc() {
|
||||||
|
let mut common_conf =
|
||||||
|
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
|
||||||
|
common_conf.crc_flag = true.into();
|
||||||
|
let pdu_header = PduHeader::new_for_file_data_default(common_conf, 0);
|
||||||
|
let file_data: [u8; 4] = [1, 2, 3, 4];
|
||||||
|
let fd_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 10, &file_data);
|
||||||
|
let mut buf: [u8; 64] = [0; 64];
|
||||||
|
let written = fd_pdu.write_to_bytes(&mut buf).unwrap();
|
||||||
|
assert_eq!(written, fd_pdu.len_written());
|
||||||
|
let finished_pdu_from_raw = FileDataPdu::from_bytes(&buf).unwrap();
|
||||||
|
assert_eq!(finished_pdu_from_raw, fd_pdu);
|
||||||
|
buf[written - 1] -= 1;
|
||||||
|
let crc: u16 = ((buf[written - 2] as u16) << 8) | buf[written - 1] as u16;
|
||||||
|
let error = FileDataPdu::from_bytes(&buf).unwrap_err();
|
||||||
|
if let PduError::ChecksumError(e) = error {
|
||||||
|
assert_eq!(e, crc);
|
||||||
|
} else {
|
||||||
|
panic!("expected crc error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_with_seg_metadata_serialization() {
|
fn test_with_seg_metadata_serialization() {
|
||||||
let src_id = UbfU8::new(1);
|
|
||||||
let dest_id = UbfU8::new(2);
|
|
||||||
let transaction_seq_num = UbfU8::new(3);
|
|
||||||
let common_conf =
|
let common_conf =
|
||||||
CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_seq_num).unwrap();
|
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
|
||||||
let pdu_header = PduHeader::new_for_file_data(
|
let pdu_header = PduHeader::new_for_file_data(
|
||||||
common_conf,
|
common_conf,
|
||||||
0,
|
0,
|
||||||
@ -386,11 +416,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_with_seg_metadata_deserialization() {
|
fn test_with_seg_metadata_deserialization() {
|
||||||
let src_id = UbfU8::new(1);
|
|
||||||
let dest_id = UbfU8::new(2);
|
|
||||||
let transaction_seq_num = UbfU8::new(3);
|
|
||||||
let common_conf =
|
let common_conf =
|
||||||
CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_seq_num).unwrap();
|
CommonPduConfig::new_with_byte_fields(TEST_SRC_ID, TEST_DEST_ID, TEST_SEQ_NUM).unwrap();
|
||||||
let pdu_header = PduHeader::new_for_file_data(
|
let pdu_header = PduHeader::new_for_file_data(
|
||||||
common_conf,
|
common_conf,
|
||||||
0,
|
0,
|
||||||
|
@ -120,14 +120,17 @@ impl<'fs_responses> FinishedPdu<'fs_responses> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn calc_pdu_datafield_len(&self) -> usize {
|
fn calc_pdu_datafield_len(&self) -> usize {
|
||||||
let mut base_len = 2;
|
let mut datafield_len = 2;
|
||||||
if let Some(fs_responses) = self.fs_responses {
|
if let Some(fs_responses) = self.fs_responses {
|
||||||
base_len += fs_responses.len();
|
datafield_len += fs_responses.len();
|
||||||
}
|
}
|
||||||
if let Some(fault_location) = self.fault_location {
|
if let Some(fault_location) = self.fault_location {
|
||||||
base_len += fault_location.len_full();
|
datafield_len += fault_location.len_full();
|
||||||
}
|
}
|
||||||
base_len
|
if self.crc_flag() == CrcFlag::WithCrc {
|
||||||
|
datafield_len += 2;
|
||||||
|
}
|
||||||
|
datafield_len
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates [Self] from a raw bytestream.
|
/// Generates [Self] from a raw bytestream.
|
||||||
@ -246,7 +249,7 @@ impl WritablePduPacket for FinishedPdu<'_> {
|
|||||||
if let Some(fault_location) = self.fault_location {
|
if let Some(fault_location) = self.fault_location {
|
||||||
current_idx += fault_location.write_to_be_bytes(&mut buf[current_idx..])?;
|
current_idx += fault_location.write_to_be_bytes(&mut buf[current_idx..])?;
|
||||||
}
|
}
|
||||||
if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc {
|
if self.crc_flag() == CrcFlag::WithCrc {
|
||||||
current_idx = add_pdu_crc(buf, current_idx);
|
current_idx = add_pdu_crc(buf, current_idx);
|
||||||
}
|
}
|
||||||
Ok(current_idx)
|
Ok(current_idx)
|
||||||
@ -260,9 +263,11 @@ impl WritablePduPacket for FinishedPdu<'_> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::cfdp::pdu::tests::{common_pdu_conf, verify_raw_header};
|
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::pdu::{FileDirectiveType, PduHeader};
|
||||||
use crate::cfdp::{ConditionCode, CrcFlag, Direction, LargeFileFlag};
|
use crate::cfdp::{ConditionCode, CrcFlag, Direction, LargeFileFlag, TransmissionMode};
|
||||||
|
|
||||||
fn generic_finished_pdu(
|
fn generic_finished_pdu(
|
||||||
crc_flag: CrcFlag,
|
crc_flag: CrcFlag,
|
||||||
@ -292,6 +297,22 @@ mod tests {
|
|||||||
assert_eq!(finished_pdu.filestore_responses(), None);
|
assert_eq!(finished_pdu.filestore_responses(), None);
|
||||||
assert_eq!(finished_pdu.fault_location(), None);
|
assert_eq!(finished_pdu.fault_location(), None);
|
||||||
assert_eq!(finished_pdu.pdu_header().pdu_datafield_len, 2);
|
assert_eq!(finished_pdu.pdu_header().pdu_datafield_len, 2);
|
||||||
|
|
||||||
|
assert_eq!(finished_pdu.crc_flag(), CrcFlag::NoCrc);
|
||||||
|
assert_eq!(finished_pdu.file_flag(), LargeFileFlag::Normal);
|
||||||
|
assert_eq!(finished_pdu.pdu_type(), PduType::FileDirective);
|
||||||
|
assert_eq!(
|
||||||
|
finished_pdu.file_directive_type(),
|
||||||
|
Some(FileDirectiveType::FinishedPdu)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
finished_pdu.transmission_mode(),
|
||||||
|
TransmissionMode::Acknowledged
|
||||||
|
);
|
||||||
|
assert_eq!(finished_pdu.direction(), Direction::TowardsSender);
|
||||||
|
assert_eq!(finished_pdu.source_id(), TEST_SRC_ID.into());
|
||||||
|
assert_eq!(finished_pdu.dest_id(), TEST_DEST_ID.into());
|
||||||
|
assert_eq!(finished_pdu.transaction_seq_num(), TEST_SEQ_NUM.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generic_serialization_test_no_error(delivery_code: DeliveryCode, file_status: FileStatus) {
|
fn generic_serialization_test_no_error(delivery_code: DeliveryCode, file_status: FileStatus) {
|
||||||
@ -371,4 +392,27 @@ mod tests {
|
|||||||
let read_back = read_back.unwrap();
|
let read_back = read_back.unwrap();
|
||||||
assert_eq!(finished_pdu, read_back);
|
assert_eq!(finished_pdu, read_back);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_crc() {
|
||||||
|
let finished_pdu = generic_finished_pdu(
|
||||||
|
CrcFlag::WithCrc,
|
||||||
|
LargeFileFlag::Normal,
|
||||||
|
DeliveryCode::Complete,
|
||||||
|
FileStatus::Retained,
|
||||||
|
);
|
||||||
|
let mut buf: [u8; 64] = [0; 64];
|
||||||
|
let written = finished_pdu.write_to_bytes(&mut buf).unwrap();
|
||||||
|
assert_eq!(written, finished_pdu.len_written());
|
||||||
|
let finished_pdu_from_raw = FinishedPdu::from_bytes(&buf).unwrap();
|
||||||
|
assert_eq!(finished_pdu_from_raw, finished_pdu);
|
||||||
|
buf[written - 1] -= 1;
|
||||||
|
let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16;
|
||||||
|
let error = FinishedPdu::from_bytes(&buf).unwrap_err();
|
||||||
|
if let PduError::ChecksumError(e) = error {
|
||||||
|
assert_eq!(e, crc);
|
||||||
|
} else {
|
||||||
|
panic!("expected crc error");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> {
|
|||||||
if let Some(opts) = self.options {
|
if let Some(opts) = self.options {
|
||||||
len += opts.len();
|
len += opts.len();
|
||||||
}
|
}
|
||||||
if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc {
|
if self.crc_flag() == CrcFlag::WithCrc {
|
||||||
len += 2;
|
len += 2;
|
||||||
}
|
}
|
||||||
len
|
len
|
||||||
@ -288,7 +288,7 @@ impl WritablePduPacket for MetadataPdu<'_, '_, '_> {
|
|||||||
buf[current_idx..current_idx + opts.len()].copy_from_slice(opts);
|
buf[current_idx..current_idx + opts.len()].copy_from_slice(opts);
|
||||||
current_idx += opts.len();
|
current_idx += opts.len();
|
||||||
}
|
}
|
||||||
if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc {
|
if self.crc_flag() == CrcFlag::WithCrc {
|
||||||
current_idx = add_pdu_crc(buf, current_idx);
|
current_idx = add_pdu_crc(buf, current_idx);
|
||||||
}
|
}
|
||||||
Ok(current_idx)
|
Ok(current_idx)
|
||||||
@ -453,6 +453,7 @@ pub mod tests {
|
|||||||
+ dest_filename.len_full()
|
+ dest_filename.len_full()
|
||||||
+ 2
|
+ 2
|
||||||
);
|
);
|
||||||
|
assert_eq!(written, metadata_pdu.len_written());
|
||||||
let pdu_read_back = MetadataPdu::from_bytes(&buf).unwrap();
|
let pdu_read_back = MetadataPdu::from_bytes(&buf).unwrap();
|
||||||
assert_eq!(pdu_read_back, metadata_pdu);
|
assert_eq!(pdu_read_back, metadata_pdu);
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,12 @@ use core::fmt::{Display, Formatter};
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
pub mod ack;
|
||||||
pub mod eof;
|
pub mod eof;
|
||||||
pub mod file_data;
|
pub mod file_data;
|
||||||
pub mod finished;
|
pub mod finished;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
|
pub mod nak;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
@ -41,15 +43,20 @@ pub enum PduError {
|
|||||||
src_id_len: usize,
|
src_id_len: usize,
|
||||||
dest_id_len: usize,
|
dest_id_len: usize,
|
||||||
},
|
},
|
||||||
|
/// Wrong directive type, for example when parsing the directive field for a file directive
|
||||||
|
/// PDU.
|
||||||
WrongDirectiveType {
|
WrongDirectiveType {
|
||||||
found: FileDirectiveType,
|
found: FileDirectiveType,
|
||||||
expected: FileDirectiveType,
|
expected: FileDirectiveType,
|
||||||
},
|
},
|
||||||
/// The directive type field contained a value not in the range of permitted values.
|
/// The directive type field contained a value not in the range of permitted values. This can
|
||||||
|
/// also happen if an invalid value is passed to the ACK PDU constructor.
|
||||||
InvalidDirectiveType {
|
InvalidDirectiveType {
|
||||||
found: u8,
|
found: u8,
|
||||||
expected: Option<FileDirectiveType>,
|
expected: Option<FileDirectiveType>,
|
||||||
},
|
},
|
||||||
|
InvalidSegmentRequestFormat,
|
||||||
|
InvalidStartOrEndOfScopeValue,
|
||||||
/// Invalid condition code. Contains the raw detected value.
|
/// Invalid condition code. Contains the raw detected value.
|
||||||
InvalidConditionCode(u8),
|
InvalidConditionCode(u8),
|
||||||
/// Invalid checksum type which is not part of the checksums listed in the
|
/// Invalid checksum type which is not part of the checksums listed in the
|
||||||
@ -70,9 +77,15 @@ impl Display for PduError {
|
|||||||
PduError::InvalidEntityLen(raw_id) => {
|
PduError::InvalidEntityLen(raw_id) => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Invalid PDU entity ID length {raw_id}, only [1, 2, 4, 8] are allowed"
|
"invalid PDU entity ID length {raw_id}, only [1, 2, 4, 8] are allowed"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
PduError::InvalidSegmentRequestFormat => {
|
||||||
|
write!(f, "invalid segment request format for NAK PDU")
|
||||||
|
}
|
||||||
|
PduError::InvalidStartOrEndOfScopeValue => {
|
||||||
|
write!(f, "invalid start or end of scope for NAK PDU")
|
||||||
|
}
|
||||||
PduError::InvalidTransactionSeqNumLen(raw_id) => {
|
PduError::InvalidTransactionSeqNumLen(raw_id) => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
|
793
src/cfdp/pdu/nak.rs
Normal file
793
src/cfdp/pdu/nak.rs
Normal file
@ -0,0 +1,793 @@
|
|||||||
|
use crate::{
|
||||||
|
cfdp::{CrcFlag, Direction, LargeFileFlag},
|
||||||
|
ByteConversionError,
|
||||||
|
};
|
||||||
|
use core::{marker::PhantomData, mem::size_of};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
add_pdu_crc, generic_length_checks_pdu_deserialization, CfdpPdu, FileDirectiveType, PduError,
|
||||||
|
PduHeader, WritablePduPacket,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Helper type to encapsulate both normal file size segment requests and large file size segment
|
||||||
|
/// requests.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum SegmentRequests<'a> {
|
||||||
|
U32Pairs(&'a [(u32, u32)]),
|
||||||
|
U64Pairs(&'a [(u64, u64)]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegmentRequests<'_> {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
SegmentRequests::U32Pairs(pairs) => pairs.is_empty(),
|
||||||
|
SegmentRequests::U64Pairs(pairs) => pairs.is_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NAK PDU abstraction specialized in the creation of NAK PDUs.
|
||||||
|
///
|
||||||
|
/// It exposes a specialized API which simplifies to generate these NAK PDUs with the
|
||||||
|
/// format according to CFDP chapter 5.2.6.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct NakPduCreator<'seg_reqs> {
|
||||||
|
pdu_header: PduHeader,
|
||||||
|
start_of_scope: u64,
|
||||||
|
end_of_scope: u64,
|
||||||
|
segment_requests: Option<SegmentRequests<'seg_reqs>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'seg_reqs> NakPduCreator<'seg_reqs> {
|
||||||
|
/// Please note that the start of scope and the end of scope need to be smaller or equal
|
||||||
|
/// to [u32::MAX] if the large file flag of the passed PDU configuration is
|
||||||
|
/// [LargeFileFlag::Normal].
|
||||||
|
///
|
||||||
|
/// ## Errrors
|
||||||
|
///
|
||||||
|
pub fn new_no_segment_requests(
|
||||||
|
pdu_header: PduHeader,
|
||||||
|
start_of_scope: u64,
|
||||||
|
end_of_scope: u64,
|
||||||
|
) -> Result<NakPduCreator<'seg_reqs>, PduError> {
|
||||||
|
Self::new_generic(pdu_header, start_of_scope, end_of_scope, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default constructor for normal file sizes.
|
||||||
|
pub fn new(
|
||||||
|
pdu_header: PduHeader,
|
||||||
|
start_of_scope: u32,
|
||||||
|
end_of_scope: u32,
|
||||||
|
segment_requests: &'seg_reqs [(u32, u32)],
|
||||||
|
) -> Result<NakPduCreator, PduError> {
|
||||||
|
let mut passed_segment_requests = None;
|
||||||
|
if !segment_requests.is_empty() {
|
||||||
|
passed_segment_requests = Some(SegmentRequests::U32Pairs(segment_requests));
|
||||||
|
}
|
||||||
|
Self::new_generic(
|
||||||
|
pdu_header,
|
||||||
|
start_of_scope.into(),
|
||||||
|
end_of_scope.into(),
|
||||||
|
passed_segment_requests,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_large_file_size(
|
||||||
|
pdu_header: PduHeader,
|
||||||
|
start_of_scope: u64,
|
||||||
|
end_of_scope: u64,
|
||||||
|
segment_requests: &'seg_reqs [(u64, u64)],
|
||||||
|
) -> Result<NakPduCreator, PduError> {
|
||||||
|
let mut passed_segment_requests = None;
|
||||||
|
if !segment_requests.is_empty() {
|
||||||
|
passed_segment_requests = Some(SegmentRequests::U64Pairs(segment_requests));
|
||||||
|
}
|
||||||
|
Self::new_generic(
|
||||||
|
pdu_header,
|
||||||
|
start_of_scope,
|
||||||
|
end_of_scope,
|
||||||
|
passed_segment_requests,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_generic(
|
||||||
|
mut pdu_header: PduHeader,
|
||||||
|
start_of_scope: u64,
|
||||||
|
end_of_scope: u64,
|
||||||
|
segment_requests: Option<SegmentRequests<'seg_reqs>>,
|
||||||
|
) -> Result<NakPduCreator, PduError> {
|
||||||
|
// Force correct direction flag.
|
||||||
|
pdu_header.pdu_conf.direction = Direction::TowardsSender;
|
||||||
|
if let Some(ref segment_requests) = segment_requests {
|
||||||
|
match segment_requests {
|
||||||
|
SegmentRequests::U32Pairs(_) => {
|
||||||
|
if start_of_scope > u32::MAX as u64 || end_of_scope > u32::MAX as u64 {
|
||||||
|
return Err(PduError::InvalidStartOrEndOfScopeValue);
|
||||||
|
}
|
||||||
|
pdu_header.pdu_conf.file_flag = LargeFileFlag::Normal;
|
||||||
|
}
|
||||||
|
SegmentRequests::U64Pairs(_) => {
|
||||||
|
pdu_header.pdu_conf.file_flag = LargeFileFlag::Large;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut nak_pdu = Self {
|
||||||
|
pdu_header,
|
||||||
|
start_of_scope,
|
||||||
|
end_of_scope,
|
||||||
|
segment_requests,
|
||||||
|
};
|
||||||
|
nak_pdu.pdu_header.pdu_datafield_len = nak_pdu.calc_pdu_datafield_len() as u16;
|
||||||
|
Ok(nak_pdu)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_of_scope(&self) -> u64 {
|
||||||
|
self.start_of_scope
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_of_scope(&self) -> u64 {
|
||||||
|
self.end_of_scope
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn segment_requests(&self) -> Option<&SegmentRequests> {
|
||||||
|
self.segment_requests.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_segment_reqs(&self) -> usize {
|
||||||
|
match &self.segment_requests {
|
||||||
|
Some(seg_reqs) => match seg_reqs {
|
||||||
|
SegmentRequests::U32Pairs(pairs) => pairs.len(),
|
||||||
|
SegmentRequests::U64Pairs(pairs) => pairs.len(),
|
||||||
|
},
|
||||||
|
None => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pdu_header(&self) -> &PduHeader {
|
||||||
|
&self.pdu_header
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_pdu_datafield_len(&self) -> usize {
|
||||||
|
let mut datafield_len = 1;
|
||||||
|
if self.file_flag() == LargeFileFlag::Normal {
|
||||||
|
datafield_len += 8;
|
||||||
|
datafield_len += self.num_segment_reqs() * 8;
|
||||||
|
} else {
|
||||||
|
datafield_len += 16;
|
||||||
|
datafield_len += self.num_segment_reqs() * 16;
|
||||||
|
}
|
||||||
|
if self.crc_flag() == CrcFlag::WithCrc {
|
||||||
|
datafield_len += 2;
|
||||||
|
}
|
||||||
|
datafield_len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CfdpPdu for NakPduCreator<'_> {
|
||||||
|
fn pdu_header(&self) -> &PduHeader {
|
||||||
|
&self.pdu_header
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_directive_type(&self) -> Option<FileDirectiveType> {
|
||||||
|
Some(FileDirectiveType::NakPdu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WritablePduPacket for NakPduCreator<'_> {
|
||||||
|
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::NakPdu as u8;
|
||||||
|
current_idx += 1;
|
||||||
|
|
||||||
|
let mut write_start_end_of_scope_normal = || {
|
||||||
|
let start_of_scope = u32::try_from(self.start_of_scope).unwrap();
|
||||||
|
let end_of_scope = u32::try_from(self.end_of_scope).unwrap();
|
||||||
|
buf[current_idx..current_idx + 4].copy_from_slice(&start_of_scope.to_be_bytes());
|
||||||
|
current_idx += 4;
|
||||||
|
buf[current_idx..current_idx + 4].copy_from_slice(&end_of_scope.to_be_bytes());
|
||||||
|
current_idx += 4;
|
||||||
|
};
|
||||||
|
if let Some(ref seg_reqs) = self.segment_requests {
|
||||||
|
match seg_reqs {
|
||||||
|
SegmentRequests::U32Pairs(pairs) => {
|
||||||
|
// Unwrap is okay here, the API should prevent invalid values which would trigger a
|
||||||
|
// panic here.
|
||||||
|
write_start_end_of_scope_normal();
|
||||||
|
for (next_start_offset, next_end_offset) in *pairs {
|
||||||
|
buf[current_idx..current_idx + 4]
|
||||||
|
.copy_from_slice(&next_start_offset.to_be_bytes());
|
||||||
|
current_idx += 4;
|
||||||
|
buf[current_idx..current_idx + 4]
|
||||||
|
.copy_from_slice(&next_end_offset.to_be_bytes());
|
||||||
|
current_idx += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SegmentRequests::U64Pairs(pairs) => {
|
||||||
|
buf[current_idx..current_idx + 8]
|
||||||
|
.copy_from_slice(&self.start_of_scope.to_be_bytes());
|
||||||
|
current_idx += 8;
|
||||||
|
buf[current_idx..current_idx + 8]
|
||||||
|
.copy_from_slice(&self.end_of_scope.to_be_bytes());
|
||||||
|
current_idx += 8;
|
||||||
|
for (next_start_offset, next_end_offset) in *pairs {
|
||||||
|
buf[current_idx..current_idx + 8]
|
||||||
|
.copy_from_slice(&next_start_offset.to_be_bytes());
|
||||||
|
current_idx += 8;
|
||||||
|
buf[current_idx..current_idx + 8]
|
||||||
|
.copy_from_slice(&next_end_offset.to_be_bytes());
|
||||||
|
current_idx += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write_start_end_of_scope_normal();
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Special iterator type for the NAK PDU which allows to iterate over both normal and large file
|
||||||
|
/// segment requests.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SegmentRequestIter<'a, T> {
|
||||||
|
seq_req_raw: &'a [u8],
|
||||||
|
current_idx: usize,
|
||||||
|
phantom: core::marker::PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SegReqFromBytes {
|
||||||
|
fn from_bytes(bytes: &[u8]) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegReqFromBytes for u32 {
|
||||||
|
fn from_bytes(bytes: &[u8]) -> u32 {
|
||||||
|
u32::from_be_bytes(bytes.try_into().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegReqFromBytes for u64 {
|
||||||
|
fn from_bytes(bytes: &[u8]) -> u64 {
|
||||||
|
u64::from_be_bytes(bytes.try_into().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for SegmentRequestIter<'a, T>
|
||||||
|
where
|
||||||
|
T: SegReqFromBytes,
|
||||||
|
{
|
||||||
|
type Item = (T, T);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let value = self.next_at_offset(self.current_idx);
|
||||||
|
self.current_idx += 2 * size_of::<T>();
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'b, u32> {
|
||||||
|
fn eq(&self, other: &SegmentRequests) -> bool {
|
||||||
|
match other {
|
||||||
|
SegmentRequests::U32Pairs(pairs) => self.compare_pairs(pairs),
|
||||||
|
SegmentRequests::U64Pairs(pairs) => {
|
||||||
|
if pairs.is_empty() && self.seq_req_raw.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> PartialEq<SegmentRequests<'a>> for SegmentRequestIter<'b, u64> {
|
||||||
|
fn eq(&self, other: &SegmentRequests) -> bool {
|
||||||
|
match other {
|
||||||
|
SegmentRequests::U32Pairs(pairs) => {
|
||||||
|
if pairs.is_empty() && self.seq_req_raw.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
SegmentRequests::U64Pairs(pairs) => self.compare_pairs(pairs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> SegmentRequestIter<'a, T>
|
||||||
|
where
|
||||||
|
T: SegReqFromBytes + PartialEq,
|
||||||
|
{
|
||||||
|
fn compare_pairs(&self, pairs: &[(T, T)]) -> bool {
|
||||||
|
if pairs.is_empty() && self.seq_req_raw.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let size = size_of::<T>();
|
||||||
|
if pairs.len() * 2 * size != self.seq_req_raw.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, pair) in pairs.iter().enumerate() {
|
||||||
|
let next_val = self.next_at_offset(i * 2 * size).unwrap();
|
||||||
|
if next_val != *pair {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SegReqFromBytes> SegmentRequestIter<'_, T> {
|
||||||
|
fn next_at_offset(&self, mut offset: usize) -> Option<(T, T)> {
|
||||||
|
if offset + size_of::<T>() * 2 > self.seq_req_raw.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_offset = T::from_bytes(&self.seq_req_raw[offset..offset + size_of::<T>()]);
|
||||||
|
offset += size_of::<T>();
|
||||||
|
|
||||||
|
let end_offset = T::from_bytes(&self.seq_req_raw[offset..offset + size_of::<T>()]);
|
||||||
|
Some((start_offset, end_offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NAK PDU abstraction specialized in the reading NAK PDUs from a raw bytestream.
|
||||||
|
///
|
||||||
|
/// This is a zero-copy class where the segment requests can be read using a special iterator
|
||||||
|
/// API without the need to copy them.
|
||||||
|
///
|
||||||
|
/// The NAK format is expected to be conforming to CFDP chapter 5.2.6.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct NakPduReader<'seg_reqs> {
|
||||||
|
pdu_header: PduHeader,
|
||||||
|
start_of_scope: u64,
|
||||||
|
end_of_scope: u64,
|
||||||
|
seg_reqs_raw: &'seg_reqs [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CfdpPdu for NakPduReader<'_> {
|
||||||
|
fn pdu_header(&self) -> &PduHeader {
|
||||||
|
&self.pdu_header
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_directive_type(&self) -> Option<FileDirectiveType> {
|
||||||
|
Some(FileDirectiveType::NakPdu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'seg_reqs> NakPduReader<'seg_reqs> {
|
||||||
|
pub fn new(buf: &'seg_reqs [u8]) -> Result<NakPduReader, PduError> {
|
||||||
|
Self::from_bytes(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(buf: &'seg_reqs [u8]) -> Result<NakPduReader, PduError> {
|
||||||
|
let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?;
|
||||||
|
let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?;
|
||||||
|
// Minimum length of 9: 1 byte directive field and start and end of scope for normal file
|
||||||
|
// size.
|
||||||
|
generic_length_checks_pdu_deserialization(buf, 9, full_len_without_crc)?;
|
||||||
|
let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| {
|
||||||
|
PduError::InvalidDirectiveType {
|
||||||
|
found: buf[current_idx],
|
||||||
|
expected: Some(FileDirectiveType::NakPdu),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
if directive_type != FileDirectiveType::NakPdu {
|
||||||
|
return Err(PduError::WrongDirectiveType {
|
||||||
|
found: directive_type,
|
||||||
|
expected: FileDirectiveType::AckPdu,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
current_idx += 1;
|
||||||
|
let start_of_scope;
|
||||||
|
let end_of_scope;
|
||||||
|
if pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large {
|
||||||
|
if current_idx + 16 > buf.len() {
|
||||||
|
return Err(PduError::ByteConversionError(
|
||||||
|
ByteConversionError::FromSliceTooSmall {
|
||||||
|
found: buf.len(),
|
||||||
|
expected: current_idx + 16,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
start_of_scope =
|
||||||
|
u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap());
|
||||||
|
current_idx += 8;
|
||||||
|
end_of_scope =
|
||||||
|
u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap());
|
||||||
|
current_idx += 8;
|
||||||
|
} else {
|
||||||
|
start_of_scope =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()) as u64;
|
||||||
|
current_idx += 4;
|
||||||
|
end_of_scope =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()) as u64;
|
||||||
|
current_idx += 4;
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
pdu_header,
|
||||||
|
start_of_scope,
|
||||||
|
end_of_scope,
|
||||||
|
seg_reqs_raw: &buf[current_idx..full_len_without_crc],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_of_scope(&self) -> u64 {
|
||||||
|
self.start_of_scope
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_of_scope(&self) -> u64 {
|
||||||
|
self.end_of_scope
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_segment_reqs(&self) -> usize {
|
||||||
|
if self.seg_reqs_raw.is_empty() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if self.file_flag() == LargeFileFlag::Normal {
|
||||||
|
self.seg_reqs_raw.len() / 8
|
||||||
|
} else {
|
||||||
|
self.seg_reqs_raw.len() / 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function returns [None] if this NAK PDUs contains segment requests for a large file.
|
||||||
|
pub fn get_normal_segment_requests_iterator(&self) -> Option<SegmentRequestIter<'_, u32>> {
|
||||||
|
if self.file_flag() == LargeFileFlag::Large {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(SegmentRequestIter {
|
||||||
|
seq_req_raw: self.seg_reqs_raw,
|
||||||
|
current_idx: 0,
|
||||||
|
phantom: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function returns [None] if this NAK PDUs contains segment requests for a normal file.
|
||||||
|
pub fn get_large_segment_requests_iterator(&self) -> Option<SegmentRequestIter<'_, u64>> {
|
||||||
|
if self.file_flag() == LargeFileFlag::Normal {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(SegmentRequestIter {
|
||||||
|
seq_req_raw: self.seg_reqs_raw,
|
||||||
|
current_idx: 0,
|
||||||
|
phantom: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> PartialEq<NakPduCreator<'a>> for NakPduReader<'b> {
|
||||||
|
fn eq(&self, other: &NakPduCreator<'a>) -> bool {
|
||||||
|
if self.pdu_header() != other.pdu_header()
|
||||||
|
|| self.end_of_scope() != other.end_of_scope()
|
||||||
|
|| self.start_of_scope() != other.start_of_scope()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if both segment requests are empty or None
|
||||||
|
match (self.seg_reqs_raw.is_empty(), other.segment_requests()) {
|
||||||
|
(true, None) => true,
|
||||||
|
(true, Some(seg_reqs)) => seg_reqs.is_empty(),
|
||||||
|
(false, None) => false,
|
||||||
|
_ => {
|
||||||
|
// Compare based on file_flag
|
||||||
|
if self.file_flag() == LargeFileFlag::Normal {
|
||||||
|
let normal_iter = self.get_normal_segment_requests_iterator().unwrap();
|
||||||
|
normal_iter == *other.segment_requests().unwrap()
|
||||||
|
} else {
|
||||||
|
let large_iter = self.get_large_segment_requests_iterator().unwrap();
|
||||||
|
large_iter == *other.segment_requests().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::cfdp::{
|
||||||
|
pdu::tests::{common_pdu_conf, verify_raw_header, TEST_DEST_ID, TEST_SEQ_NUM, TEST_SRC_ID},
|
||||||
|
PduType, TransmissionMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn check_generic_fields(nak_pdu: &impl CfdpPdu) {
|
||||||
|
assert_eq!(nak_pdu.crc_flag(), CrcFlag::NoCrc);
|
||||||
|
assert_eq!(nak_pdu.file_flag(), LargeFileFlag::Normal);
|
||||||
|
assert_eq!(nak_pdu.pdu_type(), PduType::FileDirective);
|
||||||
|
assert_eq!(
|
||||||
|
nak_pdu.file_directive_type(),
|
||||||
|
Some(FileDirectiveType::NakPdu),
|
||||||
|
);
|
||||||
|
assert_eq!(nak_pdu.transmission_mode(), TransmissionMode::Acknowledged);
|
||||||
|
assert_eq!(nak_pdu.direction(), Direction::TowardsSender);
|
||||||
|
assert_eq!(nak_pdu.source_id(), TEST_SRC_ID.into());
|
||||||
|
assert_eq!(nak_pdu.dest_id(), TEST_DEST_ID.into());
|
||||||
|
assert_eq!(nak_pdu.transaction_seq_num(), TEST_SEQ_NUM.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seg_request_api() {
|
||||||
|
let seg_req = SegmentRequests::U32Pairs(&[]);
|
||||||
|
assert!(seg_req.is_empty());
|
||||||
|
let seg_req = SegmentRequests::U64Pairs(&[]);
|
||||||
|
assert!(seg_req.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_creator() {
|
||||||
|
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
|
||||||
|
let nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 0, 0)
|
||||||
|
.expect("creating NAK PDU creator failed");
|
||||||
|
assert_eq!(nak_pdu.start_of_scope(), 0);
|
||||||
|
assert_eq!(nak_pdu.end_of_scope(), 0);
|
||||||
|
assert_eq!(nak_pdu.segment_requests(), None);
|
||||||
|
assert_eq!(nak_pdu.num_segment_reqs(), 0);
|
||||||
|
check_generic_fields(&nak_pdu);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization_empty() {
|
||||||
|
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
|
||||||
|
let nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 100, 300)
|
||||||
|
.expect("creating NAK PDU creator failed");
|
||||||
|
assert_eq!(nak_pdu.start_of_scope(), 100);
|
||||||
|
assert_eq!(nak_pdu.end_of_scope(), 300);
|
||||||
|
let mut buf: [u8; 64] = [0; 64];
|
||||||
|
nak_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing NAK PDU to buffer failed");
|
||||||
|
verify_raw_header(nak_pdu.pdu_header(), &buf);
|
||||||
|
let mut current_idx = nak_pdu.pdu_header().header_len();
|
||||||
|
assert_eq!(current_idx + 9, nak_pdu.len_written());
|
||||||
|
assert_eq!(buf[current_idx], FileDirectiveType::NakPdu as u8);
|
||||||
|
current_idx += 1;
|
||||||
|
let start_of_scope =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap());
|
||||||
|
assert_eq!(start_of_scope, 100);
|
||||||
|
current_idx += 4;
|
||||||
|
let end_of_scope =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap());
|
||||||
|
assert_eq!(end_of_scope, 300);
|
||||||
|
current_idx += 4;
|
||||||
|
assert_eq!(current_idx, nak_pdu.len_written());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization_two_segments() {
|
||||||
|
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
|
||||||
|
let nak_pdu = NakPduCreator::new(pdu_header, 100, 300, &[(0, 0), (32, 64)])
|
||||||
|
.expect("creating NAK PDU creator failed");
|
||||||
|
let mut buf: [u8; 64] = [0; 64];
|
||||||
|
nak_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing NAK PDU to buffer failed");
|
||||||
|
verify_raw_header(nak_pdu.pdu_header(), &buf);
|
||||||
|
let mut current_idx = nak_pdu.pdu_header().header_len();
|
||||||
|
assert_eq!(current_idx + 9 + 16, nak_pdu.len_written());
|
||||||
|
assert_eq!(buf[current_idx], FileDirectiveType::NakPdu as u8);
|
||||||
|
current_idx += 1;
|
||||||
|
let start_of_scope =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap());
|
||||||
|
assert_eq!(start_of_scope, 100);
|
||||||
|
current_idx += 4;
|
||||||
|
let end_of_scope =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap());
|
||||||
|
assert_eq!(end_of_scope, 300);
|
||||||
|
current_idx += 4;
|
||||||
|
let first_seg_start =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap());
|
||||||
|
assert_eq!(first_seg_start, 0);
|
||||||
|
current_idx += 4;
|
||||||
|
let first_seg_end =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap());
|
||||||
|
assert_eq!(first_seg_end, 0);
|
||||||
|
current_idx += 4;
|
||||||
|
let second_seg_start =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap());
|
||||||
|
assert_eq!(second_seg_start, 32);
|
||||||
|
current_idx += 4;
|
||||||
|
let second_seg_end =
|
||||||
|
u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap());
|
||||||
|
assert_eq!(second_seg_end, 64);
|
||||||
|
current_idx += 4;
|
||||||
|
assert_eq!(current_idx, nak_pdu.len_written());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialization_empty() {
|
||||||
|
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
|
||||||
|
let nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 100, 300)
|
||||||
|
.expect("creating NAK PDU creator failed");
|
||||||
|
let mut buf: [u8; 64] = [0; 64];
|
||||||
|
nak_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing NAK PDU to buffer failed");
|
||||||
|
let nak_pdu_deser = NakPduReader::from_bytes(&buf).expect("deserializing NAK PDU failed");
|
||||||
|
assert_eq!(nak_pdu_deser, nak_pdu);
|
||||||
|
check_generic_fields(&nak_pdu_deser);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialization_large_segments() {
|
||||||
|
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large);
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
|
||||||
|
let nak_pdu =
|
||||||
|
NakPduCreator::new_large_file_size(pdu_header, 100, 300, &[(50, 100), (200, 300)])
|
||||||
|
.expect("creating NAK PDU creator failed");
|
||||||
|
let mut buf: [u8; 128] = [0; 128];
|
||||||
|
nak_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing NAK PDU to buffer failed");
|
||||||
|
let nak_pdu_deser = NakPduReader::from_bytes(&buf).expect("deserializing NAK PDU failed");
|
||||||
|
assert_eq!(nak_pdu_deser, nak_pdu);
|
||||||
|
assert_eq!(nak_pdu_deser.start_of_scope(), 100);
|
||||||
|
assert_eq!(nak_pdu_deser.end_of_scope(), 300);
|
||||||
|
assert_eq!(nak_pdu_deser.num_segment_reqs(), 2);
|
||||||
|
assert!(nak_pdu_deser
|
||||||
|
.get_large_segment_requests_iterator()
|
||||||
|
.is_some());
|
||||||
|
assert!(nak_pdu_deser
|
||||||
|
.get_normal_segment_requests_iterator()
|
||||||
|
.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
nak_pdu_deser
|
||||||
|
.get_large_segment_requests_iterator()
|
||||||
|
.unwrap()
|
||||||
|
.count(),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
for (idx, large_segments) in nak_pdu_deser
|
||||||
|
.get_large_segment_requests_iterator()
|
||||||
|
.unwrap()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if idx == 0 {
|
||||||
|
assert_eq!(large_segments.0, 50);
|
||||||
|
assert_eq!(large_segments.1, 100);
|
||||||
|
} else {
|
||||||
|
assert_eq!(large_segments.0, 200);
|
||||||
|
assert_eq!(large_segments.1, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialization_normal_segments() {
|
||||||
|
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
|
||||||
|
let nak_pdu = NakPduCreator::new(pdu_header, 100, 300, &[(50, 100), (200, 300)])
|
||||||
|
.expect("creating NAK PDU creator failed");
|
||||||
|
let mut buf: [u8; 128] = [0; 128];
|
||||||
|
nak_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing NAK PDU to buffer failed");
|
||||||
|
let nak_pdu_deser = NakPduReader::from_bytes(&buf).expect("deserializing NAK PDU failed");
|
||||||
|
assert_eq!(nak_pdu_deser, nak_pdu);
|
||||||
|
assert_eq!(nak_pdu_deser.start_of_scope(), 100);
|
||||||
|
assert_eq!(nak_pdu_deser.end_of_scope(), 300);
|
||||||
|
assert_eq!(nak_pdu_deser.num_segment_reqs(), 2);
|
||||||
|
assert!(nak_pdu_deser
|
||||||
|
.get_normal_segment_requests_iterator()
|
||||||
|
.is_some());
|
||||||
|
assert!(nak_pdu_deser
|
||||||
|
.get_large_segment_requests_iterator()
|
||||||
|
.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
nak_pdu_deser
|
||||||
|
.get_normal_segment_requests_iterator()
|
||||||
|
.unwrap()
|
||||||
|
.count(),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
for (idx, large_segments) in nak_pdu_deser
|
||||||
|
.get_normal_segment_requests_iterator()
|
||||||
|
.unwrap()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if idx == 0 {
|
||||||
|
assert_eq!(large_segments.0, 50);
|
||||||
|
assert_eq!(large_segments.1, 100);
|
||||||
|
} else {
|
||||||
|
assert_eq!(large_segments.0, 200);
|
||||||
|
assert_eq!(large_segments.1, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_is_empty() {
|
||||||
|
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
|
||||||
|
let nak_pdu_0 =
|
||||||
|
NakPduCreator::new(pdu_header, 100, 300, &[]).expect("creating NAK PDU creator failed");
|
||||||
|
let nak_pdu_1 = NakPduCreator::new_no_segment_requests(pdu_header, 100, 300)
|
||||||
|
.expect("creating NAK PDU creator failed");
|
||||||
|
assert_eq!(nak_pdu_0, nak_pdu_1);
|
||||||
|
// Assert the segment request is mapped to None.
|
||||||
|
assert!(nak_pdu_0.segment_requests().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_generic_invalid_input() {
|
||||||
|
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
|
||||||
|
let u32_list = SegmentRequests::U32Pairs(&[(0, 50), (50, 100)]);
|
||||||
|
if let Err(PduError::InvalidStartOrEndOfScopeValue) = NakPduCreator::new_generic(
|
||||||
|
pdu_header,
|
||||||
|
u32::MAX as u64 + 1,
|
||||||
|
u32::MAX as u64 + 2,
|
||||||
|
Some(u32_list),
|
||||||
|
) {
|
||||||
|
} else {
|
||||||
|
panic!("API call did not fail");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_target_buf_too_small() {
|
||||||
|
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0);
|
||||||
|
let nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 100, 300)
|
||||||
|
.expect("creating NAK PDU creator failed");
|
||||||
|
assert_eq!(nak_pdu.start_of_scope(), 100);
|
||||||
|
assert_eq!(nak_pdu.end_of_scope(), 300);
|
||||||
|
let mut buf: [u8; 5] = [0; 5];
|
||||||
|
let error = nak_pdu.write_to_bytes(&mut buf);
|
||||||
|
assert!(error.is_err());
|
||||||
|
let e = error.unwrap_err();
|
||||||
|
match e {
|
||||||
|
PduError::ByteConversionError(conv_error) => match conv_error {
|
||||||
|
ByteConversionError::ToSliceTooSmall { found, expected } => {
|
||||||
|
assert_eq!(expected, nak_pdu.len_written());
|
||||||
|
assert_eq!(found, 5);
|
||||||
|
}
|
||||||
|
_ => panic!("unexpected error {conv_error}"),
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected error {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 0, 0)
|
||||||
|
.expect("creating NAK PDU creator failed");
|
||||||
|
let mut nak_vec = nak_pdu.to_vec().expect("writing NAK to vector failed");
|
||||||
|
assert_eq!(nak_vec.len(), pdu_header.header_len() + 9 + 2);
|
||||||
|
assert_eq!(nak_vec.len(), nak_pdu.len_written());
|
||||||
|
let nak_pdu_deser = NakPduReader::new(&nak_vec).expect("reading NAK PDU failed");
|
||||||
|
assert_eq!(nak_pdu_deser, nak_pdu);
|
||||||
|
nak_vec[nak_pdu.len_written() - 1] -= 1;
|
||||||
|
let nak_pdu_deser = NakPduReader::new(&nak_vec);
|
||||||
|
assert!(nak_pdu_deser.is_err());
|
||||||
|
if let Err(PduError::ChecksumError(raw)) = nak_pdu_deser {
|
||||||
|
assert_eq!(
|
||||||
|
raw,
|
||||||
|
u16::from_be_bytes(nak_vec[nak_pdu.len_written() - 2..].try_into().unwrap())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user