CFDP initial packet support #14

Merged
muellerr merged 49 commits from cfdp_first_support into main 2023-07-02 17:31:17 +02:00
7 changed files with 173 additions and 20 deletions
Showing only changes of commit ce0848dc28 - Show all commits

View File

@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- The `EcssEnumeration` now requires the `UnsignedEnum` trait and only adds the `pfc` method to it.
- Renamed `byte_width` usages to `len` (part of new `UnsignedEnum` trait)
- Moved `ecss::CRC_CCITT_FALSE` CRC constant to the root module. This CRC type is not just used by
the PUS standard, but by the CCSDS Telecommand standard and the CFDP standard as well.
# [v0.5.4] 2023-02-12

View File

@ -103,7 +103,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> {
pub fn written_len(&self) -> usize {
// One directive type octet, and one byte of the parameter field.
let mut len = self.pdu_header.written_len() + 2;
let mut len = self.pdu_header.header_len() + 2;
if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large {
len += 8;
} else {
@ -116,6 +116,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> {
}
len
}
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
let expected_len = self.written_len();
if buf.len() < expected_len {
@ -129,7 +130,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> {
buf[current_idx] = FileDirectiveType::MetadataPdu as u8;
current_idx += 1;
buf[current_idx] = ((self.metadata_params.closure_requested as u8) << 7)
| ((self.metadata_params.checksum_type as u8) << 4);
| (self.metadata_params.checksum_type as u8);
current_idx += 1;
if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large {
buf[current_idx..current_idx + core::mem::size_of::<u64>()]
@ -155,6 +156,69 @@ impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> {
}
Ok(current_idx)
}
pub fn from_be_bytes<'longest: 'src_name + 'dest_name + 'opts>(
buf: &'longest [u8],
) -> Result<MetadataPdu<'src_name, 'dest_name, 'opts>, PduError> {
let (pdu_header, mut current_idx) = PduHeader::from_be_bytes(buf)?;
let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?;
let is_large_file = pdu_header.pdu_conf.file_flag == LargeFileFlag::Large;
// Minimal length: 1 byte + FSS (4 byte) + 2 empty LV (1 byte)
let mut min_expected_len = current_idx + 7;
if is_large_file {
min_expected_len += 4;
}
min_expected_len = core::cmp::max(min_expected_len, pdu_header.pdu_len());
if buf.len() < min_expected_len {
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
found: buf.len(),
expected: min_expected_len,
})
.into());
}
let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| {
PduError::InvalidDirectiveType((buf[current_idx], FileDirectiveType::MetadataPdu))
})?;
if directive_type != FileDirectiveType::MetadataPdu {
return Err(PduError::WrongDirectiveType((
directive_type,
FileDirectiveType::MetadataPdu,
)));
}
current_idx += 1;
let file_size = if pdu_header.pdu_conf.file_flag == LargeFileFlag::Large {
u64::from_be_bytes(buf[current_idx + 1..current_idx + 9].try_into().unwrap())
} else {
u32::from_be_bytes(buf[current_idx + 1..current_idx + 5].try_into().unwrap()) as u64
};
let metadata_params = MetadataGenericParams {
closure_requested: ((buf[current_idx] >> 6) & 0b1) != 0,
checksum_type: ChecksumType::try_from(buf[current_idx] & 0b1111)
.map_err(|_| PduError::InvalidChecksumType(buf[current_idx] & 0b1111))?,
file_size,
};
current_idx += 5;
if is_large_file {
current_idx += 4;
}
let src_file_name = Lv::from_be_bytes(&buf[current_idx..])?;
current_idx += src_file_name.len_full();
let dest_file_name = Lv::from_be_bytes(&buf[current_idx..])?;
current_idx += dest_file_name.len_full();
// All left-over bytes are options.
let mut options = None;
if current_idx < full_len_without_crc {
options = Some(&buf[current_idx..full_len_without_crc]);
}
Ok(Self {
pdu_header,
metadata_params,
src_file_name,
dest_file_name,
options,
})
}
}
#[cfg(test)]
@ -188,7 +252,7 @@ pub mod tests {
MetadataPdu::new(pdu_header, metadata_params, src_filename, dest_filename);
assert_eq!(
metadata_pdu.written_len(),
pdu_header.written_len() + 1 + 1 + 4 + src_len + dest_len
pdu_header.header_len() + 1 + 1 + 4 + src_len + dest_len
);
assert_eq!(metadata_pdu.src_file_name(), src_filename);
assert_eq!(metadata_pdu.dest_file_name(), dest_filename);
@ -213,9 +277,23 @@ pub mod tests {
let written = res.unwrap();
assert_eq!(
written,
pdu_header.written_len() + 1 + 1 + 4 + src_len + dest_len
pdu_header.header_len() + 1 + 1 + 4 + src_len + dest_len
);
verify_raw_header(&pdu_header, &buf);
assert_eq!(buf[7], FileDirectiveType::MetadataPdu as u8);
assert_eq!(buf[8] >> 6, false as u8);
assert_eq!(buf[8] & 0b1111, ChecksumType::Crc32 as u8);
assert_eq!(u32::from_be_bytes(buf[9..13].try_into().unwrap()), 10);
let mut current_idx = 13;
let src_name_from_raw =
Lv::from_be_bytes(&buf[current_idx..]).expect("Creating source name LV failed");
assert_eq!(src_name_from_raw, src_filename);
current_idx += src_name_from_raw.len_full();
let dest_name_from_raw =
Lv::from_be_bytes(&buf[current_idx..]).expect("Creating dest name LV failed");
assert_eq!(dest_name_from_raw, dest_filename);
current_idx += dest_name_from_raw.len_full();
// No options, so no additional data here.
assert_eq!(current_idx, written);
}
}

