18 Commits

Author SHA1 Message Date
muellerr 70d8915c3f Update README feature list 2025-08-13 17:42:04 +02:00
muellerr 4f391d1139 add first cancellation tests for dest handler
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-10 12:27:19 +02:00
muellerr 82f8e22bd9 fix another test 2024-09-10 11:43:32 +02:00
muellerr cd03e5d18a dest handler cancel handling
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-09 21:19:52 +02:00
muellerr df97b38cc5 add additional test for dest handler
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-09 10:59:53 +02:00
muellerr a0b863f7d6 source handler test
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-07 11:59:18 +02:00
muellerr 467037c126 test stub
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-06 09:28:36 +02:00
muellerr 3575f331d4 doc corrections
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:58:40 +02:00
muellerr 8a331c2971 use better timer types
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:57:15 +02:00
muellerr a51ab5e878 some more renaming
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:49:50 +02:00
muellerr 80c91b59d3 some renaming
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:46:06 +02:00
muellerr 0766a0e6c9 add more error handling
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:44:56 +02:00
muellerr 69eed4a46d complete basic documentation
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 12:34:47 +02:00
muellerr 45163ff8aa dooocs
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 12:10:40 +02:00
muellerr 512abdd98a optimization and test stub 2024-09-05 00:34:36 +02:00
muellerr 08b9bf99a5 remove last std dependency 2024-09-05 00:25:02 +02:00
muellerr 5a4bd9710a remove one more path dependency
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 00:06:40 +02:00
muellerr befe3d66e0 init commit
Rust/cfdp/pipeline/head There was a failure building this commit
2024-08-29 15:23:47 +02:00
11 changed files with 71 additions and 382 deletions
-4
View File
@@ -7,7 +7,3 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.1.0] 2024-09-11
Initial release
+6 -9
View File
@@ -20,10 +20,6 @@ crc = "3"
smallvec = "1"
derive-new = "0.6"
[dependencies.spacepackets]
version = "0.12"
default-features = false
[dependencies.thiserror]
version = "1"
optional = true
@@ -36,9 +32,11 @@ optional = true
version = "1"
optional = true
[dependencies.defmt]
version = "0.3"
optional = true
[dependencies.spacepackets]
version = "0.12"
default-features = false
git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
branch = "main"
[features]
default = ["std"]
@@ -51,8 +49,7 @@ alloc = [
"hashbrown",
"spacepackets/alloc"
]
serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde"]
defmt = ["dep:defmt", "spacepackets/defmt"]
serde = ["dep:serde", "spacepackets/serde"]
[dev-dependencies]
tempfile = "3"
+16 -7
View File
@@ -13,23 +13,32 @@ The underlying base packet library used to generate the packets to be sent is th
# Features
The goal of this library is to be flexible enough to support the use-cases of both on-board
software and of ground software. It has support to make integration on [std] systems as simple
as possible, but also has sufficient abstraction to allow for integration on`no_std` environments.
Currently, the handlers still require the [std] feature until
[thiserror supports `error_in_core`](https://github.com/dtolnay/thiserror/pull/304).
`cfdp-rs` currently supports following features:
- Unacknowledged (class 1) file transfers for both source and destination side.
The following features have not been implemented yet. PRs or notifications for demand are welcome!
- Acknowledged (class 2) file transfers for both source and destination side.
- Suspending transfers
- Inactivity handling
- Start and end of transmission and reception opportunity handling
- Keep Alive and Prompt PDU handling
## Rust features
`cfdp-rs` supports various runtime environments and is also suitable for `no_std` environments.
It is recommended to activate the `alloc` feature at the very least to allow using the primary
components provided by this crate. These components will only allocate memory at initialization
time and thus are still viable for systems where run-time allocation is prohibited.
## Default features
### Default features
- [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
- [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which require allocation support.
Enabled by the `std` feature.
## Optional Features
### Optional Features
- [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s
- [`defmt`](https://defmt.ferrous-systems.com/): Add support for the `defmt` by adding the
+1 -1
View File
@@ -20,7 +20,7 @@ def generate_cov_report(open_report: bool, format: str):
out_path = "./target/debug/lcov.info"
os.system(
f"grcov . -s . --binary-path ./target/debug/ -t {format} --branch --ignore-not-existing "
f"--ignore \"examples/*\" -o {out_path}"
f"-o {out_path}"
)
if format == "lcov":
os.system(
-37
View File
@@ -1,37 +0,0 @@
Python Interoperability Example for cfdp-rs
=======
This example application showcases the interoperability of the CFDP handlers written in Rust
with a Python implementation which uses [cfdp-py](https://github.com/us-irs/cfdp-py) library.
Both the Rust and the Python app exchange packet data units via a UDP interface and launch
both a destination and source handler. As such, they are both able to send and receive files.
Both applications can be started with the command line argument `-f` to initiate a file transfer.
You can run both applications with `-h` to get more information about the available options.
## Running the Python App
It is recommended to run the Python App in a dedicated virtual environment. For example, on a
Unix system you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
and activate a virtual environment.
After that, you can install the required dependencies using
```sh
pip install -r requirements.txt
```
and then run the application using `./main.py` or `python3 main.py`.
It is recommended to run `./main.py -h` first to get an overview of some possible options.
Running the Python App with `./main.py -f` will cause the Python App to start a file copy operation
with fixed temporary paths.
## Running the Rust App
You can run the Rust application using `cargo`, for example `cargo run --example python-interop`.
It is recommended to run `cargo run --example python-interop -- -h` to get an overview of some
possible launch options.
Running the Rust App with `cargo run --example python-interop -- -f` will cause the Rust app to
start a file copy operation with fixed temporary paths.
+43 -247
View File
@@ -76,7 +76,6 @@ enum CompletionDisposition {
/// This enumeration models the different transaction steps of the destination entity handler.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TransactionStep {
Idle = 0,
TransactionStart = 1,
@@ -93,7 +92,7 @@ struct TransferState<Countdown: CountdownProvider> {
transaction_id: Option<TransactionId>,
metadata_params: MetadataGenericParams,
progress: u64,
// file_size_eof: u64,
file_size_eof: u64,
metadata_only: bool,
condition_code: ConditionCode,
delivery_code: DeliveryCode,
@@ -111,7 +110,7 @@ impl<CheckTimer: CountdownProvider> Default for TransferState<CheckTimer> {
transaction_id: None,
metadata_params: Default::default(),
progress: Default::default(),
// file_size_eof: Default::default(),
file_size_eof: Default::default(),
metadata_only: false,
condition_code: ConditionCode::NoError,
delivery_code: DeliveryCode::Incomplete,
@@ -368,30 +367,8 @@ impl<
}
}
/// This function models the Cancel.request CFDP primitive and is the recommended way
/// to cancel a transaction. It will cause a Notice Of Cancellation at this entity.
/// Please note that the state machine might still be active because a cancelled transfer
/// might still require some packets to be sent to the remote sender entity.
///
/// If no unexpected errors occur, this function returns whether the current transfer
/// was cancelled. It returns [false] if the state machine is currently idle or if there
/// is a transaction ID missmatch.
pub fn cancel_request(&mut self, transaction_id: &TransactionId) -> bool {
if self.state() == super::State::Idle {
return false;
}
if let Some(active_id) = self.transaction_id() {
if active_id == *transaction_id {
self.trigger_notice_of_completion_cancelled(
ConditionCode::CancelRequestReceived,
EntityIdTlv::new(self.local_cfg.id),
);
self.step = TransactionStep::TransferCompletion;
return true;
}
}
false
pub fn cancel_request(&mut self, transaction_id: &TransactionId) {
// TODO: Implement.
}
/// Returns [None] if the state machine is IDLE, and the transmission mode of the current
@@ -572,16 +549,11 @@ impl<
} else {
// This is an EOF (Cancel), perform Cancel Response Procedures according to chapter
// 4.6.6 of the standard.
self.trigger_notice_of_completion_cancelled(
eof_pdu.condition_code(),
EntityIdTlv::new(self.tparams.remote_cfg.unwrap().entity_id),
);
self.trigger_notice_of_completion_cancelled(eof_pdu.condition_code());
self.tparams.tstate.progress = eof_pdu.file_size();
if eof_pdu.file_size() > 0 {
self.tparams.tstate.delivery_code = DeliveryCode::Incomplete;
} else {
self.tparams.tstate.delivery_code = DeliveryCode::Complete;
}
self.tparams.tstate.delivery_code = DeliveryCode::Incomplete;
self.tparams.tstate.fault_location_finished =
Some(EntityIdTlv::new(self.tparams.remote_cfg.unwrap().entity_id));
// TODO: The cancel EOF also supplies a checksum and a progress number. We could cross
// check that checksum, but how would we deal with a checksum failure? The standard
// does not specify anything for this case.. It could be part of the status report
@@ -594,19 +566,9 @@ impl<
Ok(())
}
fn trigger_notice_of_completion_cancelled(
&mut self,
cond_code: ConditionCode,
fault_location: EntityIdTlv,
) {
fn trigger_notice_of_completion_cancelled(&mut self, cond_code: ConditionCode) {
self.tparams.tstate.completion_disposition = CompletionDisposition::Cancelled;
self.tparams.tstate.condition_code = cond_code;
self.tparams.tstate.fault_location_finished = Some(fault_location);
// For anything larger than 0, we'd have to do a checksum check to verify whether
// the delivery is really complete, and we need the EOF checksum for that..
if self.tparams.tstate.progress == 0 {
self.tparams.tstate.delivery_code = DeliveryCode::Complete;
}
}
/// Returns whether the transfer can be completed regularly.
@@ -857,8 +819,10 @@ impl<
|| self.tparams.metadata_params().closure_requested
{
sent_packets += self.send_finished_pdu()?;
self.step = TransactionStep::SendingFinishedPdu;
} else {
self.reset();
}
self.step = TransactionStep::SendingFinishedPdu;
Ok(sent_packets)
}
@@ -873,7 +837,6 @@ impl<
.disposition_on_cancellation
&& self.tstate().delivery_code == DeliveryCode::Incomplete
{
// Safety: We already verified that the path is valid during the transaction start.
let dest_path = unsafe {
from_utf8_unchecked(
&self.tparams.file_properties.dest_path_buf
@@ -883,6 +846,7 @@ impl<
if self.vfs.exists(dest_path)? && self.vfs.is_file(dest_path)? {
self.vfs.remove_file(dest_path)?;
}
// Safety: We already verified that the path is valid during the transaction start.
self.tstate_mut().file_status = FileStatus::DiscardDeliberately;
}
let tstate = self.tstate();
@@ -1006,14 +970,14 @@ mod tests {
pdu::{finished::FinishedPduReader, metadata::MetadataPduCreator, WritablePduPacket},
ChecksumType, TransmissionMode,
},
util::{UbfU16, UnsignedByteFieldU8},
util::{UbfU16, UnsignedByteFieldU16},
};
use crate::{
filestore::NativeFilestore,
tests::{
basic_remote_cfg_table, SentPdu, TestCfdpSender, TestCfdpUser, TestFaultHandler,
LOCAL_ID, REMOTE_ID,
LOCAL_ID,
},
CountdownProvider, FaultHandler, IndicationConfig, PduRawWithInfo,
StdRemoteEntityConfigProvider, TimerCreatorProvider, CRC_32,
@@ -1021,6 +985,8 @@ mod tests {
use super::*;
const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2);
#[derive(Debug)]
struct TestCheckTimer {
counter: Cell<u32>,
@@ -1088,24 +1054,23 @@ mod tests {
dest_path: PathBuf,
check_dest_file: bool,
check_handler_idle_at_drop: bool,
expected_file_size: u64,
closure_requested: bool,
pdu_header: PduHeader,
expected_full_data: Vec<u8>,
expected_file_size: u64,
buf: [u8; 512],
}
impl DestHandlerTestbench {
fn new_with_fixed_paths(fault_handler: TestFaultHandler, closure_requested: bool) -> Self {
fn new(fault_handler: TestFaultHandler, closure_requested: bool) -> Self {
let (src_path, dest_path) = init_full_filepaths_textfile();
assert!(!Path::exists(&dest_path));
Self::new(fault_handler, closure_requested, true, src_path, dest_path)
Self::new_with_custom_paths(fault_handler, closure_requested, src_path, dest_path)
}
fn new(
fn new_with_custom_paths(
fault_handler: TestFaultHandler,
closure_requested: bool,
check_dest_file: bool,
src_path: PathBuf,
dest_path: PathBuf,
) -> Self {
@@ -1119,7 +1084,7 @@ mod tests {
src_path,
closure_requested,
dest_path,
check_dest_file,
check_dest_file: false,
check_handler_idle_at_drop: true,
expected_file_size: 0,
pdu_header: create_pdu_header(UbfU16::new(0)),
@@ -1147,10 +1112,6 @@ mod tests {
&mut self.handler.local_cfg.indication_cfg
}
fn get_next_pdu(&mut self) -> Option<SentPdu> {
self.handler.pdu_sender.retrieve_next_pdu()
}
fn indication_cfg(&mut self) -> &IndicationConfig {
&self.handler.local_cfg.indication_cfg
}
@@ -1384,21 +1345,10 @@ mod tests {
assert_eq!(dest_handler.step(), TransactionStep::Idle);
}
#[test]
fn test_cancelling_idle_fsm() {
let fault_handler = TestFaultHandler::default();
let test_sender = TestCfdpSender::default();
let mut dest_handler = default_dest_handler(fault_handler, test_sender, Arc::default());
assert!(!dest_handler.cancel_request(&TransactionId::new(
UnsignedByteFieldU8::new(0).into(),
UnsignedByteFieldU8::new(0).into()
)));
}
#[test]
fn test_empty_file_transfer_not_acked_no_closure() {
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
let mut tb = DestHandlerTestbench::new(fault_handler, false);
let mut test_user = tb.test_user_from_cached_paths(0);
tb.generic_transfer_init(&mut test_user, 0)
.expect("transfer init failed");
@@ -1415,7 +1365,7 @@ mod tests {
let file_size = file_data.len() as u64;
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
let mut tb = DestHandlerTestbench::new(fault_handler, false);
let mut test_user = tb.test_user_from_cached_paths(file_size);
tb.generic_transfer_init(&mut test_user, file_size)
.expect("transfer init failed");
@@ -1436,7 +1386,7 @@ mod tests {
let segment_len = 256;
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
let mut tb = DestHandlerTestbench::new(fault_handler, false);
let mut test_user = tb.test_user_from_cached_paths(file_size);
tb.generic_transfer_init(&mut test_user, file_size)
.expect("transfer init failed");
@@ -1463,7 +1413,7 @@ mod tests {
let segment_len = 256;
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
let mut tb = DestHandlerTestbench::new(fault_handler, false);
let mut test_user = tb.test_user_from_cached_paths(file_size);
let transaction_id = tb
.generic_transfer_init(&mut test_user, file_size)
@@ -1509,7 +1459,7 @@ mod tests {
let segment_len = 256;
let fault_handler = TestFaultHandler::default();
let mut testbench = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
let mut testbench = DestHandlerTestbench::new(fault_handler, false);
let mut test_user = testbench.test_user_from_cached_paths(file_size);
let transaction_id = testbench
.generic_transfer_init(&mut test_user, file_size)
@@ -1588,7 +1538,7 @@ mod tests {
#[test]
fn test_file_transfer_with_closure() {
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, true);
let mut tb = DestHandlerTestbench::new(fault_handler, true);
let mut test_user = tb.test_user_from_cached_paths(0);
tb.generic_transfer_init(&mut test_user, 0)
.expect("transfer init failed");
@@ -1606,11 +1556,15 @@ mod tests {
tb.check_completion_indication_success(&mut test_user);
}
#[test]
fn test_file_transfer_with_closure_check_limit_reached() {
// TODO: Implement test.
}
#[test]
fn test_finished_pdu_insertion_rejected() {
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
tb.check_dest_file = false;
let mut tb = DestHandlerTestbench::new(fault_handler, false);
let mut user = tb.test_user_from_cached_paths(0);
let finished_pdu = FinishedPduCreator::new_default(
PduHeader::new_no_file_data(CommonPduConfig::default(), 0),
@@ -1635,7 +1589,7 @@ mod tests {
#[test]
fn test_metadata_insertion_twice_fails() {
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, true);
let mut tb = DestHandlerTestbench::new(fault_handler, true);
let mut user = tb.test_user_from_cached_paths(0);
tb.generic_transfer_init(&mut user, 0)
.expect("transfer init failed");
@@ -1664,7 +1618,7 @@ mod tests {
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, true);
let mut tb = DestHandlerTestbench::new(fault_handler, true);
let mut user = tb.test_user_from_cached_paths(file_size);
tb.generic_transfer_init(&mut user, file_size)
.expect("transfer init failed");
@@ -1753,10 +1707,9 @@ mod tests {
let src_path = tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf();
let dest_path = tempfile::TempDir::new().unwrap();
let mut dest_path_buf = dest_path.into_path();
let mut tb = DestHandlerTestbench::new(
let mut tb = DestHandlerTestbench::new_with_custom_paths(
fault_handler,
false,
false,
src_path.clone(),
dest_path_buf.clone(),
);
@@ -1774,7 +1727,7 @@ mod tests {
#[test]
fn test_tranfer_cancellation_empty_file_with_eof_pdu() {
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
let mut tb = DestHandlerTestbench::new(fault_handler, false);
let mut test_user = tb.test_user_from_cached_paths(0);
tb.generic_transfer_init(&mut test_user, 0)
.expect("transfer init failed");
@@ -1796,13 +1749,13 @@ mod tests {
assert_eq!(packets, 0);
}
fn generic_tranfer_cancellation_partial_file_with_eof_pdu(with_closure: bool) {
fn generic_tranfer_cancellation_partial_file_with_eof_pdu_no_closure(with_closure: bool) {
let file_data_str = "Hello World!";
let file_data = file_data_str.as_bytes();
let file_size = 5;
let file_size = file_data.len() as u64;
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, with_closure);
let mut tb = DestHandlerTestbench::new(fault_handler, with_closure);
let mut test_user = tb.test_user_from_cached_paths(file_size);
tb.generic_transfer_init(&mut test_user, file_size)
.expect("transfer init failed");
@@ -1852,171 +1805,14 @@ mod tests {
} else {
assert_eq!(packets, 0);
}
tb.expected_file_size = file_size;
tb.expected_full_data = file_data[0..file_size as usize].to_vec();
}
#[test]
fn test_tranfer_cancellation_partial_file_with_eof_pdu_no_closure() {
generic_tranfer_cancellation_partial_file_with_eof_pdu(false);
generic_tranfer_cancellation_partial_file_with_eof_pdu_no_closure(false);
}
#[test]
fn test_tranfer_cancellation_partial_file_with_eof_pdu_with_closure() {
generic_tranfer_cancellation_partial_file_with_eof_pdu(true);
}
#[test]
fn test_tranfer_cancellation_empty_file_with_cancel_api() {
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
let mut test_user = tb.test_user_from_cached_paths(0);
let transaction_id = tb
.generic_transfer_init(&mut test_user, 0)
.expect("transfer init failed");
tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
tb.handler.cancel_request(&transaction_id);
let packets = tb
.handler
.state_machine_no_packet(&mut test_user)
.expect("state machine call with EOF insertion failed");
assert_eq!(packets, 0);
}
#[test]
fn test_tranfer_cancellation_empty_file_with_cancel_api_and_closure() {
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, true);
let mut test_user = tb.test_user_from_cached_paths(0);
let transaction_id = tb
.generic_transfer_init(&mut test_user, 0)
.expect("transfer init failed");
tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
tb.handler.cancel_request(&transaction_id);
let packets = tb
.handler
.state_machine_no_packet(&mut test_user)
.expect("state machine call with EOF insertion failed");
assert_eq!(packets, 1);
let next_pdu = tb.get_next_pdu().unwrap();
assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
next_pdu.file_directive_type.unwrap(),
FileDirectiveType::FinishedPdu
);
let finished_pdu =
FinishedPduReader::new(&next_pdu.raw_pdu).expect("finished pdu read failed");
assert_eq!(
finished_pdu.condition_code(),
ConditionCode::CancelRequestReceived
);
assert_eq!(finished_pdu.file_status(), FileStatus::Retained);
// Empty file, so still complete.
assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Complete);
assert_eq!(
finished_pdu.fault_location(),
Some(EntityIdTlv::new(REMOTE_ID.into()))
);
}
#[test]
fn test_tranfer_cancellation_partial_file_with_cancel_api_and_closure() {
let file_data_str = "Hello World!";
let file_data = file_data_str.as_bytes();
let file_size = 5;
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, true);
let mut test_user = tb.test_user_from_cached_paths(file_size);
let transaction_id = tb
.generic_transfer_init(&mut test_user, file_size)
.expect("transfer init failed");
tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
tb.generic_file_data_insert(&mut test_user, 0, &file_data[0..5])
.expect("file data insertion failed");
tb.handler.cancel_request(&transaction_id);
let packets = tb
.handler
.state_machine_no_packet(&mut test_user)
.expect("state machine call with EOF insertion failed");
assert_eq!(packets, 1);
let next_pdu = tb.get_next_pdu().unwrap();
assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
next_pdu.file_directive_type.unwrap(),
FileDirectiveType::FinishedPdu
);
let finished_pdu =
FinishedPduReader::new(&next_pdu.raw_pdu).expect("finished pdu read failed");
assert_eq!(
finished_pdu.condition_code(),
ConditionCode::CancelRequestReceived
);
assert_eq!(finished_pdu.file_status(), FileStatus::Retained);
assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Incomplete);
assert_eq!(
finished_pdu.fault_location(),
Some(EntityIdTlv::new(REMOTE_ID.into()))
);
tb.expected_file_size = file_size;
tb.expected_full_data = file_data[0..file_size as usize].to_vec();
}
// Only incomplete received files will be removed.
#[test]
fn test_tranfer_cancellation_file_disposition_not_done_for_empty_file() {
let fault_handler = TestFaultHandler::default();
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
let remote_cfg_mut = tb
.handler
.remote_cfg_table
.get_mut(LOCAL_ID.value())
.unwrap();
remote_cfg_mut.disposition_on_cancellation = true;
let mut test_user = tb.test_user_from_cached_paths(0);
let transaction_id = tb
.generic_transfer_init(&mut test_user, 0)
.expect("transfer init failed");
tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
tb.handler.cancel_request(&transaction_id);
let packets = tb
.handler
.state_machine_no_packet(&mut test_user)
.expect("state machine call with EOF insertion failed");
assert_eq!(packets, 0);
}
#[test]
fn test_tranfer_cancellation_file_disposition_not_done_for_incomplete_file() {
let file_data_str = "Hello World!";
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, false);
tb.check_dest_file = false;
let remote_cfg_mut = tb
.handler
.remote_cfg_table
.get_mut(LOCAL_ID.value())
.unwrap();
remote_cfg_mut.disposition_on_cancellation = true;
let mut test_user = tb.test_user_from_cached_paths(file_size);
let transaction_id = tb
.generic_transfer_init(&mut test_user, file_size)
.expect("transfer init failed");
tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
tb.generic_file_data_insert(&mut test_user, 0, &file_data[0..5])
.expect("file data insertion failed");
tb.handler.cancel_request(&transaction_id);
let packets = tb
.handler
.state_machine_no_packet(&mut test_user)
.expect("state machine call with EOF insertion failed");
assert_eq!(packets, 0);
// File was disposed.
assert!(!Path::exists(tb.dest_path()));
generic_tranfer_cancellation_partial_file_with_eof_pdu_no_closure(true);
}
}
-2
View File
@@ -9,8 +9,6 @@ use std::path::Path;
pub use std_mod::*;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum FilestoreError {
FileDoesNotExist,
+5 -67
View File
@@ -6,63 +6,15 @@
//! a transaction closure for the unacknowledged mode. Using the unacknowledged mode with no
//! transaction closure is applicable for simplex communication paths, while the unacknowledged
//! mode with closure is the easiest way to get a confirmation of a successful file transfer,
//! including a CRC check on the remote side to verify file integrity. The acknowledged mode is
//! including a CRC check on the remote side to verify file int egrity. The acknowledged mode is
//! the most complex mode which includes multiple mechanism to ensure succesfull packet transaction
//! even for unreliable connections, including lost segment detection. As such, it can be compared
//! to a specialized TCP for file transfers with remote systems.
//!
//! The goal of this library is to be flexible enough to support the use-cases of both on-board
//! software and of ground software. It has support to make integration on [std] systems as simple
//! as possible, but also has sufficient abstraction to allow for integration on `no_std`
//! environments. Currently, the handlers still require the [std] feature until
//! [thiserror supports `error_in_core`](https://github.com/dtolnay/thiserror/pull/304).
//! It is recommended to activate the `alloc` feature at the very least to allow using the primary
//! components provided by this crate. These components will only allocate memory at initialization
//! time and thus are still viable for systems where run-time allocation is prohibited.
//!
//! The core of this library are the [crate::dest::DestinationHandler] and the
//! [crate::source::SourceHandler] components which model the CFDP destination and source entity
//! The core of these high-level components are the [crate::dest::DestinationHandler] and the
//! [crate::source::SourceHandler] component. These model the CFDP destination and source entity
//! respectively. You can find high-level and API documentation for both handlers in the respective
//! [crate::dest] and [crate::source] module.
//!
//! # Examples
//!
//! This library currently features two example application which showcase how the provided
//! components could be used to provide CFDP services.
//!
//! The [end-to-end test](https://egit.irs.uni-stuttgart.de/rust/cfdp/src/branch/main/tests/end-to-end.rs)
//! is an integration tests which spawns a CFDP source entity and a CFDP destination entity,
//! moves them to separate threads and then performs a small file copy operation.
//! You can run the integration test for a transfer with no closure and with printout to the
//! standard console by running:
//!
//! ```sh
//! cargo test end_to_end_test_no_closure -- --nocapture
//! ```
//!
//! or with closure:
//!
//! ```sh
//! cargo test end_to_end_test_with_closure -- --nocapture
//! ```
//!
//! The [Python Interoperability](https://egit.irs.uni-stuttgart.de/rust/cfdp/src/branch/main/examples/python-interop)
//! example showcases the interoperability of the CFDP handlers written in Rust with a Python
//! implementation. The dedicated example documentation shows how to run this example.
//!
//! # Notes on the user hooks and scheduling
//!
//! Both examples feature implementations of the [UserFaultHookProvider] and the [user::CfdpUser]
//! trait which simply print some information to the console to monitor the progress of a file
//! copy operation. These implementations could be adapted for other handler integrations. For
//! example, they could signal a GUI application to display some information for the user.
//!
//! Even though both examples move the newly spawned handlers to dedicated threads, this is not
//! the only way they could be scheduled. For example, to support an arbitrary (or bounded)
//! amount of file copy operations on either source or destination side, those handlers could be
//! moved into a [std::collections::HashMap] structure which is then scheduled inside a thread, or
//! you could schedule a fixed amount of handlers inside a
//! [threadpool](https://docs.rs/threadpool/latest/threadpool/).
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
@@ -102,16 +54,11 @@ use spacepackets::{
pub use std_mod::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum EntityType {
Sending,
Receiving,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TimerContext {
CheckLimit {
local_id: UnsignedByteField,
@@ -229,8 +176,6 @@ pub trait TimerCreatorProvider {
/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside
/// the class documentation. Defaults to 2, so the timer may expire two times.
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RemoteEntityConfig {
pub entity_id: UnsignedByteField,
pub max_packet_len: usize,
@@ -291,8 +236,7 @@ pub trait RemoteEntityConfigProvider {
/// This is a thin wrapper around a [HashMap] to store remote entity configurations.
/// It implements the full [RemoteEntityConfigProvider] trait.
#[cfg(feature = "std")]
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Default)]
pub struct StdRemoteEntityConfigProvider(pub HashMap<u64, RemoteEntityConfig>);
#[cfg(feature = "std")]
@@ -314,9 +258,7 @@ impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
/// This is a thin wrapper around a [alloc::vec::Vec] to store remote entity configurations.
/// It implements the full [RemoteEntityConfigProvider] trait.
#[cfg(feature = "alloc")]
#[derive(Default, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Default)]
pub struct VecRemoteEntityConfigProvider(pub alloc::vec::Vec<RemoteEntityConfig>);
#[cfg(feature = "alloc")]
@@ -546,9 +488,6 @@ impl<UserHandler: UserFaultHookProvider> FaultHandler<UserHandler> {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct IndicationConfig {
pub eof_sent: bool,
pub eof_recv: bool,
@@ -720,7 +659,6 @@ pub mod std_mod {
/// number of that transfer which is also determined by the CFDP source entity.
#[derive(Debug, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TransactionId {
source_id: UnsignedByteField,
seq_num: UnsignedByteField,
-5
View File
@@ -10,8 +10,6 @@ use spacepackets::{
pub use alloc_mod::*;
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FilePathTooLarge(pub usize);
/// This trait is an abstraction for different Put Request structures which can be used
@@ -169,8 +167,6 @@ impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TlvWithInvalidType(pub(crate) ());
impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static, 'static> {
@@ -240,7 +236,6 @@ pub mod alloc_mod {
/// Owned variant of [PutRequest] with no lifetimes which is also [Clone]able.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PutRequestOwned {
pub destination_id: UnsignedByteField,
source_file: Option<alloc::string::String>,
-1
View File
@@ -76,7 +76,6 @@ use super::{
/// This enumeration models the different transaction steps of the source entity handler.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TransactionStep {
Idle = 0,
TransactionStart = 1,
-2
View File
@@ -15,8 +15,6 @@ use spacepackets::{
use super::TransactionId;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TransactionFinishedParams {
pub id: TransactionId,
pub condition_code: ConditionCode,