Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70d8915c3f | |||
|
4f391d1139
|
|||
|
82f8e22bd9
|
|||
|
cd03e5d18a
|
|||
|
df97b38cc5
|
|||
|
a0b863f7d6
|
|||
|
467037c126
|
|||
|
3575f331d4
|
|||
|
8a331c2971
|
|||
|
a51ab5e878
|
|||
|
80c91b59d3
|
|||
|
0766a0e6c9
|
|||
|
69eed4a46d
|
|||
|
45163ff8aa
|
|||
|
512abdd98a
|
|||
|
08b9bf99a5
|
|||
|
5a4bd9710a
|
|||
| befe3d66e0 |
@@ -29,7 +29,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@1.81.0
|
- uses: dtolnay/rust-toolchain@1.75.0
|
||||||
- run: cargo check --release
|
- run: cargo check --release
|
||||||
|
|
||||||
cross-check:
|
cross-check:
|
||||||
|
|||||||
@@ -7,14 +7,3 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
# [unreleased]
|
# [unreleased]
|
||||||
|
|
||||||
# [v0.2.0] 2024-11-26
|
|
||||||
|
|
||||||
- Bumped `thiserror` to v2
|
|
||||||
- Bumped `spacepackets` to v0.13
|
|
||||||
- The source and destination handlers can now be used without the `std` feature and only require
|
|
||||||
the `alloc` feature.
|
|
||||||
|
|
||||||
# [v0.1.0] 2024-09-11
|
|
||||||
|
|
||||||
Initial release
|
|
||||||
|
|||||||
+14
-17
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cfdp-rs"
|
name = "cfdp-rs"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.81.0"
|
rust-version = "1.75.0"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
description = "High level CCSDS File Delivery Protocol components"
|
description = "High level CCSDS File Delivery Protocol components"
|
||||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/cfdp"
|
homepage = "https://egit.irs.uni-stuttgart.de/rust/cfdp"
|
||||||
@@ -18,47 +18,44 @@ name = "cfdp"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
crc = "3"
|
crc = "3"
|
||||||
smallvec = "1"
|
smallvec = "1"
|
||||||
derive-new = ">=0.6, <=0.7"
|
derive-new = "0.6"
|
||||||
|
|
||||||
[dependencies.spacepackets]
|
|
||||||
version = "0.13"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.thiserror]
|
[dependencies.thiserror]
|
||||||
version = "2"
|
version = "1"
|
||||||
default-features = false
|
optional = true
|
||||||
|
|
||||||
[dependencies.hashbrown]
|
[dependencies.hashbrown]
|
||||||
version = ">=0.14, <=0.15"
|
version = "0.14"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1"
|
version = "1"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.defmt]
|
[dependencies.spacepackets]
|
||||||
version = "0.3"
|
version = "0.12"
|
||||||
optional = true
|
default-features = false
|
||||||
|
git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
|
||||||
|
branch = "main"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
"alloc",
|
"alloc",
|
||||||
"thiserror/std",
|
"thiserror",
|
||||||
"spacepackets/std"
|
"spacepackets/std"
|
||||||
]
|
]
|
||||||
alloc = [
|
alloc = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"spacepackets/alloc"
|
"spacepackets/alloc"
|
||||||
]
|
]
|
||||||
serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde"]
|
serde = ["dep:serde", "spacepackets/serde"]
|
||||||
defmt = ["dep:defmt", "spacepackets/defmt"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
fern = "0.7"
|
fern = "0.6"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|
||||||
|
|||||||
@@ -13,22 +13,32 @@ The underlying base packet library used to generate the packets to be sent is th
|
|||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
The goal of this library is to be flexible enough to support the use-cases of both on-board
|
`cfdp-rs` currently supports following features:
|
||||||
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
|
|
||||||
and can be used on these systems as well as long as the `alloc` feature is activated.
|
|
||||||
|
|
||||||
Please note even though the `alloc` feature is required for the core handlers, these components
|
- Unacknowledged (class 1) file transfers for both source and destination side.
|
||||||
will only allocate memory at initialization time and thus are still viable for systems where
|
|
||||||
run-time allocation is prohibited.
|
|
||||||
|
|
||||||
## Default features
|
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
|
||||||
|
|
||||||
- [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
|
- [`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.
|
- [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which require allocation support.
|
||||||
Enabled by the `std` feature.
|
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
|
- [`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
|
- [`defmt`](https://defmt.ferrous-systems.com/): Add support for the `defmt` by adding the
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ def generate_cov_report(open_report: bool, format: str):
|
|||||||
out_path = "./target/debug/lcov.info"
|
out_path = "./target/debug/lcov.info"
|
||||||
os.system(
|
os.system(
|
||||||
f"grcov . -s . --binary-path ./target/debug/ -t {format} --branch --ignore-not-existing "
|
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":
|
if format == "lcov":
|
||||||
os.system(
|
os.system(
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
|
||||||
cargo +nightly doc --all-features --open
|
|
||||||
@@ -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.
|
|
||||||
+45
-249
@@ -55,6 +55,7 @@ use spacepackets::{
|
|||||||
},
|
},
|
||||||
util::{UnsignedByteField, UnsignedEnum},
|
util::{UnsignedByteField, UnsignedEnum},
|
||||||
};
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct FileProperties {
|
struct FileProperties {
|
||||||
@@ -75,7 +76,6 @@ enum CompletionDisposition {
|
|||||||
/// This enumeration models the different transaction steps of the destination entity handler.
|
/// This enumeration models the different transaction steps of the destination entity handler.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum TransactionStep {
|
pub enum TransactionStep {
|
||||||
Idle = 0,
|
Idle = 0,
|
||||||
TransactionStart = 1,
|
TransactionStart = 1,
|
||||||
@@ -92,7 +92,7 @@ struct TransferState<Countdown: CountdownProvider> {
|
|||||||
transaction_id: Option<TransactionId>,
|
transaction_id: Option<TransactionId>,
|
||||||
metadata_params: MetadataGenericParams,
|
metadata_params: MetadataGenericParams,
|
||||||
progress: u64,
|
progress: u64,
|
||||||
// file_size_eof: u64,
|
file_size_eof: u64,
|
||||||
metadata_only: bool,
|
metadata_only: bool,
|
||||||
condition_code: ConditionCode,
|
condition_code: ConditionCode,
|
||||||
delivery_code: DeliveryCode,
|
delivery_code: DeliveryCode,
|
||||||
@@ -110,7 +110,7 @@ impl<CheckTimer: CountdownProvider> Default for TransferState<CheckTimer> {
|
|||||||
transaction_id: None,
|
transaction_id: None,
|
||||||
metadata_params: Default::default(),
|
metadata_params: Default::default(),
|
||||||
progress: Default::default(),
|
progress: Default::default(),
|
||||||
// file_size_eof: Default::default(),
|
file_size_eof: Default::default(),
|
||||||
metadata_only: false,
|
metadata_only: false,
|
||||||
condition_code: ConditionCode::NoError,
|
condition_code: ConditionCode::NoError,
|
||||||
delivery_code: DeliveryCode::Incomplete,
|
delivery_code: DeliveryCode::Incomplete,
|
||||||
@@ -188,7 +188,7 @@ impl<CheckTimer: CountdownProvider> TransactionParams<CheckTimer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum DestError {
|
pub enum DestError {
|
||||||
/// File directive expected, but none specified
|
/// File directive expected, but none specified
|
||||||
#[error("expected file directive")]
|
#[error("expected file directive")]
|
||||||
@@ -214,7 +214,6 @@ pub enum DestError {
|
|||||||
#[error("pdu error {0}")]
|
#[error("pdu error {0}")]
|
||||||
Pdu(#[from] PduError),
|
Pdu(#[from] PduError),
|
||||||
#[error("io error {0}")]
|
#[error("io error {0}")]
|
||||||
#[cfg(feature = "std")]
|
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("file store error {0}")]
|
#[error("file store error {0}")]
|
||||||
Filestore(#[from] FilestoreError),
|
Filestore(#[from] FilestoreError),
|
||||||
@@ -368,30 +367,8 @@ impl<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function models the Cancel.request CFDP primitive and is the recommended way
|
pub fn cancel_request(&mut self, transaction_id: &TransactionId) {
|
||||||
/// to cancel a transaction. It will cause a Notice Of Cancellation at this entity.
|
// TODO: Implement.
|
||||||
/// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns [None] if the state machine is IDLE, and the transmission mode of the current
|
/// Returns [None] if the state machine is IDLE, and the transmission mode of the current
|
||||||
@@ -572,16 +549,11 @@ impl<
|
|||||||
} else {
|
} else {
|
||||||
// This is an EOF (Cancel), perform Cancel Response Procedures according to chapter
|
// This is an EOF (Cancel), perform Cancel Response Procedures according to chapter
|
||||||
// 4.6.6 of the standard.
|
// 4.6.6 of the standard.
|
||||||
self.trigger_notice_of_completion_cancelled(
|
self.trigger_notice_of_completion_cancelled(eof_pdu.condition_code());
|
||||||
eof_pdu.condition_code(),
|
|
||||||
EntityIdTlv::new(self.tparams.remote_cfg.unwrap().entity_id),
|
|
||||||
);
|
|
||||||
self.tparams.tstate.progress = eof_pdu.file_size();
|
self.tparams.tstate.progress = eof_pdu.file_size();
|
||||||
if eof_pdu.file_size() > 0 {
|
self.tparams.tstate.delivery_code = DeliveryCode::Incomplete;
|
||||||
self.tparams.tstate.delivery_code = DeliveryCode::Incomplete;
|
self.tparams.tstate.fault_location_finished =
|
||||||
} else {
|
Some(EntityIdTlv::new(self.tparams.remote_cfg.unwrap().entity_id));
|
||||||
self.tparams.tstate.delivery_code = DeliveryCode::Complete;
|
|
||||||
}
|
|
||||||
// TODO: The cancel EOF also supplies a checksum and a progress number. We could cross
|
// 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
|
// 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
|
// does not specify anything for this case.. It could be part of the status report
|
||||||
@@ -594,19 +566,9 @@ impl<
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_notice_of_completion_cancelled(
|
fn trigger_notice_of_completion_cancelled(&mut self, cond_code: ConditionCode) {
|
||||||
&mut self,
|
|
||||||
cond_code: ConditionCode,
|
|
||||||
fault_location: EntityIdTlv,
|
|
||||||
) {
|
|
||||||
self.tparams.tstate.completion_disposition = CompletionDisposition::Cancelled;
|
self.tparams.tstate.completion_disposition = CompletionDisposition::Cancelled;
|
||||||
self.tparams.tstate.condition_code = cond_code;
|
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.
|
/// Returns whether the transfer can be completed regularly.
|
||||||
@@ -857,8 +819,10 @@ impl<
|
|||||||
|| self.tparams.metadata_params().closure_requested
|
|| self.tparams.metadata_params().closure_requested
|
||||||
{
|
{
|
||||||
sent_packets += self.send_finished_pdu()?;
|
sent_packets += self.send_finished_pdu()?;
|
||||||
|
self.step = TransactionStep::SendingFinishedPdu;
|
||||||
|
} else {
|
||||||
|
self.reset();
|
||||||
}
|
}
|
||||||
self.step = TransactionStep::SendingFinishedPdu;
|
|
||||||
Ok(sent_packets)
|
Ok(sent_packets)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,7 +837,6 @@ impl<
|
|||||||
.disposition_on_cancellation
|
.disposition_on_cancellation
|
||||||
&& self.tstate().delivery_code == DeliveryCode::Incomplete
|
&& self.tstate().delivery_code == DeliveryCode::Incomplete
|
||||||
{
|
{
|
||||||
// Safety: We already verified that the path is valid during the transaction start.
|
|
||||||
let dest_path = unsafe {
|
let dest_path = unsafe {
|
||||||
from_utf8_unchecked(
|
from_utf8_unchecked(
|
||||||
&self.tparams.file_properties.dest_path_buf
|
&self.tparams.file_properties.dest_path_buf
|
||||||
@@ -883,6 +846,7 @@ impl<
|
|||||||
if self.vfs.exists(dest_path)? && self.vfs.is_file(dest_path)? {
|
if self.vfs.exists(dest_path)? && self.vfs.is_file(dest_path)? {
|
||||||
self.vfs.remove_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;
|
self.tstate_mut().file_status = FileStatus::DiscardDeliberately;
|
||||||
}
|
}
|
||||||
let tstate = self.tstate();
|
let tstate = self.tstate();
|
||||||
@@ -1006,14 +970,14 @@ mod tests {
|
|||||||
pdu::{finished::FinishedPduReader, metadata::MetadataPduCreator, WritablePduPacket},
|
pdu::{finished::FinishedPduReader, metadata::MetadataPduCreator, WritablePduPacket},
|
||||||
ChecksumType, TransmissionMode,
|
ChecksumType, TransmissionMode,
|
||||||
},
|
},
|
||||||
util::{UbfU16, UnsignedByteFieldU8},
|
util::{UbfU16, UnsignedByteFieldU16},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
filestore::NativeFilestore,
|
filestore::NativeFilestore,
|
||||||
tests::{
|
tests::{
|
||||||
basic_remote_cfg_table, SentPdu, TestCfdpSender, TestCfdpUser, TestFaultHandler,
|
basic_remote_cfg_table, SentPdu, TestCfdpSender, TestCfdpUser, TestFaultHandler,
|
||||||
LOCAL_ID, REMOTE_ID,
|
LOCAL_ID,
|
||||||
},
|
},
|
||||||
CountdownProvider, FaultHandler, IndicationConfig, PduRawWithInfo,
|
CountdownProvider, FaultHandler, IndicationConfig, PduRawWithInfo,
|
||||||
StdRemoteEntityConfigProvider, TimerCreatorProvider, CRC_32,
|
StdRemoteEntityConfigProvider, TimerCreatorProvider, CRC_32,
|
||||||
@@ -1021,6 +985,8 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct TestCheckTimer {
|
struct TestCheckTimer {
|
||||||
counter: Cell<u32>,
|
counter: Cell<u32>,
|
||||||
@@ -1088,24 +1054,23 @@ mod tests {
|
|||||||
dest_path: PathBuf,
|
dest_path: PathBuf,
|
||||||
check_dest_file: bool,
|
check_dest_file: bool,
|
||||||
check_handler_idle_at_drop: bool,
|
check_handler_idle_at_drop: bool,
|
||||||
|
expected_file_size: u64,
|
||||||
closure_requested: bool,
|
closure_requested: bool,
|
||||||
pdu_header: PduHeader,
|
pdu_header: PduHeader,
|
||||||
expected_full_data: Vec<u8>,
|
expected_full_data: Vec<u8>,
|
||||||
expected_file_size: u64,
|
|
||||||
buf: [u8; 512],
|
buf: [u8; 512],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DestHandlerTestbench {
|
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();
|
let (src_path, dest_path) = init_full_filepaths_textfile();
|
||||||
assert!(!Path::exists(&dest_path));
|
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,
|
fault_handler: TestFaultHandler,
|
||||||
closure_requested: bool,
|
closure_requested: bool,
|
||||||
check_dest_file: bool,
|
|
||||||
src_path: PathBuf,
|
src_path: PathBuf,
|
||||||
dest_path: PathBuf,
|
dest_path: PathBuf,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -1119,7 +1084,7 @@ mod tests {
|
|||||||
src_path,
|
src_path,
|
||||||
closure_requested,
|
closure_requested,
|
||||||
dest_path,
|
dest_path,
|
||||||
check_dest_file,
|
check_dest_file: false,
|
||||||
check_handler_idle_at_drop: true,
|
check_handler_idle_at_drop: true,
|
||||||
expected_file_size: 0,
|
expected_file_size: 0,
|
||||||
pdu_header: create_pdu_header(UbfU16::new(0)),
|
pdu_header: create_pdu_header(UbfU16::new(0)),
|
||||||
@@ -1147,10 +1112,6 @@ mod tests {
|
|||||||
&mut self.handler.local_cfg.indication_cfg
|
&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 {
|
fn indication_cfg(&mut self) -> &IndicationConfig {
|
||||||
&self.handler.local_cfg.indication_cfg
|
&self.handler.local_cfg.indication_cfg
|
||||||
}
|
}
|
||||||
@@ -1384,21 +1345,10 @@ mod tests {
|
|||||||
assert_eq!(dest_handler.step(), TransactionStep::Idle);
|
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]
|
#[test]
|
||||||
fn test_empty_file_transfer_not_acked_no_closure() {
|
fn test_empty_file_transfer_not_acked_no_closure() {
|
||||||
let fault_handler = TestFaultHandler::default();
|
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);
|
let mut test_user = tb.test_user_from_cached_paths(0);
|
||||||
tb.generic_transfer_init(&mut test_user, 0)
|
tb.generic_transfer_init(&mut test_user, 0)
|
||||||
.expect("transfer init failed");
|
.expect("transfer init failed");
|
||||||
@@ -1415,7 +1365,7 @@ mod tests {
|
|||||||
let file_size = file_data.len() as u64;
|
let file_size = file_data.len() as u64;
|
||||||
let fault_handler = TestFaultHandler::default();
|
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 mut test_user = tb.test_user_from_cached_paths(file_size);
|
||||||
tb.generic_transfer_init(&mut test_user, file_size)
|
tb.generic_transfer_init(&mut test_user, file_size)
|
||||||
.expect("transfer init failed");
|
.expect("transfer init failed");
|
||||||
@@ -1436,7 +1386,7 @@ mod tests {
|
|||||||
let segment_len = 256;
|
let segment_len = 256;
|
||||||
let fault_handler = TestFaultHandler::default();
|
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 mut test_user = tb.test_user_from_cached_paths(file_size);
|
||||||
tb.generic_transfer_init(&mut test_user, file_size)
|
tb.generic_transfer_init(&mut test_user, file_size)
|
||||||
.expect("transfer init failed");
|
.expect("transfer init failed");
|
||||||
@@ -1463,7 +1413,7 @@ mod tests {
|
|||||||
let segment_len = 256;
|
let segment_len = 256;
|
||||||
let fault_handler = TestFaultHandler::default();
|
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 mut test_user = tb.test_user_from_cached_paths(file_size);
|
||||||
let transaction_id = tb
|
let transaction_id = tb
|
||||||
.generic_transfer_init(&mut test_user, file_size)
|
.generic_transfer_init(&mut test_user, file_size)
|
||||||
@@ -1509,7 +1459,7 @@ mod tests {
|
|||||||
let segment_len = 256;
|
let segment_len = 256;
|
||||||
|
|
||||||
let fault_handler = TestFaultHandler::default();
|
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 mut test_user = testbench.test_user_from_cached_paths(file_size);
|
||||||
let transaction_id = testbench
|
let transaction_id = testbench
|
||||||
.generic_transfer_init(&mut test_user, file_size)
|
.generic_transfer_init(&mut test_user, file_size)
|
||||||
@@ -1588,7 +1538,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_file_transfer_with_closure() {
|
fn test_file_transfer_with_closure() {
|
||||||
let fault_handler = TestFaultHandler::default();
|
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);
|
let mut test_user = tb.test_user_from_cached_paths(0);
|
||||||
tb.generic_transfer_init(&mut test_user, 0)
|
tb.generic_transfer_init(&mut test_user, 0)
|
||||||
.expect("transfer init failed");
|
.expect("transfer init failed");
|
||||||
@@ -1606,11 +1556,15 @@ mod tests {
|
|||||||
tb.check_completion_indication_success(&mut test_user);
|
tb.check_completion_indication_success(&mut test_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_file_transfer_with_closure_check_limit_reached() {
|
||||||
|
// TODO: Implement test.
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_finished_pdu_insertion_rejected() {
|
fn test_finished_pdu_insertion_rejected() {
|
||||||
let fault_handler = TestFaultHandler::default();
|
let fault_handler = TestFaultHandler::default();
|
||||||
let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
|
let mut tb = DestHandlerTestbench::new(fault_handler, false);
|
||||||
tb.check_dest_file = false;
|
|
||||||
let mut user = tb.test_user_from_cached_paths(0);
|
let mut user = tb.test_user_from_cached_paths(0);
|
||||||
let finished_pdu = FinishedPduCreator::new_default(
|
let finished_pdu = FinishedPduCreator::new_default(
|
||||||
PduHeader::new_no_file_data(CommonPduConfig::default(), 0),
|
PduHeader::new_no_file_data(CommonPduConfig::default(), 0),
|
||||||
@@ -1635,7 +1589,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_metadata_insertion_twice_fails() {
|
fn test_metadata_insertion_twice_fails() {
|
||||||
let fault_handler = TestFaultHandler::default();
|
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);
|
let mut user = tb.test_user_from_cached_paths(0);
|
||||||
tb.generic_transfer_init(&mut user, 0)
|
tb.generic_transfer_init(&mut user, 0)
|
||||||
.expect("transfer init failed");
|
.expect("transfer init failed");
|
||||||
@@ -1664,7 +1618,7 @@ mod tests {
|
|||||||
let file_data = file_data_str.as_bytes();
|
let file_data = file_data_str.as_bytes();
|
||||||
let file_size = file_data.len() as u64;
|
let file_size = file_data.len() as u64;
|
||||||
let fault_handler = TestFaultHandler::default();
|
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);
|
let mut user = tb.test_user_from_cached_paths(file_size);
|
||||||
tb.generic_transfer_init(&mut user, file_size)
|
tb.generic_transfer_init(&mut user, file_size)
|
||||||
.expect("transfer init failed");
|
.expect("transfer init failed");
|
||||||
@@ -1753,10 +1707,9 @@ mod tests {
|
|||||||
let src_path = tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf();
|
let src_path = tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf();
|
||||||
let dest_path = tempfile::TempDir::new().unwrap();
|
let dest_path = tempfile::TempDir::new().unwrap();
|
||||||
let mut dest_path_buf = dest_path.into_path();
|
let mut dest_path_buf = dest_path.into_path();
|
||||||
let mut tb = DestHandlerTestbench::new(
|
let mut tb = DestHandlerTestbench::new_with_custom_paths(
|
||||||
fault_handler,
|
fault_handler,
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
src_path.clone(),
|
src_path.clone(),
|
||||||
dest_path_buf.clone(),
|
dest_path_buf.clone(),
|
||||||
);
|
);
|
||||||
@@ -1774,7 +1727,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_tranfer_cancellation_empty_file_with_eof_pdu() {
|
fn test_tranfer_cancellation_empty_file_with_eof_pdu() {
|
||||||
let fault_handler = TestFaultHandler::default();
|
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);
|
let mut test_user = tb.test_user_from_cached_paths(0);
|
||||||
tb.generic_transfer_init(&mut test_user, 0)
|
tb.generic_transfer_init(&mut test_user, 0)
|
||||||
.expect("transfer init failed");
|
.expect("transfer init failed");
|
||||||
@@ -1796,13 +1749,13 @@ mod tests {
|
|||||||
assert_eq!(packets, 0);
|
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_str = "Hello World!";
|
||||||
let file_data = file_data_str.as_bytes();
|
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 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);
|
let mut test_user = tb.test_user_from_cached_paths(file_size);
|
||||||
tb.generic_transfer_init(&mut test_user, file_size)
|
tb.generic_transfer_init(&mut test_user, file_size)
|
||||||
.expect("transfer init failed");
|
.expect("transfer init failed");
|
||||||
@@ -1852,171 +1805,14 @@ mod tests {
|
|||||||
} else {
|
} else {
|
||||||
assert_eq!(packets, 0);
|
assert_eq!(packets, 0);
|
||||||
}
|
}
|
||||||
tb.expected_file_size = file_size;
|
|
||||||
tb.expected_full_data = file_data[0..file_size as usize].to_vec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tranfer_cancellation_partial_file_with_eof_pdu_no_closure() {
|
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]
|
#[test]
|
||||||
fn test_tranfer_cancellation_partial_file_with_eof_pdu_with_closure() {
|
fn test_tranfer_cancellation_partial_file_with_eof_pdu_with_closure() {
|
||||||
generic_tranfer_cancellation_partial_file_with_eof_pdu(true);
|
generic_tranfer_cancellation_partial_file_with_eof_pdu_no_closure(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()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+95
-33
@@ -1,37 +1,101 @@
|
|||||||
|
use alloc::string::{String, ToString};
|
||||||
|
use core::fmt::Display;
|
||||||
use spacepackets::cfdp::ChecksumType;
|
use spacepackets::cfdp::ChecksumType;
|
||||||
use spacepackets::ByteConversionError;
|
use spacepackets::ByteConversionError;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
|
use std::error::Error;
|
||||||
|
use std::path::Path;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
pub use std_mod::*;
|
pub use std_mod::*;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(all(feature = "defmt", not(feature = "std")), derive(defmt::Format))]
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum FilestoreError {
|
pub enum FilestoreError {
|
||||||
#[error("file does not exist")]
|
|
||||||
FileDoesNotExist,
|
FileDoesNotExist,
|
||||||
#[error("file already exists")]
|
|
||||||
FileAlreadyExists,
|
FileAlreadyExists,
|
||||||
#[error("directory does not exist")]
|
|
||||||
DirDoesNotExist,
|
DirDoesNotExist,
|
||||||
#[error("permission error")]
|
|
||||||
Permission,
|
Permission,
|
||||||
#[error("is not a file")]
|
|
||||||
IsNotFile,
|
IsNotFile,
|
||||||
#[error("is not a directory")]
|
|
||||||
IsNotDirectory,
|
IsNotDirectory,
|
||||||
#[error("byte conversion: {0}")]
|
ByteConversion(ByteConversionError),
|
||||||
ByteConversion(#[from] ByteConversionError),
|
Io {
|
||||||
#[error("IO error: {0})")]
|
raw_errno: Option<i32>,
|
||||||
#[cfg(feature = "std")]
|
string: String,
|
||||||
Io(#[from] std::io::Error),
|
},
|
||||||
#[error("checksum type not implemented: {0:?}")]
|
|
||||||
ChecksumTypeNotImplemented(ChecksumType),
|
ChecksumTypeNotImplemented(ChecksumType),
|
||||||
#[error("utf8 error")]
|
|
||||||
Utf8Error,
|
Utf8Error,
|
||||||
#[error("other error")]
|
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ByteConversionError> for FilestoreError {
|
||||||
|
fn from(value: ByteConversionError) -> Self {
|
||||||
|
Self::ByteConversion(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FilestoreError {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match self {
|
||||||
|
FilestoreError::FileDoesNotExist => {
|
||||||
|
write!(f, "file does not exist")
|
||||||
|
}
|
||||||
|
FilestoreError::FileAlreadyExists => {
|
||||||
|
write!(f, "file already exists")
|
||||||
|
}
|
||||||
|
FilestoreError::DirDoesNotExist => {
|
||||||
|
write!(f, "directory does not exist")
|
||||||
|
}
|
||||||
|
FilestoreError::Permission => {
|
||||||
|
write!(f, "permission error")
|
||||||
|
}
|
||||||
|
FilestoreError::IsNotFile => {
|
||||||
|
write!(f, "is not a file")
|
||||||
|
}
|
||||||
|
FilestoreError::IsNotDirectory => {
|
||||||
|
write!(f, "is not a directory")
|
||||||
|
}
|
||||||
|
FilestoreError::ByteConversion(e) => {
|
||||||
|
write!(f, "filestore error: {e}")
|
||||||
|
}
|
||||||
|
FilestoreError::Io { raw_errno, string } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"filestore generic IO error with raw errno {:?}: {}",
|
||||||
|
raw_errno, string
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FilestoreError::ChecksumTypeNotImplemented(checksum_type) => {
|
||||||
|
write!(f, "checksum {:?} not implemented", checksum_type)
|
||||||
|
}
|
||||||
|
FilestoreError::Utf8Error => {
|
||||||
|
write!(f, "utf8 error")
|
||||||
|
}
|
||||||
|
FilestoreError::Other => {
|
||||||
|
write!(f, "some filestore error occured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for FilestoreError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
FilestoreError::ByteConversion(e) => Some(e),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl From<std::io::Error> for FilestoreError {
|
||||||
|
fn from(value: std::io::Error) -> Self {
|
||||||
|
Self::Io {
|
||||||
|
raw_errno: value.raw_os_error(),
|
||||||
|
string: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait VirtualFilestore {
|
pub trait VirtualFilestore {
|
||||||
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>;
|
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>;
|
||||||
|
|
||||||
@@ -56,7 +120,14 @@ pub trait VirtualFilestore {
|
|||||||
|
|
||||||
fn filename_from_full_path(path: &str) -> Option<&str>
|
fn filename_from_full_path(path: &str) -> Option<&str>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized,
|
||||||
|
{
|
||||||
|
// Convert the path string to a Path
|
||||||
|
let path = Path::new(path);
|
||||||
|
|
||||||
|
// Extract the file name using the file_name() method
|
||||||
|
path.file_name().and_then(|name| name.to_str())
|
||||||
|
}
|
||||||
|
|
||||||
fn is_file(&self, path: &str) -> Result<bool, FilestoreError>;
|
fn is_file(&self, path: &str) -> Result<bool, FilestoreError>;
|
||||||
|
|
||||||
@@ -121,7 +192,6 @@ pub mod std_mod {
|
|||||||
use std::{
|
use std::{
|
||||||
fs::{self, File, OpenOptions},
|
fs::{self, File, OpenOptions},
|
||||||
io::{BufReader, Read, Seek, SeekFrom, Write},
|
io::{BufReader, Read, Seek, SeekFrom, Write},
|
||||||
path::Path,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -169,7 +239,10 @@ pub mod std_mod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> {
|
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> {
|
||||||
fs::create_dir(dir_path)?;
|
fs::create_dir(dir_path).map_err(|e| FilestoreError::Io {
|
||||||
|
raw_errno: e.raw_os_error(),
|
||||||
|
string: e.to_string(),
|
||||||
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,17 +356,6 @@ pub mod std_mod {
|
|||||||
_ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)),
|
_ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filename_from_full_path(path: &str) -> Option<&str>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
// Convert the path string to a Path
|
|
||||||
let path = Path::new(path);
|
|
||||||
|
|
||||||
// Extract the file name using the file_name() method
|
|
||||||
path.file_name().and_then(|name| name.to_str())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NativeFilestore {
|
impl NativeFilestore {
|
||||||
@@ -329,7 +391,7 @@ pub mod std_mod {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{fs, path::Path, println, string::ToString};
|
use std::{fs, path::Path, println};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
@@ -642,7 +704,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error.to_string(),
|
error.to_string(),
|
||||||
format!("byte conversion: {}", byte_conv_error)
|
format!("filestore error: {}", byte_conv_error)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected error");
|
panic!("unexpected error");
|
||||||
@@ -765,7 +827,7 @@ mod tests {
|
|||||||
if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
|
if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error.to_string(),
|
error.to_string(),
|
||||||
format!("checksum type not implemented: {:?}", cksum_type)
|
format!("checksum {:?} not implemented", cksum_type)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected error");
|
panic!("unexpected error");
|
||||||
|
|||||||
+15
-75
@@ -6,64 +6,15 @@
|
|||||||
//! a transaction closure for the unacknowledged mode. Using the unacknowledged mode with no
|
//! a transaction closure for the unacknowledged mode. Using the unacknowledged mode with no
|
||||||
//! transaction closure is applicable for simplex communication paths, while the unacknowledged
|
//! 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,
|
//! 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
|
//! 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
|
//! even for unreliable connections, including lost segment detection. As such, it can be compared
|
||||||
//! to a specialized TCP for file transfers with remote systems.
|
//! 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
|
//! The core of these high-level components are the [crate::dest::DestinationHandler] and the
|
||||||
//! software and of ground software. It has support to make integration on [std] systems as simple
|
//! [crate::source::SourceHandler] component. These model the CFDP destination and source entity
|
||||||
//! as possible, but also has sufficient abstraction to allow for integration on `no_std`
|
|
||||||
//! environments and can be used on these systems as well as long as the [alloc] feature is used
|
|
||||||
//! as well.
|
|
||||||
//!
|
|
||||||
//! Please note even though the [alloc] feature is required for the core handlers, 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
|
|
||||||
//! respectively. You can find high-level and API documentation for both handlers in the respective
|
//! respectively. You can find high-level and API documentation for both handlers in the respective
|
||||||
//! [crate::dest] and [crate::source] module.
|
//! [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]
|
#![no_std]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
@@ -71,11 +22,12 @@ extern crate alloc;
|
|||||||
#[cfg(any(feature = "std", test))]
|
#[cfg(any(feature = "std", test))]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "std")]
|
||||||
pub mod dest;
|
pub mod dest;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
pub mod filestore;
|
pub mod filestore;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "std")]
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
@@ -83,6 +35,8 @@ pub mod user;
|
|||||||
use crate::time::CountdownProvider;
|
use crate::time::CountdownProvider;
|
||||||
use core::{cell::RefCell, fmt::Debug, hash::Hash};
|
use core::{cell::RefCell, fmt::Debug, hash::Hash};
|
||||||
use crc::{Crc, CRC_32_ISCSI, CRC_32_ISO_HDLC};
|
use crc::{Crc, CRC_32_ISCSI, CRC_32_ISO_HDLC};
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub use alloc_mod::*;
|
pub use alloc_mod::*;
|
||||||
@@ -100,16 +54,11 @@ use spacepackets::{
|
|||||||
pub use std_mod::*;
|
pub use std_mod::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum EntityType {
|
pub enum EntityType {
|
||||||
Sending,
|
Sending,
|
||||||
Receiving,
|
Receiving,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum TimerContext {
|
pub enum TimerContext {
|
||||||
CheckLimit {
|
CheckLimit {
|
||||||
local_id: UnsignedByteField,
|
local_id: UnsignedByteField,
|
||||||
@@ -227,8 +176,6 @@ pub trait TimerCreatorProvider {
|
|||||||
/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside
|
/// * `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.
|
/// the class documentation. Defaults to 2, so the timer may expire two times.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct RemoteEntityConfig {
|
pub struct RemoteEntityConfig {
|
||||||
pub entity_id: UnsignedByteField,
|
pub entity_id: UnsignedByteField,
|
||||||
pub max_packet_len: usize,
|
pub max_packet_len: usize,
|
||||||
@@ -286,12 +233,11 @@ pub trait RemoteEntityConfigProvider {
|
|||||||
fn remove_config(&mut self, remote_id: u64) -> bool;
|
fn remove_config(&mut self, remote_id: u64) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is a thin wrapper around a [hashbrown::HashMap] to store remote entity configurations.
|
/// This is a thin wrapper around a [HashMap] to store remote entity configurations.
|
||||||
/// It implements the full [RemoteEntityConfigProvider] trait.
|
/// It implements the full [RemoteEntityConfigProvider] trait.
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "std")]
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
pub struct StdRemoteEntityConfigProvider(pub HashMap<u64, RemoteEntityConfig>);
|
||||||
pub struct StdRemoteEntityConfigProvider(pub hashbrown::HashMap<u64, RemoteEntityConfig>);
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
|
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
|
||||||
@@ -312,9 +258,7 @@ impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
|
|||||||
/// This is a thin wrapper around a [alloc::vec::Vec] to store remote entity configurations.
|
/// This is a thin wrapper around a [alloc::vec::Vec] to store remote entity configurations.
|
||||||
/// It implements the full [RemoteEntityConfigProvider] trait.
|
/// It implements the full [RemoteEntityConfigProvider] trait.
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct VecRemoteEntityConfigProvider(pub alloc::vec::Vec<RemoteEntityConfig>);
|
pub struct VecRemoteEntityConfigProvider(pub alloc::vec::Vec<RemoteEntityConfig>);
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
@@ -544,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 struct IndicationConfig {
|
||||||
pub eof_sent: bool,
|
pub eof_sent: bool,
|
||||||
pub eof_recv: bool,
|
pub eof_recv: bool,
|
||||||
@@ -718,7 +659,6 @@ pub mod std_mod {
|
|||||||
/// number of that transfer which is also determined by the CFDP source entity.
|
/// number of that transfer which is also determined by the CFDP source entity.
|
||||||
#[derive(Debug, Eq, Copy, Clone)]
|
#[derive(Debug, Eq, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct TransactionId {
|
pub struct TransactionId {
|
||||||
source_id: UnsignedByteField,
|
source_id: UnsignedByteField,
|
||||||
seq_num: UnsignedByteField,
|
seq_num: UnsignedByteField,
|
||||||
@@ -877,7 +817,7 @@ impl<'raw> PduRawWithInfo<'raw> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if pdu_header.pdu_datafield_len() < 1 {
|
if pdu_header.pdu_datafield_len() < 1 {
|
||||||
return Err(PduError::Format);
|
return Err(PduError::FormatError);
|
||||||
}
|
}
|
||||||
// Route depending on PDU type and directive type if applicable. Retrieve directive type
|
// Route depending on PDU type and directive type if applicable. Retrieve directive type
|
||||||
// from the raw stream for better performance (with sanity and directive code check).
|
// from the raw stream for better performance (with sanity and directive code check).
|
||||||
@@ -1546,7 +1486,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_id_hashable_usable_as_map_key() {
|
fn transaction_id_hashable_usable_as_map_key() {
|
||||||
let mut map = hashbrown::HashMap::new();
|
let mut map = HashMap::new();
|
||||||
let transaction_id_0 = TransactionId::new(
|
let transaction_id_0 = TransactionId::new(
|
||||||
UnsignedByteFieldU8::new(1).into(),
|
UnsignedByteFieldU8::new(1).into(),
|
||||||
UnsignedByteFieldU8::new(2).into(),
|
UnsignedByteFieldU8::new(2).into(),
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ use spacepackets::{
|
|||||||
pub use alloc_mod::*;
|
pub use alloc_mod::*;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[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);
|
pub struct FilePathTooLarge(pub usize);
|
||||||
|
|
||||||
/// This trait is an abstraction for different Put Request structures which can be used
|
/// 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)]
|
#[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) ());
|
pub struct TlvWithInvalidType(pub(crate) ());
|
||||||
|
|
||||||
impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static, 'static> {
|
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.
|
/// Owned variant of [PutRequest] with no lifetimes which is also [Clone]able.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub struct PutRequestOwned {
|
pub struct PutRequestOwned {
|
||||||
pub destination_id: UnsignedByteField,
|
pub destination_id: UnsignedByteField,
|
||||||
source_file: Option<alloc::string::String>,
|
source_file: Option<alloc::string::String>,
|
||||||
|
|||||||
+19
-1
@@ -76,7 +76,6 @@ use super::{
|
|||||||
/// This enumeration models the different transaction steps of the source entity handler.
|
/// This enumeration models the different transaction steps of the source entity handler.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum TransactionStep {
|
pub enum TransactionStep {
|
||||||
Idle = 0,
|
Idle = 0,
|
||||||
TransactionStart = 1,
|
TransactionStart = 1,
|
||||||
@@ -748,6 +747,25 @@ impl<
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) {
|
fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) {
|
||||||
|
/*
|
||||||
|
def _notice_of_completion(self):
|
||||||
|
if self.cfg.indication_cfg.transaction_finished_indication_required:
|
||||||
|
assert self._params.transaction_id is not None
|
||||||
|
# This happens for unacknowledged file copy operation with no closure.
|
||||||
|
if self._params.finished_params is None:
|
||||||
|
self._params.finished_params = FinishedParams(
|
||||||
|
condition_code=ConditionCode.NO_ERROR,
|
||||||
|
delivery_code=DeliveryCode.DATA_COMPLETE,
|
||||||
|
file_status=FileStatus.FILE_STATUS_UNREPORTED,
|
||||||
|
)
|
||||||
|
indication_params = TransactionFinishedParams(
|
||||||
|
transaction_id=self._params.transaction_id,
|
||||||
|
finished_params=self._params.finished_params,
|
||||||
|
)
|
||||||
|
self.user.transaction_finished_indication(indication_params)
|
||||||
|
# Transaction finished
|
||||||
|
self.reset()
|
||||||
|
*/
|
||||||
let tstate = self.tstate.as_ref().unwrap();
|
let tstate = self.tstate.as_ref().unwrap();
|
||||||
if self.local_cfg.indication_cfg.transaction_finished {
|
if self.local_cfg.indication_cfg.transaction_finished {
|
||||||
// The first case happens for unacknowledged file copy operation with no closure.
|
// The first case happens for unacknowledged file copy operation with no closure.
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ use spacepackets::{
|
|||||||
use super::TransactionId;
|
use super::TransactionId;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct TransactionFinishedParams {
|
pub struct TransactionFinishedParams {
|
||||||
pub id: TransactionId,
|
pub id: TransactionId,
|
||||||
pub condition_code: ConditionCode,
|
pub condition_code: ConditionCode,
|
||||||
|
|||||||
Reference in New Issue
Block a user