CFDP initial packet support #14
@ -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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user