add first PDU header tests
This commit is contained in:
parent
074882c160
commit
53eb184534
@ -69,29 +69,49 @@ impl From<ByteConversionError> for PduError {
|
|||||||
pub struct CommonPduConfig {
|
pub struct CommonPduConfig {
|
||||||
source_entity_id: UnsignedByteField,
|
source_entity_id: UnsignedByteField,
|
||||||
dest_entity_id: UnsignedByteField,
|
dest_entity_id: UnsignedByteField,
|
||||||
transaction_seq_num: UnsignedByteField,
|
pub transaction_seq_num: UnsignedByteField,
|
||||||
trans_mode: TransmissionMode,
|
pub trans_mode: TransmissionMode,
|
||||||
file_flag: LargeFileFlag,
|
pub file_flag: LargeFileFlag,
|
||||||
crc_flag: CrcFlag,
|
pub crc_flag: CrcFlag,
|
||||||
direction: Direction,
|
pub direction: Direction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Build might be applicable here..
|
||||||
impl CommonPduConfig {
|
impl CommonPduConfig {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
source_id: UnsignedByteField,
|
source_id: impl Into<UnsignedByteField>,
|
||||||
dest_id: UnsignedByteField,
|
dest_id: impl Into<UnsignedByteField>,
|
||||||
transaction_seq_num: UnsignedByteField,
|
transaction_seq_num: impl Into<UnsignedByteField>,
|
||||||
trans_mode: TransmissionMode,
|
trans_mode: TransmissionMode,
|
||||||
file_flag: LargeFileFlag,
|
file_flag: LargeFileFlag,
|
||||||
crc_flag: CrcFlag,
|
crc_flag: CrcFlag,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
) -> Result<Self, PduError> {
|
) -> Result<Self, PduError> {
|
||||||
|
let source_id = source_id.into();
|
||||||
|
let dest_id = dest_id.into();
|
||||||
|
let transaction_seq_num = transaction_seq_num.into();
|
||||||
if source_id.len() != dest_id.len() {
|
if source_id.len() != dest_id.len() {
|
||||||
return Err(PduError::SourceDestIdLenMissmatch((
|
return Err(PduError::SourceDestIdLenMissmatch((
|
||||||
source_id.len(),
|
source_id.len(),
|
||||||
dest_id.len(),
|
dest_id.len(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
if source_id.len() != 1
|
||||||
|
&& source_id.len() != 2
|
||||||
|
&& source_id.len() != 4
|
||||||
|
&& source_id.len() != 8
|
||||||
|
{
|
||||||
|
return Err(PduError::InvalidEntityLen(source_id.len() as u8));
|
||||||
|
}
|
||||||
|
if transaction_seq_num.len() != 1
|
||||||
|
&& transaction_seq_num.len() != 2
|
||||||
|
&& transaction_seq_num.len() != 4
|
||||||
|
&& transaction_seq_num.len() != 8
|
||||||
|
{
|
||||||
|
return Err(PduError::InvalidTransactionSeqNumLen(
|
||||||
|
transaction_seq_num.len() as u8,
|
||||||
|
));
|
||||||
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
source_entity_id: source_id,
|
source_entity_id: source_id,
|
||||||
dest_entity_id: dest_id,
|
dest_entity_id: dest_id,
|
||||||
@ -102,7 +122,34 @@ impl CommonPduConfig {
|
|||||||
direction,
|
direction,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_with_defaults(
|
||||||
|
source_id: impl Into<UnsignedByteField>,
|
||||||
|
dest_id: impl Into<UnsignedByteField>,
|
||||||
|
transaction_seq_num: impl Into<UnsignedByteField>,
|
||||||
|
) -> Result<Self, PduError> {
|
||||||
|
Self::new(
|
||||||
|
source_id,
|
||||||
|
dest_id,
|
||||||
|
transaction_seq_num,
|
||||||
|
TransmissionMode::Acknowledged,
|
||||||
|
LargeFileFlag::Normal,
|
||||||
|
CrcFlag::NoCrc,
|
||||||
|
Direction::TowardsReceiver,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn source_id(&self) -> UnsignedByteField {
|
||||||
|
self.source_entity_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dest_id(&self) -> UnsignedByteField {
|
||||||
|
self.dest_entity_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MIN_HEADER_LEN: usize = 4;
|
||||||
|
|
||||||
/// Abstraction for the PDU header common to all CFDP PDUs
|
/// Abstraction for the PDU header common to all CFDP PDUs
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
@ -132,7 +179,7 @@ impl PduHeader {
|
|||||||
|
|
||||||
pub fn new_no_file_data(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self {
|
pub fn new_no_file_data(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self {
|
||||||
PduHeader {
|
PduHeader {
|
||||||
pdu_type: PduType::FileData,
|
pdu_type: PduType::FileDirective,
|
||||||
pdu_conf,
|
pdu_conf,
|
||||||
seg_metadata_flag: SegmentMetadataFlag::NotPresent,
|
seg_metadata_flag: SegmentMetadataFlag::NotPresent,
|
||||||
seg_ctrl: SegmentationControl::NoRecordBoundaryPreservation,
|
seg_ctrl: SegmentationControl::NoRecordBoundaryPreservation,
|
||||||
@ -148,11 +195,13 @@ impl PduHeader {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
if buf.len()
|
if buf.len()
|
||||||
< 4 + self.pdu_conf.source_entity_id.len() + self.pdu_conf.transaction_seq_num.len()
|
< MIN_HEADER_LEN
|
||||||
|
+ self.pdu_conf.source_entity_id.len()
|
||||||
|
+ self.pdu_conf.transaction_seq_num.len()
|
||||||
{
|
{
|
||||||
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
|
||||||
found: buf.len(),
|
found: buf.len(),
|
||||||
expected: 4,
|
expected: MIN_HEADER_LEN,
|
||||||
})
|
})
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
@ -167,9 +216,10 @@ impl PduHeader {
|
|||||||
buf[current_idx..current_idx + 2].copy_from_slice(&self.pdu_datafield_len.to_be_bytes());
|
buf[current_idx..current_idx + 2].copy_from_slice(&self.pdu_datafield_len.to_be_bytes());
|
||||||
current_idx += 2;
|
current_idx += 2;
|
||||||
buf[current_idx] = ((self.seg_ctrl as u8) << 7)
|
buf[current_idx] = ((self.seg_ctrl as u8) << 7)
|
||||||
| ((self.pdu_conf.source_entity_id.len() as u8) << 4)
|
| (((self.pdu_conf.source_entity_id.len() - 1) as u8) << 4)
|
||||||
| ((self.seg_metadata_flag as u8) << 3)
|
| ((self.seg_metadata_flag as u8) << 3)
|
||||||
| (self.pdu_conf.transaction_seq_num.len() as u8);
|
| ((self.pdu_conf.transaction_seq_num.len() - 1) as u8);
|
||||||
|
current_idx += 1;
|
||||||
self.pdu_conf.source_entity_id.write_to_be_bytes(
|
self.pdu_conf.source_entity_id.write_to_be_bytes(
|
||||||
&mut buf[current_idx..current_idx + self.pdu_conf.source_entity_id.len()],
|
&mut buf[current_idx..current_idx + self.pdu_conf.source_entity_id.len()],
|
||||||
)?;
|
)?;
|
||||||
@ -185,19 +235,19 @@ impl PduHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_be_bytes(buf: &[u8]) -> Result<Self, PduError> {
|
pub fn from_be_bytes(buf: &[u8]) -> Result<Self, PduError> {
|
||||||
if buf.len() < 4 {
|
if buf.len() < MIN_HEADER_LEN {
|
||||||
return Err(PduError::ByteConversionError(
|
return Err(PduError::ByteConversionError(
|
||||||
ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
ByteConversionError::FromSliceTooSmall(SizeMissmatch {
|
||||||
found: buf.len(),
|
found: buf.len(),
|
||||||
expected: 4,
|
expected: MIN_HEADER_LEN,
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let cfdp_version_raw = buf[0] >> 5 & 0b111;
|
let cfdp_version_raw = (buf[0] >> 5) & 0b111;
|
||||||
if cfdp_version_raw != CFDP_VERSION_2 {
|
if cfdp_version_raw != CFDP_VERSION_2 {
|
||||||
return Err(PduError::CfdpVersionMissmatch(cfdp_version_raw));
|
return Err(PduError::CfdpVersionMissmatch(cfdp_version_raw));
|
||||||
}
|
}
|
||||||
// Conversion for 1 bit value always works
|
// unwrap for single bit fields: This operation will always succeed.
|
||||||
let pdu_type = PduType::try_from((buf[0] >> 4) & 0b1).unwrap();
|
let pdu_type = PduType::try_from((buf[0] >> 4) & 0b1).unwrap();
|
||||||
let direction = Direction::try_from((buf[0] >> 3) & 0b1).unwrap();
|
let direction = Direction::try_from((buf[0] >> 3) & 0b1).unwrap();
|
||||||
let trans_mode = TransmissionMode::try_from((buf[0] >> 2) & 0b1).unwrap();
|
let trans_mode = TransmissionMode::try_from((buf[0] >> 2) & 0b1).unwrap();
|
||||||
@ -232,6 +282,8 @@ impl PduHeader {
|
|||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
let mut current_idx = 4;
|
let mut current_idx = 4;
|
||||||
|
// It is okay to unwrap here because we checked the validity of the expected length and of
|
||||||
|
// the remaining buffer length.
|
||||||
let source_id =
|
let source_id =
|
||||||
UnsignedByteField::new_from_be_bytes(expected_len_entity_ids, &buf[current_idx..])
|
UnsignedByteField::new_from_be_bytes(expected_len_entity_ids, &buf[current_idx..])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -268,4 +320,76 @@ impl PduHeader {
|
|||||||
pub fn common_pdu_conf(&self) -> &CommonPduConfig {
|
pub fn common_pdu_conf(&self) -> &CommonPduConfig {
|
||||||
&self.pdu_conf
|
&self.pdu_conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn seg_metadata_flag(&self) -> SegmentMetadataFlag {
|
||||||
|
self.seg_metadata_flag
|
||||||
|
}
|
||||||
|
pub fn seg_ctrl(&self) -> SegmentationControl {
|
||||||
|
self.seg_ctrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::cfdp::pdu::{CommonPduConfig, PduHeader};
|
||||||
|
use crate::cfdp::{
|
||||||
|
Direction, PduType, SegmentMetadataFlag, SegmentationControl, CFDP_VERSION_2,
|
||||||
|
};
|
||||||
|
use crate::util::UnsignedU8;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_state() {
|
||||||
|
let src_id = UnsignedU8::new(1);
|
||||||
|
let dest_id = UnsignedU8::new(2);
|
||||||
|
let transaction_id = UnsignedU8::new(3);
|
||||||
|
let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id)
|
||||||
|
.expect("common config creation failed");
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5);
|
||||||
|
assert_eq!(pdu_header.pdu_type(), PduType::FileDirective);
|
||||||
|
let common_conf_ref = pdu_header.common_pdu_conf();
|
||||||
|
assert_eq!(*common_conf_ref, common_pdu_cfg);
|
||||||
|
// These should be 0 and ignored for non-filedata PDUs
|
||||||
|
assert_eq!(
|
||||||
|
pdu_header.seg_metadata_flag(),
|
||||||
|
SegmentMetadataFlag::NotPresent
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
pdu_header.seg_ctrl(),
|
||||||
|
SegmentationControl::NoRecordBoundaryPreservation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization() {
|
||||||
|
let src_id = UnsignedU8::new(1);
|
||||||
|
let dest_id = UnsignedU8::new(2);
|
||||||
|
let transaction_id = UnsignedU8::new(3);
|
||||||
|
let common_pdu_cfg = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id)
|
||||||
|
.expect("common config creation failed");
|
||||||
|
let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5);
|
||||||
|
let mut buf: [u8; 7] = [0; 7];
|
||||||
|
let res = pdu_header.write_to_be_bytes(&mut buf);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
assert_eq!((buf[0] >> 5) & 0b111, CFDP_VERSION_2);
|
||||||
|
// File directive
|
||||||
|
assert_eq!((buf[0] >> 4) & 1, 0);
|
||||||
|
// Towards receiver
|
||||||
|
assert_eq!((buf[0] >> 3) & 1, 0);
|
||||||
|
// Acknowledged
|
||||||
|
assert_eq!((buf[0] >> 2) & 1, 0);
|
||||||
|
// No CRC
|
||||||
|
assert_eq!((buf[0] >> 1) & 1, 0);
|
||||||
|
// Regular file size
|
||||||
|
assert_eq!(buf[0] & 1, 0);
|
||||||
|
let pdu_datafield_len = u16::from_be_bytes(buf[1..3].try_into().unwrap());
|
||||||
|
assert_eq!(pdu_datafield_len, 5);
|
||||||
|
// No record boundary preservation
|
||||||
|
assert_eq!((buf[3] >> 7) & 1, 0);
|
||||||
|
// Entity ID length raw value is actual number of octets - 1 => 0
|
||||||
|
assert_eq!((buf[3] >> 4) & 0b111, 0);
|
||||||
|
// No segment metadata
|
||||||
|
assert_eq!((buf[3] >> 3) & 0b1, 0);
|
||||||
|
// Transaction Sequence ID length raw value is actual number of octets - 1 => 0
|
||||||
|
assert_eq!(buf[3] & 0b111, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user