//! # NAK PDU packet implementation. use crate::{ cfdp::{CrcFlag, Direction, LargeFileFlag}, ByteConversionError, }; use super::{ add_pdu_crc, generic_length_checks_pdu_deserialization, CfdpPdu, FileDirectiveType, PduError, PduHeader, WritablePduPacket, }; /// Invalid start or end of scope value. #[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)] #[error("invalid start or end of scope value for NAK PDU")] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct InvalidStartOrEndOfScopeError((u64, u64)); fn calculate_pdu_datafield_len(pdu_header: &PduHeader, num_segment_reqs: usize) -> usize { let mut datafield_len = 1; if pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Normal { datafield_len += 8; datafield_len += num_segment_reqs * 8; } else { datafield_len += 16; datafield_len += num_segment_reqs * 16; } if pdu_header.common_pdu_conf().crc_flag == CrcFlag::WithCrc { datafield_len += 2; } datafield_len } fn write_start_and_end_of_scope( buf: &mut [u8], file_flag: LargeFileFlag, mut current_index: usize, start_of_scope: u64, end_of_scope: u64, ) -> usize { if file_flag == LargeFileFlag::Normal { let start_of_scope = u32::try_from(start_of_scope).unwrap(); let end_of_scope = u32::try_from(end_of_scope).unwrap(); buf[current_index..current_index + 4].copy_from_slice(&start_of_scope.to_be_bytes()); current_index += 4; buf[current_index..current_index + 4].copy_from_slice(&end_of_scope.to_be_bytes()); current_index += 4; } else { buf[current_index..current_index + 8].copy_from_slice(&start_of_scope.to_be_bytes()); current_index += 8; buf[current_index..current_index + 8].copy_from_slice(&end_of_scope.to_be_bytes()); current_index += 8; } current_index } /// Buffer is too small to even hold a PDU header. #[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)] #[error("packet buffer too small for PDU header")] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TooSmallForPduHeaderError; /// This function can be used to retrieve the maximum amount of segment request given a PDU /// configuration to stay below a certain maximum packet size. This is useful to calculate how many /// NAK PDUs are required inside a NAK sequence. /// /// Returns an error if the packet buffer can not even hold the PDU header. pub fn calculate_max_segment_requests( mut max_packet_size: usize, pdu_header: &PduHeader, ) -> Result { let mut decrement = pdu_header.header_len() + 1; if pdu_header.common_pdu_conf().crc_flag == CrcFlag::WithCrc { decrement += 2; } if pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Normal { decrement += 8; } else { decrement += 16; } if max_packet_size < decrement { return Err(TooSmallForPduHeaderError); } max_packet_size -= decrement; Ok( if pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Normal { max_packet_size / 8 } else { max_packet_size / 16 }, ) } /// Helper type to encapsulate both normal file size segment requests and large file size segment /// requests. #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SegmentRequests<'a> { /// Segment requests for normal file sizes bounded by [u32::MAX]. U32Pairs(&'a [(u32, u32)]), /// Segment requests for large file sizes bounded by [u64::MAX]. U64Pairs(&'a [(u64, u64)]), } impl SegmentRequests<'_> { /// Wrapped segment requests are empty. #[inline] pub fn is_empty(&self) -> bool { match self { SegmentRequests::U32Pairs(pairs) => pairs.is_empty(), SegmentRequests::U64Pairs(pairs) => pairs.is_empty(), } } } /// NAK PDU abstraction specialized in the creation of NAK PDUs. /// /// It exposes a specialized API which simplifies to generate these NAK PDUs with the /// format according to CFDP chapter 5.2.6. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct NakPduCreator<'seg_reqs> { pdu_header: PduHeader, start_of_scope: u64, end_of_scope: u64, segment_requests: Option>, } impl<'seg_reqs> NakPduCreator<'seg_reqs> { /// Please note that the start of scope and the end of scope need to be smaller or equal /// to [u32::MAX] if the large file flag of the passed PDU configuration is /// [LargeFileFlag::Normal]. pub fn new_no_segment_requests( pdu_header: PduHeader, start_of_scope: u64, end_of_scope: u64, ) -> Result, InvalidStartOrEndOfScopeError> { Self::new(pdu_header, start_of_scope, end_of_scope, None) } /// Default constructor for normal file sizes. pub fn new_normal_file_size( pdu_header: PduHeader, start_of_scope: u32, end_of_scope: u32, segment_requests: &'seg_reqs [(u32, u32)], ) -> Result, InvalidStartOrEndOfScopeError> { let mut passed_segment_requests = None; if !segment_requests.is_empty() { passed_segment_requests = Some(SegmentRequests::U32Pairs(segment_requests)); } Self::new( pdu_header, start_of_scope.into(), end_of_scope.into(), passed_segment_requests, ) } /// Constructor for large file sizes. pub fn new_large_file_size( pdu_header: PduHeader, start_of_scope: u64, end_of_scope: u64, segment_requests: &'seg_reqs [(u64, u64)], ) -> Result, InvalidStartOrEndOfScopeError> { let mut passed_segment_requests = None; if !segment_requests.is_empty() { passed_segment_requests = Some(SegmentRequests::U64Pairs(segment_requests)); } Self::new( pdu_header, start_of_scope, end_of_scope, passed_segment_requests, ) } /// The large file flag of the PDU header will be set automatically based on the type of /// [SegmentRequests]. pub fn new( mut pdu_header: PduHeader, start_of_scope: u64, end_of_scope: u64, segment_requests: Option>, ) -> Result, InvalidStartOrEndOfScopeError> { // Force correct direction flag. pdu_header.pdu_conf.direction = Direction::TowardsSender; if let Some(ref segment_requests) = segment_requests { match segment_requests { SegmentRequests::U32Pairs(_) => { if start_of_scope > u32::MAX as u64 || end_of_scope > u32::MAX as u64 { return Err(InvalidStartOrEndOfScopeError(( start_of_scope, end_of_scope, ))); } pdu_header.pdu_conf.file_flag = LargeFileFlag::Normal; } SegmentRequests::U64Pairs(_) => { pdu_header.pdu_conf.file_flag = LargeFileFlag::Large; } } }; let mut nak_pdu = Self { pdu_header, start_of_scope, end_of_scope, segment_requests, }; nak_pdu.pdu_header.pdu_datafield_len = nak_pdu.calc_pdu_datafield_len() as u16; Ok(nak_pdu) } /// Start of scope of the NAK PDU. #[inline] pub fn start_of_scope(&self) -> u64 { self.start_of_scope } /// End of scope of the NAK PDU. #[inline] pub fn end_of_scope(&self) -> u64 { self.end_of_scope } /// Semgent requests. #[inline] pub fn segment_requests(&self) -> Option<&SegmentRequests<'_>> { self.segment_requests.as_ref() } /// Number of segment requests. #[inline] pub fn num_segment_reqs(&self) -> usize { match &self.segment_requests { Some(seg_reqs) => match seg_reqs { SegmentRequests::U32Pairs(pairs) => pairs.len(), SegmentRequests::U64Pairs(pairs) => pairs.len(), }, None => 0, } } /// PDU header. #[inline] pub fn pdu_header(&self) -> &PduHeader { &self.pdu_header } fn calc_pdu_datafield_len(&self) -> usize { calculate_pdu_datafield_len(&self.pdu_header, self.num_segment_reqs()) } /// Write [Self] to the provided buffer and returns the written size. pub 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_index = self.pdu_header.write_to_bytes(buf)?; buf[current_index] = FileDirectiveType::Nak as u8; current_index += 1; current_index = write_start_and_end_of_scope( buf, self.file_flag(), current_index, self.start_of_scope, self.end_of_scope, ); if let Some(ref seg_reqs) = self.segment_requests { match seg_reqs { SegmentRequests::U32Pairs(pairs) => { // Unwrap is okay here, the API should prevent invalid values which would trigger a // panic here. for (next_start_offset, next_end_offset) in *pairs { buf[current_index..current_index + 4] .copy_from_slice(&next_start_offset.to_be_bytes()); current_index += 4; buf[current_index..current_index + 4] .copy_from_slice(&next_end_offset.to_be_bytes()); current_index += 4; } } SegmentRequests::U64Pairs(pairs) => { for (next_start_offset, next_end_offset) in *pairs { buf[current_index..current_index + 8] .copy_from_slice(&next_start_offset.to_be_bytes()); current_index += 8; buf[current_index..current_index + 8] .copy_from_slice(&next_end_offset.to_be_bytes()); current_index += 8; } } } } if self.crc_flag() == CrcFlag::WithCrc { current_index = add_pdu_crc(buf, current_index); } Ok(current_index) } #[inline] fn len_written(&self) -> usize { self.pdu_header.header_len() + self.calc_pdu_datafield_len() } } impl CfdpPdu for NakPduCreator<'_> { #[inline] fn pdu_header(&self) -> &PduHeader { self.pdu_header() } #[inline] fn file_directive_type(&self) -> Option { Some(FileDirectiveType::Nak) } } impl WritablePduPacket for NakPduCreator<'_> { fn write_to_bytes(&self, buf: &mut [u8]) -> Result { self.write_to_bytes(buf) } fn len_written(&self) -> usize { self.len_written() } } /// This is a NAK PDU constructor which exposes the sequence list as a mutable slice. /// /// This avoids the need of a separate slice for the lost segments. Instead, the lost segments /// can be written into the mutable slice directly. The NAK creation has to be finalized using /// the [Self::finish] call. #[derive(Debug)] pub struct NakPduCreatorWithReservedSeqReqsBuf<'buf> { pdu_header: PduHeader, num_segment_reqs: usize, param_field_offset: usize, buf: &'buf mut [u8], } impl<'buf> NakPduCreatorWithReservedSeqReqsBuf<'buf> { /// Calculartes the maximum amount of segment requests which fit into a packet with the /// provided parameters. pub fn calculate_max_segment_requests( max_packet_size: usize, pdu_header: &PduHeader, ) -> Result { calculate_max_segment_requests(max_packet_size, pdu_header) } /// Constructor. pub fn new( buf: &'buf mut [u8], mut pdu_header: PduHeader, num_segment_reqs: usize, ) -> Result { pdu_header.pdu_datafield_len = calculate_pdu_datafield_len(&pdu_header, num_segment_reqs) as u16; let written_len = pdu_header.header_len() + pdu_header.pdu_datafield_len as usize; if buf.len() < written_len { return Err(ByteConversionError::ToSliceTooSmall { found: buf.len(), expected: written_len, }); } // Force correct direction flag. pdu_header.pdu_conf.direction = Direction::TowardsSender; let param_field_offset = pdu_header.header_len() + 1; Ok(Self { pdu_header, num_segment_reqs, param_field_offset, buf, }) } } impl NakPduCreatorWithReservedSeqReqsBuf<'_> { /// PDU header. #[inline] pub fn pdu_header(&self) -> &PduHeader { &self.pdu_header } /// Number of segment requests. #[inline] pub fn num_segment_reqs(&self) -> usize { self.num_segment_reqs } /// This function allows writing the start and end of scope fields in the mutable /// buffer slice directly. fn set_start_and_end_of_scope( &mut self, start_of_scope: u64, end_of_scope: u64, ) -> Result<(), InvalidStartOrEndOfScopeError> { if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Normal && (start_of_scope > u32::MAX as u64 || end_of_scope > u32::MAX as u64) { return Err(InvalidStartOrEndOfScopeError(( start_of_scope, end_of_scope, ))); } write_start_and_end_of_scope( self.buf, self.pdu_header.common_pdu_conf().file_flag, self.pdu_header.header_len() + 1, start_of_scope, end_of_scope, ); Ok(()) } /// Length when written to a buffer. pub fn len_written(&self) -> usize { self.pdu_header.header_len() + self.calc_pdu_datafield_len() } /// Mutable accesss to the segment requests buffer. #[inline] pub fn segment_request_buffer_mut(&mut self) -> &mut [u8] { let len = self.segment_request_buffer_len(); let segment_req_buf_offset = self.segment_request_offset(); &mut self.buf[segment_req_buf_offset..segment_req_buf_offset + len] } /// Shared accesss to the segment requests buffer. #[inline] pub fn segment_request_buffer(&self) -> &[u8] { let len = self.segment_request_buffer_len(); let segment_req_buf_offset = self.segment_request_offset(); &self.buf[segment_req_buf_offset..segment_req_buf_offset + len] } /// Iterator over all segment requests. #[inline] pub fn segment_request_iter(&self) -> SegmentRequestIter<'_> { SegmentRequestIter::new( self.segment_request_buffer(), self.pdu_header.common_pdu_conf().file_flag, ) } /// Finalizes the NAK PDU creation. /// /// It writes all NAK PDU fields which were not written by the dedicated setter methods, /// in addition to the start and end of scope. It also adds a CRC-16 depending on the PDU /// header configuration. pub fn finish( mut self, start_of_scope: u64, end_of_scope: u64, ) -> Result { self.set_start_and_end_of_scope(start_of_scope, end_of_scope)?; let mut current_idx = self.pdu_header.write_to_bytes(self.buf).unwrap(); self.buf[current_idx] = FileDirectiveType::Nak as u8; current_idx += 1; if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { current_idx += 16 + self.num_segment_reqs * 16; } else { current_idx += 8 + self.num_segment_reqs * 8; } if self.pdu_header.common_pdu_conf().crc_flag == CrcFlag::WithCrc { current_idx = add_pdu_crc(self.buf, current_idx); } Ok(current_idx) } fn calc_pdu_datafield_len(&self) -> usize { calculate_pdu_datafield_len(&self.pdu_header, self.num_segment_reqs) } #[inline] fn segment_request_buffer_len(&self) -> usize { self.num_segment_reqs * if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Normal { 8 } else { 16 } } fn segment_request_offset(&self) -> usize { self.param_field_offset + if self.pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Normal { 8 } else { 16 } } } /// Special iterator type for the NAK PDU which allows to iterate over both normal and large file /// segment requests. #[derive(Debug)] pub struct SegmentRequestIter<'a> { seq_req_raw: &'a [u8], large_file: LargeFileFlag, current_idx: usize, } impl<'a> SegmentRequestIter<'a> { fn new(seq_req_raw: &'a [u8], large_file: LargeFileFlag) -> SegmentRequestIter<'a> { SegmentRequestIter { seq_req_raw, large_file, current_idx: 0, } } } impl Iterator for SegmentRequestIter<'_> { type Item = (u64, u64); fn next(&mut self) -> Option { let value = self.next_at_offset(self.current_idx); if value.is_none() { return value; } self.current_idx += 2 * self.increment(); value } } impl SegmentRequestIter<'_> { const fn increment(&self) -> usize { match self.large_file { LargeFileFlag::Normal => core::mem::size_of::(), LargeFileFlag::Large => core::mem::size_of::(), } } fn next_at_offset(&self, mut offset: usize) -> Option<(u64, u64)> { let increment = self.increment(); if offset + increment * 2 > self.seq_req_raw.len() { return None; } match self.large_file { LargeFileFlag::Normal => { let start_offset = u32::from_be_bytes( self.seq_req_raw[offset..offset + increment] .try_into() .unwrap(), ); offset += increment; let end_offset = u32::from_be_bytes( self.seq_req_raw[offset..offset + increment] .try_into() .unwrap(), ); Some((start_offset as u64, end_offset as u64)) } LargeFileFlag::Large => { let start_offset = u64::from_be_bytes( self.seq_req_raw[offset..offset + increment] .try_into() .unwrap(), ); offset += increment; let end_offset = u64::from_be_bytes( self.seq_req_raw[offset..offset + increment] .try_into() .unwrap(), ); Some((start_offset, end_offset)) } } } fn compare_pairs + Copy>(&self, pairs: &[(T, T)]) -> bool { if pairs.is_empty() && self.seq_req_raw.is_empty() { return true; } if pairs.len() * 2 * self.increment() != self.seq_req_raw.len() { return false; } for (i, pair) in pairs.iter().enumerate() { let next_val = self.next_at_offset(i * 2 * self.increment()).unwrap(); let pair = (pair.0.into(), pair.1.into()); if next_val != pair { return false; } } true } } impl<'a> PartialEq> for SegmentRequestIter<'_> { fn eq(&self, other: &SegmentRequests<'a>) -> bool { match other { SegmentRequests::U32Pairs(pairs) => self.compare_pairs(pairs), SegmentRequests::U64Pairs(pairs) => self.compare_pairs(pairs), } } } /// NAK PDU abstraction specialized in the reading NAK PDUs from a raw bytestream. /// /// This is a zero-copy class where the segment requests can be read using a special iterator /// API without the need to copy them. /// /// The NAK format is expected to be conforming to CFDP chapter 5.2.6. #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct NakPduReader<'seg_reqs> { pdu_header: PduHeader, start_of_scope: u64, end_of_scope: u64, seg_reqs_raw: &'seg_reqs [u8], } impl CfdpPdu for NakPduReader<'_> { fn pdu_header(&self) -> &PduHeader { &self.pdu_header } fn file_directive_type(&self) -> Option { Some(FileDirectiveType::Nak) } } impl<'seg_reqs> NakPduReader<'seg_reqs> { /// Constructor from a raw bytestream. pub fn new(buf: &'seg_reqs [u8]) -> Result, PduError> { Self::from_bytes(buf) } /// Constructor from a raw bytestream. pub fn from_bytes(buf: &'seg_reqs [u8]) -> Result, PduError> { 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::Nak), } })?; if directive_type != FileDirectiveType::Nak { return Err(PduError::WrongDirectiveType { found: directive_type, expected: FileDirectiveType::Ack, }); } 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::ByteConversion( 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 { if current_idx + 8 > buf.len() { return Err(PduError::ByteConversion( ByteConversionError::FromSliceTooSmall { found: buf.len(), expected: current_idx + 8, }, )); } start_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()) as u64; current_idx += 4; end_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()) as u64; current_idx += 4; } Ok(Self { pdu_header, start_of_scope, end_of_scope, seg_reqs_raw: &buf[current_idx..full_len_without_crc], }) } /// Start of scope. pub fn start_of_scope(&self) -> u64 { self.start_of_scope } /// End of scope. pub fn end_of_scope(&self) -> u64 { self.end_of_scope } /// Number of segment requests. pub fn num_segment_reqs(&self) -> usize { if self.seg_reqs_raw.is_empty() { return 0; } if self.file_flag() == LargeFileFlag::Normal { self.seg_reqs_raw.len() / (2 * core::mem::size_of::()) } else { self.seg_reqs_raw.len() / (2 * core::mem::size_of::()) } } /// Get a generic segment request iterator. pub fn get_segment_requests_iterator(&self) -> Option> { if self.seg_reqs_raw.is_empty() { return None; } Some(SegmentRequestIter::new(self.seg_reqs_raw, self.file_flag())) } } impl<'a> PartialEq> for NakPduReader<'_> { fn eq(&self, other: &NakPduCreator<'a>) -> bool { if self.pdu_header() != other.pdu_header() || self.end_of_scope() != other.end_of_scope() || self.start_of_scope() != other.start_of_scope() { return false; } // Check if both segment requests are empty or None match (self.seg_reqs_raw.is_empty(), other.segment_requests()) { (true, None) => true, (true, Some(seg_reqs)) => seg_reqs.is_empty(), (false, None) => false, _ => { let normal_iter = self.get_segment_requests_iterator().unwrap(); normal_iter == *other.segment_requests().unwrap() } } } } #[cfg(test)] mod tests { use alloc::string::ToString; use crate::cfdp::{ pdu::tests::{common_pdu_conf, verify_raw_header, TEST_DEST_ID, TEST_SEQ_NUM, TEST_SRC_ID}, PduType, TransmissionMode, }; use super::*; fn check_generic_fields(nak_pdu: &impl CfdpPdu) { assert_eq!(nak_pdu.crc_flag(), CrcFlag::NoCrc); assert_eq!(nak_pdu.file_flag(), LargeFileFlag::Normal); assert_eq!(nak_pdu.pdu_type(), PduType::FileDirective); assert_eq!(nak_pdu.file_directive_type(), Some(FileDirectiveType::Nak),); assert_eq!(nak_pdu.transmission_mode(), TransmissionMode::Acknowledged); assert_eq!(nak_pdu.direction(), Direction::TowardsSender); assert_eq!(nak_pdu.source_id(), TEST_SRC_ID.into()); assert_eq!(nak_pdu.dest_id(), TEST_DEST_ID.into()); assert_eq!(nak_pdu.transaction_seq_num(), TEST_SEQ_NUM.into()); } #[test] fn test_seg_request_api() { let seg_req = SegmentRequests::U32Pairs(&[]); assert!(seg_req.is_empty()); let seg_req = SegmentRequests::U64Pairs(&[]); assert!(seg_req.is_empty()); } #[test] fn test_basic_creator() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 0, 0) .expect("creating NAK PDU creator failed"); assert_eq!(nak_pdu.start_of_scope(), 0); assert_eq!(nak_pdu.end_of_scope(), 0); assert_eq!(nak_pdu.segment_requests(), None); assert_eq!(nak_pdu.num_segment_reqs(), 0); check_generic_fields(&nak_pdu); } #[test] fn test_serialization_empty() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 100, 300) .expect("creating NAK PDU creator failed"); assert_eq!(nak_pdu.start_of_scope(), 100); assert_eq!(nak_pdu.end_of_scope(), 300); let mut buf: [u8; 64] = [0; 64]; nak_pdu .write_to_bytes(&mut buf) .expect("writing NAK PDU to buffer failed"); verify_raw_header(nak_pdu.pdu_header(), &buf); let mut current_idx = nak_pdu.pdu_header().header_len(); assert_eq!(current_idx + 9, nak_pdu.len_written()); assert_eq!(buf[current_idx], FileDirectiveType::Nak as u8); current_idx += 1; let start_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(start_of_scope, 100); current_idx += 4; let end_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(end_of_scope, 300); current_idx += 4; assert_eq!(current_idx, nak_pdu.len_written()); } #[test] fn test_serialization_two_segments() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let nak_pdu = NakPduCreator::new_normal_file_size(pdu_header, 100, 300, &[(0, 0), (32, 64)]) .expect("creating NAK PDU creator failed"); let mut buf: [u8; 64] = [0; 64]; nak_pdu .write_to_bytes(&mut buf) .expect("writing NAK PDU to buffer failed"); verify_raw_header(nak_pdu.pdu_header(), &buf); let mut current_idx = nak_pdu.pdu_header().header_len(); assert_eq!(current_idx + 9 + 16, nak_pdu.len_written()); assert_eq!(buf[current_idx], FileDirectiveType::Nak as u8); current_idx += 1; let start_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(start_of_scope, 100); current_idx += 4; let end_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(end_of_scope, 300); current_idx += 4; let first_seg_start = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(first_seg_start, 0); current_idx += 4; let first_seg_end = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(first_seg_end, 0); current_idx += 4; let second_seg_start = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(second_seg_start, 32); current_idx += 4; let second_seg_end = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(second_seg_end, 64); current_idx += 4; assert_eq!(current_idx, nak_pdu.len_written()); } #[test] fn test_deserialization_empty() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 100, 300) .expect("creating NAK PDU creator failed"); let mut buf: [u8; 64] = [0; 64]; nak_pdu .write_to_bytes(&mut buf) .expect("writing NAK PDU to buffer failed"); let nak_pdu_deser = NakPduReader::from_bytes(&buf).expect("deserializing NAK PDU failed"); assert_eq!(nak_pdu_deser, nak_pdu); check_generic_fields(&nak_pdu_deser); } #[test] fn test_deserialization_large_segments() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let nak_pdu = NakPduCreator::new_large_file_size(pdu_header, 100, 300, &[(50, 100), (200, 300)]) .expect("creating NAK PDU creator failed"); let mut buf: [u8; 128] = [0; 128]; nak_pdu .write_to_bytes(&mut buf) .expect("writing NAK PDU to buffer failed"); let nak_pdu_deser = NakPduReader::from_bytes(&buf).expect("deserializing NAK PDU failed"); assert_eq!(nak_pdu_deser, nak_pdu); assert_eq!(nak_pdu_deser.start_of_scope(), 100); assert_eq!(nak_pdu_deser.end_of_scope(), 300); assert_eq!(nak_pdu_deser.num_segment_reqs(), 2); assert_eq!( nak_pdu_deser .get_segment_requests_iterator() .unwrap() .count(), 2 ); let segment_iter = nak_pdu_deser.get_segment_requests_iterator(); assert!(segment_iter.is_some()); let segment_iter = segment_iter.unwrap(); for (idx, segments) in segment_iter.enumerate() { if idx == 0 { assert_eq!(segments.0, 50); assert_eq!(segments.1, 100); } else { assert_eq!(segments.0, 200); assert_eq!(segments.1, 300); } } } #[test] fn test_deserialization_normal_segments() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let nak_pdu = NakPduCreator::new_normal_file_size(pdu_header, 100, 300, &[(50, 100), (200, 300)]) .expect("creating NAK PDU creator failed"); let mut buf: [u8; 128] = [0; 128]; nak_pdu .write_to_bytes(&mut buf) .expect("writing NAK PDU to buffer failed"); let nak_pdu_deser = NakPduReader::from_bytes(&buf).expect("deserializing NAK PDU failed"); assert_eq!(nak_pdu_deser, nak_pdu); assert_eq!(nak_pdu_deser.start_of_scope(), 100); assert_eq!(nak_pdu_deser.end_of_scope(), 300); assert_eq!(nak_pdu_deser.num_segment_reqs(), 2); assert_eq!( nak_pdu_deser .get_segment_requests_iterator() .unwrap() .count(), 2 ); let segment_iter = nak_pdu_deser.get_segment_requests_iterator(); assert!(segment_iter.is_some()); let segment_iter = segment_iter.unwrap(); for (idx, segments) in segment_iter.enumerate() { if idx == 0 { assert_eq!(segments.0, 50); assert_eq!(segments.1, 100); } else { assert_eq!(segments.0, 200); assert_eq!(segments.1, 300); } } } #[test] fn test_empty_is_empty() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let nak_pdu_0 = NakPduCreator::new_normal_file_size(pdu_header, 100, 300, &[]) .expect("creating NAK PDU creator failed"); let nak_pdu_1 = NakPduCreator::new_no_segment_requests(pdu_header, 100, 300) .expect("creating NAK PDU creator failed"); assert_eq!(nak_pdu_0, nak_pdu_1); // Assert the segment request is mapped to None. assert!(nak_pdu_0.segment_requests().is_none()); } #[test] fn test_new_generic_invalid_input() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let u32_list = SegmentRequests::U32Pairs(&[(0, 50), (50, 100)]); //let error = NakPduCreator::new_generic(pdu_header, 100, 300, Some(u32_list)); let error = NakPduCreator::new( pdu_header, u32::MAX as u64 + 1, u32::MAX as u64 + 2, Some(u32_list), ); assert!(error.is_err()); let error = error.unwrap_err(); assert_eq!( error.to_string(), "invalid start or end of scope value for NAK PDU" ); } #[test] fn test_target_buf_too_small() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 100, 300) .expect("creating NAK PDU creator failed"); assert_eq!(nak_pdu.start_of_scope(), 100); assert_eq!(nak_pdu.end_of_scope(), 300); let mut buf: [u8; 5] = [0; 5]; let error = nak_pdu.write_to_bytes(&mut buf); assert!(error.is_err()); let e = error.unwrap_err(); match e { PduError::ByteConversion(conv_error) => match conv_error { ByteConversionError::ToSliceTooSmall { found, expected } => { assert_eq!(expected, nak_pdu.len_written()); assert_eq!(found, 5); } _ => panic!("unexpected error {conv_error}"), }, _ => panic!("unexpected error {e}"), } } #[test] fn test_with_crc() { let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let nak_pdu = NakPduCreator::new_no_segment_requests(pdu_header, 0, 0) .expect("creating NAK PDU creator failed"); let mut nak_vec = nak_pdu.to_vec().expect("writing NAK to vector failed"); assert_eq!(nak_vec.len(), pdu_header.header_len() + 9 + 2); assert_eq!(nak_vec.len(), nak_pdu.len_written()); let nak_pdu_deser = NakPduReader::new(&nak_vec).expect("reading NAK PDU failed"); assert_eq!(nak_pdu_deser, nak_pdu); nak_vec[nak_pdu.len_written() - 1] -= 1; let nak_pdu_deser = NakPduReader::new(&nak_vec); assert!(nak_pdu_deser.is_err()); if let Err(PduError::Checksum(raw)) = nak_pdu_deser { assert_eq!( raw, u16::from_be_bytes(nak_vec[nak_pdu.len_written() - 2..].try_into().unwrap()) ); } } #[test] fn test_with_reserved_lost_segment_buf_no_segments_normal_file_0() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let mut buf: [u8; 64] = [0; 64]; let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let mut nak_pdu = NakPduCreatorWithReservedSeqReqsBuf::new(&mut buf, pdu_header, 0).unwrap(); assert_eq!(nak_pdu.len_written(), pdu_header.header_len() + 9); assert!(nak_pdu.segment_request_buffer().is_empty()); assert!(nak_pdu.segment_request_buffer_mut().is_empty()); assert_eq!(nak_pdu.segment_request_iter().count(), 0); let pdu_header = *nak_pdu.pdu_header(); let len_written = nak_pdu.finish(0, 0).unwrap(); verify_raw_header(&pdu_header, &buf); let mut current_idx = pdu_header.header_len(); assert_eq!(current_idx + 9, len_written); assert_eq!(buf[current_idx], FileDirectiveType::Nak as u8); current_idx += 1; let start_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); current_idx += 4; let end_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(start_of_scope, 0); assert_eq!(end_of_scope, 0); } #[test] fn test_with_reserved_lost_segment_buf_no_segments_normal_file_1() { let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal); let mut buf: [u8; 64] = [0; 64]; let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let mut nak_pdu = NakPduCreatorWithReservedSeqReqsBuf::new(&mut buf, pdu_header, 0).unwrap(); assert!(nak_pdu.segment_request_buffer().is_empty()); assert!(nak_pdu.segment_request_buffer_mut().is_empty()); assert_eq!(nak_pdu.segment_request_iter().count(), 0); let pdu_header = *nak_pdu.pdu_header(); let len_written = nak_pdu.finish(100, 200).unwrap(); verify_raw_header(&pdu_header, &buf); let mut current_idx = pdu_header.header_len(); assert_eq!(current_idx + 1 + 8 + 2, len_written); assert_eq!(buf[current_idx], FileDirectiveType::Nak as u8); current_idx += 1; let start_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); current_idx += 4; let end_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); assert_eq!(start_of_scope, 100); assert_eq!(end_of_scope, 200); } #[test] fn test_with_reserved_lost_segment_buf_no_segments_large_file_0() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large); let mut buf: [u8; 64] = [0; 64]; let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let mut nak_pdu = NakPduCreatorWithReservedSeqReqsBuf::new(&mut buf, pdu_header, 0).unwrap(); assert_eq!(nak_pdu.len_written(), pdu_header.header_len() + 1 + 16); assert!(nak_pdu.segment_request_buffer().is_empty()); assert!(nak_pdu.segment_request_buffer_mut().is_empty()); assert_eq!(nak_pdu.segment_request_iter().count(), 0); let pdu_header = *nak_pdu.pdu_header(); let len_written = nak_pdu.finish(0, 0).unwrap(); verify_raw_header(&pdu_header, &buf); let mut current_idx = pdu_header.header_len(); assert_eq!(current_idx + 1 + 16, len_written); assert_eq!(buf[current_idx], FileDirectiveType::Nak as u8); current_idx += 1; let start_of_scope = u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); current_idx += 8; let end_of_scope = u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); assert_eq!(start_of_scope, 0); assert_eq!(end_of_scope, 0); } #[test] fn test_with_reserved_lost_segment_buf_invalid_scope() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let mut buf: [u8; 64] = [0; 64]; let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let mut nak_pdu = NakPduCreatorWithReservedSeqReqsBuf::new(&mut buf, pdu_header, 2).unwrap(); assert_eq!( nak_pdu .set_start_and_end_of_scope(100, u32::MAX as u64 + 1) .unwrap_err(), InvalidStartOrEndOfScopeError((100, (u32::MAX as u64) + 1)) ); } #[test] fn test_with_reserved_lost_segment_buf_no_segments_large_file_1() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large); let mut buf: [u8; 64] = [0; 64]; let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let mut nak_pdu = NakPduCreatorWithReservedSeqReqsBuf::new(&mut buf, pdu_header, 0).unwrap(); assert!(nak_pdu.segment_request_buffer().is_empty()); assert!(nak_pdu.segment_request_buffer_mut().is_empty()); assert_eq!(nak_pdu.segment_request_iter().count(), 0); assert_eq!(nak_pdu.len_written(), pdu_header.header_len() + 1 + 16); let pdu_header = *nak_pdu.pdu_header(); let len_written = nak_pdu .finish(100, u32::MAX as u64 + 1) .expect("setting start and end of scope failed"); verify_raw_header(&pdu_header, &buf); let mut current_idx = pdu_header.header_len(); assert_eq!(current_idx + 1 + 16, len_written); assert_eq!(buf[current_idx], FileDirectiveType::Nak as u8); current_idx += 1; let start_of_scope = u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); current_idx += 8; let end_of_scope = u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); assert_eq!(start_of_scope, 100); assert_eq!(end_of_scope, u32::MAX as u64 + 1); } #[test] fn test_with_reserved_lost_segments_buf_two_segments_normal_file() { let num_segments = 2; let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let mut buf: [u8; 64] = [0; 64]; let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let mut nak_pdu = NakPduCreatorWithReservedSeqReqsBuf::new(&mut buf, pdu_header, num_segments).unwrap(); assert_eq!(nak_pdu.segment_request_buffer().len(), num_segments * 8); assert_eq!(nak_pdu.segment_request_buffer_mut().len(), num_segments * 8); let seg_req_buf_mut = nak_pdu.segment_request_buffer_mut(); assert_eq!(seg_req_buf_mut.len(), num_segments * 8); // Slice is statically declared here, but the actual purpose is that we can iterate // over the slice and fill the mutable segment request slice during the iteration. let seg_reqs: [(u32, u32); 2] = [(0, 20), (20, 40)]; for (i, seg_req) in seg_reqs.iter().enumerate() { let offset = i * 8; seg_req_buf_mut[offset..offset + 4].copy_from_slice(&seg_req.0.to_be_bytes()); seg_req_buf_mut[offset + 4..offset + 8].copy_from_slice(&seg_req.1.to_be_bytes()); } let pdu_header = *nak_pdu.pdu_header(); for (seg1, seg2) in nak_pdu.segment_request_iter().zip(seg_reqs.iter()) { assert_eq!(seg1.0 as u32, seg2.0); assert_eq!(seg1.1 as u32, seg2.1); } let len_written = nak_pdu.finish(0, 40).unwrap(); verify_raw_header(&pdu_header, &buf); let mut current_idx = pdu_header.header_len(); assert_eq!(current_idx + 1 + 8 + num_segments * 8, len_written); assert_eq!(buf[current_idx], FileDirectiveType::Nak as u8); current_idx += 1; let start_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); current_idx += 4; let end_of_scope = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); current_idx += 4; assert_eq!(start_of_scope, 0); assert_eq!(end_of_scope, 40); for seg_req in seg_reqs.iter() { let seg_start = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); current_idx += 4; let seg_end = u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()); current_idx += 4; assert_eq!(seg_start, seg_req.0); assert_eq!(seg_end, seg_req.1); } } #[test] fn test_with_reserved_lost_segments_buf_two_segments_large_file() { let num_segments = 2; let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large); let mut buf: [u8; 128] = [0; 128]; let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); let mut nak_pdu = NakPduCreatorWithReservedSeqReqsBuf::new(&mut buf, pdu_header, num_segments).unwrap(); nak_pdu .set_start_and_end_of_scope(100, u32::MAX as u64 + 1) .expect("setting start and end of scope failed"); assert_eq!(nak_pdu.segment_request_buffer().len(), num_segments * 16); assert_eq!( nak_pdu.segment_request_buffer_mut().len(), num_segments * 16 ); let seg_req_buf_mut = nak_pdu.segment_request_buffer_mut(); assert_eq!(seg_req_buf_mut.len(), num_segments * 16); // Slice is statically declared here, but the actual purpose is that we can iterate // over the slice and fill the mutable segment request slice during the iteration. let seg_reqs: [(u64, u64); 2] = [(0, 20), (20, u32::MAX as u64 + 1)]; for (i, seg_req) in seg_reqs.iter().enumerate() { let offset = i * 16; seg_req_buf_mut[offset..offset + 8].copy_from_slice(&seg_req.0.to_be_bytes()); seg_req_buf_mut[offset + 8..offset + 16].copy_from_slice(&seg_req.1.to_be_bytes()); } let pdu_header = *nak_pdu.pdu_header(); for (seg1, seg2) in nak_pdu.segment_request_iter().zip(seg_reqs.iter()) { assert_eq!(seg1.0, seg2.0); assert_eq!(seg1.1, seg2.1); } let len_written = nak_pdu.finish(0, u32::MAX as u64 + 1).unwrap(); verify_raw_header(&pdu_header, &buf); let mut current_idx = pdu_header.header_len(); assert_eq!(current_idx + 1 + 16 + num_segments * 16, len_written); assert_eq!(buf[current_idx], FileDirectiveType::Nak as u8); current_idx += 1; let start_of_scope = u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); current_idx += 8; let end_of_scope = u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); current_idx += 8; assert_eq!(start_of_scope, 0); assert_eq!(end_of_scope, u32::MAX as u64 + 1); for seg_req in seg_reqs.iter() { let seg_start = u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); current_idx += 8; let seg_end = u64::from_be_bytes(buf[current_idx..current_idx + 8].try_into().unwrap()); current_idx += 8; assert_eq!(seg_start, seg_req.0); assert_eq!(seg_end, seg_req.1); } } #[test] fn test_reserved_lost_segment_finish_buf_too_small() { let mut buf: [u8; 64] = [0; 64]; let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); assert_eq!( NakPduCreatorWithReservedSeqReqsBuf::new(&mut buf[0..10], pdu_header, 0).unwrap_err(), ByteConversionError::ToSliceTooSmall { found: 10, expected: pdu_header.header_len() + 1 + 8 } ); } #[test] fn test_max_segment_req_calculator() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); // 7 byte header, 1 byte directive, 8 bytes start and end of segment, leaves 48 bytes for // 6 segment requests (8 bytes each) assert_eq!(6, calculate_max_segment_requests(64, &pdu_header).unwrap()); assert_eq!( 6, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(64, &pdu_header) .unwrap() ); assert_eq!(6, calculate_max_segment_requests(65, &pdu_header).unwrap()); assert_eq!( 6, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(65, &pdu_header) .unwrap() ); assert_eq!(5, calculate_max_segment_requests(63, &pdu_header).unwrap()); assert_eq!( 5, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(63, &pdu_header) .unwrap() ); assert_eq!(7, calculate_max_segment_requests(72, &pdu_header).unwrap()); assert_eq!( 7, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(72, &pdu_header) .unwrap() ); } #[test] fn test_max_segment_req_calculator_large_file() { let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); // 7 byte header, 1 byte directive, 16 bytes start and end of segment, leaves 48 bytes for // 3 large segment requests (16 bytes each) assert_eq!(3, calculate_max_segment_requests(72, &pdu_header).unwrap()); assert_eq!( 3, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(72, &pdu_header) .unwrap() ); assert_eq!(3, calculate_max_segment_requests(73, &pdu_header).unwrap()); assert_eq!( 3, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(73, &pdu_header) .unwrap() ); assert_eq!(2, calculate_max_segment_requests(71, &pdu_header).unwrap()); assert_eq!( 2, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(71, &pdu_header) .unwrap() ); assert_eq!(4, calculate_max_segment_requests(88, &pdu_header).unwrap()); assert_eq!( 4, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(88, &pdu_header) .unwrap() ); } #[test] fn test_max_segment_req_calculator_large_file_with_crc() { let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Large); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0); // 7 byte header, 1 byte directive, 16 bytes start and end of segment, leaves 48 bytes for // 3 large segment requests (16 bytes each) assert_eq!(3, calculate_max_segment_requests(74, &pdu_header).unwrap()); assert_eq!( 3, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(74, &pdu_header) .unwrap() ); assert_eq!(3, calculate_max_segment_requests(75, &pdu_header).unwrap()); assert_eq!( 3, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(75, &pdu_header) .unwrap() ); assert_eq!(2, calculate_max_segment_requests(73, &pdu_header).unwrap()); assert_eq!( 2, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(73, &pdu_header) .unwrap() ); assert_eq!(4, calculate_max_segment_requests(90, &pdu_header).unwrap()); assert_eq!( 4, NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests(90, &pdu_header) .unwrap() ); } }