diff --git a/.idea/runConfigurations/Check.xml b/.idea/runConfigurations/Check.xml new file mode 100644 index 0000000..502aa84 --- /dev/null +++ b/.idea/runConfigurations/Check.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Test.xml b/.idea/runConfigurations/Test.xml new file mode 100644 index 0000000..d051ff1 --- /dev/null +++ b/.idea/runConfigurations/Test.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a2f15..50a2301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,23 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +## Added + +- Added new `util` module which contains the following (new) helper modules: + - `UnsignedEnum` trait as an abstraction for unsigned byte fields with variable lengths. It is + not tied to the ECSS PFC value like the `EcssEnumeration` trait. + - `GenericUnsignedByteField` and helper typedefs `UnsignedU8`, `UnsignedU16`, `UnsignedU32` + and `UnsignedU64` as helper types implementing `UnsignedEnum` + - `UnsignedByteField` as a type-erased helper. +- Initial CFDP support: Added PDU packet implementation. + +## Changed + +- 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 ## Added diff --git a/Cargo.toml b/Cargo.toml index debc410..ec45f26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ crc = "3" delegate = ">=0.8, <0.10" [dependencies.num_enum] -version = "0.5" +version = "0.6" default-features = false [dependencies.serde] diff --git a/README.md b/README.md index ca85e6c..a440074 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Currently, this includes the following components: - Space Packet implementation according to [CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf) +- CCSDS File Delivery Protocol (CFDP) packet implementations according to + [CCSDS Blue Book 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf) - PUS Telecommand and PUS Telemetry implementation according to the [ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). - CUC (CCSDS Unsegmented Time Code) implementation according to diff --git a/src/cfdp/lv.rs b/src/cfdp/lv.rs new file mode 100644 index 0000000..96f932d --- /dev/null +++ b/src/cfdp/lv.rs @@ -0,0 +1,308 @@ +//! Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. +use crate::cfdp::TlvLvError; +use crate::{ByteConversionError, SizeMissmatch}; +use core::str::Utf8Error; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "std")] +use std::string::String; + +pub const MIN_LV_LEN: usize = 1; + +/// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. +/// +/// # Lifetimes +/// * `data`: If the LV is generated from a raw bytestream, this will be the lifetime of +/// the raw bytestream. If the LV is generated from a raw slice or a similar data reference, +/// this will be the lifetime of that data reference. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Lv<'data> { + data: Option<&'data [u8]>, +} + +pub(crate) fn generic_len_check_data_serialization( + buf: &[u8], + data_len: usize, + min_overhead: usize, +) -> Result<(), ByteConversionError> { + if buf.len() < data_len + min_overhead { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: data_len + min_overhead, + })); + } + Ok(()) +} + +pub(crate) fn generic_len_check_deserialization( + buf: &[u8], + min_overhead: usize, +) -> Result<(), ByteConversionError> { + if buf.len() < min_overhead { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: min_overhead, + })); + } + Ok(()) +} + +impl<'data> Lv<'data> { + pub fn new(data: &[u8]) -> Result { + if data.len() > u8::MAX as usize { + return Err(TlvLvError::DataTooLarge(data.len())); + } + Ok(Lv { data: Some(data) }) + } + + /// Creates a LV with an empty value field. + pub fn new_empty() -> Lv<'data> { + Lv { data: None } + } + + /// Helper function to build a string LV. This is especially useful for the file or directory + /// path LVs + pub fn new_from_str(str_slice: &str) -> Result { + Self::new(str_slice.as_bytes()) + } + + /// Helper function to build a string LV. This is especially useful for the file or directory + /// path LVs + #[cfg(feature = "std")] + pub fn new_from_string(string: &'data String) -> Result, TlvLvError> { + Self::new(string.as_bytes()) + } + + /// Returns the length of the value part, not including the length byte. + pub fn len_value(&self) -> usize { + if self.data.is_none() { + return 0; + } + self.data.unwrap().len() + } + + /// Returns the full raw length, including the length byte. + pub fn len_full(&self) -> usize { + self.len_value() + 1 + } + + /// Checks whether the value field is empty. + pub fn is_empty(&self) -> bool { + self.data.is_none() + } + + pub fn value(&self) -> Option<&[u8]> { + self.data + } + + /// Convenience function to extract the value as a [str]. This is useful if the LV is + /// known to contain a [str], for example being a file name. + pub fn value_as_str(&self) -> Option> { + self.data?; + Some(core::str::from_utf8(self.data.unwrap())) + } + + /// Writes the LV to a raw buffer. Please note that the first byte will contain the length + /// of the value, but the values may not exceed a length of [u8::MAX]. + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + generic_len_check_data_serialization(buf, self.len_value(), MIN_LV_LEN)?; + Ok(self.write_to_be_bytes_no_len_check(buf)) + } + + /// Reads a LV from a raw buffer. + pub fn from_bytes(buf: &'data [u8]) -> Result, ByteConversionError> { + generic_len_check_deserialization(buf, MIN_LV_LEN)?; + Self::from_be_bytes_no_len_check(buf) + } + + pub(crate) fn write_to_be_bytes_no_len_check(&self, buf: &mut [u8]) -> usize { + if self.data.is_none() { + buf[0] = 0; + return MIN_LV_LEN; + } + let data = self.data.unwrap(); + // Length check in constructor ensures the length always has a valid value. + buf[0] = data.len() as u8; + buf[MIN_LV_LEN..data.len() + MIN_LV_LEN].copy_from_slice(data); + MIN_LV_LEN + data.len() + } + + pub(crate) fn from_be_bytes_no_len_check( + buf: &'data [u8], + ) -> Result, ByteConversionError> { + let value_len = buf[0] as usize; + generic_len_check_deserialization(buf, value_len + MIN_LV_LEN)?; + let mut data = None; + if value_len > 0 { + data = Some(&buf[MIN_LV_LEN..MIN_LV_LEN + value_len]) + } + Ok(Self { data }) + } +} + +#[cfg(test)] +pub mod tests { + use crate::cfdp::lv::Lv; + use crate::cfdp::TlvLvError; + use crate::ByteConversionError; + use std::string::String; + + #[test] + fn test_basic() { + let lv_data: [u8; 4] = [1, 2, 3, 4]; + let lv_res = Lv::new(&lv_data); + assert!(lv_res.is_ok()); + let lv = lv_res.unwrap(); + assert!(lv.value().is_some()); + let val = lv.value().unwrap(); + assert_eq!(val[0], 1); + assert_eq!(val[1], 2); + assert_eq!(val[2], 3); + assert_eq!(val[3], 4); + assert!(!lv.is_empty()); + assert_eq!(lv.len_full(), 5); + assert_eq!(lv.len_value(), 4); + } + + #[test] + fn test_empty() { + let lv_empty = Lv::new_empty(); + assert_eq!(lv_empty.len_value(), 0); + assert_eq!(lv_empty.len_full(), 1); + assert!(lv_empty.is_empty()); + assert_eq!(lv_empty.value(), None); + let mut buf: [u8; 4] = [0xff; 4]; + let res = lv_empty.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, 1); + assert_eq!(buf[0], 0); + } + + #[test] + fn test_serialization() { + let lv_data: [u8; 4] = [1, 2, 3, 4]; + let lv_res = Lv::new(&lv_data); + assert!(lv_res.is_ok()); + let lv = lv_res.unwrap(); + let mut buf: [u8; 16] = [0; 16]; + let res = lv.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, 5); + assert_eq!(buf[0], 4); + assert_eq!(buf[1], 1); + assert_eq!(buf[2], 2); + assert_eq!(buf[3], 3); + assert_eq!(buf[4], 4); + } + + #[test] + fn test_deserialization() { + let mut buf: [u8; 16] = [0; 16]; + buf[0] = 4; + buf[1] = 1; + buf[2] = 2; + buf[3] = 3; + buf[4] = 4; + let lv = Lv::from_bytes(&buf); + assert!(lv.is_ok()); + let lv = lv.unwrap(); + assert!(!lv.is_empty()); + assert!(lv.value().is_some()); + assert_eq!(lv.len_value(), 4); + assert_eq!(lv.len_full(), 5); + let val = lv.value().unwrap(); + assert_eq!(val[0], 1); + assert_eq!(val[1], 2); + assert_eq!(val[2], 3); + assert_eq!(val[3], 4); + } + + #[test] + fn test_deserialization_empty() { + let buf: [u8; 2] = [0; 2]; + let lv_empty = Lv::from_bytes(&buf); + assert!(lv_empty.is_ok()); + let lv_empty = lv_empty.unwrap(); + assert!(lv_empty.is_empty()); + assert!(lv_empty.value().is_none()); + } + + #[test] + fn test_data_too_large() { + let data_big: [u8; u8::MAX as usize + 1] = [0; u8::MAX as usize + 1]; + let lv = Lv::new(&data_big); + assert!(lv.is_err()); + let error = lv.unwrap_err(); + if let TlvLvError::DataTooLarge(size) = error { + assert_eq!(size, u8::MAX as usize + 1); + } else { + panic!("invalid exception {:?}", error) + } + } + + #[test] + fn test_serialization_buf_too_small() { + let mut buf: [u8; 3] = [0; 3]; + let lv_data: [u8; 4] = [1, 2, 3, 4]; + let lv = Lv::new(&lv_data).unwrap(); + let res = lv.write_to_be_bytes(&mut buf); + assert!(res.is_err()); + let error = res.unwrap_err(); + if let ByteConversionError::ToSliceTooSmall(missmatch) = error { + assert_eq!(missmatch.expected, 5); + assert_eq!(missmatch.found, 3); + } else { + panic!("invalid error {}", error); + } + } + + #[test] + fn test_deserialization_buf_too_small() { + let mut buf: [u8; 3] = [0; 3]; + buf[0] = 4; + let res = Lv::from_bytes(&buf); + assert!(res.is_err()); + let error = res.unwrap_err(); + if let ByteConversionError::FromSliceTooSmall(missmatch) = error { + assert_eq!(missmatch.found, 3); + assert_eq!(missmatch.expected, 5); + } else { + panic!("invalid error {}", error); + } + } + + fn verify_test_str_lv(lv: Lv) { + let mut buf: [u8; 16] = [0; 16]; + let res = lv.write_to_be_bytes(&mut buf); + assert!(res.is_ok()); + let res = res.unwrap(); + assert_eq!(res, 8 + 1); + assert_eq!(buf[0], 8); + assert_eq!(buf[1], 't' as u8); + assert_eq!(buf[2], 'e' as u8); + assert_eq!(buf[3], 's' as u8); + assert_eq!(buf[4], 't' as u8); + assert_eq!(buf[5], '.' as u8); + assert_eq!(buf[6], 'b' as u8); + assert_eq!(buf[7], 'i' as u8); + assert_eq!(buf[8], 'n' as u8); + } + #[test] + fn test_str_helper() { + let test_str = "test.bin"; + let str_lv = Lv::new_from_str(test_str); + assert!(str_lv.is_ok()); + verify_test_str_lv(str_lv.unwrap()); + } + + #[test] + fn test_string_helper() { + let string = String::from("test.bin"); + let str_lv = Lv::new_from_string(&string); + assert!(str_lv.is_ok()); + verify_test_str_lv(str_lv.unwrap()); + } +} diff --git a/src/cfdp/mod.rs b/src/cfdp/mod.rs new file mode 100644 index 0000000..b6c082f --- /dev/null +++ b/src/cfdp/mod.rs @@ -0,0 +1,203 @@ +//! Low-level CCSDS File Delivery Protocol (CFDP) support according to [CCSDS 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf). +use crate::ByteConversionError; +use core::fmt::{Display, Formatter}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "std")] +use std::error::Error; + +pub mod lv; +pub mod pdu; +pub mod tlv; + +/// This is the name of the standard this module is based on. +pub const CFDP_VERSION_2_NAME: &str = "CCSDS 727.0-B-5"; +/// Currently, only this version is supported. +pub const CFDP_VERSION_2: u8 = 0b001; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum PduType { + FileDirective = 0, + FileData = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum Direction { + TowardsReceiver = 0, + TowardsSender = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum TransmissionMode { + Acknowledged = 0, + Unacknowledged = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum CrcFlag { + NoCrc = 0, + WithCrc = 1, +} + +/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75) +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum SegmentMetadataFlag { + NotPresent = 0, + Present = 1, +} + +/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75) +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum SegmentationControl { + NoRecordBoundaryPreservation = 0, + WithRecordBoundaryPreservation = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum FaultHandlerCode { + NoticeOfCancellation = 0b0001, + NoticeOfSuspension = 0b0010, + IgnoreError = 0b0011, + AbandonTransaction = 0b0100, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum LenInBytes { + ZeroOrNone = 0, + OneByte = 1, + TwoBytes = 2, + ThreeBytes = 4, + FourBytes = 8, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum ConditionCode { + /// This is not an error condition for which a faulty handler override can be specified + NoError = 0b0000, + PositiveAckLimitReached = 0b0001, + KeepAliveLimitReached = 0b0010, + InvalidTransmissionMode = 0b0011, + FilestoreRejection = 0b0100, + FileChecksumFailure = 0b0101, + FileSizeError = 0b0110, + NakLimitReached = 0b0111, + InactivityDetected = 0b1000, + CheckLimitReached = 0b1001, + UnsupportedChecksumType = 0b1011, + /// Not an actual fault condition for which fault handler overrides can be specified + SuspendRequestReceived = 0b1110, + /// Not an actual fault condition for which fault handler overrides can be specified + CancelRequestReceived = 0b1111, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum LargeFileFlag { + /// 32 bit maximum file size and FSS size + Normal = 0, + /// 64 bit maximum file size and FSS size + Large = 1, +} + +/// Checksum types according to the +/// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/) +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum ChecksumType { + /// Modular legacy checksum + Modular = 0, + Crc32Proximity1 = 1, + Crc32C = 2, + /// Polynomial: 0x4C11DB7. Preferred checksum for now. + Crc32 = 3, + NullChecksum = 15, +} + +pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4]; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TlvLvError { + DataTooLarge(usize), + ByteConversionError(ByteConversionError), + /// First value: Found value. Second value: Expected value if there is one. + InvalidTlvTypeField((u8, Option)), + /// Logically invalid value length detected. The value length may not exceed 255 bytes. + /// Depending on the concrete TLV type, the value length may also be logically invalid. + InvalidValueLength(usize), + /// Only applies to filestore requests and responses. Second name was missing where one is + /// expected. + SecondNameMissing, + /// Invalid action code for filestore requests or responses. + InvalidFilestoreActionCode(u8), +} + +impl From for TlvLvError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversionError(value) + } +} + +impl Display for TlvLvError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + TlvLvError::DataTooLarge(data_len) => { + write!( + f, + "data with size {} larger than allowed {} bytes", + data_len, + u8::MAX + ) + } + TlvLvError::ByteConversionError(e) => { + write!(f, "{}", e) + } + TlvLvError::InvalidTlvTypeField((found, expected)) => { + write!( + f, + "invalid TLV type field, found {found}, possibly expected {expected:?}" + ) + } + TlvLvError::InvalidValueLength(len) => { + write!(f, "invalid value length {len} detected") + } + TlvLvError::SecondNameMissing => { + write!(f, "second name missing for filestore request or response") + } + TlvLvError::InvalidFilestoreActionCode(raw) => { + write!(f, "invalid filestore action code with raw value {raw}") + } + } + } +} + +#[cfg(feature = "std")] +impl Error for TlvLvError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + TlvLvError::ByteConversionError(e) => Some(e), + _ => None, + } + } +} diff --git a/src/cfdp/pdu/eof.rs b/src/cfdp/pdu/eof.rs new file mode 100644 index 0000000..71f977b --- /dev/null +++ b/src/cfdp/pdu/eof.rs @@ -0,0 +1,206 @@ +use crate::cfdp::pdu::{ + add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, + FileDirectiveType, PduError, PduHeader, +}; +use crate::cfdp::tlv::EntityIdTlv; +use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag}; +use crate::{ByteConversionError, SizeMissmatch}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Finished PDU abstraction. +/// +/// For more information, refer to CFDP chapter 5.2.2. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct EofPdu { + pdu_header: PduHeader, + condition_code: ConditionCode, + file_checksum: u32, + file_size: u64, + fault_location: Option, +} + +impl EofPdu { + pub fn new_no_error(pdu_header: PduHeader, file_checksum: u32, file_size: u64) -> Self { + let mut eof_pdu = Self { + pdu_header, + condition_code: ConditionCode::NoError, + file_checksum, + file_size, + fault_location: None, + }; + eof_pdu.pdu_header.pdu_datafield_len = eof_pdu.calc_pdu_datafield_len() as u16; + eof_pdu + } + + pub fn pdu_header(&self) -> &PduHeader { + &self.pdu_header + } + + pub fn written_len(&self) -> usize { + self.pdu_header.header_len() + self.calc_pdu_datafield_len() + } + + pub fn condition_code(&self) -> ConditionCode { + self.condition_code + } + + pub fn file_checksum(&self) -> u32 { + self.file_checksum + } + + pub fn file_size(&self) -> u64 { + self.file_size + } + + fn calc_pdu_datafield_len(&self) -> usize { + // One directive type octet, 4 bits condition code, 4 spare bits. + let mut len = 2 + core::mem::size_of::() + 4; + if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { + len += 4; + } + if let Some(fault_location) = self.fault_location { + len += fault_location.len_full(); + } + len + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + let expected_len = self.written_len(); + if buf.len() < expected_len { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: expected_len, + }) + .into()); + } + let mut current_idx = self.pdu_header.write_to_bytes(buf)?; + buf[current_idx] = FileDirectiveType::EofPdu as u8; + current_idx += 1; + buf[current_idx] = (self.condition_code as u8) << 4; + current_idx += 1; + buf[current_idx..current_idx + 4].copy_from_slice(&self.file_checksum.to_be_bytes()); + current_idx += 4; + current_idx += write_fss_field( + self.pdu_header.pdu_conf.file_flag, + self.file_size, + &mut buf[current_idx..], + )?; + if let Some(fault_location) = self.fault_location { + current_idx += fault_location.write_to_be_bytes(buf)?; + } + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + current_idx = add_pdu_crc(buf, current_idx); + } + Ok(current_idx) + } + + pub fn from_bytes(buf: &[u8]) -> Result { + let (pdu_header, mut current_idx) = PduHeader::from_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; + let mut min_expected_len = 2 + 4 + 4; + if is_large_file { + min_expected_len += 4; + } + generic_length_checks_pdu_deserialization(buf, min_expected_len, full_len_without_crc)?; + let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { + PduError::InvalidDirectiveType((buf[current_idx], FileDirectiveType::EofPdu)) + })?; + if directive_type != FileDirectiveType::EofPdu { + return Err(PduError::WrongDirectiveType(( + directive_type, + FileDirectiveType::EofPdu, + ))); + } + current_idx += 1; + let condition_code = ConditionCode::try_from((buf[current_idx] >> 4) & 0b1111) + .map_err(|_| PduError::InvalidConditionCode((buf[current_idx] >> 4) & 0b1111))?; + current_idx += 1; + let file_checksum = + u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); + current_idx += 4; + let (fss_field_len, file_size) = + read_fss_field(pdu_header.pdu_conf.file_flag, &buf[current_idx..]); + current_idx += fss_field_len; + let mut fault_location = None; + if condition_code != ConditionCode::NoError && current_idx < full_len_without_crc { + fault_location = Some(EntityIdTlv::from_bytes(&buf[current_idx..])?); + } + Ok(Self { + pdu_header, + condition_code, + file_checksum, + file_size, + fault_location, + }) + } +} + +#[cfg(test)] +mod tests { + use crate::cfdp::pdu::eof::EofPdu; + use crate::cfdp::pdu::tests::{common_pdu_conf, verify_raw_header}; + use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; + use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag}; + + #[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 eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); + assert_eq!(eof_pdu.written_len(), pdu_header.header_len() + 2 + 4 + 4); + assert_eq!(eof_pdu.file_checksum(), 0x01020304); + assert_eq!(eof_pdu.file_size(), 12); + assert_eq!(eof_pdu.condition_code(), ConditionCode::NoError); + } + + #[test] + fn test_serialization() { + let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, 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 res = eof_pdu.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, eof_pdu.written_len()); + verify_raw_header(eof_pdu.pdu_header(), &buf); + let mut current_idx = eof_pdu.pdu_header().header_len(); + buf[current_idx] = FileDirectiveType::EofPdu as u8; + current_idx += 1; + assert_eq!( + (buf[current_idx] >> 4) & 0b1111, + ConditionCode::NoError as u8 + ); + current_idx += 1; + assert_eq!( + u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()), + 0x01020304 + ); + current_idx += 4; + assert_eq!( + u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()), + 12 + ); + current_idx += 4; + assert_eq!(current_idx, written); + } + + #[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 eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); + let mut buf: [u8; 64] = [0; 64]; + eof_pdu.write_to_bytes(&mut buf).unwrap(); + let eof_read_back = EofPdu::from_bytes(&buf); + if !eof_read_back.is_ok() { + let e = eof_read_back.unwrap_err(); + panic!("deserialization failed with: {e}") + } + let eof_read_back = eof_read_back.unwrap(); + assert_eq!(eof_read_back, eof_pdu); + } +} diff --git a/src/cfdp/pdu/file_data.rs b/src/cfdp/pdu/file_data.rs new file mode 100644 index 0000000..ee6846c --- /dev/null +++ b/src/cfdp/pdu/file_data.rs @@ -0,0 +1,397 @@ +use crate::cfdp::pdu::{ + add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, + PduError, PduHeader, +}; +use crate::cfdp::{CrcFlag, LargeFileFlag, PduType, SegmentMetadataFlag}; +use crate::{ByteConversionError, SizeMissmatch}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum RecordContinuationState { + NoStartNoEnd = 0b00, + StartWithoutEnd = 0b01, + EndWithoutStart = 0b10, + StartAndEnd = 0b11, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct SegmentMetadata<'seg_meta> { + record_continuation_state: RecordContinuationState, + metadata: Option<&'seg_meta [u8]>, +} + +impl<'seg_meta> SegmentMetadata<'seg_meta> { + pub fn new( + record_continuation_state: RecordContinuationState, + metadata: Option<&'seg_meta [u8]>, + ) -> Option { + if let Some(metadata) = metadata { + if metadata.len() > 2_usize.pow(6) - 1 { + return None; + } + } + Some(Self { + record_continuation_state, + metadata, + }) + } + + pub fn written_len(&self) -> usize { + // Map empty metadata to 0 and slice to its length. + 1 + self.metadata.map_or(0, |meta| meta.len()) + } + + pub(crate) fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.written_len() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.written_len(), + })); + } + buf[0] = ((self.record_continuation_state as u8) << 6) + | self.metadata.map_or(0, |meta| meta.len() as u8); + if let Some(metadata) = self.metadata { + buf[1..1 + metadata.len()].copy_from_slice(metadata) + } + Ok(self.written_len()) + } + + pub(crate) fn from_bytes(buf: &'seg_meta [u8]) -> Result { + if buf.is_empty() { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 2, + })); + } + let mut metadata = None; + let seg_metadata_len = (buf[0] & 0b111111) as usize; + if seg_metadata_len > 0 { + metadata = Some(&buf[1..1 + seg_metadata_len]); + } + Ok(Self { + // Can't fail, only 2 bits + record_continuation_state: RecordContinuationState::try_from((buf[0] >> 6) & 0b11) + .unwrap(), + metadata, + }) + } +} + +/// File Data PDU abstraction. +/// +/// For more information, refer to CFDP chapter 5.3. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct FileDataPdu<'seg_meta, 'file_data> { + pdu_header: PduHeader, + #[cfg_attr(feature = "serde", serde(borrow))] + segment_metadata: Option>, + offset: u64, + file_data: &'file_data [u8], +} + +impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { + pub fn new_with_seg_metadata( + pdu_header: PduHeader, + segment_metadata: SegmentMetadata<'seg_meta>, + offset: u64, + file_data: &'file_data [u8], + ) -> Self { + Self::new_generic(pdu_header, Some(segment_metadata), offset, file_data) + } + + pub fn new_no_seg_metadata( + pdu_header: PduHeader, + offset: u64, + file_data: &'file_data [u8], + ) -> Self { + Self::new_generic(pdu_header, None, offset, file_data) + } + + pub fn new_generic( + mut pdu_header: PduHeader, + segment_metadata: Option>, + offset: u64, + file_data: &'file_data [u8], + ) -> Self { + pdu_header.pdu_type = PduType::FileData; + if segment_metadata.is_some() { + pdu_header.seg_metadata_flag = SegmentMetadataFlag::Present; + } + let mut pdu = Self { + pdu_header, + segment_metadata, + offset, + file_data, + }; + pdu.pdu_header.pdu_datafield_len = pdu.calc_pdu_datafield_len() as u16; + pdu + } + + fn calc_pdu_datafield_len(&self) -> usize { + let mut len = core::mem::size_of::(); + if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { + len += 4; + } + if self.segment_metadata.is_some() { + len += self.segment_metadata.as_ref().unwrap().written_len() + } + len += self.file_data.len(); + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + len += 2; + } + len + } + pub fn written_len(&self) -> usize { + self.pdu_header.header_len() + self.calc_pdu_datafield_len() + } + + pub fn offset(&self) -> u64 { + self.offset + } + + pub fn file_data(&self) -> &'file_data [u8] { + self.file_data + } + + pub fn segment_metadata(&self) -> Option<&SegmentMetadata> { + self.segment_metadata.as_ref() + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.written_len() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.written_len(), + }) + .into()); + } + let mut current_idx = self.pdu_header.write_to_bytes(buf)?; + if self.segment_metadata.is_some() { + current_idx += self + .segment_metadata + .as_ref() + .unwrap() + .write_to_bytes(&mut buf[current_idx..])?; + } + current_idx += write_fss_field( + self.pdu_header.common_pdu_conf().file_flag, + self.offset, + &mut buf[current_idx..], + )?; + buf[current_idx..current_idx + self.file_data.len()].copy_from_slice(self.file_data); + current_idx += self.file_data.len(); + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + current_idx = add_pdu_crc(buf, current_idx); + } + Ok(current_idx) + } + + pub fn from_bytes<'longest: 'seg_meta + 'file_data>( + buf: &'longest [u8], + ) -> Result { + let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; + let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; + let min_expected_len = current_idx + core::mem::size_of::(); + generic_length_checks_pdu_deserialization(buf, min_expected_len, full_len_without_crc)?; + let mut segment_metadata = None; + if pdu_header.seg_metadata_flag == SegmentMetadataFlag::Present { + segment_metadata = Some(SegmentMetadata::from_bytes(&buf[current_idx..])?); + current_idx += segment_metadata.as_ref().unwrap().written_len(); + } + let (fss, offset) = read_fss_field(pdu_header.pdu_conf.file_flag, &buf[current_idx..]); + current_idx += fss; + if current_idx > full_len_without_crc { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: current_idx, + expected: full_len_without_crc, + }) + .into()); + } + Ok(Self { + pdu_header, + segment_metadata, + offset, + file_data: &buf[current_idx..full_len_without_crc], + }) + } +} + +#[cfg(test)] +mod tests { + use crate::cfdp::pdu::file_data::{FileDataPdu, RecordContinuationState, SegmentMetadata}; + use crate::cfdp::pdu::{CommonPduConfig, PduHeader}; + use crate::cfdp::{SegmentMetadataFlag, SegmentationControl}; + use crate::util::UbfU8; + + #[test] + fn test_basic() { + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_num = UbfU8::new(3); + let common_conf = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + 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); + assert_eq!(fd_pdu.file_data(), file_data); + assert_eq!(fd_pdu.offset(), 10); + assert!(fd_pdu.segment_metadata().is_none()); + assert_eq!( + fd_pdu.written_len(), + fd_pdu.pdu_header.header_len() + core::mem::size_of::() + 4 + ); + } + + #[test] + fn test_serialization() { + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_num = UbfU8::new(3); + let common_conf = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + 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; 32] = [0; 32]; + let res = fd_pdu.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!( + written, + fd_pdu.pdu_header.header_len() + core::mem::size_of::() + 4 + ); + let mut current_idx = fd_pdu.pdu_header.header_len(); + let file_size = u32::from_be_bytes( + buf[fd_pdu.pdu_header.header_len()..fd_pdu.pdu_header.header_len() + 4] + .try_into() + .unwrap(), + ); + current_idx += 4; + assert_eq!(file_size, 10); + assert_eq!(buf[current_idx], 1); + current_idx += 1; + assert_eq!(buf[current_idx], 2); + current_idx += 1; + assert_eq!(buf[current_idx], 3); + current_idx += 1; + assert_eq!(buf[current_idx], 4); + } + + #[test] + fn test_deserialization() { + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_num = UbfU8::new(3); + let common_conf = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + 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; 32] = [0; 32]; + fd_pdu.write_to_bytes(&mut buf).unwrap(); + let fd_pdu_read_back = FileDataPdu::from_bytes(&buf); + assert!(fd_pdu_read_back.is_ok()); + let fd_pdu_read_back = fd_pdu_read_back.unwrap(); + assert_eq!(fd_pdu_read_back, fd_pdu); + } + + #[test] + 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 = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + let pdu_header = PduHeader::new_for_file_data( + common_conf, + 0, + SegmentMetadataFlag::Present, + SegmentationControl::WithRecordBoundaryPreservation, + ); + let file_data: [u8; 4] = [1, 2, 3, 4]; + let seg_metadata: [u8; 4] = [4, 3, 2, 1]; + let segment_meta = + SegmentMetadata::new(RecordContinuationState::StartAndEnd, Some(&seg_metadata)) + .unwrap(); + let fd_pdu = FileDataPdu::new_with_seg_metadata(pdu_header, segment_meta, 10, &file_data); + assert!(fd_pdu.segment_metadata().is_some()); + assert_eq!(*fd_pdu.segment_metadata().unwrap(), segment_meta); + assert_eq!( + fd_pdu.written_len(), + fd_pdu.pdu_header.header_len() + + 1 + + seg_metadata.len() + + core::mem::size_of::() + + 4 + ); + let mut buf: [u8; 32] = [0; 32]; + fd_pdu + .write_to_bytes(&mut buf) + .expect("writing FD PDU failed"); + let mut current_idx = fd_pdu.pdu_header.header_len(); + assert_eq!( + RecordContinuationState::try_from((buf[current_idx] >> 6) & 0b11).unwrap(), + RecordContinuationState::StartAndEnd + ); + assert_eq!((buf[current_idx] & 0b111111) as usize, seg_metadata.len()); + current_idx += 1; + assert_eq!(buf[current_idx], 4); + current_idx += 1; + assert_eq!(buf[current_idx], 3); + current_idx += 1; + assert_eq!(buf[current_idx], 2); + current_idx += 1; + assert_eq!(buf[current_idx], 1); + current_idx += 1; + // Still verify that the rest is written correctly. + assert_eq!( + u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()), + 10 + ); + current_idx += 4; + assert_eq!(buf[current_idx], 1); + current_idx += 1; + assert_eq!(buf[current_idx], 2); + current_idx += 1; + assert_eq!(buf[current_idx], 3); + current_idx += 1; + assert_eq!(buf[current_idx], 4); + current_idx += 1; + assert_eq!(current_idx, fd_pdu.written_len()); + } + + #[test] + 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 = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num).unwrap(); + let pdu_header = PduHeader::new_for_file_data( + common_conf, + 0, + SegmentMetadataFlag::Present, + SegmentationControl::WithRecordBoundaryPreservation, + ); + let file_data: [u8; 4] = [1, 2, 3, 4]; + let seg_metadata: [u8; 4] = [4, 3, 2, 1]; + let segment_meta = + SegmentMetadata::new(RecordContinuationState::StartAndEnd, Some(&seg_metadata)) + .unwrap(); + let fd_pdu = FileDataPdu::new_with_seg_metadata(pdu_header, segment_meta, 10, &file_data); + let mut buf: [u8; 32] = [0; 32]; + fd_pdu + .write_to_bytes(&mut buf) + .expect("writing FD PDU failed"); + let fd_pdu_read_back = FileDataPdu::from_bytes(&buf); + assert!(fd_pdu_read_back.is_ok()); + let fd_pdu_read_back = fd_pdu_read_back.unwrap(); + assert_eq!(fd_pdu_read_back, fd_pdu); + } +} diff --git a/src/cfdp/pdu/finished.rs b/src/cfdp/pdu/finished.rs new file mode 100644 index 0000000..29cf747 --- /dev/null +++ b/src/cfdp/pdu/finished.rs @@ -0,0 +1,336 @@ +use crate::cfdp::pdu::{ + add_pdu_crc, generic_length_checks_pdu_deserialization, FileDirectiveType, PduError, PduHeader, +}; +use crate::cfdp::tlv::{EntityIdTlv, Tlv, TlvType, TlvTypeField}; +use crate::cfdp::{ConditionCode, CrcFlag, PduType, TlvLvError}; +use crate::{ByteConversionError, SizeMissmatch}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum DeliveryCode { + Complete = 0, + Incomplete = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum FileStatus { + DiscardDeliberately = 0b00, + DiscardedFsRejection = 0b01, + Retained = 0b10, + Unreported = 0b11, +} + +/// Finished PDU abstraction. +/// +/// For more information, refer to CFDP chapter 5.2.3. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct FinishedPdu<'fs_responses> { + pdu_header: PduHeader, + condition_code: ConditionCode, + delivery_code: DeliveryCode, + file_status: FileStatus, + fs_responses: Option<&'fs_responses [u8]>, + fault_location: Option, +} + +impl<'fs_responses> FinishedPdu<'fs_responses> { + /// Default finished PDU: No error (no fault location field) and no filestore responses. + pub fn new_default( + pdu_header: PduHeader, + delivery_code: DeliveryCode, + file_status: FileStatus, + ) -> Self { + Self::new_generic( + pdu_header, + ConditionCode::NoError, + delivery_code, + file_status, + None, + None, + ) + } + + pub fn new_with_error( + pdu_header: PduHeader, + condition_code: ConditionCode, + delivery_code: DeliveryCode, + file_status: FileStatus, + fault_location: EntityIdTlv, + ) -> Self { + Self::new_generic( + pdu_header, + condition_code, + delivery_code, + file_status, + None, + Some(fault_location), + ) + } + + pub fn new_generic( + mut pdu_header: PduHeader, + condition_code: ConditionCode, + delivery_code: DeliveryCode, + file_status: FileStatus, + fs_responses: Option<&'fs_responses [u8]>, + fault_location: Option, + ) -> Self { + pdu_header.pdu_type = PduType::FileDirective; + let mut finished_pdu = Self { + pdu_header, + condition_code, + delivery_code, + file_status, + fs_responses, + fault_location, + }; + finished_pdu.pdu_header.pdu_datafield_len = finished_pdu.calc_pdu_datafield_len() as u16; + finished_pdu + } + pub fn pdu_header(&self) -> &PduHeader { + &self.pdu_header + } + + pub fn written_len(&self) -> usize { + self.pdu_header.header_len() + self.calc_pdu_datafield_len() + } + + pub fn condition_code(&self) -> ConditionCode { + self.condition_code + } + + pub fn delivery_code(&self) -> DeliveryCode { + self.delivery_code + } + + pub fn file_status(&self) -> FileStatus { + self.file_status + } + + pub fn filestore_responses(&self) -> Option<&'fs_responses [u8]> { + self.fs_responses + } + + pub fn fault_location(&self) -> Option { + self.fault_location + } + + fn calc_pdu_datafield_len(&self) -> usize { + let mut base_len = 2; + if let Some(fs_responses) = self.fs_responses { + base_len += fs_responses.len(); + } + if let Some(fault_location) = self.fault_location { + base_len += fault_location.len_full(); + } + base_len + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + let expected_len = self.written_len(); + if buf.len() < expected_len { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: expected_len, + }) + .into()); + } + + let mut current_idx = self.pdu_header.write_to_bytes(buf)?; + buf[current_idx] = FileDirectiveType::FinishedPdu as u8; + current_idx += 1; + buf[current_idx] = ((self.condition_code as u8) << 4) + | ((self.delivery_code as u8) << 2) + | self.file_status as u8; + current_idx += 1; + if let Some(fs_responses) = self.fs_responses { + buf[current_idx..current_idx + fs_responses.len()].copy_from_slice(fs_responses); + current_idx += fs_responses.len(); + } + if let Some(fault_location) = self.fault_location { + current_idx += fault_location.write_to_be_bytes(&mut buf[current_idx..])?; + } + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + current_idx = add_pdu_crc(buf, current_idx); + } + Ok(current_idx) + } + + /// Generates [Self] from a raw bytestream. + pub fn from_bytes(buf: &'fs_responses [u8]) -> Result { + let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; + let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; + let min_expected_len = current_idx + 2; + generic_length_checks_pdu_deserialization(buf, min_expected_len, full_len_without_crc)?; + let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { + PduError::InvalidDirectiveType((buf[current_idx], FileDirectiveType::FinishedPdu)) + })?; + if directive_type != FileDirectiveType::FinishedPdu { + return Err(PduError::WrongDirectiveType(( + directive_type, + FileDirectiveType::FinishedPdu, + ))); + } + current_idx += 1; + let condition_code = ConditionCode::try_from((buf[current_idx] >> 4) & 0b1111) + .map_err(|_| PduError::InvalidConditionCode((buf[current_idx] >> 4) & 0b1111))?; + // Unwrap is okay here for both of the following operations which can not fail. + let delivery_code = DeliveryCode::try_from((buf[current_idx] >> 2) & 0b1).unwrap(); + let file_status = FileStatus::try_from(buf[current_idx] & 0b11).unwrap(); + current_idx += 1; + let (fs_responses, fault_location) = + Self::parse_tlv_fields(current_idx, full_len_without_crc, buf)?; + Ok(Self { + pdu_header, + condition_code, + delivery_code, + file_status, + fs_responses, + fault_location, + }) + } + + fn parse_tlv_fields( + mut current_idx: usize, + full_len_without_crc: usize, + buf: &'fs_responses [u8], + ) -> Result<(Option<&'fs_responses [u8]>, Option), PduError> { + let mut fs_responses = None; + let mut fault_location = None; + let start_of_fs_responses = current_idx; + // There are leftover filestore response(s) and/or a fault location field. + while current_idx < full_len_without_crc { + let next_tlv = Tlv::from_bytes(&buf[current_idx..])?; + match next_tlv.tlv_type_field() { + TlvTypeField::Standard(tlv_type) => { + if tlv_type == TlvType::FilestoreResponse { + current_idx += next_tlv.len_full(); + if current_idx == full_len_without_crc { + fs_responses = Some(&buf[start_of_fs_responses..current_idx]); + } + } else if tlv_type == TlvType::EntityId { + // At least one FS response is included. + if current_idx > full_len_without_crc { + fs_responses = Some(&buf[start_of_fs_responses..current_idx]); + } + fault_location = Some(EntityIdTlv::from_bytes(&buf[current_idx..])?); + current_idx += fault_location.as_ref().unwrap().len_full(); + // This is considered a configuration error: The entity ID has to be the + // last TLV, everything else would break the whole handling of the packet + // TLVs. + if current_idx != full_len_without_crc { + return Err(PduError::FormatError); + } + } else { + return Err(TlvLvError::InvalidTlvTypeField((tlv_type as u8, None)).into()); + } + } + TlvTypeField::Custom(raw) => { + return Err(TlvLvError::InvalidTlvTypeField((raw, None)).into()); + } + } + } + Ok((fs_responses, fault_location)) + } +} + +#[cfg(test)] +mod tests { + use crate::cfdp::pdu::finished::{DeliveryCode, FileStatus, FinishedPdu}; + use crate::cfdp::pdu::tests::{common_pdu_conf, verify_raw_header}; + use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; + use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag}; + + fn generic_finished_pdu( + crc_flag: CrcFlag, + fss: LargeFileFlag, + delivery_code: DeliveryCode, + file_status: FileStatus, + ) -> FinishedPdu<'static> { + let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0); + FinishedPdu::new_default(pdu_header, delivery_code, file_status) + } + + #[test] + fn test_basic() { + let finished_pdu = generic_finished_pdu( + CrcFlag::NoCrc, + LargeFileFlag::Normal, + DeliveryCode::Complete, + FileStatus::Retained, + ); + assert_eq!(finished_pdu.condition_code(), ConditionCode::NoError); + assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Complete); + assert_eq!(finished_pdu.file_status(), FileStatus::Retained); + assert_eq!(finished_pdu.filestore_responses(), None); + assert_eq!(finished_pdu.fault_location(), None); + assert_eq!(finished_pdu.pdu_header().pdu_datafield_len, 2); + } + + fn generic_serialization_test_no_error(delivery_code: DeliveryCode, file_status: FileStatus) { + let finished_pdu = generic_finished_pdu( + CrcFlag::NoCrc, + LargeFileFlag::Normal, + delivery_code, + file_status, + ); + let mut buf: [u8; 64] = [0; 64]; + let written = finished_pdu.write_to_bytes(&mut buf); + assert!(written.is_ok()); + let written = written.unwrap(); + assert_eq!(written, finished_pdu.written_len()); + assert_eq!(written, finished_pdu.pdu_header().header_len() + 2); + verify_raw_header(finished_pdu.pdu_header(), &buf); + let mut current_idx = finished_pdu.pdu_header().header_len(); + assert_eq!(buf[current_idx], FileDirectiveType::FinishedPdu as u8); + current_idx += 1; + assert_eq!( + (buf[current_idx] >> 4) & 0b1111, + ConditionCode::NoError as u8 + ); + assert_eq!((buf[current_idx] >> 2) & 0b1, delivery_code as u8); + assert_eq!(buf[current_idx] & 0b11, file_status as u8); + assert_eq!(current_idx + 1, written); + } + + #[test] + fn test_serialization_simple() { + generic_serialization_test_no_error(DeliveryCode::Complete, FileStatus::Retained); + } + + #[test] + fn test_serialization_simple_2() { + generic_serialization_test_no_error( + DeliveryCode::Incomplete, + FileStatus::DiscardDeliberately, + ); + } + + #[test] + fn test_serialization_simple_3() { + generic_serialization_test_no_error(DeliveryCode::Incomplete, FileStatus::Unreported); + } + + #[test] + fn test_deserialization_simple() { + let finished_pdu = generic_finished_pdu( + CrcFlag::NoCrc, + LargeFileFlag::Normal, + DeliveryCode::Complete, + FileStatus::Retained, + ); + let mut buf: [u8; 64] = [0; 64]; + finished_pdu.write_to_bytes(&mut buf).unwrap(); + let read_back = FinishedPdu::from_bytes(&buf); + assert!(read_back.is_ok()); + let read_back = read_back.unwrap(); + assert_eq!(finished_pdu, read_back); + } +} diff --git a/src/cfdp/pdu/metadata.rs b/src/cfdp/pdu/metadata.rs new file mode 100644 index 0000000..fbdb982 --- /dev/null +++ b/src/cfdp/pdu/metadata.rs @@ -0,0 +1,527 @@ +use crate::cfdp::lv::Lv; +use crate::cfdp::pdu::{ + add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, + FileDirectiveType, PduError, PduHeader, +}; +use crate::cfdp::tlv::Tlv; +use crate::cfdp::{ChecksumType, CrcFlag, LargeFileFlag, PduType}; +use crate::{ByteConversionError, SizeMissmatch}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct MetadataGenericParams { + closure_requested: bool, + checksum_type: ChecksumType, + file_size: u64, +} + +impl MetadataGenericParams { + pub fn new(closure_requested: bool, checksum_type: ChecksumType, file_size: u64) -> Self { + Self { + closure_requested, + checksum_type, + file_size, + } + } +} + +pub fn build_metadata_opts_from_slice( + buf: &mut [u8], + tlvs: &[Tlv], +) -> Result { + let mut written = 0; + for tlv in tlvs { + written += tlv.write_to_bytes(&mut buf[written..])?; + } + Ok(written) +} + +#[cfg(feature = "alloc")] +pub fn build_metadata_opts_from_vec( + buf: &mut [u8], + tlvs: &Vec, +) -> Result { + build_metadata_opts_from_slice(buf, tlvs.as_slice()) +} + +/// Helper structure to loop through all options of a metadata PDU. It should be noted that +/// iterators in Rust are not fallible, but the TLV creation can fail, for example if the raw TLV +/// data is invalid for some reason. In that case, the iterator will yield [None] because there +/// is no way to recover from this. +/// +/// The user can accumulate the length of all TLVs yielded by the iterator and compare it against +/// the full length of the options to check whether the iterator was able to parse all TLVs +/// successfully. +pub struct OptionsIter<'opts> { + opt_buf: &'opts [u8], + current_idx: usize, +} + +impl<'opts> Iterator for OptionsIter<'opts> { + type Item = Tlv<'opts>; + + fn next(&mut self) -> Option { + if self.current_idx == self.opt_buf.len() { + return None; + } + let tlv = Tlv::from_bytes(&self.opt_buf[self.current_idx..]); + // There are not really fallible iterators so we can't continue here.. + if tlv.is_err() { + return None; + } + let tlv = tlv.unwrap(); + self.current_idx += tlv.len_full(); + Some(tlv) + } +} + +/// Metadata PDU abstraction. +/// +/// For more information, refer to CFDP chapter 5.2.5. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct MetadataPdu<'src_name, 'dest_name, 'opts> { + pdu_header: PduHeader, + metadata_params: MetadataGenericParams, + #[cfg_attr(feature = "serde", serde(borrow))] + src_file_name: Lv<'src_name>, + #[cfg_attr(feature = "serde", serde(borrow))] + dest_file_name: Lv<'dest_name>, + options: Option<&'opts [u8]>, +} + +impl<'src_name, 'dest_name, 'opts> MetadataPdu<'src_name, 'dest_name, 'opts> { + pub fn new_no_opts( + pdu_header: PduHeader, + metadata_params: MetadataGenericParams, + src_file_name: Lv<'src_name>, + dest_file_name: Lv<'dest_name>, + ) -> Self { + Self::new( + pdu_header, + metadata_params, + src_file_name, + dest_file_name, + None, + ) + } + + pub fn new_with_opts( + pdu_header: PduHeader, + metadata_params: MetadataGenericParams, + src_file_name: Lv<'src_name>, + dest_file_name: Lv<'dest_name>, + options: &'opts [u8], + ) -> Self { + Self::new( + pdu_header, + metadata_params, + src_file_name, + dest_file_name, + Some(options), + ) + } + + pub fn new( + mut pdu_header: PduHeader, + metadata_params: MetadataGenericParams, + src_file_name: Lv<'src_name>, + dest_file_name: Lv<'dest_name>, + options: Option<&'opts [u8]>, + ) -> Self { + pdu_header.pdu_type = PduType::FileDirective; + let mut pdu = Self { + pdu_header, + metadata_params, + src_file_name, + dest_file_name, + options, + }; + pdu.pdu_header.pdu_datafield_len = pdu.calc_pdu_datafield_len() as u16; + pdu + } + + pub fn src_file_name(&self) -> Lv<'src_name> { + self.src_file_name + } + + pub fn dest_file_name(&self) -> Lv<'dest_name> { + self.dest_file_name + } + + pub fn options(&self) -> Option<&'opts [u8]> { + self.options + } + + /// Yield an iterator which can be used to loop through all options. Returns [None] if the + /// options field is empty. + pub fn options_iter(&self) -> Option> { + self.options?; + Some(OptionsIter { + opt_buf: self.options.unwrap(), + current_idx: 0, + }) + } + + pub fn written_len(&self) -> usize { + self.pdu_header.header_len() + self.calc_pdu_datafield_len() + } + + fn calc_pdu_datafield_len(&self) -> usize { + // One directve type octet and one byte of the directive parameter field. + let mut len = 2; + if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { + len += 8; + } else { + len += 4; + } + len += self.src_file_name.len_full(); + len += self.dest_file_name.len_full(); + if let Some(opts) = self.options { + len += opts.len(); + } + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + len += 2; + } + len + } + + pub fn pdu_header(&self) -> &PduHeader { + &self.pdu_header + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + let expected_len = self.written_len(); + if buf.len() < expected_len { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: expected_len, + }) + .into()); + } + + let mut current_idx = self.pdu_header.write_to_bytes(buf)?; + 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); + current_idx += 1; + current_idx += write_fss_field( + self.pdu_header.common_pdu_conf().file_flag, + self.metadata_params.file_size, + &mut buf[current_idx..], + )?; + current_idx += self + .src_file_name + .write_to_be_bytes(&mut buf[current_idx..])?; + current_idx += self + .dest_file_name + .write_to_be_bytes(&mut buf[current_idx..])?; + if let Some(opts) = self.options { + buf[current_idx..current_idx + opts.len()].copy_from_slice(opts); + current_idx += opts.len(); + } + if self.pdu_header.pdu_conf.crc_flag == CrcFlag::WithCrc { + current_idx = add_pdu_crc(buf, current_idx); + } + Ok(current_idx) + } + + pub fn from_bytes<'longest: 'src_name + 'dest_name + 'opts>( + buf: &'longest [u8], + ) -> Result { + let (pdu_header, mut current_idx) = PduHeader::from_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; + } + generic_length_checks_pdu_deserialization(buf, min_expected_len, full_len_without_crc)?; + 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 (fss_len, file_size) = + read_fss_field(pdu_header.pdu_conf.file_flag, &buf[current_idx + 1..]); + 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 += 1 + fss_len; + let src_file_name = Lv::from_bytes(&buf[current_idx..])?; + current_idx += src_file_name.len_full(); + let dest_file_name = Lv::from_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)] +pub mod tests { + use crate::cfdp::lv::Lv; + use crate::cfdp::pdu::metadata::{ + build_metadata_opts_from_slice, build_metadata_opts_from_vec, MetadataGenericParams, + MetadataPdu, + }; + use crate::cfdp::pdu::tests::{common_pdu_conf, verify_raw_header}; + use crate::cfdp::pdu::{FileDirectiveType, PduHeader}; + use crate::cfdp::tlv::{Tlv, TlvType}; + use crate::cfdp::{ + ChecksumType, CrcFlag, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, + }; + use std::vec; + + const SRC_FILENAME: &'static str = "hello-world.txt"; + const DEST_FILENAME: &'static str = "hello-world2.txt"; + + fn generic_metadata_pdu<'opts>( + crc_flag: CrcFlag, + fss: LargeFileFlag, + opts: Option<&'opts [u8]>, + ) -> ( + Lv<'static>, + Lv<'static>, + MetadataPdu<'static, 'static, 'opts>, + ) { + let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0); + let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 0x1010); + let src_filename = Lv::new_from_str(SRC_FILENAME).expect("Generating string LV failed"); + let dest_filename = + Lv::new_from_str(DEST_FILENAME).expect("Generating destination LV failed"); + ( + src_filename, + dest_filename, + MetadataPdu::new( + pdu_header, + metadata_params, + src_filename, + dest_filename, + opts, + ), + ) + } + + #[test] + fn test_basic() { + let (src_filename, dest_filename, metadata_pdu) = + generic_metadata_pdu(CrcFlag::NoCrc, LargeFileFlag::Normal, None); + assert_eq!( + metadata_pdu.written_len(), + metadata_pdu.pdu_header().header_len() + + 1 + + 1 + + 4 + + src_filename.len_full() + + dest_filename.len_full() + ); + assert_eq!(metadata_pdu.src_file_name(), src_filename); + assert_eq!(metadata_pdu.dest_file_name(), dest_filename); + assert_eq!(metadata_pdu.options(), None); + } + + #[test] + fn test_serialization() { + let (src_filename, dest_filename, metadata_pdu) = + generic_metadata_pdu(CrcFlag::NoCrc, LargeFileFlag::Normal, None); + let mut buf: [u8; 64] = [0; 64]; + let res = metadata_pdu.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!( + written, + metadata_pdu.pdu_header.header_len() + + 1 + + 1 + + 4 + + src_filename.len_full() + + dest_filename.len_full() + ); + verify_raw_header(metadata_pdu.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()), 0x1010); + let mut current_idx = 13; + let src_name_from_raw = + Lv::from_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_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); + } + + #[test] + fn test_deserialization() { + let (_, _, metadata_pdu) = + generic_metadata_pdu(CrcFlag::NoCrc, LargeFileFlag::Normal, None); + let mut buf: [u8; 64] = [0; 64]; + metadata_pdu.write_to_bytes(&mut buf).unwrap(); + let pdu_read_back = MetadataPdu::from_bytes(&buf); + assert!(pdu_read_back.is_ok()); + let pdu_read_back = pdu_read_back.unwrap(); + assert_eq!(pdu_read_back, metadata_pdu); + } + + #[test] + fn test_with_crc_flag() { + let (src_filename, dest_filename, metadata_pdu) = + generic_metadata_pdu(CrcFlag::WithCrc, LargeFileFlag::Normal, None); + let mut buf: [u8; 64] = [0; 64]; + let write_res = metadata_pdu.write_to_bytes(&mut buf); + assert!(write_res.is_ok()); + let written = write_res.unwrap(); + assert_eq!( + written, + metadata_pdu.pdu_header().header_len() + + 1 + + 1 + + core::mem::size_of::() + + src_filename.len_full() + + dest_filename.len_full() + + 2 + ); + let pdu_read_back = MetadataPdu::from_bytes(&buf).unwrap(); + assert_eq!(pdu_read_back, metadata_pdu); + } + + #[test] + fn test_with_large_file_flag() { + let (src_filename, dest_filename, metadata_pdu) = + generic_metadata_pdu(CrcFlag::NoCrc, LargeFileFlag::Large, None); + let mut buf: [u8; 64] = [0; 64]; + let write_res = metadata_pdu.write_to_bytes(&mut buf); + assert!(write_res.is_ok()); + let written = write_res.unwrap(); + assert_eq!( + written, + metadata_pdu.pdu_header().header_len() + + 1 + + 1 + + core::mem::size_of::() + + src_filename.len_full() + + dest_filename.len_full() + ); + let pdu_read_back = MetadataPdu::from_bytes(&buf).unwrap(); + assert_eq!(pdu_read_back, metadata_pdu); + } + + #[test] + fn test_opts_builders() { + let tlv1 = Tlv::new_empty(TlvType::FlowLabel); + let msg_to_user: [u8; 4] = [1, 2, 3, 4]; + let tlv2 = Tlv::new(TlvType::MsgToUser, &msg_to_user).unwrap(); + let tlv_slice = [tlv1, tlv2]; + let mut buf: [u8; 32] = [0; 32]; + let opts = build_metadata_opts_from_slice(&mut buf, &tlv_slice); + assert!(opts.is_ok()); + let opts_len = opts.unwrap(); + assert_eq!(opts_len, tlv1.len_full() + tlv2.len_full()); + let tlv1_conv_back = Tlv::from_bytes(&buf).unwrap(); + assert_eq!(tlv1_conv_back, tlv1); + let tlv2_conv_back = Tlv::from_bytes(&buf[tlv1_conv_back.len_full()..]).unwrap(); + assert_eq!(tlv2_conv_back, tlv2); + } + + #[test] + fn test_opts_builders_from_vec() { + let tlv1 = Tlv::new_empty(TlvType::FlowLabel); + let msg_to_user: [u8; 4] = [1, 2, 3, 4]; + let tlv2 = Tlv::new(TlvType::MsgToUser, &msg_to_user).unwrap(); + let tlv_vec = vec![tlv1, tlv2]; + let mut buf: [u8; 32] = [0; 32]; + let opts = build_metadata_opts_from_vec(&mut buf, &tlv_vec); + assert!(opts.is_ok()); + let opts_len = opts.unwrap(); + assert_eq!(opts_len, tlv1.len_full() + tlv2.len_full()); + let tlv1_conv_back = Tlv::from_bytes(&buf).unwrap(); + assert_eq!(tlv1_conv_back, tlv1); + let tlv2_conv_back = Tlv::from_bytes(&buf[tlv1_conv_back.len_full()..]).unwrap(); + assert_eq!(tlv2_conv_back, tlv2); + } + + #[test] + fn test_with_opts() { + let tlv1 = Tlv::new_empty(TlvType::FlowLabel); + let msg_to_user: [u8; 4] = [1, 2, 3, 4]; + let tlv2 = Tlv::new(TlvType::MsgToUser, &msg_to_user).unwrap(); + let tlv_vec = vec![tlv1, tlv2]; + let mut opts_buf: [u8; 32] = [0; 32]; + let opts_len = build_metadata_opts_from_vec(&mut opts_buf, &tlv_vec).unwrap(); + let (src_filename, dest_filename, metadata_pdu) = generic_metadata_pdu( + CrcFlag::NoCrc, + LargeFileFlag::Normal, + Some(&opts_buf[..opts_len]), + ); + let mut buf: [u8; 128] = [0; 128]; + let write_res = metadata_pdu.write_to_bytes(&mut buf); + assert!(write_res.is_ok()); + let written = write_res.unwrap(); + assert_eq!( + written, + metadata_pdu.pdu_header.header_len() + + 1 + + 1 + + 4 + + src_filename.len_full() + + dest_filename.len_full() + + opts_len + ); + let pdu_read_back = MetadataPdu::from_bytes(&buf).unwrap(); + assert_eq!(pdu_read_back, metadata_pdu); + let opts_iter = pdu_read_back.options_iter(); + assert!(opts_iter.is_some()); + let opts_iter = opts_iter.unwrap(); + let mut accumulated_len = 0; + for (idx, opt) in opts_iter.enumerate() { + assert_eq!(tlv_vec[idx], opt); + accumulated_len += opt.len_full(); + } + assert_eq!(accumulated_len, pdu_read_back.options().unwrap().len()); + } + + #[test] + fn test_corrects_pdu_header() { + let pdu_header = PduHeader::new_for_file_data( + common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal), + 0, + SegmentMetadataFlag::NotPresent, + SegmentationControl::NoRecordBoundaryPreservation, + ); + let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, 10); + let src_filename = Lv::new_from_str(SRC_FILENAME).expect("Generating string LV failed"); + let dest_filename = + Lv::new_from_str(DEST_FILENAME).expect("Generating destination LV failed"); + let metadata_pdu = + MetadataPdu::new_no_opts(pdu_header, metadata_params, src_filename, dest_filename); + assert_eq!(metadata_pdu.pdu_header().pdu_type(), PduType::FileDirective); + } +} diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs new file mode 100644 index 0000000..2ef5771 --- /dev/null +++ b/src/cfdp/pdu/mod.rs @@ -0,0 +1,907 @@ +//! 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")] +use std::error::Error; + +pub mod eof; +pub mod file_data; +pub mod finished; +pub mod metadata; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum FileDirectiveType { + EofPdu = 0x04, + FinishedPdu = 0x05, + AckPdu = 0x06, + MetadataPdu = 0x07, + NakPdu = 0x08, + PromptPdu = 0x09, + KeepAlivePdu = 0x0c, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum PduError { + ByteConversionError(ByteConversionError), + /// Found version ID invalid, not equal to [CFDP_VERSION_2]. + CfdpVersionMissmatch(u8), + /// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported. + InvalidEntityLen(u8), + /// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported. + InvalidTransactionSeqNumLen(u8), + /// 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 condition code. Contains the raw detected value. + InvalidConditionCode(u8), + /// 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), + /// Generic error for invalid PDU formats. + FormatError, + /// Error handling a TLV field. + TlvLvError(TlvLvError), +} + +impl Display for PduError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + PduError::InvalidEntityLen(raw_id) => { + write!( + f, + "Invalid PDU entity ID length {raw_id}, only [1, 2, 4, 8] are allowed" + ) + } + PduError::InvalidTransactionSeqNumLen(raw_id) => { + write!( + f, + "invalid PDUtransaction seq num length {raw_id}, only [1, 2, 4, 8] are allowed" + ) + } + PduError::CfdpVersionMissmatch(raw) => { + write!( + f, + "cfdp version missmatch, found {raw}, expected {CFDP_VERSION_2}" + ) + } + PduError::SourceDestIdLenMissmatch((src_len, dest_len)) => { + write!( + f, + "missmatch of PDU source length {src_len} and destination length {dest_len}" + ) + } + PduError::ByteConversionError(e) => { + write!(f, "{}", e) + } + PduError::FileSizeTooLarge(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::InvalidConditionCode(raw_code) => { + write!(f, "found invalid condition code with raw value {raw_code}") + } + 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}") + } + PduError::TlvLvError(error) => { + write!(f, "pdu tlv error: {error}") + } + PduError::FormatError => { + write!(f, "generic PDU format error") + } + } + } +} + +#[cfg(feature = "std")] +impl Error for PduError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + PduError::ByteConversionError(e) => Some(e), + PduError::TlvLvError(e) => Some(e), + _ => None, + } + } +} + +impl From for PduError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversionError(value) + } +} + +impl From for PduError { + fn from(e: TlvLvError) -> Self { + Self::TlvLvError(e) + } +} + +/// Common configuration fields for a PDU. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CommonPduConfig { + source_entity_id: UnsignedByteField, + dest_entity_id: UnsignedByteField, + pub transaction_seq_num: UnsignedByteField, + pub trans_mode: TransmissionMode, + pub file_flag: LargeFileFlag, + pub crc_flag: CrcFlag, + pub direction: Direction, +} + +// TODO: Builder pattern might be applicable here.. +impl CommonPduConfig { + pub fn new( + source_id: impl Into, + dest_id: impl Into, + transaction_seq_num: impl Into, + trans_mode: TransmissionMode, + file_flag: LargeFileFlag, + crc_flag: CrcFlag, + direction: Direction, + ) -> Result { + 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() { + return Err(PduError::SourceDestIdLenMissmatch(( + source_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 { + source_entity_id: source_id, + dest_entity_id: dest_id, + transaction_seq_num, + trans_mode, + file_flag, + crc_flag, + direction, + }) + } + + pub fn new_with_defaults( + source_id: impl Into, + dest_id: impl Into, + transaction_seq_num: impl Into, + ) -> Result { + 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 + } +} + +pub const FIXED_HEADER_LEN: usize = 4; + +/// Abstraction for the PDU header common to all CFDP PDUs. +/// +/// For detailed information, refer to chapter 5.1 of the CFDP standard. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PduHeader { + pdu_type: PduType, + pdu_conf: CommonPduConfig, + seg_metadata_flag: SegmentMetadataFlag, + seg_ctrl: SegmentationControl, + pdu_datafield_len: u16, +} + +impl PduHeader { + pub fn new_for_file_data( + pdu_conf: CommonPduConfig, + pdu_datafield_len: u16, + seg_metadata_flag: SegmentMetadataFlag, + seg_ctrl: SegmentationControl, + ) -> Self { + Self::new_generic( + PduType::FileData, + pdu_conf, + pdu_datafield_len, + seg_metadata_flag, + seg_ctrl, + ) + } + + pub fn new_for_file_data_default(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self { + Self::new_generic( + PduType::FileData, + pdu_conf, + pdu_datafield_len, + SegmentMetadataFlag::NotPresent, + SegmentationControl::NoRecordBoundaryPreservation, + ) + } + pub fn new_no_file_data(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self { + Self::new_generic( + PduType::FileDirective, + pdu_conf, + pdu_datafield_len, + SegmentMetadataFlag::NotPresent, + SegmentationControl::NoRecordBoundaryPreservation, + ) + } + + pub fn new_generic( + pdu_type: PduType, + pdu_conf: CommonPduConfig, + pdu_datafield_len: u16, + seg_metadata_flag: SegmentMetadataFlag, + seg_ctrl: SegmentationControl, + ) -> Self { + Self { + pdu_type, + pdu_conf, + seg_metadata_flag, + seg_ctrl, + pdu_datafield_len, + } + } + + /// 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_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. + if self.pdu_conf.source_entity_id.len() != self.pdu_conf.dest_entity_id.len() { + return Err(PduError::SourceDestIdLenMissmatch(( + self.pdu_conf.source_entity_id.len(), + self.pdu_conf.dest_entity_id.len(), + ))); + } + if buf.len() + < FIXED_HEADER_LEN + + self.pdu_conf.source_entity_id.len() + + self.pdu_conf.transaction_seq_num.len() + { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: FIXED_HEADER_LEN, + }) + .into()); + } + let mut current_idx = 0; + buf[current_idx] = (CFDP_VERSION_2 << 5) + | ((self.pdu_type as u8) << 4) + | ((self.pdu_conf.direction as u8) << 3) + | ((self.pdu_conf.trans_mode as u8) << 2) + | ((self.pdu_conf.crc_flag as u8) << 1) + | (self.pdu_conf.file_flag as u8); + current_idx += 1; + buf[current_idx..current_idx + 2].copy_from_slice(&self.pdu_datafield_len.to_be_bytes()); + current_idx += 2; + buf[current_idx] = ((self.seg_ctrl as u8) << 7) + | (((self.pdu_conf.source_entity_id.len() - 1) as u8) << 4) + | ((self.seg_metadata_flag as u8) << 3) + | ((self.pdu_conf.transaction_seq_num.len() - 1) as u8); + current_idx += 1; + self.pdu_conf.source_entity_id.write_to_be_bytes( + &mut buf[current_idx..current_idx + self.pdu_conf.source_entity_id.len()], + )?; + current_idx += self.pdu_conf.source_entity_id.len(); + self.pdu_conf.transaction_seq_num.write_to_be_bytes( + &mut buf[current_idx..current_idx + self.pdu_conf.transaction_seq_num.len()], + )?; + current_idx += self.pdu_conf.transaction_seq_num.len(); + self.pdu_conf.dest_entity_id.write_to_be_bytes( + &mut buf[current_idx..current_idx + self.pdu_conf.dest_entity_id.len()], + )?; + current_idx += self.pdu_conf.dest_entity_id.len(); + 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(), + ))); + } + return Ok(self.pdu_len() - 2); + } + 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 [Self::verify_length_and_checksum] + /// function. + pub fn from_bytes(buf: &[u8]) -> Result<(Self, usize), PduError> { + if buf.len() < FIXED_HEADER_LEN { + return Err(PduError::ByteConversionError( + ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: FIXED_HEADER_LEN, + }), + )); + } + let cfdp_version_raw = (buf[0] >> 5) & 0b111; + if cfdp_version_raw != CFDP_VERSION_2 { + return Err(PduError::CfdpVersionMissmatch(cfdp_version_raw)); + } + // unwrap for single bit fields: This operation will always succeed. + let pdu_type = PduType::try_from((buf[0] >> 4) & 0b1).unwrap(); + let direction = Direction::try_from((buf[0] >> 3) & 0b1).unwrap(); + let trans_mode = TransmissionMode::try_from((buf[0] >> 2) & 0b1).unwrap(); + let crc_flag = CrcFlag::try_from((buf[0] >> 1) & 0b1).unwrap(); + let file_flag = LargeFileFlag::try_from(buf[0] & 0b1).unwrap(); + let pdu_datafield_len = u16::from_be_bytes(buf[1..3].try_into().unwrap()); + let seg_ctrl = SegmentationControl::try_from((buf[3] >> 7) & 0b1).unwrap(); + let expected_len_entity_ids = (((buf[3] >> 4) & 0b111) + 1) as usize; + if (expected_len_entity_ids != 1) + && (expected_len_entity_ids != 2) + && (expected_len_entity_ids != 4) + && (expected_len_entity_ids != 8) + { + return Err(PduError::InvalidEntityLen(expected_len_entity_ids as u8)); + } + let seg_metadata_flag = SegmentMetadataFlag::try_from((buf[3] >> 3) & 0b1).unwrap(); + let expected_len_seq_num = ((buf[3] & 0b111) + 1) as usize; + if (expected_len_seq_num != 1) + && (expected_len_seq_num != 2) + && (expected_len_seq_num != 4) + && (expected_len_seq_num != 8) + { + return Err(PduError::InvalidTransactionSeqNumLen( + expected_len_seq_num as u8, + )); + } + if buf.len() < (4 + 2 * expected_len_entity_ids + expected_len_seq_num) { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 4 + 2 * expected_len_entity_ids + expected_len_seq_num, + }) + .into()); + } + 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 = + UnsignedByteField::new_from_be_bytes(expected_len_entity_ids, &buf[current_idx..]) + .unwrap(); + current_idx += expected_len_entity_ids; + let transaction_seq_num = + UnsignedByteField::new_from_be_bytes(expected_len_seq_num, &buf[current_idx..]) + .unwrap(); + current_idx += expected_len_seq_num; + let dest_id = + UnsignedByteField::new_from_be_bytes(expected_len_entity_ids, &buf[current_idx..]) + .unwrap(); + current_idx += expected_len_entity_ids; + let common_pdu_conf = CommonPduConfig::new( + source_id, + dest_id, + transaction_seq_num, + trans_mode, + file_flag, + crc_flag, + direction, + ) + .unwrap(); + Ok(( + PduHeader { + pdu_type, + pdu_conf: common_pdu_conf, + seg_metadata_flag, + seg_ctrl, + pdu_datafield_len, + }, + current_idx, + )) + } + pub fn pdu_type(&self) -> PduType { + self.pdu_type + } + + pub fn common_pdu_conf(&self) -> &CommonPduConfig { + &self.pdu_conf + } + + pub fn seg_metadata_flag(&self) -> SegmentMetadataFlag { + self.seg_metadata_flag + } + pub fn seg_ctrl(&self) -> SegmentationControl { + self.seg_ctrl + } +} + +pub(crate) fn write_fss_field( + file_flag: LargeFileFlag, + file_size: u64, + buf: &mut [u8], +) -> Result { + Ok(if file_flag == LargeFileFlag::Large { + buf[..core::mem::size_of::()].copy_from_slice(&file_size.to_be_bytes()); + core::mem::size_of::() + } else { + if file_size > u32::MAX as u64 { + return Err(PduError::FileSizeTooLarge(file_size)); + } + buf[..core::mem::size_of::()].copy_from_slice(&(file_size as u32).to_be_bytes()); + core::mem::size_of::() + }) +} + +pub(crate) fn read_fss_field(file_flag: LargeFileFlag, buf: &[u8]) -> (usize, u64) { + if file_flag == LargeFileFlag::Large { + ( + core::mem::size_of::(), + u64::from_be_bytes(buf[..core::mem::size_of::()].try_into().unwrap()), + ) + } else { + ( + core::mem::size_of::(), + u32::from_be_bytes(buf[..core::mem::size_of::()].try_into().unwrap()).into(), + ) + } +} + +// This is a generic length check applicable to most PDU deserializations. It first checks whether +// a given buffer can hold an expected minimum size, and then it checks whether the PDU datafield +// length is larger than that expected minimum size. +pub(crate) fn generic_length_checks_pdu_deserialization( + buf: &[u8], + min_expected_len: usize, + full_len_without_crc: usize, +) -> Result<(), ByteConversionError> { + // Buffer too short to hold additional expected minimum datasize. + if buf.len() < min_expected_len { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: min_expected_len, + })); + } + // This can happen if the PDU datafield length value is invalid. + if full_len_without_crc < min_expected_len { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: full_len_without_crc, + expected: min_expected_len, + })); + } + Ok(()) +} + +pub(crate) fn add_pdu_crc(buf: &mut [u8], mut current_idx: usize) -> usize { + let mut digest = CRC_CCITT_FALSE.digest(); + digest.update(&buf[..current_idx]); + buf[current_idx..current_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes()); + current_idx += 2; + current_idx +} + +#[cfg(test)] +mod tests { + use crate::cfdp::pdu::{CommonPduConfig, PduError, PduHeader, FIXED_HEADER_LEN}; + use crate::cfdp::{ + CrcFlag, Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, + TransmissionMode, CFDP_VERSION_2, + }; + use crate::util::{ + UbfU8, UnsignedByteField, UnsignedByteFieldU16, UnsignedByteFieldU8, UnsignedEnum, + }; + use crate::ByteConversionError; + use std::format; + + pub(crate) fn common_pdu_conf(crc_flag: CrcFlag, fss: LargeFileFlag) -> CommonPduConfig { + let src_id = UbfU8::new(5); + let dest_id = UbfU8::new(10); + let transaction_seq_num = UbfU8::new(20); + let mut pdu_conf = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_num) + .expect("Generating common PDU config"); + pdu_conf.crc_flag = crc_flag; + pdu_conf.file_flag = fss; + pdu_conf + } + + pub(crate) fn verify_raw_header(pdu_conf: &PduHeader, buf: &[u8]) { + assert_eq!((buf[0] >> 5) & 0b111, CFDP_VERSION_2); + // File directive + assert_eq!((buf[0] >> 4) & 1, pdu_conf.pdu_type as u8); + // Towards receiver + assert_eq!((buf[0] >> 3) & 1, pdu_conf.pdu_conf.direction as u8); + // Acknowledged + assert_eq!((buf[0] >> 2) & 1, pdu_conf.pdu_conf.trans_mode as u8); + // No CRC + assert_eq!((buf[0] >> 1) & 1, pdu_conf.pdu_conf.crc_flag as u8); + // Regular file size + assert_eq!(buf[0] & 1, pdu_conf.pdu_conf.file_flag as u8); + let pdu_datafield_len = u16::from_be_bytes(buf[1..3].try_into().unwrap()); + assert_eq!(pdu_datafield_len, pdu_conf.pdu_datafield_len); + // No record boundary preservation + assert_eq!((buf[3] >> 7) & 1, pdu_conf.seg_ctrl as u8); + // Entity ID length raw value is actual number of octets - 1 => 0 + let entity_id_len = pdu_conf.pdu_conf.source_entity_id.len(); + assert_eq!((buf[3] >> 4) & 0b111, entity_id_len as u8 - 1); + // No segment metadata + assert_eq!((buf[3] >> 3) & 0b1, pdu_conf.seg_metadata_flag as u8); + // Transaction Sequence ID length raw value is actual number of octets - 1 => 0 + let seq_num_len = pdu_conf.pdu_conf.transaction_seq_num.len(); + assert_eq!(buf[3] & 0b111, seq_num_len as u8 - 1); + let mut current_idx = 4; + let mut byte_field_check = |field_len: usize, ubf: &UnsignedByteField| { + match field_len { + 1 => assert_eq!(buf[current_idx], ubf.value() as u8), + 2 => assert_eq!( + u16::from_be_bytes( + buf[current_idx..current_idx + field_len] + .try_into() + .unwrap() + ), + ubf.value() as u16 + ), + 4 => assert_eq!( + u32::from_be_bytes( + buf[current_idx..current_idx + field_len] + .try_into() + .unwrap() + ), + ubf.value() as u32 + ), + 8 => assert_eq!( + u64::from_be_bytes( + buf[current_idx..current_idx + field_len] + .try_into() + .unwrap() + ), + ubf.value() as u64 + ), + _ => panic!("invalid entity ID length"), + } + current_idx += field_len + }; + byte_field_check(entity_id_len, &pdu_conf.pdu_conf.source_entity_id); + byte_field_check(seq_num_len, &pdu_conf.pdu_conf.transaction_seq_num); + byte_field_check(entity_id_len, &pdu_conf.pdu_conf.dest_entity_id); + } + + #[test] + fn test_basic_state() { + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::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 + ); + assert_eq!(pdu_header.pdu_datafield_len, 5); + assert_eq!(pdu_header.header_len(), 7); + } + + #[test] + fn test_serialization_1() { + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::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_bytes(&mut buf); + assert!(res.is_ok()); + // 4 byte fixed header plus three bytes src, dest ID and transaction ID + assert_eq!(res.unwrap(), 7); + verify_raw_header(&pdu_header, &buf); + } + + #[test] + fn test_deserialization_1() { + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::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_bytes(&mut buf); + assert!(res.is_ok()); + let deser_res = PduHeader::from_bytes(&buf); + assert!(deser_res.is_ok()); + let (header_read_back, read_size) = deser_res.unwrap(); + assert_eq!(read_size, 7); + assert_eq!(header_read_back, pdu_header); + } + + #[test] + fn test_serialization_2() { + let src_id = UnsignedByteFieldU16::new(0x0001); + let dest_id = UnsignedByteFieldU16::new(0x0203); + let transaction_id = UnsignedByteFieldU16::new(0x0405); + let mut common_pdu_cfg = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + common_pdu_cfg.crc_flag = CrcFlag::WithCrc; + common_pdu_cfg.direction = Direction::TowardsSender; + common_pdu_cfg.trans_mode = TransmissionMode::Unacknowledged; + common_pdu_cfg.file_flag = LargeFileFlag::Large; + let pdu_header = PduHeader::new_for_file_data( + common_pdu_cfg, + 5, + SegmentMetadataFlag::Present, + SegmentationControl::WithRecordBoundaryPreservation, + ); + assert_eq!(pdu_header.header_len(), 10); + let mut buf: [u8; 16] = [0; 16]; + let res = pdu_header.write_to_bytes(&mut buf); + assert!(res.is_ok(), "{}", format!("Result {res:?} not okay")); + // 4 byte fixed header, 6 bytes additional fields + assert_eq!(res.unwrap(), 10); + verify_raw_header(&pdu_header, &buf); + } + + #[test] + fn test_deserialization_2() { + let src_id = UnsignedByteFieldU16::new(0x0001); + let dest_id = UnsignedByteFieldU16::new(0x0203); + let transaction_id = UnsignedByteFieldU16::new(0x0405); + let mut common_pdu_cfg = + CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_id) + .expect("common config creation failed"); + common_pdu_cfg.crc_flag = CrcFlag::WithCrc; + common_pdu_cfg.direction = Direction::TowardsSender; + common_pdu_cfg.trans_mode = TransmissionMode::Unacknowledged; + common_pdu_cfg.file_flag = LargeFileFlag::Large; + let pdu_header = PduHeader::new_for_file_data( + common_pdu_cfg, + 5, + SegmentMetadataFlag::Present, + SegmentationControl::WithRecordBoundaryPreservation, + ); + let mut buf: [u8; 16] = [0; 16]; + let res = pdu_header.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let deser_res = PduHeader::from_bytes(&buf); + assert!(deser_res.is_ok()); + let (header_read_back, read_size) = deser_res.unwrap(); + assert_eq!(read_size, 10); + assert_eq!(header_read_back, pdu_header); + } + + #[test] + fn test_invalid_raw_version() { + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::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_bytes(&mut buf); + assert!(res.is_ok()); + buf[0] &= !0b1110_0000; + buf[0] |= (CFDP_VERSION_2 + 1) << 5; + let res = PduHeader::from_bytes(&buf); + assert!(res.is_err()); + let error = res.unwrap_err(); + if let PduError::CfdpVersionMissmatch(raw_version) = error { + assert_eq!(raw_version, CFDP_VERSION_2 + 1); + } else { + panic!("invalid exception: {}", error); + } + } + + #[test] + fn test_buf_too_small_1() { + let buf: [u8; 3] = [0; 3]; + let res = PduHeader::from_bytes(&buf); + assert!(res.is_err()); + let error = res.unwrap_err(); + if let PduError::ByteConversionError(ByteConversionError::FromSliceTooSmall(missmatch)) = + error + { + assert_eq!(missmatch.found, 3); + assert_eq!(missmatch.expected, FIXED_HEADER_LEN); + } else { + panic!("invalid exception: {}", error); + } + } + + #[test] + fn test_buf_too_small_2() { + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::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_bytes(&mut buf); + assert!(res.is_ok()); + let header = PduHeader::from_bytes(&buf[0..6]); + assert!(header.is_err()); + let error = header.unwrap_err(); + if let PduError::ByteConversionError(ByteConversionError::FromSliceTooSmall(missmatch)) = + error + { + assert_eq!(missmatch.found, 6); + assert_eq!(missmatch.expected, 7); + } + } + + #[test] + fn test_invalid_seq_len() { + let src_id = UbfU8::new(1); + let dest_id = UbfU8::new(2); + let transaction_seq_id = UbfU8::new(3); + let invalid_byte_field = UnsignedByteField::new(3, 5); + let pdu_conf_res = CommonPduConfig::new_with_defaults(src_id, dest_id, invalid_byte_field); + assert!(pdu_conf_res.is_err()); + let error = pdu_conf_res.unwrap_err(); + if let PduError::InvalidTransactionSeqNumLen(len) = error { + assert_eq!(len, 3); + } else { + panic!("Invalid exception: {}", error) + } + let pdu_conf_res = CommonPduConfig::new_with_defaults( + invalid_byte_field, + invalid_byte_field, + transaction_seq_id, + ); + assert!(pdu_conf_res.is_err()); + let error = pdu_conf_res.unwrap_err(); + if let PduError::InvalidEntityLen(len) = error { + assert_eq!(len, 3); + } else { + panic!("Invalid exception: {}", error) + } + } + #[test] + fn test_missmatch_src_dest_id() { + let src_id = UnsignedByteField::new(1, 5); + let dest_id = UnsignedByteField::new(2, 5); + let transaction_seq_id = UbfU8::new(3); + let pdu_conf_res = CommonPduConfig::new_with_defaults(src_id, dest_id, transaction_seq_id); + assert!(pdu_conf_res.is_err()); + let error = pdu_conf_res.unwrap_err(); + if let PduError::SourceDestIdLenMissmatch((src_len, dest_len)) = error { + assert_eq!(src_len, 1); + assert_eq!(dest_len, 2); + } + } + + #[test] + fn test_invalid_raw_src_id_len() { + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::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_bytes(&mut buf); + assert!(res.is_ok()); + buf[3] &= !0b0111_0000; + // Equivalent to the length of three + buf[3] |= 0b10 << 4; + let header_res = PduHeader::from_bytes(&buf); + assert!(header_res.is_err()); + let error = header_res.unwrap_err(); + if let PduError::InvalidEntityLen(len) = error { + assert_eq!(len, 3); + } else { + panic!("invalid exception {:?}", error) + } + } + + #[test] + fn test_invalid_transaction_seq_id_len() { + let src_id = UnsignedByteFieldU8::new(1); + let dest_id = UnsignedByteFieldU8::new(2); + let transaction_id = UnsignedByteFieldU8::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_bytes(&mut buf); + assert!(res.is_ok()); + buf[3] &= !0b0000_0111; + // Equivalent to the length of three + buf[3] |= 0b10; + let header_res = PduHeader::from_bytes(&buf); + assert!(header_res.is_err()); + let error = header_res.unwrap_err(); + if let PduError::InvalidTransactionSeqNumLen(len) = error { + assert_eq!(len, 3); + } else { + panic!("invalid exception {:?}", error) + } + } +} diff --git a/src/cfdp/tlv.rs b/src/cfdp/tlv.rs new file mode 100644 index 0000000..2bb3e9d --- /dev/null +++ b/src/cfdp/tlv.rs @@ -0,0 +1,765 @@ +//! Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9. +use crate::cfdp::lv::{ + generic_len_check_data_serialization, generic_len_check_deserialization, Lv, MIN_LV_LEN, +}; +use crate::cfdp::TlvLvError; +use crate::util::{UnsignedByteField, UnsignedByteFieldError, UnsignedEnum}; +use crate::{ByteConversionError, SizeMissmatch}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub const MIN_TLV_LEN: usize = 2; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum TlvType { + FilestoreRequest = 0x00, + FilestoreResponse = 0x01, + MsgToUser = 0x02, + FaultHandler = 0x04, + FlowLabel = 0x05, + EntityId = 0x06, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TlvTypeField { + Standard(TlvType), + Custom(u8), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum FilestoreActionCode { + CreateFile = 0b0000, + DeleteFile = 0b0001, + RenameFile = 0b0010, + /// This operation appends one file to another. The first specified name will form the first + /// part of the new file and the name of the new file. This function can be used to get + /// similar functionality to the UNIX cat utility (albeit for only two files). + AppendFile = 0b0011, + /// This operation replaces the content of the first specified file with the content of + /// the secondly specified file. + ReplaceFile = 0b0100, + CreateDirectory = 0b0101, + RemoveDirectory = 0b0110, + DenyFile = 0b0111, + DenyDirectory = 0b1000, +} + +impl From for TlvTypeField { + fn from(value: u8) -> Self { + match TlvType::try_from(value) { + Ok(tlv_type) => TlvTypeField::Standard(tlv_type), + Err(_) => TlvTypeField::Custom(value), + } + } +} + +impl From for u8 { + fn from(value: TlvTypeField) -> Self { + match value { + TlvTypeField::Standard(std) => std as u8, + TlvTypeField::Custom(custom) => custom, + } + } +} + +/// Generic CFDP type-length-value (TLV) abstraction as specified in CFDP 5.1.9. +/// +/// # Lifetimes +/// * `data`: If the TLV is generated from a raw bytestream, this will be the lifetime of +/// the raw bytestream. If the TLV is generated from a raw slice or a similar data reference, +/// this will be the lifetime of that data reference. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Tlv<'data> { + tlv_type_field: TlvTypeField, + #[cfg_attr(feature = "serde", serde(borrow))] + lv: Lv<'data>, +} + +impl<'data> Tlv<'data> { + pub fn new(tlv_type: TlvType, data: &[u8]) -> Result { + Ok(Tlv { + tlv_type_field: TlvTypeField::Standard(tlv_type), + lv: Lv::new(data)?, + }) + } + + /// Creates a TLV with an empty value field. + pub fn new_empty(tlv_type: TlvType) -> Tlv<'data> { + Tlv { + tlv_type_field: TlvTypeField::Standard(tlv_type), + lv: Lv::new_empty(), + } + } + + pub fn tlv_type_field(&self) -> TlvTypeField { + self.tlv_type_field + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + generic_len_check_data_serialization(buf, self.len_value(), MIN_TLV_LEN)?; + buf[0] = self.tlv_type_field.into(); + self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]); + Ok(self.len_full()) + } + + pub fn value(&self) -> Option<&[u8]> { + self.lv.value() + } + + /// Returns the length of the value part, not including the length byte. + pub fn len_value(&self) -> usize { + self.lv.len_value() + } + + /// Returns the full raw length, including the length byte. + pub fn len_full(&self) -> usize { + self.lv.len_full() + 1 + } + + /// Checks whether the value field is empty. + pub fn is_empty(&self) -> bool { + self.lv.is_empty() + } + + /// Creates a TLV give a raw bytestream. Please note that is is not necessary to pass the + /// bytestream with the exact size of the expected TLV. This function will take care + /// of parsing the length byte, and the length of the parsed TLV can be retrieved using + /// [Self::len_full]. + pub fn from_bytes(buf: &'data [u8]) -> Result, TlvLvError> { + generic_len_check_deserialization(buf, MIN_TLV_LEN)?; + Ok(Self { + tlv_type_field: TlvTypeField::from(buf[0]), + lv: Lv::from_bytes(&buf[MIN_LV_LEN..])?, + }) + } +} + +pub(crate) fn verify_tlv_type(raw_type: u8, expected_tlv_type: TlvType) -> Result<(), TlvLvError> { + let tlv_type = TlvType::try_from(raw_type) + .map_err(|_| TlvLvError::InvalidTlvTypeField((raw_type, Some(expected_tlv_type as u8))))?; + if tlv_type != expected_tlv_type { + return Err(TlvLvError::InvalidTlvTypeField(( + tlv_type as u8, + Some(expected_tlv_type as u8), + ))); + } + Ok(()) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct EntityIdTlv { + entity_id: UnsignedByteField, +} + +impl EntityIdTlv { + pub fn new(entity_id: UnsignedByteField) -> Self { + Self { entity_id } + } + + fn len_check(buf: &[u8]) -> Result<(), ByteConversionError> { + if buf.len() < 2 { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 2, + })); + } + Ok(()) + } + + pub fn len_value(&self) -> usize { + self.entity_id.len() + } + + pub fn len_full(&self) -> usize { + 2 + self.entity_id.len() + } + + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + Self::len_check(buf)?; + buf[0] = TlvType::EntityId as u8; + buf[1] = self.entity_id.len() as u8; + self.entity_id.write_to_be_bytes(&mut buf[2..]) + } + + pub fn from_bytes(buf: &[u8]) -> Result { + Self::len_check(buf)?; + verify_tlv_type(buf[0], TlvType::EntityId)?; + let len = buf[1]; + if len != 1 && len != 2 && len != 4 && len != 8 { + return Err(TlvLvError::InvalidValueLength(len as usize)); + } + // Okay to unwrap here. The checks before make sure that the deserialization never fails + let entity_id = UnsignedByteField::new_from_be_bytes(len as usize, &buf[2..]).unwrap(); + Ok(Self { entity_id }) + } + + /// Convert to a generic [Tlv], which also erases the programmatic type information. + pub fn to_tlv(self, buf: &mut [u8]) -> Result { + Self::len_check(buf)?; + self.entity_id + .write_to_be_bytes(&mut buf[2..2 + self.entity_id.len()])?; + Tlv::new(TlvType::EntityId, &buf[2..2 + self.entity_id.len()]).map_err(|e| match e { + TlvLvError::ByteConversionError(e) => e, + // All other errors are impossible. + _ => panic!("unexpected TLV error"), + }) + } +} + +impl<'data> TryFrom> for EntityIdTlv { + type Error = TlvLvError; + + fn try_from(value: Tlv) -> Result { + match value.tlv_type_field { + TlvTypeField::Standard(tlv_type) => { + if tlv_type != TlvType::EntityId { + return Err(TlvLvError::InvalidTlvTypeField(( + tlv_type as u8, + Some(TlvType::EntityId as u8), + ))); + } + } + TlvTypeField::Custom(val) => { + return Err(TlvLvError::InvalidTlvTypeField(( + val, + Some(TlvType::EntityId as u8), + ))); + } + } + if value.len_value() != 1 + && value.len_value() != 2 + && value.len_value() != 4 + && value.len_value() != 8 + { + return Err(TlvLvError::InvalidValueLength(value.len_value())); + } + Ok(Self::new( + UnsignedByteField::new_from_be_bytes(value.len_value(), value.value().unwrap()) + .map_err(|e| match e { + UnsignedByteFieldError::ByteConversionError(e) => e, + // This can not happen, we checked for the length validity, and the data is always smaller than + // 255 bytes. + _ => panic!("unexpected error"), + })?, + )) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct FilestoreRequestTlv<'first_name, 'second_name> { + action_code: FilestoreActionCode, + #[cfg_attr(feature = "serde", serde(borrow))] + first_name: Lv<'first_name>, + #[cfg_attr(feature = "serde", serde(borrow))] + second_name: Option>, +} + +impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> { + pub fn new_create_file(first_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::CreateFile, first_name, None) + } + + pub fn new_delete_file(first_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::DeleteFile, first_name, None) + } + + pub fn new_rename_file( + source_name: Lv<'first_name>, + target_name: Lv<'second_name>, + ) -> Result { + Self::new( + FilestoreActionCode::RenameFile, + source_name, + Some(target_name), + ) + } + + /// This operation appends one file to another. The first specified name will form the first + /// part of the new file and the name of the new file. This function can be used to get + /// similar functionality to the UNIX cat utility (albeit for only two files). + pub fn new_append_file( + first_file: Lv<'first_name>, + second_file: Lv<'second_name>, + ) -> Result { + Self::new( + FilestoreActionCode::AppendFile, + first_file, + Some(second_file), + ) + } + + /// This operation replaces the content of the first specified file with the content of + /// the secondly specified file. This function can be used to get similar functionality to + /// the UNIX copy (cp) utility if the target file already exists. + pub fn new_replace_file( + replaced_file: Lv<'first_name>, + new_file: Lv<'second_name>, + ) -> Result { + Self::new( + FilestoreActionCode::ReplaceFile, + replaced_file, + Some(new_file), + ) + } + + pub fn new_create_directory(dir_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::CreateDirectory, dir_name, None) + } + + pub fn new_remove_directory(dir_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::RemoveDirectory, dir_name, None) + } + + pub fn new_deny_file(file_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::DenyFile, file_name, None) + } + + pub fn new_deny_directory(dir_name: Lv<'first_name>) -> Result { + Self::new(FilestoreActionCode::DenyDirectory, dir_name, None) + } + + /// This function will return [None] if the respective action code requires two names but + /// only one is passed. It will also returns [None] if the cumulative length of the first + /// name and the second name exceeds 255 bytes. + /// + /// This is the case for the rename, append and replace filestore request. + pub fn new( + action_code: FilestoreActionCode, + first_name: Lv<'first_name>, + second_name: Option>, + ) -> Result { + let mut base_value_len = first_name.len_full(); + if Self::has_second_filename(action_code) { + if second_name.is_none() { + return Err(TlvLvError::SecondNameMissing); + } + base_value_len += second_name.as_ref().unwrap().len_full(); + } + if base_value_len > u8::MAX as usize { + return Err(TlvLvError::InvalidValueLength(base_value_len)); + } + Ok(Self { + action_code, + first_name, + second_name, + }) + } + + pub fn has_second_filename(action_code: FilestoreActionCode) -> bool { + if action_code == FilestoreActionCode::RenameFile + || action_code == FilestoreActionCode::AppendFile + || action_code == FilestoreActionCode::ReplaceFile + { + return true; + } + false + } + + pub fn action_code(&self) -> FilestoreActionCode { + self.action_code + } + + pub fn first_name(&self) -> Lv<'first_name> { + self.first_name + } + + pub fn second_name(&self) -> Option> { + self.second_name + } + + pub fn len_value(&self) -> usize { + let mut len = 1 + self.first_name.len_full(); + if let Some(second_name) = self.second_name { + len += second_name.len_full(); + } + len + } + + pub fn len_full(&self) -> usize { + 2 + self.len_value() + } + + pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.len_full() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.len_full(), + })); + } + buf[0] = TlvType::FilestoreRequest as u8; + buf[1] = self.len_value() as u8; + buf[2] = (self.action_code as u8) << 4; + let mut current_idx = 3; + // Length checks were already performed. + self.first_name.write_to_be_bytes_no_len_check( + &mut buf[current_idx..current_idx + self.first_name.len_full()], + ); + current_idx += self.first_name.len_full(); + if let Some(second_name) = self.second_name { + second_name.write_to_be_bytes_no_len_check( + &mut buf[current_idx..current_idx + second_name.len_full()], + ); + current_idx += second_name.len_full(); + } + Ok(current_idx) + } + + pub fn from_bytes<'longest: 'first_name + 'second_name>( + buf: &'longest [u8], + ) -> Result { + if buf.len() < 2 { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: 2, + }) + .into()); + } + verify_tlv_type(buf[0], TlvType::FilestoreRequest)?; + let len = buf[1] as usize; + let mut current_idx = 2; + let action_code = FilestoreActionCode::try_from((buf[2] >> 4) & 0b1111) + .map_err(|_| TlvLvError::InvalidFilestoreActionCode((buf[2] >> 4) & 0b1111))?; + current_idx += 1; + let first_name = Lv::from_bytes(&buf[current_idx..])?; + let mut second_name = None; + + current_idx += first_name.len_full(); + if Self::has_second_filename(action_code) { + if current_idx >= 2 + len { + return Err(TlvLvError::SecondNameMissing); + } + second_name = Some(Lv::from_bytes(&buf[current_idx..])?); + } + Ok(Self { + action_code, + first_name, + second_name, + }) + } +} + +#[cfg(test)] +mod tests { + use crate::cfdp::lv::Lv; + use crate::cfdp::tlv::{FilestoreActionCode, FilestoreRequestTlv, Tlv, TlvType, TlvTypeField}; + use crate::cfdp::TlvLvError; + use crate::util::{UbfU8, UnsignedEnum}; + + const TLV_TEST_STR_0: &'static str = "hello.txt"; + const TLV_TEST_STR_1: &'static str = "hello2.txt"; + + #[test] + fn test_basic() { + let entity_id = UbfU8::new(5); + let mut buf: [u8; 4] = [0; 4]; + assert!(entity_id.write_to_be_bytes(&mut buf).is_ok()); + let tlv_res = Tlv::new(TlvType::EntityId, &buf[0..1]); + assert!(tlv_res.is_ok()); + let tlv_res = tlv_res.unwrap(); + assert_eq!( + tlv_res.tlv_type_field(), + TlvTypeField::Standard(TlvType::EntityId) + ); + assert_eq!(tlv_res.len_full(), 3); + assert_eq!(tlv_res.len_value(), 1); + assert!(!tlv_res.is_empty()); + assert!(tlv_res.value().is_some()); + assert_eq!(tlv_res.value().unwrap()[0], 5); + } + + #[test] + fn test_serialization() { + let entity_id = UbfU8::new(5); + let mut buf: [u8; 4] = [0; 4]; + assert!(entity_id.write_to_be_bytes(&mut buf).is_ok()); + let tlv_res = Tlv::new(TlvType::EntityId, &buf[0..1]); + assert!(tlv_res.is_ok()); + let tlv_res = tlv_res.unwrap(); + let mut ser_buf: [u8; 4] = [0; 4]; + assert!(tlv_res.write_to_bytes(&mut ser_buf).is_ok()); + assert_eq!(ser_buf[0], TlvType::EntityId as u8); + assert_eq!(ser_buf[1], 1); + assert_eq!(ser_buf[2], 5); + } + + #[test] + fn test_deserialization() { + let entity_id = UbfU8::new(5); + let mut buf: [u8; 4] = [0; 4]; + assert!(entity_id.write_to_be_bytes(&mut buf[2..]).is_ok()); + buf[0] = TlvType::EntityId as u8; + buf[1] = 1; + let tlv_from_raw = Tlv::from_bytes(&mut buf); + assert!(tlv_from_raw.is_ok()); + let tlv_from_raw = tlv_from_raw.unwrap(); + assert_eq!( + tlv_from_raw.tlv_type_field(), + TlvTypeField::Standard(TlvType::EntityId) + ); + assert_eq!(tlv_from_raw.len_value(), 1); + assert_eq!(tlv_from_raw.len_full(), 3); + assert!(tlv_from_raw.value().is_some()); + assert_eq!(tlv_from_raw.value().unwrap()[0], 5); + } + + #[test] + fn test_empty() { + let tlv_empty = Tlv::new_empty(TlvType::MsgToUser); + assert!(tlv_empty.value().is_none()); + assert!(tlv_empty.is_empty()); + assert_eq!(tlv_empty.len_full(), 2); + assert_eq!(tlv_empty.len_value(), 0); + assert_eq!( + tlv_empty.tlv_type_field(), + TlvTypeField::Standard(TlvType::MsgToUser) + ); + } + + #[test] + fn test_empty_serialization() { + let tlv_empty = Tlv::new_empty(TlvType::MsgToUser); + let mut buf: [u8; 4] = [0; 4]; + assert!(tlv_empty.write_to_bytes(&mut buf).is_ok()); + assert_eq!(buf[0], TlvType::MsgToUser as u8); + assert_eq!(buf[1], 0); + } + + #[test] + fn test_empty_deserialization() { + let mut buf: [u8; 4] = [0; 4]; + buf[0] = TlvType::MsgToUser as u8; + buf[1] = 0; + let tlv_empty = Tlv::from_bytes(&mut buf); + assert!(tlv_empty.is_ok()); + let tlv_empty = tlv_empty.unwrap(); + assert!(tlv_empty.is_empty()); + assert!(tlv_empty.value().is_none()); + assert_eq!( + tlv_empty.tlv_type_field(), + TlvTypeField::Standard(TlvType::MsgToUser) + ); + assert_eq!(tlv_empty.len_full(), 2); + assert_eq!(tlv_empty.len_value(), 0); + } + + #[test] + fn test_buf_too_large() { + let buf_too_large: [u8; u8::MAX as usize + 1] = [0; u8::MAX as usize + 1]; + let tlv_res = Tlv::new(TlvType::MsgToUser, &buf_too_large); + assert!(tlv_res.is_err()); + let error = tlv_res.unwrap_err(); + if let TlvLvError::DataTooLarge(size) = error { + assert_eq!(size, u8::MAX as usize + 1); + } else { + panic!("unexpected error {:?}", error); + } + } + + #[test] + fn test_deserialization_custom_tlv_type() { + let mut buf: [u8; 4] = [0; 4]; + buf[0] = 3; + buf[1] = 1; + buf[2] = 5; + let tlv = Tlv::from_bytes(&mut buf); + assert!(tlv.is_ok()); + let tlv = tlv.unwrap(); + assert_eq!(tlv.tlv_type_field(), TlvTypeField::Custom(3)); + assert_eq!(tlv.len_value(), 1); + assert_eq!(tlv.len_full(), 3); + } + + fn generic_fs_request_test_one_file( + action_code: FilestoreActionCode, + ) -> FilestoreRequestTlv<'static, 'static> { + assert!(!FilestoreRequestTlv::has_second_filename(action_code)); + let first_name = Lv::new_from_str(TLV_TEST_STR_0).unwrap(); + let fs_request = match action_code { + FilestoreActionCode::CreateFile => FilestoreRequestTlv::new_create_file(first_name), + FilestoreActionCode::DeleteFile => FilestoreRequestTlv::new_delete_file(first_name), + FilestoreActionCode::CreateDirectory => { + FilestoreRequestTlv::new_create_directory(first_name) + } + FilestoreActionCode::RemoveDirectory => { + FilestoreRequestTlv::new_remove_directory(first_name) + } + FilestoreActionCode::DenyFile => FilestoreRequestTlv::new_deny_file(first_name), + FilestoreActionCode::DenyDirectory => { + FilestoreRequestTlv::new_deny_directory(first_name) + } + _ => panic!("invalid action code"), + }; + assert!(fs_request.is_ok()); + let fs_request = fs_request.unwrap(); + assert_eq!(fs_request.len_value(), 1 + first_name.len_full()); + assert_eq!(fs_request.len_full(), fs_request.len_value() + 2); + assert_eq!(fs_request.action_code(), action_code); + assert_eq!(fs_request.first_name(), first_name); + assert_eq!(fs_request.second_name(), None); + fs_request + } + + fn generic_fs_request_test_two_files( + action_code: FilestoreActionCode, + ) -> FilestoreRequestTlv<'static, 'static> { + assert!(FilestoreRequestTlv::has_second_filename(action_code)); + let first_name = Lv::new_from_str(TLV_TEST_STR_0).unwrap(); + let second_name = Lv::new_from_str(TLV_TEST_STR_1).unwrap(); + let fs_request = match action_code { + FilestoreActionCode::ReplaceFile => { + FilestoreRequestTlv::new_replace_file(first_name, second_name) + } + FilestoreActionCode::AppendFile => { + FilestoreRequestTlv::new_append_file(first_name, second_name) + } + FilestoreActionCode::RenameFile => { + FilestoreRequestTlv::new_rename_file(first_name, second_name) + } + _ => panic!("invalid action code"), + }; + assert!(fs_request.is_ok()); + let fs_request = fs_request.unwrap(); + assert_eq!( + fs_request.len_value(), + 1 + first_name.len_full() + second_name.len_full() + ); + assert_eq!(fs_request.len_full(), fs_request.len_value() + 2); + assert_eq!(fs_request.action_code(), action_code); + assert_eq!(fs_request.first_name(), first_name); + assert!(fs_request.second_name().is_some()); + assert_eq!(fs_request.second_name().unwrap(), second_name); + fs_request + } + + #[test] + fn test_fs_request_basic_create_file() { + generic_fs_request_test_one_file(FilestoreActionCode::CreateFile); + } + + #[test] + fn test_fs_request_basic_delete() { + generic_fs_request_test_one_file(FilestoreActionCode::DeleteFile); + } + + #[test] + fn test_fs_request_basic_create_dir() { + generic_fs_request_test_one_file(FilestoreActionCode::CreateDirectory); + } + + #[test] + fn test_fs_request_basic_remove_dir() { + generic_fs_request_test_one_file(FilestoreActionCode::RemoveDirectory); + } + + #[test] + fn test_fs_request_basic_deny_file() { + generic_fs_request_test_one_file(FilestoreActionCode::DenyFile); + } + + #[test] + fn test_fs_request_basic_deny_dir() { + generic_fs_request_test_one_file(FilestoreActionCode::DenyDirectory); + } + + #[test] + fn test_fs_request_basic_append_file() { + generic_fs_request_test_two_files(FilestoreActionCode::AppendFile); + } + + #[test] + fn test_fs_request_basic_rename_file() { + generic_fs_request_test_two_files(FilestoreActionCode::RenameFile); + } + + #[test] + fn test_fs_request_basic_replace_file() { + generic_fs_request_test_two_files(FilestoreActionCode::ReplaceFile); + } + + fn check_fs_request_first_part( + buf: &[u8], + action_code: FilestoreActionCode, + expected_val_len: u8, + ) -> usize { + assert_eq!(buf[0], TlvType::FilestoreRequest as u8); + assert_eq!(buf[1], expected_val_len); + assert_eq!((buf[2] >> 4) & 0b1111, action_code as u8); + let lv = Lv::from_bytes(&buf[3..]); + assert!(lv.is_ok()); + let lv = lv.unwrap(); + assert_eq!(lv.value_as_str().unwrap().unwrap(), TLV_TEST_STR_0); + 3 + lv.len_full() + } + + #[test] + fn test_fs_request_serialization_one_file() { + let req = generic_fs_request_test_one_file(FilestoreActionCode::CreateFile); + let mut buf: [u8; 64] = [0; 64]; + let res = req.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, 3 + 1 + TLV_TEST_STR_0.len()); + assert_eq!(written, req.len_full()); + check_fs_request_first_part( + &buf, + FilestoreActionCode::CreateFile, + 1 + 1 + TLV_TEST_STR_0.len() as u8, + ); + } + + #[test] + fn test_fs_request_deserialization_one_file() { + let req = generic_fs_request_test_one_file(FilestoreActionCode::CreateFile); + let mut buf: [u8; 64] = [0; 64]; + let res = req.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let req_conv_back = FilestoreRequestTlv::from_bytes(&buf); + assert!(req_conv_back.is_ok()); + let req_conv_back = req_conv_back.unwrap(); + assert_eq!(req_conv_back, req); + } + + #[test] + fn test_fs_request_serialization_two_files() { + let req = generic_fs_request_test_two_files(FilestoreActionCode::RenameFile); + let mut buf: [u8; 64] = [0; 64]; + let res = req.write_to_bytes(&mut buf); + assert!(res.is_ok()); + let written = res.unwrap(); + assert_eq!(written, req.len_full()); + assert_eq!( + written, + 3 + 1 + TLV_TEST_STR_0.len() + 1 + TLV_TEST_STR_1.len() + ); + let current_idx = check_fs_request_first_part( + &buf, + FilestoreActionCode::RenameFile, + 1 + 1 + TLV_TEST_STR_0.len() as u8 + 1 + TLV_TEST_STR_1.len() as u8, + ); + let second_lv = Lv::from_bytes(&buf[current_idx..]); + assert!(second_lv.is_ok()); + let second_lv = second_lv.unwrap(); + assert_eq!(second_lv.value_as_str().unwrap().unwrap(), TLV_TEST_STR_1); + assert_eq!(current_idx + second_lv.len_full(), req.len_full()); + } + + #[test] + fn test_fs_request_deserialization_two_files() { + let req = generic_fs_request_test_two_files(FilestoreActionCode::RenameFile); + let mut buf: [u8; 64] = [0; 64]; + req.write_to_bytes(&mut buf).unwrap(); + let req_conv_back = FilestoreRequestTlv::from_bytes(&buf); + assert!(req_conv_back.is_ok()); + let req_conv_back = req_conv_back.unwrap(); + assert_eq!(req_conv_back, req); + } +} diff --git a/src/ecss/mod.rs b/src/ecss/mod.rs index 8f5cd74..920632f 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, SizeMissmatch}; +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 { @@ -291,6 +290,7 @@ macro_rules! sp_header_impls { } } +use crate::util::{GenericUnsignedByteField, ToBeBytes, UnsignedEnum}; pub(crate) use ccsds_impl; pub(crate) use sp_header_impls; @@ -298,66 +298,17 @@ pub(crate) use sp_header_impls; /// and an unsigned value. The trait makes no assumptions about the actual type of the unsigned /// value and only requires implementors to implement a function which writes the enumeration into /// a raw byte format. -pub trait EcssEnumeration { +pub trait EcssEnumeration: UnsignedEnum { /// Packet Format Code, which denotes the number of bits of the enumeration fn pfc(&self) -> u8; - fn byte_width(&self) -> usize { - (self.pfc() / 8) as usize - } - fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError>; } pub trait EcssEnumerationExt: EcssEnumeration + Debug + Copy + Clone + PartialEq + Eq {} -pub trait ToBeBytes { - type ByteArray: AsRef<[u8]>; - fn to_be_bytes(&self) -> Self::ByteArray; -} - -impl ToBeBytes for () { - type ByteArray = [u8; 0]; - - fn to_be_bytes(&self) -> Self::ByteArray { - [] - } -} - -impl ToBeBytes for u8 { - type ByteArray = [u8; 1]; - - fn to_be_bytes(&self) -> Self::ByteArray { - u8::to_be_bytes(*self) - } -} - -impl ToBeBytes for u16 { - type ByteArray = [u8; 2]; - - fn to_be_bytes(&self) -> Self::ByteArray { - u16::to_be_bytes(*self) - } -} - -impl ToBeBytes for u32 { - type ByteArray = [u8; 4]; - - fn to_be_bytes(&self) -> Self::ByteArray { - u32::to_be_bytes(*self) - } -} - -impl ToBeBytes for u64 { - type ByteArray = [u8; 8]; - - fn to_be_bytes(&self) -> Self::ByteArray { - u64::to_be_bytes(*self) - } -} - #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct GenericEcssEnumWrapper { - val: TYPE, + field: GenericUnsignedByteField, } impl GenericEcssEnumWrapper { @@ -366,7 +317,19 @@ impl GenericEcssEnumWrapper { } pub fn new(val: TYPE) -> Self { - Self { val } + Self { + field: GenericUnsignedByteField::new(val), + } + } +} + +impl UnsignedEnum for GenericEcssEnumWrapper { + fn len(&self) -> usize { + (self.pfc() / 8) as usize + } + + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + self.field.write_to_be_bytes(buf) } } @@ -374,17 +337,6 @@ impl EcssEnumeration for GenericEcssEnumWrapper { fn pfc(&self) -> u8 { size_of::() as u8 * 8_u8 } - - fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> { - if buf.len() < self.byte_width() { - return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { - found: buf.len(), - expected: self.byte_width(), - })); - } - buf[0..self.byte_width()].copy_from_slice(self.val.to_be_bytes().as_ref()); - Ok(()) - } } impl EcssEnumerationExt @@ -399,7 +351,7 @@ pub type EcssEnumU64 = GenericEcssEnumWrapper; #[cfg(test)] mod tests { - use crate::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU8, EcssEnumeration}; + use crate::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU8, UnsignedEnum}; use crate::ByteConversionError; #[test] diff --git a/src/lib.rs b/src/lib.rs index a3b02e0..094322d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,8 @@ //! //! - Space Packet implementation according to //! [CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf) +//! - CCSDS File Delivery Protocol (CFDP) packet implementations according to +//! [CCSDS Blue Book 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf) //! - PUS Telecommand and PUS Telemetry implementation according to the //! [ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). //! - CUC (CCSDS Unsegmented Time Code) implementation according to @@ -60,23 +62,31 @@ extern crate alloc; extern crate std; use crate::ecss::CCSDS_HEADER_LEN; -use core::fmt::{Display, Formatter}; +use core::fmt::{Debug, Display, Formatter}; +use crc::{Crc, CRC_16_IBM_3740}; use delegate::delegate; +#[cfg(not(feature = "std"))] +use num_traits::Unsigned; #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +pub mod cfdp; pub mod ecss; pub mod tc; pub mod time; pub mod tm; +pub mod util; 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)) } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..b5d34b1 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,642 @@ +use crate::{ByteConversionError, SizeMissmatch}; +use core::fmt::{Debug, Display, Formatter}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "std")] +use std::error::Error; + +pub trait ToBeBytes { + type ByteArray: AsRef<[u8]>; + /// Length when written to big endian bytes. + fn written_len(&self) -> usize; + fn to_be_bytes(&self) -> Self::ByteArray; +} + +impl ToBeBytes for () { + type ByteArray = [u8; 0]; + + fn written_len(&self) -> usize { + 0 + } + + fn to_be_bytes(&self) -> Self::ByteArray { + [] + } +} + +impl ToBeBytes for u8 { + type ByteArray = [u8; 1]; + + fn written_len(&self) -> usize { + 1 + } + fn to_be_bytes(&self) -> Self::ByteArray { + u8::to_be_bytes(*self) + } +} + +impl ToBeBytes for u16 { + type ByteArray = [u8; 2]; + + fn written_len(&self) -> usize { + 2 + } + fn to_be_bytes(&self) -> Self::ByteArray { + u16::to_be_bytes(*self) + } +} + +impl ToBeBytes for u32 { + type ByteArray = [u8; 4]; + + fn written_len(&self) -> usize { + 4 + } + fn to_be_bytes(&self) -> Self::ByteArray { + u32::to_be_bytes(*self) + } +} + +impl ToBeBytes for u64 { + type ByteArray = [u8; 8]; + + fn written_len(&self) -> usize { + 8 + } + fn to_be_bytes(&self) -> Self::ByteArray { + u64::to_be_bytes(*self) + } +} + +#[allow(clippy::len_without_is_empty)] +pub trait UnsignedEnum { + fn len(&self) -> usize; + /// Write the unsigned enumeration to a raw buffer. Returns the written size on success. + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result; +} + +pub trait UnsignedEnumExt: UnsignedEnum + Debug + Copy + Clone + PartialEq + Eq {} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum UnsignedByteFieldError { + /// Value is too large for specified width of byte field. The first value contains the width, + /// the second value contains the detected value. + ValueTooLargeForWidth((usize, u64)), + /// Only 1, 2, 4 and 8 are allow width values. Optionally contains the expected width if + /// applicable, for example for conversions. + InvalidWidth(usize, Option), + ByteConversionError(ByteConversionError), +} + +impl From for UnsignedByteFieldError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversionError(value) + } +} + +impl Display for UnsignedByteFieldError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Self::ByteConversionError(e) => { + write!(f, "low level byte conversion error: {e}") + } + Self::InvalidWidth(val, _) => { + write!(f, "invalid width {val}, only 1, 2, 4 and 8 are allowed.") + } + Self::ValueTooLargeForWidth((width, value)) => { + write!(f, "value {value} too large for width {width}") + } + } + } +} + +#[cfg(feature = "std")] +impl Error for UnsignedByteFieldError {} + +/// Type erased variant. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct UnsignedByteField { + width: usize, + value: u64, +} + +impl UnsignedByteField { + pub fn new(width: usize, value: u64) -> Self { + Self { width, value } + } + + pub fn value(&self) -> u64 { + self.value + } + + pub fn new_from_be_bytes(width: usize, buf: &[u8]) -> Result { + if width > buf.len() { + return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch { + expected: width, + found: buf.len(), + }) + .into()); + } + match width { + 0 => Ok(Self::new(width, 0)), + 1 => Ok(Self::new(width, buf[0] as u64)), + 2 => Ok(Self::new( + width, + u16::from_be_bytes(buf[0..2].try_into().unwrap()) as u64, + )), + 4 => Ok(Self::new( + width, + u32::from_be_bytes(buf[0..4].try_into().unwrap()) as u64, + )), + 8 => Ok(Self::new( + width, + u64::from_be_bytes(buf[0..8].try_into().unwrap()), + )), + _ => Err(UnsignedByteFieldError::InvalidWidth(width, None)), + } + } +} + +impl UnsignedEnum for UnsignedByteField { + fn len(&self) -> usize { + self.width + } + + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.len() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + expected: self.len(), + found: buf.len(), + })); + } + match self.len() { + 0 => Ok(0), + 1 => { + let u8 = UnsignedByteFieldU8::try_from(*self).unwrap(); + u8.write_to_be_bytes(buf) + } + 2 => { + let u16 = UnsignedByteFieldU16::try_from(*self).unwrap(); + u16.write_to_be_bytes(buf) + } + 4 => { + let u32 = UnsignedByteFieldU32::try_from(*self).unwrap(); + u32.write_to_be_bytes(buf) + } + 8 => { + let u64 = UnsignedByteFieldU64::try_from(*self).unwrap(); + u64.write_to_be_bytes(buf) + } + _ => { + // The API does not allow this. + panic!("unexpected written length"); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct GenericUnsignedByteField { + value: TYPE, +} + +impl GenericUnsignedByteField { + pub fn new(val: TYPE) -> Self { + Self { value: val } + } +} + +impl UnsignedEnum for GenericUnsignedByteField { + fn len(&self) -> usize { + self.value.written_len() + } + + fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.len() { + return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch { + found: buf.len(), + expected: self.len(), + })); + } + buf[0..self.len()].copy_from_slice(self.value.to_be_bytes().as_ref()); + Ok(self.value.written_len()) + } +} + +pub type UnsignedByteFieldEmpty = GenericUnsignedByteField<()>; +pub type UnsignedByteFieldU8 = GenericUnsignedByteField; +pub type UnsignedByteFieldU16 = GenericUnsignedByteField; +pub type UnsignedByteFieldU32 = GenericUnsignedByteField; +pub type UnsignedByteFieldU64 = GenericUnsignedByteField; + +pub type UbfU8 = UnsignedByteFieldU8; +pub type UbfU16 = UnsignedByteFieldU16; +pub type UbfU32 = UnsignedByteFieldU32; +pub type UbfU64 = UnsignedByteFieldU64; + +impl From for UnsignedByteField { + fn from(value: UnsignedByteFieldU8) -> Self { + Self::new(1, value.value as u64) + } +} + +impl TryFrom for UnsignedByteFieldU8 { + type Error = UnsignedByteFieldError; + + fn try_from(value: UnsignedByteField) -> Result { + if value.width != 1 { + return Err(UnsignedByteFieldError::InvalidWidth(value.width, Some(1))); + } + Ok(Self::new(value.value as u8)) + } +} + +impl From for UnsignedByteField { + fn from(value: UnsignedByteFieldU16) -> Self { + Self::new(2, value.value as u64) + } +} + +impl TryFrom for UnsignedByteFieldU16 { + type Error = UnsignedByteFieldError; + + fn try_from(value: UnsignedByteField) -> Result { + if value.width != 2 { + return Err(UnsignedByteFieldError::InvalidWidth(value.width, Some(2))); + } + Ok(Self::new(value.value as u16)) + } +} + +impl From for UnsignedByteField { + fn from(value: UnsignedByteFieldU32) -> Self { + Self::new(4, value.value as u64) + } +} + +impl TryFrom for UnsignedByteFieldU32 { + type Error = UnsignedByteFieldError; + + fn try_from(value: UnsignedByteField) -> Result { + if value.width != 4 { + return Err(UnsignedByteFieldError::InvalidWidth(value.width, Some(4))); + } + Ok(Self::new(value.value as u32)) + } +} + +impl From for UnsignedByteField { + fn from(value: UnsignedByteFieldU64) -> Self { + Self::new(8, value.value) + } +} + +impl TryFrom for UnsignedByteFieldU64 { + type Error = UnsignedByteFieldError; + + fn try_from(value: UnsignedByteField) -> Result { + if value.width != 8 { + return Err(UnsignedByteFieldError::InvalidWidth(value.width, Some(8))); + } + Ok(Self::new(value.value)) + } +} + +#[cfg(test)] +pub mod tests { + use crate::util::{ + UnsignedByteField, UnsignedByteFieldError, UnsignedByteFieldU16, UnsignedByteFieldU32, + UnsignedByteFieldU64, UnsignedByteFieldU8, UnsignedEnum, + }; + use crate::ByteConversionError; + use std::format; + + #[test] + fn test_simple_u8() { + let u8 = UnsignedByteFieldU8::new(5); + assert_eq!(u8.len(), 1); + let mut buf: [u8; 8] = [0; 8]; + let len = u8 + .write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + assert_eq!(len, 1); + assert_eq!(buf[0], 5); + for i in 1..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn test_simple_u16() { + let u16 = UnsignedByteFieldU16::new(3823); + assert_eq!(u16.len(), 2); + let mut buf: [u8; 8] = [0; 8]; + let len = u16 + .write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + assert_eq!(len, 2); + let raw_val = u16::from_be_bytes(buf[0..2].try_into().unwrap()); + assert_eq!(raw_val, 3823); + for i in 2..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn test_simple_u32() { + let u32 = UnsignedByteFieldU32::new(80932); + assert_eq!(u32.len(), 4); + let mut buf: [u8; 8] = [0; 8]; + let len = u32 + .write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + assert_eq!(len, 4); + let raw_val = u32::from_be_bytes(buf[0..4].try_into().unwrap()); + assert_eq!(raw_val, 80932); + for i in 4..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn test_simple_u64() { + let u64 = UnsignedByteFieldU64::new(5999999); + assert_eq!(u64.len(), 8); + let mut buf: [u8; 8] = [0; 8]; + let len = u64 + .write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + assert_eq!(len, 8); + let raw_val = u64::from_be_bytes(buf[0..8].try_into().unwrap()); + assert_eq!(raw_val, 5999999); + } + + #[test] + fn conversions_u8() { + let u8 = UnsignedByteFieldU8::new(5); + let u8_type_erased = UnsignedByteField::from(u8); + assert_eq!(u8_type_erased.width, 1); + assert_eq!(u8_type_erased.value, 5); + let u8_conv_back = + UnsignedByteFieldU8::try_from(u8_type_erased).expect("conversion failed for u8"); + assert_eq!(u8, u8_conv_back); + assert_eq!(u8_conv_back.value, 5); + } + + #[test] + fn conversion_u8_fails() { + let field = UnsignedByteField::new(2, 60000); + let conv_fails = UnsignedByteFieldU8::try_from(field); + assert!(conv_fails.is_err()); + let err = conv_fails.unwrap_err(); + match err { + UnsignedByteFieldError::InvalidWidth(width, Some(expected)) => { + assert_eq!(width, 2); + assert_eq!(expected, 1); + } + _ => { + panic!("{}", format!("invalid error {err}")) + } + } + } + + #[test] + fn conversions_u16() { + let u16 = UnsignedByteFieldU16::new(64444); + let u16_type_erased = UnsignedByteField::from(u16); + assert_eq!(u16_type_erased.width, 2); + assert_eq!(u16_type_erased.value, 64444); + let u16_conv_back = + UnsignedByteFieldU16::try_from(u16_type_erased).expect("conversion failed for u16"); + assert_eq!(u16, u16_conv_back); + assert_eq!(u16_conv_back.value, 64444); + } + + #[test] + fn conversion_u16_fails() { + let field = UnsignedByteField::new(4, 75000); + let conv_fails = UnsignedByteFieldU16::try_from(field); + assert!(conv_fails.is_err()); + let err = conv_fails.unwrap_err(); + match err { + UnsignedByteFieldError::InvalidWidth(width, Some(expected)) => { + assert_eq!(width, 4); + assert_eq!(expected, 2); + } + _ => { + panic!("{}", format!("invalid error {err}")) + } + } + } + + #[test] + fn conversions_u32() { + let u32 = UnsignedByteFieldU32::new(75000); + let u32_type_erased = UnsignedByteField::from(u32); + assert_eq!(u32_type_erased.width, 4); + assert_eq!(u32_type_erased.value, 75000); + let u32_conv_back = + UnsignedByteFieldU32::try_from(u32_type_erased).expect("conversion failed for u32"); + assert_eq!(u32, u32_conv_back); + assert_eq!(u32_conv_back.value, 75000); + } + + #[test] + fn conversion_u32_fails() { + let field = UnsignedByteField::new(8, 75000); + let conv_fails = UnsignedByteFieldU32::try_from(field); + assert!(conv_fails.is_err()); + let err = conv_fails.unwrap_err(); + match err { + UnsignedByteFieldError::InvalidWidth(width, Some(expected)) => { + assert_eq!(width, 8); + assert_eq!(expected, 4); + } + _ => { + panic!("{}", format!("invalid error {err}")) + } + } + } + + #[test] + fn conversions_u64() { + let u64 = UnsignedByteFieldU64::new(5999999); + let u64_type_erased = UnsignedByteField::from(u64); + assert_eq!(u64_type_erased.width, 8); + assert_eq!(u64_type_erased.value, 5999999); + let u64_conv_back = + UnsignedByteFieldU64::try_from(u64_type_erased).expect("conversion failed for u64"); + assert_eq!(u64, u64_conv_back); + assert_eq!(u64_conv_back.value, 5999999); + } + + #[test] + fn conversion_u64_fails() { + let field = UnsignedByteField::new(4, 60000); + let conv_fails = UnsignedByteFieldU64::try_from(field); + assert!(conv_fails.is_err()); + let err = conv_fails.unwrap_err(); + match err { + UnsignedByteFieldError::InvalidWidth(width, Some(expected)) => { + assert_eq!(width, 4); + assert_eq!(expected, 8); + } + _ => { + panic!("{}", format!("invalid error {err}")) + } + } + } + + #[test] + fn type_erased_u8_write() { + let u8 = UnsignedByteField::new(1, 5); + assert_eq!(u8.len(), 1); + let mut buf: [u8; 8] = [0; 8]; + u8.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + assert_eq!(buf[0], 5); + for i in 1..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn type_erased_u16_write() { + let u16 = UnsignedByteField::new(2, 3823); + assert_eq!(u16.len(), 2); + let mut buf: [u8; 8] = [0; 8]; + u16.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + let raw_val = u16::from_be_bytes(buf[0..2].try_into().unwrap()); + assert_eq!(raw_val, 3823); + for i in 2..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn type_erased_u32_write() { + let u32 = UnsignedByteField::new(4, 80932); + assert_eq!(u32.len(), 4); + let mut buf: [u8; 8] = [0; 8]; + u32.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + let raw_val = u32::from_be_bytes(buf[0..4].try_into().unwrap()); + assert_eq!(raw_val, 80932); + for i in 4..8 { + assert_eq!(buf[i], 0); + } + } + + #[test] + fn type_erased_u64_write() { + let u64 = UnsignedByteField::new(8, 5999999); + assert_eq!(u64.len(), 8); + let mut buf: [u8; 8] = [0; 8]; + u64.write_to_be_bytes(&mut buf) + .expect("writing to raw buffer failed"); + let raw_val = u64::from_be_bytes(buf[0..8].try_into().unwrap()); + assert_eq!(raw_val, 5999999); + } + + #[test] + fn type_erased_u8_construction() { + let buf: [u8; 2] = [5, 10]; + let u8 = UnsignedByteField::new_from_be_bytes(1, &buf).expect("construction failed"); + assert_eq!(u8.width, 1); + assert_eq!(u8.value, 5); + } + + #[test] + fn type_erased_u16_construction() { + let buf: [u8; 2] = [0x10, 0x15]; + let u16 = UnsignedByteField::new_from_be_bytes(2, &buf).expect("construction failed"); + assert_eq!(u16.width, 2); + assert_eq!(u16.value, 0x1015); + } + + #[test] + fn type_erased_u32_construction() { + let buf: [u8; 4] = [0x01, 0x02, 0x03, 0x04]; + let u32 = UnsignedByteField::new_from_be_bytes(4, &buf).expect("construction failed"); + assert_eq!(u32.width, 4); + assert_eq!(u32.value, 0x01020304); + } + + #[test] + fn type_erased_u64_construction() { + let buf: [u8; 8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]; + let u64 = UnsignedByteField::new_from_be_bytes(8, &buf).expect("construction failed"); + assert_eq!(u64.width, 8); + assert_eq!(u64.value, 0x0102030405060708); + } + + #[test] + fn type_u16_target_buf_too_small() { + let u16 = UnsignedByteFieldU16::new(500); + let mut buf: [u8; 1] = [0; 1]; + let res = u16.write_to_be_bytes(&mut buf); + assert!(res.is_err()); + let err = res.unwrap_err(); + match err { + ByteConversionError::ToSliceTooSmall(missmatch) => { + assert_eq!(missmatch.found, 1); + assert_eq!(missmatch.expected, 2); + } + _ => { + panic!("invalid exception") + } + } + } + + #[test] + fn type_erased_u16_target_buf_too_small() { + let u16 = UnsignedByteField::new(2, 500); + let mut buf: [u8; 1] = [0; 1]; + let res = u16.write_to_be_bytes(&mut buf); + assert!(res.is_err()); + let err = res.unwrap_err(); + match err { + ByteConversionError::ToSliceTooSmall(missmatch) => { + assert_eq!(missmatch.found, 1); + assert_eq!(missmatch.expected, 2); + } + _ => { + panic!("invalid exception {}", err) + } + } + let u16 = UnsignedByteField::new_from_be_bytes(2, &buf); + assert!(u16.is_err()); + let err = u16.unwrap_err(); + if let UnsignedByteFieldError::ByteConversionError( + ByteConversionError::FromSliceTooSmall(missmatch), + ) = err + { + assert_eq!(missmatch.expected, 2); + assert_eq!(missmatch.found, 1); + } else { + panic!("unexpected exception {}", err); + } + } + + #[test] + fn type_u32_target_buf_too_small() { + let u16 = UnsignedByteFieldU32::new(500); + let mut buf: [u8; 3] = [0; 3]; + let res = u16.write_to_be_bytes(&mut buf); + assert!(res.is_err()); + let err = res.unwrap_err(); + match err { + ByteConversionError::ToSliceTooSmall(missmatch) => { + assert_eq!(missmatch.found, 3); + assert_eq!(missmatch.expected, 4); + } + _ => { + panic!("invalid exception") + } + } + } +}