View File

@ -1,6 +1,7 @@
//! CFDP Packet Data Unit (PDU) support.
use crate::cfdp::*;
use crate::util::{UnsignedByteField, UnsignedEnum};
use crate::CRC_CCITT_FALSE;
use crate::{ByteConversionError, SizeMissmatch};
use core::fmt::{Display, Formatter};
#[cfg(feature = "std")]
@ -34,7 +35,19 @@ pub enum PduError {
/// The first entry will be the source entity ID length, the second one the destination entity
/// ID length.
SourceDestIdLenMissmatch((usize, usize)),
/// The first tuple entry will be the found directive type, the second entry the expected entry
/// type.
WrongDirectiveType((FileDirectiveType, FileDirectiveType)),
/// The directive type field contained a value not in the range of permitted values.
/// The first tuple entry will be the found raw number, the second entry the expected entry
/// type.
InvalidDirectiveType((u8, FileDirectiveType)),
/// Invalid checksum type which is not part of the checksums listed in the
/// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/).
InvalidChecksumType(u8),
FileSizeTooLarge(u64),
/// If the CRC flag for a PDU is enabled and the checksum check fails. Contains raw 16-bit CRC.
ChecksumError(u16),
}
impl Display for PduError {
@ -68,7 +81,23 @@ impl Display for PduError {
write!(f, "{}", e)
}
PduError::FileSizeTooLarge(value) => {
write!(f, "file size value {} exceeds allowed 32 bit width", value)
write!(f, "file size value {value} exceeds allowed 32 bit width")
}
PduError::WrongDirectiveType((found, expected)) => {
write!(f, "found directive type {found:?}, expected {expected:?}")
}
PduError::InvalidDirectiveType((found, expected)) => {
write!(
f,
"invalid directive type value {found}, expected {expected:?} ({})",
*expected as u8
)
}
PduError::InvalidChecksumType(checksum_type) => {
write!(f, "invalid checksum type {checksum_type}")
}
PduError::ChecksumError(checksum) => {
write!(f, "checksum error for CRC {checksum:#04x}")
}
}
}
@ -214,12 +243,20 @@ impl PduHeader {
}
}
pub fn written_len(&self) -> usize {
/// Returns only the length of the PDU header when written to a raw buffer.
pub fn header_len(&self) -> usize {
FIXED_HEADER_LEN
+ self.pdu_conf.source_entity_id.len()
+ self.pdu_conf.transaction_seq_num.len()
+ self.pdu_conf.dest_entity_id.len()
}
/// Returns the full length of the PDU when written to a raw buffer, which is the header length
/// plus the PDU datafield length.
pub fn pdu_len(&self) -> usize {
self.header_len() + self.pdu_datafield_len as usize
}
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
// Internal note: There is currently no way to pass a PDU configuration like this, but
// this check is still kept for defensive programming.
@ -270,6 +307,39 @@ impl PduHeader {
Ok(current_idx)
}
/// This function first verifies that the buffer can hold the full length of the PDU parsed from
/// the header. Then, it verifies the checksum as specified in the standard if the CRC flag
/// of the PDU header is set.
///
/// This function will return the PDU length excluding the 2 CRC bytes on success. If the CRC
/// flag is not set, it will simply return the PDU length.
pub fn verify_length_and_checksum(&self, buf: &[u8]) -> Result<usize, PduError> {
if buf.len() < self.pdu_len() {
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
found: buf.len(),
expected: self.pdu_len(),
})
.into());
}
if self.pdu_conf.crc_flag == CrcFlag::WithCrc {
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&buf[..self.pdu_len()]);
if digest.finalize() != 0 {
return Err(PduError::ChecksumError(u16::from_be_bytes(
buf[self.pdu_len() - 2..self.pdu_len()].try_into().unwrap(),
)));
}
}
Ok(self.pdu_len())
}
/// Please note that this function will not verify that the passed buffer can hold the full
/// PDU length. This allows recovering the header portion even if the data field length is
/// invalid. This function will also not do the CRC procedure specified in chapter 4.1.1
/// and 4.1.2 because performing the CRC procedure requires the buffer to be large enough
/// to hold the full PDU.
///
/// Both functions can however be performed with the [verify_length_and_checksum] function.
pub fn from_be_bytes(buf: &[u8]) -> Result<(Self, usize), PduError> {
if buf.len() < FIXED_HEADER_LEN {
return Err(PduError::ByteConversionError(
@ -464,7 +534,7 @@ mod tests {
SegmentationControl::NoRecordBoundaryPreservation
);
assert_eq!(pdu_header.pdu_datafield_len, 5);
assert_eq!(pdu_header.written_len(), 7);
assert_eq!(pdu_header.header_len(), 7);
}
#[test]
@ -519,7 +589,7 @@ mod tests {
SegmentMetadataFlag::Present,
SegmentationControl::WithRecordBoundaryPreservation,
);
assert_eq!(pdu_header.written_len(), 10);
assert_eq!(pdu_header.header_len(), 10);
let mut buf: [u8; 16] = [0; 16];
let res = pdu_header.write_to_be_bytes(&mut buf);
assert!(res.is_ok(), "{}", format!("Result {res:?} not okay"));

View File

@ -3,10 +3,9 @@
//!
//! You can find the PUS telecommand definitions in the [crate::tc] module and ithe PUS telemetry definitions
//! inside the [crate::tm] module.
use crate::{ByteConversionError, CcsdsPacket};
use crate::{ByteConversionError, CcsdsPacket, CRC_CCITT_FALSE};
use core::fmt::{Debug, Display, Formatter};
use core::mem::size_of;
use crc::{Crc, CRC_16_IBM_3740};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -19,9 +18,6 @@ pub mod scheduling;
pub mod verification;
pub type CrcType = u16;
/// CRC algorithm used by the PUS standard.
pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
pub const CCSDS_HEADER_LEN: usize = size_of::<crate::zc::SpHeader>();
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
@ -261,7 +257,10 @@ pub(crate) fn user_data_from_raw(
}
}
pub(crate) fn verify_crc16_from_raw(raw_data: &[u8], crc16: u16) -> Result<(), PusError> {
pub(crate) fn verify_crc16_ccitt_false_from_raw(
raw_data: &[u8],
crc16: u16,
) -> Result<(), PusError> {
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(raw_data);
if digest.finalize() == 0 {

View File

@ -61,6 +61,7 @@ extern crate std;
use crate::ecss::CCSDS_HEADER_LEN;
use core::fmt::{Debug, Display, Formatter};
use crc::{Crc, CRC_16_IBM_3740};
use delegate::delegate;
#[cfg(not(feature = "std"))]
use num_traits::Unsigned;
@ -81,6 +82,9 @@ mod private {
pub trait Sealed {}
}
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard.
pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
pub const MAX_APID: u16 = 2u16.pow(11) - 1;
pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1;

View File

@ -33,12 +33,12 @@
//! ```
use crate::ecss::{
ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls, user_data_from_raw,
verify_crc16_from_raw, CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE,
verify_crc16_ccitt_false_from_raw, CrcType, PusError, PusPacket, PusVersion,
};
use crate::SpHeader;
use crate::{
ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SizeMissmatch, CCSDS_HEADER_LEN,
};
use crate::{SpHeader, CRC_CCITT_FALSE};
use core::mem::size_of;
use delegate::delegate;
#[cfg(feature = "serde")]
@ -437,7 +437,7 @@ impl<'raw_data> PusTc<'raw_data> {
calc_crc_on_serialization: false,
crc16: Some(crc_from_raw_data(raw_data)?),
};
verify_crc16_from_raw(raw_data, pus_tc.crc16.expect("CRC16 invalid"))?;
verify_crc16_ccitt_false_from_raw(raw_data, pus_tc.crc16.expect("CRC16 invalid"))?;
Ok((pus_tc, total_len))
}

View File

@ -2,11 +2,11 @@
//! to [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
use crate::ecss::{
ccsds_impl, crc_from_raw_data, crc_procedure, sp_header_impls, user_data_from_raw,
verify_crc16_from_raw, CrcType, PusError, PusPacket, PusVersion, CRC_CCITT_FALSE,
verify_crc16_ccitt_false_from_raw, CrcType, PusError, PusPacket, PusVersion,
};
use crate::{
ByteConversionError, CcsdsPacket, PacketType, SequenceFlags, SizeMissmatch, SpHeader,
CCSDS_HEADER_LEN,
CCSDS_HEADER_LEN, CRC_CCITT_FALSE,
};
use core::mem::size_of;
#[cfg(feature = "serde")]
@ -439,7 +439,7 @@ impl<'raw_data> PusTm<'raw_data> {
calc_crc_on_serialization: false,
crc16: Some(crc_from_raw_data(raw_data)?),
};
verify_crc16_from_raw(raw_data, pus_tm.crc16.expect("CRC16 invalid"))?;
verify_crc16_ccitt_false_from_raw(raw_data, pus_tm.crc16.expect("CRC16 invalid"))?;
Ok((pus_tm, total_len))
}