diff --git a/src/cfdp/pdu/mod.rs b/src/cfdp/pdu/mod.rs index 3d6c611..3f7d344 100644 --- a/src/cfdp/pdu/mod.rs +++ b/src/cfdp/pdu/mod.rs @@ -55,6 +55,8 @@ pub enum PduError { found: u8, expected: Option, }, + InvalidSegmentRequestFormat, + InvalidStartOrEndOfScopeValue, /// Invalid condition code. Contains the raw detected value. InvalidConditionCode(u8), /// Invalid checksum type which is not part of the checksums listed in the @@ -75,9 +77,15 @@ impl Display for PduError { PduError::InvalidEntityLen(raw_id) => { write!( f, - "Invalid PDU entity ID length {raw_id}, only [1, 2, 4, 8] are allowed" + "invalid PDU entity ID length {raw_id}, only [1, 2, 4, 8] are allowed" ) } + PduError::InvalidSegmentRequestFormat => { + write!(f, "invalid segment request format for NAK PDU") + } + PduError::InvalidStartOrEndOfScopeValue => { + write!(f, "invalid start or end of scope for NAK PDU") + } PduError::InvalidTransactionSeqNumLen(raw_id) => { write!( f, diff --git a/src/cfdp/pdu/nak.rs b/src/cfdp/pdu/nak.rs index 8b13789..9619c34 100644 --- a/src/cfdp/pdu/nak.rs +++ b/src/cfdp/pdu/nak.rs @@ -1 +1,285 @@ +use core::marker::PhantomData; +use crate::{ + cfdp::{CrcFlag, Direction, LargeFileFlag}, + ByteConversionError, +}; + +use super::{ + add_pdu_crc, generic_length_checks_pdu_deserialization, CfdpPdu, FileDirectiveType, PduError, + PduHeader, WritablePduPacket, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Special iterator type for the NAK PDU which allows to iterator over both normal and large file +/// segment requests. +pub struct SegmentRequestIter<'a, T> { + seq_req_start: &'a [u8], + current_idx: usize, + phantom: std::marker::PhantomData, +} + +trait FromBytes { + fn from_bytes(bytes: &[u8]) -> Self; +} + +impl FromBytes for u32 { + fn from_bytes(bytes: &[u8]) -> u32 { + u32::from_be_bytes(bytes.try_into().unwrap()) + } +} + +impl FromBytes for u64 { + fn from_bytes(bytes: &[u8]) -> u64 { + u64::from_be_bytes(bytes.try_into().unwrap()) + } +} + +impl<'a, T> Iterator for SegmentRequestIter<'a, T> +where + T: FromBytes, +{ + type Item = (T, T); + + fn next(&mut self) -> Option { + if self.current_idx + std::mem::size_of::() * 2 > self.seq_req_start.len() { + return None; + } + + let start_offset = T::from_bytes( + &self.seq_req_start[self.current_idx..self.current_idx + std::mem::size_of::()], + ); + self.current_idx += std::mem::size_of::(); + + let end_offset = T::from_bytes( + &self.seq_req_start[self.current_idx..self.current_idx + std::mem::size_of::()], + ); + self.current_idx += std::mem::size_of::(); + + Some((start_offset, end_offset)) + } +} + +/// NAK PDU abstraction. +/// +/// For more information, refer to CFDP chapter 5.2.6. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct NakPdu<'seg_reqs> { + pdu_header: PduHeader, + start_of_scope: u64, + end_of_scope: u64, + seg_reqs_raw: &'seg_reqs [u8], +} + +impl<'seg_reqs> NakPdu<'seg_reqs> { + pub fn new( + mut pdu_header: PduHeader, + start_of_scope: u64, + end_of_scope: u64, + seg_reqs_raw: &'seg_reqs [u8], + ) -> Result { + // Force correct direction flag. + pdu_header.pdu_conf.direction = Direction::TowardsSender; + if pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { + if seg_reqs_raw.len() % 16 != 0 { + return Err(PduError::InvalidSegmentRequestFormat); + } + } else { + if seg_reqs_raw.len() % 8 != 0 { + return Err(PduError::InvalidSegmentRequestFormat); + } + if start_of_scope > u32::MAX as u64 || end_of_scope > u32::MAX as u64 { + return Err(PduError::InvalidStartOrEndOfScopeValue); + } + } + let mut nak_pdu = Self { + pdu_header, + start_of_scope, + end_of_scope, + seg_reqs_raw, + }; + nak_pdu.pdu_header.pdu_datafield_len = nak_pdu.calc_pdu_datafield_len() as u16; + Ok(nak_pdu) + } + + pub fn start_of_scope(&self) -> u64 { + self.start_of_scope + } + + pub fn end_of_scope(&self) -> u64 { + self.end_of_scope + } + + pub fn get_normal_segment_requests_iterator(&self) -> Option> { + if self.file_flag() == LargeFileFlag::Large { + return None; + } + Some(SegmentRequestIter { + seq_req_start: self.seg_reqs_raw, + current_idx: 0, + phantom: PhantomData, + }) + } + + pub fn get_large_segment_requests_iterator(&self) -> Option> { + if self.file_flag() == LargeFileFlag::Normal { + return None; + } + Some(SegmentRequestIter { + seq_req_start: self.seg_reqs_raw, + current_idx: 0, + phantom: PhantomData, + }) + } + + pub fn num_segment_reqs(&self) -> usize { + if self.file_flag() == LargeFileFlag::Large { + self.seg_reqs_raw.len() / 16 + } else { + self.seg_reqs_raw.len() / 8 + } + } + + pub fn pdu_header(&self) -> &PduHeader { + &self.pdu_header + } + + fn calc_pdu_datafield_len(&self) -> usize { + let mut datafield_len = 1; + if self.file_flag() == LargeFileFlag::Normal { + datafield_len += 8; + datafield_len += self.num_segment_reqs() * 8; + } else { + datafield_len += 16; + datafield_len += self.num_segment_reqs() * 16; + } + if self.crc_flag() == CrcFlag::WithCrc { + datafield_len += 2; + } + datafield_len + } + + pub fn from_bytes(buf: &'seg_reqs [u8]) -> Result { + let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; + let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; + // Minimum length of 9: 1 byte directive field and start and end of scope for normal file + // size. + generic_length_checks_pdu_deserialization(buf, 9, full_len_without_crc)?; + let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { + PduError::InvalidDirectiveType { + found: buf[current_idx], + expected: Some(FileDirectiveType::NakPdu), + } + })?; + if directive_type != FileDirectiveType::NakPdu { + return Err(PduError::WrongDirectiveType { + found: directive_type, + expected: FileDirectiveType::AckPdu, + }); + } + current_idx += 1; + let start_of_scope; + let end_of_scope; + if pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { + if current_idx + 16 > buf.len() { + return Err(PduError::ByteConversionError( + ByteConversionError::FromSliceTooSmall { + found: buf.len(), + expected: current_idx + 16, + }, + )); + } + start_of_scope = + u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); + current_idx += 8; + end_of_scope = + u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); + current_idx += 8; + } else { + start_of_scope = + u64::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); + current_idx += 4; + end_of_scope = + u64::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); + current_idx += 4; + } + Self::new( + pdu_header, + start_of_scope, + end_of_scope, + &buf[current_idx..full_len_without_crc], + ) + } +} + +impl CfdpPdu for NakPdu<'_> { + fn pdu_header(&self) -> &PduHeader { + &self.pdu_header + } + + fn file_directive_type(&self) -> Option { + Some(FileDirectiveType::NakPdu) + } +} + +impl WritablePduPacket for NakPdu<'_> { + fn write_to_bytes(&self, buf: &mut [u8]) -> Result { + let expected_len = self.len_written(); + if buf.len() < expected_len { + return Err(ByteConversionError::ToSliceTooSmall { + found: buf.len(), + expected: expected_len, + } + .into()); + } + let mut current_idx = self.pdu_header.write_to_bytes(buf)?; + buf[current_idx] = FileDirectiveType::NakPdu as u8; + current_idx += 1; + + if self.file_flag() == LargeFileFlag::Large { + buf[current_idx..current_idx + 8].copy_from_slice(&self.start_of_scope.to_be_bytes()); + current_idx += 8; + buf[current_idx..current_idx + 8].copy_from_slice(&self.end_of_scope.to_be_bytes()); + current_idx += 8; + // Unwrap is okay here, we checked the file flag. + let segments_iter = self.get_large_segment_requests_iterator().unwrap(); + for (next_start_offset, next_end_offset) in segments_iter { + buf[current_idx..current_idx + 8].copy_from_slice(&next_start_offset.to_be_bytes()); + current_idx += 8; + buf[current_idx..current_idx + 8].copy_from_slice(&next_end_offset.to_be_bytes()); + current_idx += 8; + } + } else { + // Unwrap is okay here, the API should prevent invalid values which would trigger a + // panic here. + let start_of_scope = u32::try_from(self.start_of_scope).unwrap(); + let end_of_scope = u32::try_from(self.end_of_scope).unwrap(); + buf[current_idx..current_idx + 4].copy_from_slice(&start_of_scope.to_be_bytes()); + current_idx += 4; + buf[current_idx..current_idx + 4].copy_from_slice(&end_of_scope.to_be_bytes()); + current_idx += 4; + // Unwrap is okay here, we checked the file flag. + let segments_iter = self.get_normal_segment_requests_iterator().unwrap(); + for (next_start_offset, next_end_offset) in segments_iter { + buf[current_idx..current_idx + 4].copy_from_slice(&next_start_offset.to_be_bytes()); + current_idx += 4; + buf[current_idx..current_idx + 4].copy_from_slice(&next_end_offset.to_be_bytes()); + current_idx += 4; + } + } + + if self.crc_flag() == CrcFlag::WithCrc { + current_idx = add_pdu_crc(buf, current_idx); + } + Ok(current_idx) + } + + fn len_written(&self) -> usize { + self.pdu_header.header_len() + self.calc_pdu_datafield_len() + } +} + +#[cfg(test)] +mod tests {}