diff --git a/src/dest.rs b/src/dest.rs index a36722c..58a6967 100644 --- a/src/dest.rs +++ b/src/dest.rs @@ -1286,8 +1286,11 @@ impl< )?; sent_packets += 1; } else { - sent_packets += self - .write_multi_packet_nak_sequence(max_segment_reqs, acked_params.metadata_missing)?; + sent_packets += self.write_multi_packet_nak_sequence( + segments_to_send, + max_segment_reqs, + acked_params.metadata_missing, + )?; } Ok(sent_packets) @@ -1296,6 +1299,7 @@ impl< #[cold] fn write_multi_packet_nak_sequence( &mut self, + mut segments_to_send: usize, max_segments: usize, first_segment_metadata: bool, ) -> Result { @@ -1310,7 +1314,6 @@ impl< let mut segment_index = 0; let mut buf_index = 0; - let mut remaining = max_segments; let mut current_start_of_scope = 0; let mut current_end_of_scope = 0; let mut seg_buf = nak_pdu_creator.segment_request_buffer_mut(); @@ -1338,13 +1341,13 @@ impl< &self.pdu_and_cksum_buffer.borrow()[..written_len], )?; sent_packets += 1; - remaining = remaining.saturating_sub(max_segments); + segments_to_send = segments_to_send.saturating_sub(max_segments); // new PDU with the same capacity nak_pdu_creator = NakPduCreatorWithReservedSeqReqsBuf::new( self.pdu_and_cksum_buffer.get_mut(), pdu_header, - core::cmp::min(remaining, max_segments), + core::cmp::min(segments_to_send, max_segments), ) .unwrap(); seg_buf = nak_pdu_creator.segment_request_buffer_mut(); @@ -1383,6 +1386,7 @@ impl< FileDirectiveType::NakPdu, &self.pdu_and_cksum_buffer.borrow()[..written_len], )?; + sent_packets += 1; } Ok(sent_packets) } @@ -2425,6 +2429,37 @@ mod tests { tb.check_completion_indication_success(&mut test_user); } + #[test] + fn test_empty_file_transfer_invalid_remote_id() { + let fault_handler = TestFaultHandler::default(); + let mut tb = DestHandlerTestbench::new_with_fixed_paths( + fault_handler, + TransmissionMode::Unacknowledged, + false, + ); + let mut test_user = tb.test_user_from_cached_paths(0); + let mut conf = tb.pdu_conf; + // Just swap them.. + conf.set_source_and_dest_id(REMOTE_ID, LOCAL_ID).unwrap(); + let pdu_header = PduHeader::new_for_file_directive(conf, 0); + let metadata_pdu = create_metadata_pdu( + &pdu_header, + tb.src_path.as_path(), + tb.dest_path.as_path(), + 0, + false, + ); + let packet_info = create_packet_info(&metadata_pdu, &mut tb.buf); + if let Err(DestError::NoRemoteConfigFound(id)) = + tb.handler.state_machine(&mut test_user, Some(&packet_info)) + { + assert_eq!(id, REMOTE_ID.into()); + } else { + panic!("expected no remote config found error"); + } + tb.check_dest_file = false; + } + #[test] fn test_empty_file_transfer_acked() { let fault_handler = TestFaultHandler::default(); @@ -3763,4 +3798,288 @@ mod tests { assert_eq!(cancellation.progress(), 0); } } + + #[test] + fn test_multi_segment_nak() { + let file_data_str = "Hello Wooorld!"; + let file_data = file_data_str.as_bytes(); + let file_size = file_data.len() as u64; + let fault_handler = TestFaultHandler::default(); + + let mut tb = DestHandlerTestbench::new_with_fixed_paths( + fault_handler, + TransmissionMode::Acknowledged, + false, + ); + // Disable this, we only want to check the deferred procedure. + tb.remote_cfg_mut().immediate_nak_mode = false; + let mut user = tb.test_user_from_cached_paths(file_size); + let transfer_info = tb + .generic_transfer_init(&mut user, file_size) + .expect("transfer init failed"); + tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + + assert_eq!( + tb.generic_file_data_insert(&mut user, 2, &file_data[2..4]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_file_data_insert(&mut user, 6, &file_data[6..8]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_file_data_insert(&mut user, 10, &file_data[10..]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_eof_no_error(&mut user, file_data.to_vec()) + .expect("EOF no error insertion failed"), + 2 + ); + assert_eq!(tb.pdu_queue_len(), 2); + tb.check_eof_ack_pdu(ConditionCode::NoError); + assert_eq!(tb.pdu_queue_len(), 1); + let pdu = tb.get_next_pdu().unwrap(); + assert_eq!(pdu.pdu_type, PduType::FileDirective); + assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu); + let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap(); + assert_eq!(nak_pdu.start_of_scope(), 0); + assert_eq!(nak_pdu.end_of_scope(), file_size); + let seg_reqs: Vec<(u64, u64)> = nak_pdu.get_segment_requests_iterator().unwrap().collect(); + assert_eq!(seg_reqs.len(), 3); + assert_eq!(seg_reqs[0], (0, 2)); + assert_eq!(seg_reqs[1], (4, 6)); + assert_eq!(seg_reqs[2], (8, 10)); + + // We simulate the reply by re-inserting the missing file segment. + tb.generic_file_data_insert(&mut user, 0, &file_data[0..2]) + .expect("file data insertion failed"); + tb.generic_file_data_insert(&mut user, 4, &file_data[4..6]) + .expect("file data insertion failed"); + tb.generic_file_data_insert(&mut user, 8, &file_data[8..10]) + .expect("file data insertion failed"); + tb.check_completion_indication_success(&mut user); + assert_eq!(tb.pdu_queue_len(), 1); + tb.check_finished_pdu_success(); + tb.acknowledge_finished_pdu(&mut user, &transfer_info); + } + + #[test] + fn test_multi_packet_nak_sequence_large_file_flag() { + let file_data = [0_u8; 64]; + let file_size = file_data.len() as u64; + let fault_handler = TestFaultHandler::default(); + + let mut tb = DestHandlerTestbench::new_with_fixed_paths( + fault_handler, + TransmissionMode::Acknowledged, + false, + ); + // Disable this, we only want to check the deferred procedure. + tb.remote_cfg_mut().immediate_nak_mode = false; + let max_packet_len = 80; + tb.remote_cfg_mut().max_packet_len = max_packet_len; + tb.set_large_file_flag(LargeFileFlag::Large); + let mut user = tb.test_user_from_cached_paths(file_size); + let transfer_info = tb + .generic_transfer_init(&mut user, file_size) + .expect("transfer init failed"); + tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + + assert_eq!( + NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests( + max_packet_len, + &PduHeader::new_for_file_directive(tb.pdu_conf, 0), + ) + .unwrap(), + 3 + ); + let missing_segs_0: &[(u64, u64)] = &[(0, 2), (4, 6), (8, 10)]; + let missing_segs_1: &[(u64, u64)] = &[(12, 14)]; + assert_eq!( + tb.generic_file_data_insert(&mut user, 2, &file_data[2..4]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_file_data_insert(&mut user, 6, &file_data[6..8]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_file_data_insert(&mut user, 10, &file_data[10..12]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_file_data_insert(&mut user, 14, &file_data[14..file_size as usize]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_eof_no_error(&mut user, file_data.to_vec()) + .expect("EOF no error insertion failed"), + 3 + ); + assert_eq!(tb.pdu_queue_len(), 3); + tb.check_eof_ack_pdu(ConditionCode::NoError); + let pdu = tb.get_next_pdu().unwrap(); + assert_eq!(pdu.pdu_type, PduType::FileDirective); + assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu); + let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap(); + assert_eq!(nak_pdu.start_of_scope(), 0); + assert_eq!(nak_pdu.end_of_scope(), 10); + let seg_reqs: Vec<(u64, u64)> = nak_pdu.get_segment_requests_iterator().unwrap().collect(); + assert_eq!(seg_reqs, missing_segs_0); + + let pdu = tb.get_next_pdu().unwrap(); + assert_eq!(pdu.pdu_type, PduType::FileDirective); + assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu); + let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap(); + assert_eq!(nak_pdu.start_of_scope(), 10); + assert_eq!(nak_pdu.end_of_scope(), file_size); + let seg_reqs: Vec<(u64, u64)> = nak_pdu.get_segment_requests_iterator().unwrap().collect(); + assert_eq!(seg_reqs, missing_segs_1); + + for missing_seg in missing_segs_0 { + assert_eq!( + tb.generic_file_data_insert( + &mut user, + missing_seg.0, + &file_data[missing_seg.0 as usize..missing_seg.1 as usize] + ) + .expect("file data insertion failed"), + 0 + ); + } + for missing_seg in missing_segs_1 { + assert_eq!( + tb.generic_file_data_insert( + &mut user, + missing_seg.0, + &file_data[missing_seg.0 as usize..missing_seg.1 as usize] + ) + .expect("file data insertion failed"), + 1 + ); + } + + tb.check_completion_indication_success(&mut user); + assert_eq!(tb.pdu_queue_len(), 1); + tb.check_finished_pdu_success(); + tb.acknowledge_finished_pdu(&mut user, &transfer_info); + } + + #[test] + fn test_multi_packet_nak_sequence_normal_file_flag() { + let file_data = [0_u8; 64]; + let file_size = file_data.len() as u64; + let fault_handler = TestFaultHandler::default(); + + let mut tb = DestHandlerTestbench::new_with_fixed_paths( + fault_handler, + TransmissionMode::Acknowledged, + false, + ); + // Disable this, we only want to check the deferred procedure. + tb.remote_cfg_mut().immediate_nak_mode = false; + let max_packet_len = 50; + tb.remote_cfg_mut().max_packet_len = max_packet_len; + let mut user = tb.test_user_from_cached_paths(file_size); + let transfer_info = tb + .generic_transfer_init(&mut user, file_size) + .expect("transfer init failed"); + tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + + assert_eq!( + NakPduCreatorWithReservedSeqReqsBuf::calculate_max_segment_requests( + max_packet_len, + &PduHeader::new_for_file_directive(tb.pdu_conf, 0), + ) + .unwrap(), + 4 + ); + let missing_segs_0: &[(u64, u64)] = &[(0, 2), (4, 6), (8, 10), (12, 14)]; + let missing_segs_1: &[(u64, u64)] = &[(16, 18)]; + assert_eq!( + tb.generic_file_data_insert(&mut user, 2, &file_data[2..4]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_file_data_insert(&mut user, 6, &file_data[6..8]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_file_data_insert(&mut user, 10, &file_data[10..12]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_file_data_insert(&mut user, 14, &file_data[14..16]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_file_data_insert(&mut user, 18, &file_data[18..file_size as usize]) + .expect("file data insertion failed"), + 0 + ); + assert_eq!( + tb.generic_eof_no_error(&mut user, file_data.to_vec()) + .expect("EOF no error insertion failed"), + 3 + ); + assert_eq!(tb.pdu_queue_len(), 3); + tb.check_eof_ack_pdu(ConditionCode::NoError); + let pdu = tb.get_next_pdu().unwrap(); + assert_eq!(pdu.pdu_type, PduType::FileDirective); + assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu); + let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap(); + assert_eq!(nak_pdu.start_of_scope(), 0); + assert_eq!(nak_pdu.end_of_scope(), 14); + let seg_reqs: Vec<(u64, u64)> = nak_pdu.get_segment_requests_iterator().unwrap().collect(); + assert_eq!(seg_reqs, missing_segs_0); + + let pdu = tb.get_next_pdu().unwrap(); + assert_eq!(pdu.pdu_type, PduType::FileDirective); + assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu); + let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap(); + assert_eq!(nak_pdu.start_of_scope(), 14); + assert_eq!(nak_pdu.end_of_scope(), file_size); + let seg_reqs: Vec<(u64, u64)> = nak_pdu.get_segment_requests_iterator().unwrap().collect(); + assert_eq!(seg_reqs, missing_segs_1); + + for missing_seg in missing_segs_0 { + assert_eq!( + tb.generic_file_data_insert( + &mut user, + missing_seg.0, + &file_data[missing_seg.0 as usize..missing_seg.1 as usize] + ) + .expect("file data insertion failed"), + 0 + ); + } + for missing_seg in missing_segs_1 { + assert_eq!( + tb.generic_file_data_insert( + &mut user, + missing_seg.0, + &file_data[missing_seg.0 as usize..missing_seg.1 as usize] + ) + .expect("file data insertion failed"), + 1 + ); + } + + tb.check_completion_indication_success(&mut user); + assert_eq!(tb.pdu_queue_len(), 1); + tb.check_finished_pdu_success(); + tb.acknowledge_finished_pdu(&mut user, &transfer_info); + } } diff --git a/tests/end-to-end.rs b/tests/end-to-end.rs index 64132b5..1d3afaa 100644 --- a/tests/end-to-end.rs +++ b/tests/end-to-end.rs @@ -138,7 +138,7 @@ impl CfdpUser for ExampleCfdpUser { } } -fn end_to_end_test(with_closure: bool) { +fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) { // Simplified event handling using atomic signals. let stop_signal_source = Arc::new(AtomicBool::new(false)); let stop_signal_dest = stop_signal_source.clone(); @@ -173,7 +173,7 @@ fn end_to_end_test(with_closure: bool) { 1024, with_closure, false, - spacepackets::cfdp::TransmissionMode::Unacknowledged, + transmission_mode, ChecksumType::Crc32, ); let seq_count_provider = AtomicU16::default(); @@ -199,7 +199,7 @@ fn end_to_end_test(with_closure: bool) { 1024, true, false, - spacepackets::cfdp::TransmissionMode::Unacknowledged, + transmission_mode, ChecksumType::Crc32, ); let mut dest_handler = DestinationHandler::new( @@ -217,7 +217,7 @@ fn end_to_end_test(with_closure: bool) { REMOTE_ID.into(), srcfile.to_str().expect("invaid path string"), destfile.to_str().expect("invaid path string"), - Some(TransmissionMode::Unacknowledged), + Some(transmission_mode), Some(with_closure), ) .expect("put request creation failed"); @@ -326,11 +326,16 @@ fn end_to_end_test(with_closure: bool) { } #[test] -fn end_to_end_test_no_closure() { - end_to_end_test(false); +fn end_to_end_unacknowledged_no_closure() { + end_to_end_test(TransmissionMode::Unacknowledged, false); } #[test] -fn end_to_end_test_with_closure() { - end_to_end_test(true); +fn end_to_end_unacknowledged_with_closure() { + end_to_end_test(TransmissionMode::Unacknowledged, true); +} + +#[test] +fn end_to_end_acknowledged() { + end_to_end_test(TransmissionMode::Acknowledged, true); }