From ce0848dc28e4c4da6f3790d216e3f890ff026b6c Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 29 May 2023 13:46:19 +0200 Subject: [PATCH] base line metadata PDU impl done --- CHANGELOG.md | 2 + src/cfdp/pdu/metadata.rs | 86 ++++++++++++++++++++++++++++++++++++++-- src/cfdp/pdu/mod.rs | 78 ++++++++++++++++++++++++++++++++++-- src/ecss/mod.rs | 11 +++-- src/lib.rs | 4 ++ src/tc.rs | 6 +-- src/tm.rs | 6 +-- 7 files changed, 173 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e1d69f..50a2301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs index 1849a2f..91f992b 100644 --- a/src/cfdp/pdu/metadata.rs +++ b/src/cfdp/pdu/metadata.rs @@ -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 { 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::()] @@ -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, 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); } } diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 23f2f77..e2fd887 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -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 { // 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 { + 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")); diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index 5d755bc..4ebf787 100644 --- a/src/ecss/mod.rs +++ b/src/ecss/mod.rs @@ -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 = Crc::::new(&CRC_16_IBM_3740); pub const CCSDS_HEADER_LEN: usize = size_of::(); #[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 { diff --git a/src/lib.rs b/src/lib.rs index 2c3dbda..5062c18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 = Crc::::new(&CRC_16_IBM_3740); + pub const MAX_APID: u16 = 2u16.pow(11) - 1; pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1; diff --git a/src/tc.rs b/src/tc.rs index 184e799..8b75f1c 100644 --- a/src/tc.rs +++ b/src/tc.rs @@ -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)) } diff --git a/src/tm.rs b/src/tm.rs index 5813ccc..ed84a2e 100644 --- a/src/tm.rs +++ b/src/tm.rs @@ -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)) }