diff --git a/src/ecss/tm_pus_a.rs b/src/ecss/tm_pus_a.rs index 81266fc..f20d36c 100644 --- a/src/ecss/tm_pus_a.rs +++ b/src/ecss/tm_pus_a.rs @@ -1132,8 +1132,6 @@ mod tests { use super::*; use crate::time::cds::CdsTime; - #[cfg(feature = "serde")] - use crate::time::CcsdsTimeProvider; use crate::SpHeader; use crate::{ecss::PusVersion::PusA, util::UnsignedByteFieldU16}; #[cfg(feature = "serde")] diff --git a/src/lib.rs b/src/lib.rs index d7d4cb2..267a6d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,7 @@ pub mod crc; pub mod ecss; pub mod seq_count; pub mod time; +pub mod uslp; pub mod util; mod private { diff --git a/src/uslp/mod.rs b/src/uslp/mod.rs new file mode 100644 index 0000000..d6f6555 --- /dev/null +++ b/src/uslp/mod.rs @@ -0,0 +1,941 @@ +/// # Support of the CCSDS Unified Space Data Link Protocol (USLP) +use crate::{crc::CRC_CCITT_FALSE, ByteConversionError}; + +/// Only this version is supported by the library +pub const USLP_VERSION_NUMBER: u8 = 0b1100; + +/// Identifies the association of the data contained in the transfer frame. +#[derive( + Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive, +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum SourceOrDestField { + /// SCID refers to the source of the transfer frame. + Source = 0, + /// SCID refers to the destination of the transfer frame. + Dest = 1, +} + +#[derive( + Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive, +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum BypassSequenceControlFlag { + /// Acceptance of this frame on the receiving end is subject to normal frame acceptance + /// checks of FARM. + SequenceControlledQoS = 0, + /// Frame Acceptance Checks of FARM by the receiving end shall be bypassed. + ExpeditedQoS = 1, +} + +#[derive( + Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive, +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum ProtocolControlCommandFlag { + TfdfContainsUserData = 0, + TfdfContainsProtocolInfo = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum UslpError { + ByteConversion(ByteConversionError), + HeaderIsTruncated, + InvalidProtocolId(u8), + InvalidConstructionRule(u8), + InvalidVersionNumber(u8), + InvalidVcid(u8), + InvalidMapId(u8), + ChecksumFailure(u16), +} + +impl From for UslpError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversion(value) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidValueForLen { + value: u64, + len: u8, +} + +#[derive(Debug, Copy, Clone, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PrimaryHeader { + pub spacecraft_id: u16, + pub source_or_dest_field: SourceOrDestField, + pub vc_id: u8, + pub map_id: u8, + frame_len_field: u16, + pub sequence_control_flag: BypassSequenceControlFlag, + pub protocol_control_command_flag: ProtocolControlCommandFlag, + pub ocf_flag: bool, + vc_frame_count_len: u8, + vc_frame_count: u64, +} + +impl PrimaryHeader { + pub fn new( + spacecraft_id: u16, + source_or_dest_field: SourceOrDestField, + vc_id: u8, + map_id: u8, + frame_len: u16, + ) -> Result { + if vc_id > 0b111111 { + return Err(UslpError::InvalidVcid(vc_id)); + } + if map_id > 0b1111 { + return Err(UslpError::InvalidMapId(map_id)); + } + Ok(Self { + spacecraft_id, + source_or_dest_field, + vc_id, + map_id, + frame_len_field: frame_len.saturating_sub(1), + sequence_control_flag: BypassSequenceControlFlag::SequenceControlledQoS, + protocol_control_command_flag: ProtocolControlCommandFlag::TfdfContainsUserData, + ocf_flag: false, + vc_frame_count_len: 0, + vc_frame_count: 0, + }) + } + + pub fn set_vc_frame_count( + &mut self, + count_len: u8, + count: u64, + ) -> Result<(), InvalidValueForLen> { + if count > 2_u64.pow(count_len as u32 * 8) - 1 { + return Err(InvalidValueForLen { + value: count, + len: count_len, + }); + } + self.vc_frame_count_len = count_len; + self.vc_frame_count = count; + Ok(()) + } + + #[inline] + pub fn vc_frame_count(&self) -> u64 { + self.vc_frame_count + } + + #[inline] + pub fn vc_frame_count_len(&self) -> u8 { + self.vc_frame_count_len + } + + pub fn from_bytes(buf: &[u8]) -> Result { + if buf.len() < 4 { + return Err(ByteConversionError::FromSliceTooSmall { + found: buf.len(), + expected: 4, + } + .into()); + } + // Can only deal with regular frames for now. + if (buf[3] & 0b1) == 1 { + return Err(UslpError::HeaderIsTruncated); + } + // We could check this above, but this is a better error for the case where the user + // tries to read a truncated frame. + if buf.len() < 7 { + return Err(ByteConversionError::FromSliceTooSmall { + found: buf.len(), + expected: 7, + } + .into()); + } + let version_number = (buf[0] >> 4) & 0b1111; + if version_number != USLP_VERSION_NUMBER { + return Err(UslpError::InvalidVersionNumber(version_number)); + } + let source_or_dest_field = match (buf[2] >> 3) & 1 { + 0 => SourceOrDestField::Source, + 1 => SourceOrDestField::Dest, + _ => unreachable!(), + }; + let vc_frame_count_len = buf[6] & 0b111; + if buf.len() < 7 + vc_frame_count_len as usize { + return Err(ByteConversionError::FromSliceTooSmall { + found: buf.len(), + expected: 7 + vc_frame_count_len as usize, + } + .into()); + } + let vc_frame_count = match vc_frame_count_len { + 1 => buf[7] as u64, + 2 => u16::from_be_bytes(buf[7..9].try_into().unwrap()) as u64, + 4 => u32::from_be_bytes(buf[7..11].try_into().unwrap()) as u64, + len => { + let mut vcf_count = 0u64; + let mut end = len; + for byte in buf[7..7 + len as usize].iter() { + vcf_count |= (*byte as u64) << ((end - 1) * 8); + end -= 1; + } + vcf_count + } + }; + Ok(Self { + spacecraft_id: (((buf[0] as u16) & 0b1111) << 12) + | ((buf[1] as u16) << 4) + | ((buf[2] as u16) >> 4) & 0b1111, + source_or_dest_field, + vc_id: ((buf[2] & 0b111) << 3) | (buf[3] >> 5) & 0b111, + map_id: (buf[3] >> 1) & 0b1111, + frame_len_field: ((buf[4] as u16) << 8) | buf[5] as u16, + sequence_control_flag: ((buf[6] >> 7) & 0b1).try_into().unwrap(), + protocol_control_command_flag: ((buf[6] >> 6) & 0b1).try_into().unwrap(), + ocf_flag: ((buf[6] >> 3) & 0b1) != 0, + vc_frame_count_len, + vc_frame_count, + }) + } + + pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.len_header() { + return Err(ByteConversionError::ToSliceTooSmall { + found: buf.len(), + expected: self.len_header(), + }); + } + buf[0] = (USLP_VERSION_NUMBER << 4) | ((self.spacecraft_id >> 12) as u8) & 0b1111; + buf[1] = (self.spacecraft_id >> 4) as u8; + buf[2] = (((self.spacecraft_id & 0b1111) as u8) << 4) + | ((self.source_or_dest_field as u8) << 3) + | (self.vc_id >> 3) & 0b111; + buf[3] = ((self.vc_id & 0b111) << 5) | (self.map_id << 1); + buf[4..6].copy_from_slice(&self.frame_len_field.to_be_bytes()); + buf[6] = ((self.sequence_control_flag as u8) << 7) + | ((self.protocol_control_command_flag as u8) << 6) + | ((self.ocf_flag as u8) << 3) + | self.vc_frame_count_len; + let mut packet_idx = 7; + for idx in (0..self.vc_frame_count_len).rev() { + buf[packet_idx] = ((self.vc_frame_count >> (idx * 8)) & 0xff) as u8; + packet_idx += 1; + } + Ok(self.len_header()) + } + + #[inline(always)] + pub fn set_frame_len(&mut self, frame_len: usize) { + // 4.1.2.7.2 + // The field contains a length count C that equals one fewer than the total octets + // in the transfer frame. + self.frame_len_field = frame_len.saturating_sub(1) as u16; + } + + #[inline(always)] + pub fn len_header(&self) -> usize { + 7 + self.vc_frame_count_len as usize + } + + #[inline(always)] + pub fn len_frame(&self) -> usize { + // 4.1.2.7.2 + // The field contains a length count C that equals one fewer than the total octets + // in the transfer frame. + self.frame_len_field as usize + 1 + } +} + +/// Custom implementation which skips the check whether the VC frame count length field is equal. +/// Only the actual VC count value is compared. +impl PartialEq for PrimaryHeader { + fn eq(&self, other: &Self) -> bool { + self.spacecraft_id == other.spacecraft_id + && self.source_or_dest_field == other.source_or_dest_field + && self.vc_id == other.vc_id + && self.map_id == other.map_id + && self.frame_len_field == other.frame_len_field + && self.sequence_control_flag == other.sequence_control_flag + && self.protocol_control_command_flag == other.protocol_control_command_flag + && self.ocf_flag == other.ocf_flag + && self.vc_frame_count == other.vc_frame_count + } +} + +#[derive( + Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive, +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[non_exhaustive] +pub enum UslpProtocolId { + SpacePacketsOrEncapsulation = 0b00000, + /// COP-1 control commands within the TFDZ. + Cop1ControlCommands = 0b00001, + /// COP-P control commands within the TFDZ. + CopPControlCommands = 0b00010, + /// SDLS control commands within the TFDZ. + Sdls = 0b00011, + UserDefinedOctetStream = 0b00100, + /// Proximity-1 Supervisory Protocol Data Units (SPDUs) within the TFDZ. + Spdu = 0b00111, + /// Entire fixed-length TFDZ contains idle data. + Idle = 0b11111, +} + +#[derive( + Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive, +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum ConstructionRule { + /// Indicated fixed-length TFDZ whose contents are CCSDS packets concatenated together, which + /// span transfer frame boundaries. The First Header Pointer (FHP) is required for packet + /// extraction. + PacketSpanningMultipleFrames = 0b000, + StartOfMapaSduOrVcaSdu = 0b001, + ContinuingPortionOfMapaSdu = 0b010, + OctetStream = 0b011, + StartingSegment = 0b100, + ContinuingSegment = 0b101, + LastSegment = 0b110, + NoSegmentation = 0b111, +} + +impl ConstructionRule { + pub const fn applicable_to_fixed_len_tfdz(&self) -> bool { + match self { + ConstructionRule::PacketSpanningMultipleFrames => true, + ConstructionRule::StartOfMapaSduOrVcaSdu => true, + ConstructionRule::ContinuingPortionOfMapaSdu => true, + ConstructionRule::OctetStream => false, + ConstructionRule::StartingSegment => false, + ConstructionRule::ContinuingSegment => false, + ConstructionRule::LastSegment => false, + ConstructionRule::NoSegmentation => false, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TransferFrameDataFieldHeader { + /// Construction rule for the TFDZ. + construction_rule: ConstructionRule, + uslp_protocol_id: UslpProtocolId, + /// First header or last valid octet pointer. Only present if the constuction rule indicated + /// a fixed-length TFDZ. + fhp_or_lvo: Option, +} + +impl TransferFrameDataFieldHeader { + pub fn len_header(&self) -> usize { + if self.construction_rule.applicable_to_fixed_len_tfdz() { + 3 + } else { + 1 + } + } + + pub fn construction_rule(&self) -> ConstructionRule { + self.construction_rule + } + + pub fn uslp_protocol_id(&self) -> UslpProtocolId { + self.uslp_protocol_id + } + + pub fn fhp_or_lvo(&self) -> Option { + self.fhp_or_lvo + } + + pub fn from_bytes(buf: &[u8]) -> Result { + if buf.is_empty() { + return Err(ByteConversionError::FromSliceTooSmall { + found: 0, + expected: 1, + } + .into()); + } + let construction_rule = ConstructionRule::try_from((buf[0] >> 5) & 0b111) + .map_err(|e| UslpError::InvalidConstructionRule(e.number))?; + let mut fhp_or_lvo = None; + if construction_rule.applicable_to_fixed_len_tfdz() { + if buf.len() < 3 { + return Err(ByteConversionError::FromSliceTooSmall { + found: buf.len(), + expected: 3, + } + .into()); + } + fhp_or_lvo = Some(u16::from_be_bytes(buf[1..3].try_into().unwrap())); + } + Ok(Self { + construction_rule, + uslp_protocol_id: UslpProtocolId::try_from(buf[0] & 0b11111) + .map_err(|e| UslpError::InvalidProtocolId(e.number))?, + fhp_or_lvo, + }) + } +} + +/// Simple USLP transfer frame reader. +/// +/// Currently, only insert zone lengths of 0 are supported. +#[derive(Debug)] +pub struct TransferFrameReader<'buf> { + primary_header: PrimaryHeader, + data_field_header: TransferFrameDataFieldHeader, + data: &'buf [u8], + operational_control_field: Option, +} + +impl<'buf> TransferFrameReader<'buf> { + /// This function assumes an insert zone length of 0. + pub fn from_bytes( + buf: &'buf [u8], + has_fecf: bool, + ) -> Result, UslpError> { + let primary_header = PrimaryHeader::from_bytes(buf)?; + if primary_header.len_frame() > buf.len() { + return Err(ByteConversionError::FromSliceTooSmall { + expected: primary_header.len_frame(), + found: buf.len(), + } + .into()); + } + let data_field_header = + TransferFrameDataFieldHeader::from_bytes(&buf[primary_header.len_header()..])?; + let data_idx = primary_header.len_header() + data_field_header.len_header(); + let frame_len = primary_header.len_frame(); + let mut operational_control_field = None; + let mut data_len = frame_len - data_idx; + if has_fecf { + data_len -= 2; + } + if primary_header.ocf_flag { + data_len -= 4; + operational_control_field = Some(u32::from_be_bytes( + buf[data_idx + data_len..data_idx + data_len + 4] + .try_into() + .unwrap(), + )); + } + let data_end = data_idx + data_len; + if has_fecf { + let mut digest = CRC_CCITT_FALSE.digest(); + digest.update(&buf[0..frame_len]); + if digest.finalize() != 0 { + return Err(UslpError::ChecksumFailure(u16::from_be_bytes( + buf[frame_len - 2..frame_len].try_into().unwrap(), + ))); + } + } + Ok(Self { + primary_header, + data_field_header, + data: buf[data_idx..data_end].try_into().unwrap(), + operational_control_field, + }) + } + + pub fn len_frame(&self) -> usize { + self.primary_header.len_frame() + } + + pub fn primary_header(&self) -> &PrimaryHeader { + &self.primary_header + } + + pub fn data_field_header(&self) -> &TransferFrameDataFieldHeader { + &self.data_field_header + } + + pub fn data(&self) -> &'buf [u8] { + self.data + } + + pub fn operational_control_field(&self) -> &Option { + &self.operational_control_field + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn common_basic_check(buf: &[u8]) { + assert_eq!(buf[0] >> 4, USLP_VERSION_NUMBER); + // First four bits SCID. + assert_eq!(buf[0] & 0b1111, 0b1010); + // Next eight bits SCID. + assert_eq!(buf[1], 0b01011100); + // Last four bits SCID. + assert_eq!(buf[2] >> 4, 0b0011); + assert_eq!((buf[2] >> 3) & 0b1, SourceOrDestField::Dest as u8); + // First three bits VCID. + assert_eq!(buf[2] & 0b111, 0b110); + // Last three bits VCID. + assert_eq!(buf[3] >> 5, 0b101); + // MAP ID + assert_eq!((buf[3] >> 1) & 0b1111, 0b1010); + // End of primary header flag + assert_eq!(buf[3] & 0b1, 0); + assert_eq!(u16::from_be_bytes(buf[4..6].try_into().unwrap()), 0x2344); + } + + #[test] + fn test_basic_0() { + let mut buf: [u8; 8] = [0; 8]; + // Should be all zeros after writing. + buf[6] = 0xff; + let primary_header = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b1010, + 0x2345, + ) + .unwrap(); + // Virtual channel count 0. + assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 7); + common_basic_check(&buf); + assert_eq!(primary_header.vc_frame_count_len(), 0); + assert_eq!(primary_header.vc_frame_count(), 0); + // Bypass / Sequence Control Flag. + assert_eq!( + (buf[6] >> 7) & 0b1, + BypassSequenceControlFlag::SequenceControlledQoS as u8 + ); + // Protocol Control Command Flag. + assert_eq!( + (buf[6] >> 6) & 0b1, + ProtocolControlCommandFlag::TfdfContainsUserData as u8 + ); + // OCF flag. + assert_eq!((buf[6] >> 3) & 0b1, false as u8); + // VCF count length. + assert_eq!(buf[6] & 0b111, 0); + } + + #[test] + fn test_basic_1() { + let mut buf: [u8; 16] = [0; 16]; + // Should be all zeros after writing. + buf[6] = 0xff; + let mut primary_header = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b1010, + 0x2345, + ) + .unwrap(); + primary_header.sequence_control_flag = BypassSequenceControlFlag::ExpeditedQoS; + primary_header.protocol_control_command_flag = + ProtocolControlCommandFlag::TfdfContainsProtocolInfo; + primary_header.ocf_flag = true; + primary_header.set_vc_frame_count(4, 0x12345678).unwrap(); + // Virtual channel count 4. + assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 11); + assert_eq!(primary_header.vc_frame_count_len(), 4); + assert_eq!(primary_header.vc_frame_count(), 0x12345678); + common_basic_check(&buf); + // Bypass / Sequence Control Flag. + assert_eq!( + (buf[6] >> 7) & 0b1, + BypassSequenceControlFlag::ExpeditedQoS as u8 + ); + // Protocol Control Command Flag. + assert_eq!( + (buf[6] >> 6) & 0b1, + ProtocolControlCommandFlag::TfdfContainsProtocolInfo as u8 + ); + // OCF flag. + assert_eq!((buf[6] >> 3) & 0b1, true as u8); + // VCF count length. + assert_eq!(buf[6] & 0b111, 4); + assert_eq!( + u32::from_be_bytes(buf[7..11].try_into().unwrap()), + 0x12345678 + ); + } + + #[test] + fn test_vcf_count_len_two() { + let mut buf: [u8; 16] = [0; 16]; + // Should be all zeros after writing. + buf[6] = 0xff; + let mut primary_header = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b1010, + 0x2345, + ) + .unwrap(); + primary_header.set_vc_frame_count(2, 5).unwrap(); + assert_eq!(primary_header.vc_frame_count_len(), 2); + assert_eq!(primary_header.vc_frame_count(), 5); + assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 9); + assert_eq!(buf[6] & 0b111, 2); + assert_eq!(u16::from_be_bytes(buf[7..9].try_into().unwrap()), 5); + + let primary_header = PrimaryHeader::from_bytes(&buf).unwrap(); + assert_eq!(primary_header.vc_frame_count_len(), 2); + assert_eq!(primary_header.vc_frame_count(), 5); + } + + #[test] + fn test_vcf_count_len_one() { + let mut buf: [u8; 16] = [0; 16]; + // Should be all zeros after writing. + buf[6] = 0xff; + let mut primary_header = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b1010, + 0x2345, + ) + .unwrap(); + primary_header.set_vc_frame_count(1, 255).unwrap(); + assert_eq!(primary_header.vc_frame_count_len(), 1); + assert_eq!(primary_header.vc_frame_count(), 255); + assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 8); + assert_eq!(buf[6] & 0b111, 1); + assert_eq!(buf[7], 255); + + let primary_header = PrimaryHeader::from_bytes(&buf).unwrap(); + assert_eq!(primary_header.vc_frame_count_len(), 1); + assert_eq!(primary_header.vc_frame_count(), 255); + } + + #[test] + fn test_reading_0() { + let mut buf: [u8; 8] = [0; 8]; + let primary_header = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b1010, + 0x2345, + ) + .unwrap(); + assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 7); + let parsed_header = PrimaryHeader::from_bytes(&buf).unwrap(); + assert_eq!(parsed_header, primary_header); + } + + #[test] + fn test_reading_1() { + let mut buf: [u8; 16] = [0; 16]; + let mut primary_header = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b1010, + 0x2345, + ) + .unwrap(); + primary_header.sequence_control_flag = BypassSequenceControlFlag::ExpeditedQoS; + primary_header.protocol_control_command_flag = + ProtocolControlCommandFlag::TfdfContainsProtocolInfo; + primary_header.ocf_flag = true; + primary_header.set_vc_frame_count(4, 0x12345678).unwrap(); + assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 11); + let parsed_header = PrimaryHeader::from_bytes(&buf).unwrap(); + assert_eq!(parsed_header, primary_header); + } + + #[test] + fn test_invalid_vcid() { + let error = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b1101011, + 0b1010, + 0x2345, + ); + assert!(error.is_err()); + let error = error.unwrap_err(); + matches!(error, UslpError::InvalidVcid(0b1101011)); + } + + #[test] + fn test_invalid_mapid() { + let error = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b10101, + 0x2345, + ); + assert!(error.is_err()); + let error = error.unwrap_err(); + matches!(error, UslpError::InvalidMapId(0b10101)); + } + + #[test] + fn test_invalid_vc_count() { + let mut primary_header = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b1010, + 0x2345, + ) + .unwrap(); + matches!( + primary_header.set_vc_frame_count(0, 1).unwrap_err(), + InvalidValueForLen { value: 1, len: 0 } + ); + matches!( + primary_header.set_vc_frame_count(1, 256).unwrap_err(), + InvalidValueForLen { value: 256, len: 1 } + ); + } + + #[test] + fn test_frame_parser() { + let mut buf: [u8; 32] = [0; 32]; + // Build a variable frame manually. + let mut primary_header = + PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap(); + let header_len = primary_header.len_header(); + buf[header_len] = ((ConstructionRule::NoSegmentation as u8) << 5) + | (UslpProtocolId::UserDefinedOctetStream as u8) & 0b11111; + buf[header_len + 1] = 0x42; + // 1 byte TFDH, 1 byte data, 2 bytes CRC. + primary_header.set_frame_len(header_len + 4); + primary_header.write_to_be_bytes(&mut buf).unwrap(); + // Calculate and write CRC16. + let mut digest = CRC_CCITT_FALSE.digest(); + digest.update(&buf[0..header_len + 2]); + buf[header_len + 2..header_len + 4].copy_from_slice(&digest.finalize().to_be_bytes()); + // Now parse the frame. + let frame = TransferFrameReader::from_bytes(&buf, true).unwrap(); + assert_eq!(*frame.primary_header(), primary_header); + assert_eq!(frame.data().len(), 1); + assert_eq!(frame.data()[0], 0x42); + assert_eq!( + frame.data_field_header().uslp_protocol_id(), + UslpProtocolId::UserDefinedOctetStream + ); + assert_eq!( + frame.data_field_header().construction_rule(), + ConstructionRule::NoSegmentation + ); + assert!(frame.data_field_header().fhp_or_lvo().is_none()); + assert_eq!(frame.len_frame(), 11); + assert!(frame.operational_control_field().is_none()); + } + + #[test] + fn test_frame_parser_invalid_checksum() { + let mut buf: [u8; 32] = [0; 32]; + // Build a variable frame manually. + let mut primary_header = + PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap(); + let header_len = primary_header.len_header(); + buf[header_len] = ((ConstructionRule::NoSegmentation as u8) << 5) + | (UslpProtocolId::UserDefinedOctetStream as u8) & 0b11111; + buf[header_len + 1] = 0x42; + // 1 byte TFDH, 1 byte data, 2 bytes CRC. + primary_header.set_frame_len(header_len + 4); + primary_header.write_to_be_bytes(&mut buf).unwrap(); + // Now parse the frame without having calculated the checksum. + match TransferFrameReader::from_bytes(&buf, true) { + Ok(_) => panic!("transfer frame read call did not fail"), + Err(e) => { + assert_eq!(e, UslpError::ChecksumFailure(0)); + } + } + } + + #[test] + fn test_frame_parser_buf_too_small() { + let mut buf: [u8; 32] = [0; 32]; + // Build a variable frame manually. + let mut primary_header = + PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap(); + let header_len = primary_header.len_header(); + buf[header_len] = ((ConstructionRule::NoSegmentation as u8) << 5) + | (UslpProtocolId::UserDefinedOctetStream as u8) & 0b11111; + buf[header_len + 1] = 0x42; + // 1 byte TFDH, 1 byte data, 2 bytes CRC. + primary_header.set_frame_len(header_len + 4); + primary_header.write_to_be_bytes(&mut buf).unwrap(); + // Now parse the frame. + let error = TransferFrameReader::from_bytes(&buf[0..7], true).unwrap_err(); + assert_eq!( + error, + ByteConversionError::FromSliceTooSmall { + expected: primary_header.len_frame(), + found: 7 + } + .into() + ); + } + + #[test] + fn test_from_bytes_too_small_0() { + let buf: [u8; 3] = [0; 3]; + assert_eq!( + PrimaryHeader::from_bytes(&buf).unwrap_err(), + ByteConversionError::FromSliceTooSmall { + found: 3, + expected: 4 + } + .into() + ); + } + + #[test] + fn test_from_bytes_too_small_1() { + let buf: [u8; 6] = [0; 6]; + assert_eq!( + PrimaryHeader::from_bytes(&buf).unwrap_err(), + ByteConversionError::FromSliceTooSmall { + found: 6, + expected: 7 + } + .into() + ); + } + + #[test] + fn test_from_bytes_truncated_not_supported() { + let mut buf: [u8; 7] = [0; 7]; + let primary_header = + PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap(); + primary_header.write_to_be_bytes(&mut buf).unwrap(); + // Set truncated header flag manually. + buf[3] |= 0b1; + assert_eq!( + PrimaryHeader::from_bytes(&buf).unwrap_err(), + UslpError::HeaderIsTruncated + ); + } + + #[test] + fn test_from_bytes_too_small_2() { + let mut buf: [u8; 16] = [0; 16]; + // Should be all zeros after writing. + buf[6] = 0xff; + let mut primary_header = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b1010, + 0x2345, + ) + .unwrap(); + primary_header.set_vc_frame_count(4, 0x12345678).unwrap(); + primary_header.write_to_be_bytes(&mut buf).unwrap(); + + assert_eq!( + PrimaryHeader::from_bytes(&buf[0..8]).unwrap_err(), + UslpError::ByteConversion(ByteConversionError::FromSliceTooSmall { + found: 8, + expected: 11 + }) + ); + } + + #[test] + fn test_invalid_version_number() { + let mut buf: [u8; 7] = [0; 7]; + let primary_header = + PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap(); + primary_header.write_to_be_bytes(&mut buf).unwrap(); + buf[0] &= 0b00001111; + assert_eq!( + PrimaryHeader::from_bytes(&buf).unwrap_err(), + UslpError::InvalidVersionNumber(0) + ); + } + + #[test] + fn test_primary_header_buf_too_small() { + let primary_header = PrimaryHeader::new( + 0b10100101_11000011, + SourceOrDestField::Dest, + 0b110101, + 0b1010, + 0x2345, + ) + .unwrap(); + if let Err(ByteConversionError::ToSliceTooSmall { found, expected }) = + primary_header.write_to_be_bytes(&mut [0; 4]) + { + assert_eq!(found, 4); + assert_eq!(expected, 7); + } else { + panic!("writing primary header did not fail or failed with wrong error"); + } + } + + #[test] + fn test_applicability_contr_rules() { + assert!(ConstructionRule::PacketSpanningMultipleFrames.applicable_to_fixed_len_tfdz()); + assert!(ConstructionRule::StartOfMapaSduOrVcaSdu.applicable_to_fixed_len_tfdz()); + assert!(ConstructionRule::ContinuingPortionOfMapaSdu.applicable_to_fixed_len_tfdz()); + assert!(!ConstructionRule::OctetStream.applicable_to_fixed_len_tfdz()); + assert!(!ConstructionRule::StartingSegment.applicable_to_fixed_len_tfdz()); + assert!(!ConstructionRule::ContinuingSegment.applicable_to_fixed_len_tfdz()); + assert!(!ConstructionRule::LastSegment.applicable_to_fixed_len_tfdz()); + assert!(!ConstructionRule::NoSegmentation.applicable_to_fixed_len_tfdz()); + } + + #[test] + fn test_header_len_correctness() { + let mut tfdh = TransferFrameDataFieldHeader { + construction_rule: ConstructionRule::PacketSpanningMultipleFrames, + uslp_protocol_id: UslpProtocolId::UserDefinedOctetStream, + fhp_or_lvo: Some(0), + }; + assert_eq!(tfdh.len_header(), 3); + tfdh = TransferFrameDataFieldHeader { + construction_rule: ConstructionRule::StartOfMapaSduOrVcaSdu, + uslp_protocol_id: UslpProtocolId::UserDefinedOctetStream, + fhp_or_lvo: Some(0), + }; + assert_eq!(tfdh.len_header(), 3); + tfdh = TransferFrameDataFieldHeader { + construction_rule: ConstructionRule::ContinuingPortionOfMapaSdu, + uslp_protocol_id: UslpProtocolId::UserDefinedOctetStream, + fhp_or_lvo: Some(0), + }; + assert_eq!(tfdh.len_header(), 3); + tfdh = TransferFrameDataFieldHeader { + construction_rule: ConstructionRule::OctetStream, + uslp_protocol_id: UslpProtocolId::UserDefinedOctetStream, + fhp_or_lvo: None, + }; + assert_eq!(tfdh.len_header(), 1); + } + + #[test] + fn test_frame_data_field_header_from_bytes_too_small() { + let buf: [u8; 0] = []; + assert_eq!( + TransferFrameDataFieldHeader::from_bytes(&buf).unwrap_err(), + ByteConversionError::FromSliceTooSmall { + found: 0, + expected: 1 + } + .into() + ); + } +}