Continue CFDP handlers #90
@ -1,17 +1,12 @@
|
|||||||
use core::str::{from_utf8, Utf8Error};
|
|
||||||
use std::{
|
|
||||||
fs::{metadata, File},
|
|
||||||
io::{Seek, SeekFrom, Write},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::cfdp::user::TransactionFinishedParams;
|
use crate::cfdp::user::TransactionFinishedParams;
|
||||||
|
use core::str::{from_utf8, Utf8Error};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
filestore::{FilestoreError, VirtualFilestore},
|
filestore::{FilestoreError, VirtualFilestore},
|
||||||
user::{CfdpUser, MetadataReceivedParams},
|
user::{CfdpUser, MetadataReceivedParams},
|
||||||
CheckTimerCreator, LocalEntityConfig, PacketInfo, PacketTarget, RemoteEntityConfig,
|
CheckTimer, CheckTimerCreator, EntityType, LocalEntityConfig, PacketInfo, PacketTarget,
|
||||||
RemoteEntityConfigProvider, State, TransactionId, TransactionStep,
|
RemoteEntityConfig, RemoteEntityConfigProvider, State, TransactionId, TransactionStep,
|
||||||
};
|
};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
@ -60,22 +55,28 @@ struct FileProperties {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct TransferState {
|
struct TransferState {
|
||||||
transaction_id: Option<TransactionId>,
|
transaction_id: Option<TransactionId>,
|
||||||
|
metadata_params: MetadataGenericParams,
|
||||||
progress: u64,
|
progress: u64,
|
||||||
condition_code: ConditionCode,
|
condition_code: ConditionCode,
|
||||||
delivery_code: DeliveryCode,
|
delivery_code: DeliveryCode,
|
||||||
file_status: FileStatus,
|
file_status: FileStatus,
|
||||||
metadata_params: MetadataGenericParams,
|
checksum: u32,
|
||||||
|
current_check_count: u32,
|
||||||
|
current_check_timer: Option<Box<dyn CheckTimer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TransferState {
|
impl Default for TransferState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
transaction_id: None,
|
transaction_id: None,
|
||||||
|
metadata_params: Default::default(),
|
||||||
progress: Default::default(),
|
progress: Default::default(),
|
||||||
condition_code: ConditionCode::NoError,
|
condition_code: ConditionCode::NoError,
|
||||||
delivery_code: DeliveryCode::Incomplete,
|
delivery_code: DeliveryCode::Incomplete,
|
||||||
file_status: FileStatus::Unreported,
|
file_status: FileStatus::Unreported,
|
||||||
metadata_params: Default::default(),
|
checksum: 0,
|
||||||
|
current_check_count: 0,
|
||||||
|
current_check_timer: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,6 +138,7 @@ impl TransactionParams {
|
|||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.tstate.condition_code = ConditionCode::NoError;
|
self.tstate.condition_code = ConditionCode::NoError;
|
||||||
self.tstate.delivery_code = DeliveryCode::Incomplete;
|
self.tstate.delivery_code = DeliveryCode::Incomplete;
|
||||||
|
self.tstate.file_status = FileStatus::Unreported;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,10 +162,12 @@ pub enum DestError {
|
|||||||
Pdu(#[from] PduError),
|
Pdu(#[from] PduError),
|
||||||
#[error("io error {0}")]
|
#[error("io error {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("file store error {0}")]
|
||||||
|
Filestore(#[from] FilestoreError),
|
||||||
#[error("path conversion error {0}")]
|
#[error("path conversion error {0}")]
|
||||||
PathConversion(#[from] Utf8Error),
|
PathConversion(#[from] Utf8Error),
|
||||||
#[error("error building dest path from source file name and dest folder")]
|
#[error("error building dest path from source file name and dest folder")]
|
||||||
PathConcatError,
|
PathConcat,
|
||||||
#[error("no remote entity configuration found for {0:?}")]
|
#[error("no remote entity configuration found for {0:?}")]
|
||||||
NoRemoteCfgFound(UnsignedByteField),
|
NoRemoteCfgFound(UnsignedByteField),
|
||||||
}
|
}
|
||||||
@ -241,25 +245,26 @@ impl DestinationHandler {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let directive = self.packets_to_send_ctx.directive.unwrap();
|
let directive = self.packets_to_send_ctx.directive.unwrap();
|
||||||
|
let tstate = self.tstate();
|
||||||
let written_size = match directive {
|
let written_size = match directive {
|
||||||
FileDirectiveType::FinishedPdu => {
|
FileDirectiveType::FinishedPdu => {
|
||||||
let pdu_header = PduHeader::new_no_file_data(self.tparams.pdu_conf, 0);
|
let pdu_header = PduHeader::new_no_file_data(self.tparams.pdu_conf, 0);
|
||||||
let finished_pdu = if self.tparams.tstate.condition_code == ConditionCode::NoError
|
let finished_pdu = if tstate.condition_code == ConditionCode::NoError
|
||||||
|| self.tparams.tstate.condition_code == ConditionCode::UnsupportedChecksumType
|
|| tstate.condition_code == ConditionCode::UnsupportedChecksumType
|
||||||
{
|
{
|
||||||
FinishedPduCreator::new_default(
|
FinishedPduCreator::new_default(
|
||||||
pdu_header,
|
pdu_header,
|
||||||
self.tparams.tstate.delivery_code,
|
tstate.delivery_code,
|
||||||
self.tparams.tstate.file_status,
|
tstate.file_status,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// TODO: Are there cases where this ID is actually the source entity ID?
|
// TODO: Are there cases where this ID is actually the source entity ID?
|
||||||
let entity_id = EntityIdTlv::new(self.local_cfg.id);
|
let entity_id = EntityIdTlv::new(self.local_cfg.id);
|
||||||
FinishedPduCreator::new_with_error(
|
FinishedPduCreator::new_with_error(
|
||||||
pdu_header,
|
pdu_header,
|
||||||
self.tparams.tstate.condition_code,
|
tstate.condition_code,
|
||||||
self.tparams.tstate.delivery_code,
|
tstate.delivery_code,
|
||||||
self.tparams.tstate.file_status,
|
tstate.file_status,
|
||||||
entity_id,
|
entity_id,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -354,11 +359,14 @@ impl DestinationHandler {
|
|||||||
return Err(DestError::WrongStateForFileDataAndEof);
|
return Err(DestError::WrongStateForFileDataAndEof);
|
||||||
}
|
}
|
||||||
let fd_pdu = FileDataPdu::from_bytes(raw_packet)?;
|
let fd_pdu = FileDataPdu::from_bytes(raw_packet)?;
|
||||||
let mut dest_file = File::options()
|
if let Err(e) = self.vfs.write_data(
|
||||||
.write(true)
|
self.tparams.file_properties.dest_path_buf.to_str().unwrap(),
|
||||||
.open(&self.tparams.file_properties.dest_path_buf)?;
|
fd_pdu.offset(),
|
||||||
dest_file.seek(SeekFrom::Start(fd_pdu.offset()))?;
|
fd_pdu.file_data(),
|
||||||
dest_file.write_all(fd_pdu.file_data())?;
|
) {
|
||||||
|
self.declare_fault(ConditionCode::FilestoreRejection);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,50 +383,57 @@ impl DestinationHandler {
|
|||||||
// Unwrap is okay here, application logic ensures that transaction ID is valid here.
|
// Unwrap is okay here, application logic ensures that transaction ID is valid here.
|
||||||
cfdp_user.eof_recvd_indication(self.tparams.tstate.transaction_id.as_ref().unwrap());
|
cfdp_user.eof_recvd_indication(self.tparams.tstate.transaction_id.as_ref().unwrap());
|
||||||
}
|
}
|
||||||
if eof_pdu.condition_code() == ConditionCode::NoError {
|
let regular_transfer_finish = if eof_pdu.condition_code() == ConditionCode::NoError {
|
||||||
self.handle_no_error_eof_pdu(&eof_pdu)?;
|
self.handle_no_error_eof_pdu(&eof_pdu)?
|
||||||
} else {
|
} else {
|
||||||
todo!("implement cancel request handling");
|
todo!("implement cancel request handling");
|
||||||
|
};
|
||||||
|
if regular_transfer_finish {
|
||||||
|
self.file_transfer_complete_transition();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the transfer can be completed regularly.
|
||||||
fn handle_no_error_eof_pdu(&mut self, eof_pdu: &EofPdu) -> Result<bool, DestError> {
|
fn handle_no_error_eof_pdu(&mut self, eof_pdu: &EofPdu) -> Result<bool, DestError> {
|
||||||
// CFDP 4.6.1.2.9: Declare file size error if progress exceeds file size
|
// CFDP 4.6.1.2.9: Declare file size error if progress exceeds file size
|
||||||
if self.tparams.tstate.progress > eof_pdu.file_size()
|
if self.tparams.tstate.progress > eof_pdu.file_size()
|
||||||
&& self.declare_fault(ConditionCode::FileSizeError) != FaultHandlerCode::IgnoreError
|
&& self.declare_fault(ConditionCode::FileSizeError) != FaultHandlerCode::IgnoreError
|
||||||
{
|
{
|
||||||
return Ok(true);
|
return Ok(false);
|
||||||
}
|
} else if (self.tparams.tstate.progress < eof_pdu.file_size())
|
||||||
|
&& self.tparams.transmission_mode() == TransmissionMode::Acknowledged
|
||||||
if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged
|
|
||||||
&& !self.checksum_verify(eof_pdu.file_checksum())
|
|
||||||
{
|
{
|
||||||
self.start_check_limit_handling()
|
// CFDP 4.6.4.3.1: The end offset of the last received file segment and the file
|
||||||
|
// size as stated in the EOF PDU is not the same, so we need to add that segment to
|
||||||
|
// the lost segments for the deferred lost segment detection procedure.
|
||||||
|
// TODO: Proper lost segment handling.
|
||||||
|
// self._params.acked_params.lost_seg_tracker.add_lost_segment(
|
||||||
|
// (self._params.fp.progress, self._params.fp.file_size_eof)
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Continue here based on Python implementation.
|
self.tparams.tstate.checksum = eof_pdu.file_checksum();
|
||||||
|
if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged
|
||||||
// For a standard disk based file system, which is assumed to be used for now, the file
|
&& !self.checksum_verify(self.tparams.tstate.checksum)
|
||||||
// will always be retained. This might change in the future.
|
{
|
||||||
/*
|
if self.declare_fault(ConditionCode::FileChecksumFailure)
|
||||||
self.tparams.tstate.file_status = FileStatus::Retained;
|
!= FaultHandlerCode::IgnoreError
|
||||||
if self.checksum_verify(eof_pdu.file_checksum())? {
|
{
|
||||||
self.tparams.tstate.condition_code = ConditionCode::NoError;
|
return Ok(false);
|
||||||
self.tparams.tstate.delivery_code = DeliveryCode::Complete;
|
|
||||||
} else {
|
|
||||||
self.tparams.tstate.condition_code = ConditionCode::FileChecksumFailure;
|
|
||||||
}
|
}
|
||||||
*/
|
self.start_check_limit_handling();
|
||||||
// TODO: Check progress, and implement transfer completion timer as specified in the
|
}
|
||||||
// standard. This timer protects against out of order arrival of packets.
|
Ok(true)
|
||||||
// if self.tparams.tstate.progress != self.tparams.file_size() {}
|
}
|
||||||
|
|
||||||
|
fn file_transfer_complete_transition(&mut self) {
|
||||||
if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged {
|
if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged {
|
||||||
self.step = TransactionStep::TransferCompletion;
|
self.step = TransactionStep::TransferCompletion;
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: Prepare ACK PDU somehow.
|
||||||
self.step = TransactionStep::SendingAckPdu;
|
self.step = TransactionStep::SendingAckPdu;
|
||||||
}
|
}
|
||||||
Ok(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn checksum_verify(&mut self, checksum: u32) -> bool {
|
fn checksum_verify(&mut self, checksum: u32) -> bool {
|
||||||
@ -430,7 +445,7 @@ impl DestinationHandler {
|
|||||||
) {
|
) {
|
||||||
Ok(checksum_success) => checksum_success,
|
Ok(checksum_success) => checksum_success,
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
FilestoreError::ChecksumTypeNotImplemented(checksum) => {
|
FilestoreError::ChecksumTypeNotImplemented(_) => {
|
||||||
self.declare_fault(ConditionCode::UnsupportedChecksumType);
|
self.declare_fault(ConditionCode::UnsupportedChecksumType);
|
||||||
// For this case, the applicable algorithm shall the the null checksum, which
|
// For this case, the applicable algorithm shall the the null checksum, which
|
||||||
// is always succesful.
|
// is always succesful.
|
||||||
@ -445,7 +460,43 @@ impl DestinationHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_check_limit_handling(&mut self) {}
|
fn start_check_limit_handling(&mut self) {
|
||||||
|
self.step = TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling;
|
||||||
|
self.tparams.tstate.current_check_timer =
|
||||||
|
Some(self.check_timer_creator.get_check_timer_provider(
|
||||||
|
&self.local_cfg.id,
|
||||||
|
&self.tparams.remote_cfg.unwrap().entity_id,
|
||||||
|
EntityType::Receiving,
|
||||||
|
));
|
||||||
|
self.tparams.tstate.current_check_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_limit_handling(&mut self) {
|
||||||
|
if self.tparams.tstate.current_check_timer.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let check_timer = self.tparams.tstate.current_check_timer.as_ref().unwrap();
|
||||||
|
if check_timer.has_expired() {
|
||||||
|
if self.checksum_verify(self.tparams.tstate.checksum) {
|
||||||
|
self.file_transfer_complete_transition();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.tparams.tstate.current_check_count + 1
|
||||||
|
>= self.tparams.remote_cfg.unwrap().check_limit
|
||||||
|
{
|
||||||
|
self.declare_fault(ConditionCode::CheckLimitReached);
|
||||||
|
} else {
|
||||||
|
self.tparams.tstate.current_check_count += 1;
|
||||||
|
self.tparams
|
||||||
|
.tstate
|
||||||
|
.current_check_timer
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_prompt_pdu(&mut self, _raw_packet: &[u8]) -> Result<(), DestError> {
|
pub fn handle_prompt_pdu(&mut self, _raw_packet: &[u8]) -> Result<(), DestError> {
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
@ -457,6 +508,9 @@ impl DestinationHandler {
|
|||||||
if self.step == TransactionStep::TransferCompletion {
|
if self.step == TransactionStep::TransferCompletion {
|
||||||
self.transfer_completion(cfdp_user)?;
|
self.transfer_completion(cfdp_user)?;
|
||||||
}
|
}
|
||||||
|
if self.step == TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling {
|
||||||
|
self.check_limit_handling();
|
||||||
|
}
|
||||||
if self.step == TransactionStep::SendingAckPdu {
|
if self.step == TransactionStep::SendingAckPdu {
|
||||||
todo!("no support for acknowledged mode yet");
|
todo!("no support for acknowledged mode yet");
|
||||||
}
|
}
|
||||||
@ -515,9 +569,10 @@ impl DestinationHandler {
|
|||||||
self.tparams.tstate.transaction_id = Some(id);
|
self.tparams.tstate.transaction_id = Some(id);
|
||||||
cfdp_user.metadata_recvd_indication(&metadata_recvd_params);
|
cfdp_user.metadata_recvd_indication(&metadata_recvd_params);
|
||||||
|
|
||||||
if dest_path.exists() {
|
// TODO: This is the only remaining function which uses std.. the easiest way would
|
||||||
let dest_metadata = metadata(dest_path)?;
|
// probably be to use a static pre-allocated dest path buffer to store any concatenated
|
||||||
if dest_metadata.is_dir() {
|
// paths.
|
||||||
|
if dest_path.exists() && self.vfs.is_dir(dest_path.to_str().unwrap()) {
|
||||||
// Create new destination path by concatenating the last part of the source source
|
// Create new destination path by concatenating the last part of the source source
|
||||||
// name and the destination folder. For example, for a source file of /tmp/hello.txt
|
// name and the destination folder. For example, for a source file of /tmp/hello.txt
|
||||||
// and a destination name of /home/test, the resulting file name should be
|
// and a destination name of /home/test, the resulting file name should be
|
||||||
@ -526,28 +581,31 @@ impl DestinationHandler {
|
|||||||
&self.tparams.file_properties.src_file_name
|
&self.tparams.file_properties.src_file_name
|
||||||
[..self.tparams.file_properties.src_file_name_len],
|
[..self.tparams.file_properties.src_file_name_len],
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
let source_name = source_path.file_name();
|
let source_name = source_path.file_name();
|
||||||
if source_name.is_none() {
|
if source_name.is_none() {
|
||||||
return Err(DestError::PathConcatError);
|
return Err(DestError::PathConcat);
|
||||||
}
|
}
|
||||||
let source_name = source_name.unwrap();
|
let source_name = source_name.unwrap();
|
||||||
self.tparams.file_properties.dest_path_buf.push(source_name);
|
self.tparams.file_properties.dest_path_buf.push(source_name);
|
||||||
}
|
}
|
||||||
|
let dest_path_str = self.tparams.file_properties.dest_path_buf.to_str().unwrap();
|
||||||
|
if self.vfs.exists(dest_path_str) {
|
||||||
|
self.vfs.truncate_file(dest_path_str)?;
|
||||||
|
} else {
|
||||||
|
self.vfs.create_file(dest_path_str)?;
|
||||||
}
|
}
|
||||||
// This function does exactly what we require: Create a new file if it does not exist yet
|
self.tparams.tstate.file_status = FileStatus::Retained;
|
||||||
// and trucate an existing one.
|
|
||||||
File::create(&self.tparams.file_properties.dest_path_buf)?;
|
|
||||||
self.step = TransactionStep::ReceivingFileDataPdus;
|
self.step = TransactionStep::ReceivingFileDataPdus;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> {
|
fn transfer_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> {
|
||||||
|
let tstate = self.tstate();
|
||||||
let transaction_finished_params = TransactionFinishedParams {
|
let transaction_finished_params = TransactionFinishedParams {
|
||||||
id: self.tparams.tstate.transaction_id.unwrap(),
|
id: tstate.transaction_id.unwrap(),
|
||||||
condition_code: self.tparams.tstate.condition_code,
|
condition_code: tstate.condition_code,
|
||||||
delivery_code: self.tparams.tstate.delivery_code,
|
delivery_code: tstate.delivery_code,
|
||||||
file_status: self.tparams.tstate.file_status,
|
file_status: tstate.file_status,
|
||||||
};
|
};
|
||||||
cfdp_user.transaction_finished_indication(&transaction_finished_params);
|
cfdp_user.transaction_finished_indication(&transaction_finished_params);
|
||||||
// This function should never be called with metadata parameters not set
|
// This function should never be called with metadata parameters not set
|
||||||
@ -575,10 +633,11 @@ impl DestinationHandler {
|
|||||||
FaultHandlerCode::IgnoreError => todo!(),
|
FaultHandlerCode::IgnoreError => todo!(),
|
||||||
FaultHandlerCode::AbandonTransaction => todo!(),
|
FaultHandlerCode::AbandonTransaction => todo!(),
|
||||||
}
|
}
|
||||||
|
let tstate = self.tstate();
|
||||||
self.local_cfg.default_fault_handler.report_fault(
|
self.local_cfg.default_fault_handler.report_fault(
|
||||||
self.tparams.tstate.transaction_id.unwrap(),
|
tstate.transaction_id.unwrap(),
|
||||||
condition_code,
|
condition_code,
|
||||||
self.tparams.tstate.progress,
|
tstate.progress,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,19 +657,20 @@ impl DestinationHandler {
|
|||||||
self.step = TransactionStep::SendingFinishedPdu;
|
self.step = TransactionStep::SendingFinishedPdu;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tstate(&self) -> &TransferState {
|
||||||
|
&self.tparams.tstate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use core::{
|
use core::cell::Cell;
|
||||||
cell::Cell,
|
use std::fs;
|
||||||
sync::atomic::{AtomicU8, Ordering},
|
|
||||||
};
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use std::println;
|
use std::println;
|
||||||
use std::{env::temp_dir, fs};
|
|
||||||
|
|
||||||
use alloc::{format, string::String};
|
use alloc::{collections::VecDeque, string::String};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use spacepackets::{
|
use spacepackets::{
|
||||||
cfdp::{
|
cfdp::{
|
||||||
@ -649,10 +709,10 @@ mod tests {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TestFaultHandler {
|
struct TestFaultHandler {
|
||||||
notice_of_suspension_count: u32,
|
notice_of_suspension_queue: VecDeque<(TransactionId, ConditionCode, u64)>,
|
||||||
notice_of_cancellation_count: u32,
|
notice_of_cancellation_queue: VecDeque<(TransactionId, ConditionCode, u64)>,
|
||||||
abandoned_count: u32,
|
abandoned_queue: VecDeque<(TransactionId, ConditionCode, u64)>,
|
||||||
ignored_count: u32,
|
ignored_queue: VecDeque<(TransactionId, ConditionCode, u64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserFaultHandler for TestFaultHandler {
|
impl UserFaultHandler for TestFaultHandler {
|
||||||
@ -662,7 +722,8 @@ mod tests {
|
|||||||
cond: ConditionCode,
|
cond: ConditionCode,
|
||||||
progress: u64,
|
progress: u64,
|
||||||
) {
|
) {
|
||||||
self.notice_of_suspension_count += 1;
|
self.notice_of_suspension_queue
|
||||||
|
.push_back((transaction_id, cond, progress))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn notice_of_cancellation_cb(
|
fn notice_of_cancellation_cb(
|
||||||
@ -671,7 +732,8 @@ mod tests {
|
|||||||
cond: ConditionCode,
|
cond: ConditionCode,
|
||||||
progress: u64,
|
progress: u64,
|
||||||
) {
|
) {
|
||||||
self.notice_of_cancellation_count += 1;
|
self.notice_of_cancellation_queue
|
||||||
|
.push_back((transaction_id, cond, progress))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn abandoned_cb(
|
fn abandoned_cb(
|
||||||
@ -680,11 +742,13 @@ mod tests {
|
|||||||
cond: ConditionCode,
|
cond: ConditionCode,
|
||||||
progress: u64,
|
progress: u64,
|
||||||
) {
|
) {
|
||||||
self.abandoned_count += 1;
|
self.abandoned_queue
|
||||||
|
.push_back((transaction_id, cond, progress))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
|
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
|
||||||
self.ignored_count += 1;
|
self.ignored_queue
|
||||||
|
.push_back((transaction_id, cond, progress))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -763,6 +827,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct TestCheckTimer {
|
struct TestCheckTimer {
|
||||||
counter: Cell<u32>,
|
counter: Cell<u32>,
|
||||||
expiry_count: u32,
|
expiry_count: u32,
|
||||||
@ -777,6 +842,9 @@ mod tests {
|
|||||||
self.counter.set(current_counter + 1);
|
self.counter.set(current_counter + 1);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.counter.set(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestCheckTimer {
|
impl TestCheckTimer {
|
||||||
@ -901,7 +969,8 @@ mod tests {
|
|||||||
.expect("writing metadata PDU failed");
|
.expect("writing metadata PDU failed");
|
||||||
PacketInfo::new(&buf[..written_len]).expect("generating packet info failed")
|
PacketInfo::new(&buf[..written_len]).expect("generating packet info failed")
|
||||||
}
|
}
|
||||||
fn create_no_error_eof(file_data: &[u8], pdu_header: &PduHeader, buf: &mut [u8]) -> EofPdu {
|
|
||||||
|
fn create_no_error_eof(file_data: &[u8], pdu_header: &PduHeader) -> EofPdu {
|
||||||
let mut digest = CRC_32.digest();
|
let mut digest = CRC_32.digest();
|
||||||
digest.update(file_data);
|
digest.update(file_data);
|
||||||
let crc32 = digest.finalize();
|
let crc32 = digest.finalize();
|
||||||
@ -935,7 +1004,7 @@ mod tests {
|
|||||||
assert_ne!(dest_handler.state(), State::Idle);
|
assert_ne!(dest_handler.state(), State::Idle);
|
||||||
assert_eq!(dest_handler.step(), TransactionStep::ReceivingFileDataPdus);
|
assert_eq!(dest_handler.step(), TransactionStep::ReceivingFileDataPdus);
|
||||||
|
|
||||||
let eof_pdu = create_no_error_eof(&[], &pdu_header, &mut buf);
|
let eof_pdu = create_no_error_eof(&[], &pdu_header);
|
||||||
let packet_info = create_packet_info(&eof_pdu, &mut buf);
|
let packet_info = create_packet_info(&eof_pdu, &mut buf);
|
||||||
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
@ -989,7 +1058,7 @@ mod tests {
|
|||||||
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let eof_pdu = create_no_error_eof(&file_data, &pdu_header, &mut buf);
|
let eof_pdu = create_no_error_eof(file_data, &pdu_header);
|
||||||
let packet_info = create_packet_info(&eof_pdu, &mut buf);
|
let packet_info = create_packet_info(&eof_pdu, &mut buf);
|
||||||
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
@ -1066,7 +1135,7 @@ mod tests {
|
|||||||
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let eof_pdu = create_no_error_eof(&random_data, &pdu_header, &mut buf);
|
let eof_pdu = create_no_error_eof(&random_data, &pdu_header);
|
||||||
let packet_info = create_packet_info(&eof_pdu, &mut buf);
|
let packet_info = create_packet_info(&eof_pdu, &mut buf);
|
||||||
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
let result = dest_handler.state_machine(&mut test_user, Some(&packet_info));
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use core::hash::Hash;
|
use core::{cell::RefCell, fmt::Debug, hash::Hash};
|
||||||
|
|
||||||
use crc::{Crc, CRC_32_CKSUM};
|
use crc::{Crc, CRC_32_CKSUM};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@ -39,8 +39,9 @@ pub enum EntityType {
|
|||||||
/// For the receiving entity, this timer determines the expiry period for incrementing a check
|
/// For the receiving entity, this timer determines the expiry period for incrementing a check
|
||||||
/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
|
/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
|
||||||
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard.
|
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard.
|
||||||
pub trait CheckTimer {
|
pub trait CheckTimer: Debug {
|
||||||
fn has_expired(&self) -> bool;
|
fn has_expired(&self) -> bool;
|
||||||
|
fn reset(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A generic trait which allows CFDP entities to create check timers which are required to
|
/// A generic trait which allows CFDP entities to create check timers which are required to
|
||||||
@ -64,6 +65,7 @@ pub trait CheckTimerCreator {
|
|||||||
/// Simple implementation of the [CheckTimerProvider] trait assuming a standard runtime.
|
/// Simple implementation of the [CheckTimerProvider] trait assuming a standard runtime.
|
||||||
/// It also assumes that a second accuracy of the check timer period is sufficient.
|
/// It also assumes that a second accuracy of the check timer period is sufficient.
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct StdCheckTimer {
|
pub struct StdCheckTimer {
|
||||||
expiry_time_seconds: u64,
|
expiry_time_seconds: u64,
|
||||||
start_time: std::time::Instant,
|
start_time: std::time::Instant,
|
||||||
@ -88,6 +90,10 @@ impl CheckTimer for StdCheckTimer {
|
|||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.start_time = std::time::Instant::now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@ -186,7 +192,9 @@ pub trait UserFaultHandler {
|
|||||||
|
|
||||||
pub struct DefaultFaultHandler {
|
pub struct DefaultFaultHandler {
|
||||||
handler_array: [FaultHandlerCode; 10],
|
handler_array: [FaultHandlerCode; 10],
|
||||||
user_fault_handler: Box<dyn UserFaultHandler + Send>,
|
// Could also change the user fault handler trait to have non mutable methods, but that limits
|
||||||
|
// flexbility on the user side..
|
||||||
|
user_fault_handler: RefCell<Box<dyn UserFaultHandler + Send>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefaultFaultHandler {
|
impl DefaultFaultHandler {
|
||||||
@ -227,7 +235,7 @@ impl DefaultFaultHandler {
|
|||||||
.unwrap()] = FaultHandlerCode::IgnoreError;
|
.unwrap()] = FaultHandlerCode::IgnoreError;
|
||||||
Self {
|
Self {
|
||||||
handler_array: init_array,
|
handler_array: init_array,
|
||||||
user_fault_handler,
|
user_fault_handler: RefCell::new(user_fault_handler),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +248,7 @@ impl DefaultFaultHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_fault(
|
pub fn report_fault(
|
||||||
&mut self,
|
&self,
|
||||||
transaction_id: TransactionId,
|
transaction_id: TransactionId,
|
||||||
condition: ConditionCode,
|
condition: ConditionCode,
|
||||||
progress: u64,
|
progress: u64,
|
||||||
@ -250,28 +258,19 @@ impl DefaultFaultHandler {
|
|||||||
return FaultHandlerCode::IgnoreError;
|
return FaultHandlerCode::IgnoreError;
|
||||||
}
|
}
|
||||||
let fh_code = self.handler_array[array_idx.unwrap()];
|
let fh_code = self.handler_array[array_idx.unwrap()];
|
||||||
|
let mut handler_mut = self.user_fault_handler.borrow_mut();
|
||||||
match fh_code {
|
match fh_code {
|
||||||
FaultHandlerCode::NoticeOfCancellation => {
|
FaultHandlerCode::NoticeOfCancellation => {
|
||||||
self.user_fault_handler.notice_of_cancellation_cb(
|
handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress);
|
||||||
transaction_id,
|
|
||||||
condition,
|
|
||||||
progress,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
FaultHandlerCode::NoticeOfSuspension => {
|
FaultHandlerCode::NoticeOfSuspension => {
|
||||||
self.user_fault_handler.notice_of_suspension_cb(
|
handler_mut.notice_of_suspension_cb(transaction_id, condition, progress);
|
||||||
transaction_id,
|
|
||||||
condition,
|
|
||||||
progress,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
FaultHandlerCode::IgnoreError => {
|
FaultHandlerCode::IgnoreError => {
|
||||||
self.user_fault_handler
|
handler_mut.ignore_cb(transaction_id, condition, progress);
|
||||||
.ignore_cb(transaction_id, condition, progress);
|
|
||||||
}
|
}
|
||||||
FaultHandlerCode::AbandonTransaction => {
|
FaultHandlerCode::AbandonTransaction => {
|
||||||
self.user_fault_handler
|
handler_mut.abandoned_cb(transaction_id, condition, progress);
|
||||||
.abandoned_cb(transaction_id, condition, progress);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fh_code
|
fh_code
|
||||||
@ -347,9 +346,10 @@ pub enum TransactionStep {
|
|||||||
Idle = 0,
|
Idle = 0,
|
||||||
TransactionStart = 1,
|
TransactionStart = 1,
|
||||||
ReceivingFileDataPdus = 2,
|
ReceivingFileDataPdus = 2,
|
||||||
SendingAckPdu = 3,
|
ReceivingFileDataPdusWithCheckLimitHandling = 3,
|
||||||
TransferCompletion = 4,
|
SendingAckPdu = 4,
|
||||||
SendingFinishedPdu = 5,
|
TransferCompletion = 5,
|
||||||
|
SendingFinishedPdu = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
Loading…
Reference in New Issue
Block a user