Compare commits
No commits in common. "python-interop-example" and "main" have entirely different histories.
python-int
...
main
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -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.75.0
|
- uses: dtolnay/rust-toolchain@1.81.0
|
||||||
- run: cargo check --release
|
- run: cargo check --release
|
||||||
|
|
||||||
cross-check:
|
cross-check:
|
||||||
|
@ -7,3 +7,12 @@ 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]
|
||||||
|
|
||||||
|
- 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
|
||||||
|
28
Cargo.toml
28
Cargo.toml
@ -18,45 +18,49 @@ name = "cfdp"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
crc = "3"
|
crc = "3"
|
||||||
smallvec = "1"
|
smallvec = "1"
|
||||||
derive-new = "0.6"
|
derive-new = ">=0.6, <=0.7"
|
||||||
|
|
||||||
|
[dependencies.spacepackets]
|
||||||
|
version = "0.13"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[dependencies.thiserror]
|
[dependencies.thiserror]
|
||||||
version = "1"
|
version = "2"
|
||||||
optional = true
|
default-features = false
|
||||||
|
|
||||||
[dependencies.hashbrown]
|
[dependencies.hashbrown]
|
||||||
version = "0.14"
|
version = ">=0.14, <=0.15"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1"
|
version = "1"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.spacepackets]
|
[dependencies.defmt]
|
||||||
version = "0.12"
|
version = "0.3"
|
||||||
default-features = false
|
optional = true
|
||||||
git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
|
|
||||||
branch = "main"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
"alloc",
|
"alloc",
|
||||||
"thiserror",
|
"thiserror/std",
|
||||||
"spacepackets/std"
|
"spacepackets/std"
|
||||||
]
|
]
|
||||||
alloc = [
|
alloc = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"spacepackets/alloc"
|
"spacepackets/alloc"
|
||||||
]
|
]
|
||||||
serde = ["dep:serde", "spacepackets/serde"]
|
serde = ["dep:serde", "spacepackets/serde", "hashbrown/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.6"
|
fern = "0.7"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
12
README.md
12
README.md
@ -13,10 +13,14 @@ The underlying base packet library used to generate the packets to be sent is th
|
|||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
`cfdp-rs` supports various runtime environments and is also suitable for `no_std` environments.
|
The goal of this library is to be flexible enough to support the use-cases of both on-board
|
||||||
It is recommended to activate the `alloc` feature at the very least to allow using the primary
|
software and of ground software. It has support to make integration on `std` systems as simple
|
||||||
components provided by this crate. These components will only allocate memory at initialization
|
as possible, but also has sufficient abstraction to allow for integration on`no_std` environments
|
||||||
time and thus are still viable for systems where run-time allocation is prohibited.
|
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
|
||||||
|
will only allocate memory at initialization time and thus are still viable for systems where
|
||||||
|
run-time allocation is prohibited.
|
||||||
|
|
||||||
## Default features
|
## Default features
|
||||||
|
|
||||||
|
@ -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"-o {out_path}"
|
f"--ignore \"examples/*\" -o {out_path}"
|
||||||
)
|
)
|
||||||
if format == "lcov":
|
if format == "lcov":
|
||||||
os.system(
|
os.system(
|
||||||
|
37
examples/python-interop/README.md
Normal file
37
examples/python-interop/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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.
|
@ -1,7 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import tempfile
|
||||||
import socket
|
import socket
|
||||||
import select
|
import select
|
||||||
import threading
|
import threading
|
||||||
@ -51,8 +53,6 @@ from spacepackets.cfdp.tlv.msg_to_user import ProxyPutResponseParams
|
|||||||
from spacepackets.countdown import Countdown
|
from spacepackets.countdown import Countdown
|
||||||
from spacepackets.seqcount import SeqCountProvider
|
from spacepackets.seqcount import SeqCountProvider
|
||||||
from spacepackets.util import ByteFieldU16, UnsignedByteField
|
from spacepackets.util import ByteFieldU16, UnsignedByteField
|
||||||
from tmtccmd.config.cfdp import CfdpParams, generic_cfdp_params_to_put_request
|
|
||||||
from tmtccmd.config.args import add_cfdp_procedure_arguments, cfdp_args_to_cfdp_params
|
|
||||||
|
|
||||||
|
|
||||||
PYTHON_ENTITY_ID = ByteFieldU16(1)
|
PYTHON_ENTITY_ID = ByteFieldU16(1)
|
||||||
@ -544,23 +544,44 @@ class CustomCheckTimerProvider(CheckTimerProvider):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(prog="CFDP Local Entity Application")
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="CFDP Local Entity Application",
|
||||||
|
formatter_class=argparse.RawTextHelpFormatter,
|
||||||
|
)
|
||||||
parser.add_argument("-v", "--verbose", action="count", default=0)
|
parser.add_argument("-v", "--verbose", action="count", default=0)
|
||||||
add_cfdp_procedure_arguments(parser)
|
parser.add_argument(
|
||||||
|
"-f",
|
||||||
|
help="Perform a file-copy operation",
|
||||||
|
action="store_true",
|
||||||
|
dest="file_copy",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-m",
|
||||||
|
"--mode",
|
||||||
|
dest="transmission_mode",
|
||||||
|
help=(
|
||||||
|
f"Specify the transfer type{os.linesep}"
|
||||||
|
f' - "0" or "ack" for unacknowledged (Class 0) transfers{os.linesep}'
|
||||||
|
f' - "1" or "nak" for acknowledged (Class 1) transfers. Default value'
|
||||||
|
),
|
||||||
|
default="nak",
|
||||||
|
)
|
||||||
|
# Optional Boolean argument where you can specify True/False
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
type=bool,
|
||||||
|
nargs="?",
|
||||||
|
const=True,
|
||||||
|
default=None,
|
||||||
|
dest="closure_requested",
|
||||||
|
help="Request transaction closure for the unacknowledged mode",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
stop_signal = threading.Event()
|
stop_signal = threading.Event()
|
||||||
|
|
||||||
logging_level = logging.INFO
|
logging_level = logging.INFO
|
||||||
if args.verbose >= 1:
|
if args.verbose >= 1:
|
||||||
logging_level = logging.DEBUG
|
logging_level = logging.DEBUG
|
||||||
if args.source is not None and args.target is not None:
|
|
||||||
# Generate a put request from the CLI arguments.
|
|
||||||
cfdp_params = CfdpParams()
|
|
||||||
cfdp_args_to_cfdp_params(args, cfdp_params)
|
|
||||||
put_req = generic_cfdp_params_to_put_request(
|
|
||||||
cfdp_params, PYTHON_ENTITY_ID, RUST_ENTITY_ID, PYTHON_ENTITY_ID
|
|
||||||
)
|
|
||||||
PUT_REQ_QUEUE.put(put_req)
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging_level)
|
logging.basicConfig(level=logging_level)
|
||||||
|
|
||||||
@ -611,16 +632,6 @@ def main():
|
|||||||
local_addr = ipaddress.ip_address("0.0.0.0")
|
local_addr = ipaddress.ip_address("0.0.0.0")
|
||||||
# Localhost as default.
|
# Localhost as default.
|
||||||
remote_addr = ipaddress.ip_address("127.0.0.1")
|
remote_addr = ipaddress.ip_address("127.0.0.1")
|
||||||
"""
|
|
||||||
if Path(LOCAL_CFG_JSON_PATH).exists():
|
|
||||||
addr_from_cfg = parse_remote_addr_from_json(Path(LOCAL_CFG_JSON_PATH))
|
|
||||||
if addr_from_cfg is not None:
|
|
||||||
try:
|
|
||||||
remote_addr = ipaddress.ip_address(addr_from_cfg)
|
|
||||||
except ValueError:
|
|
||||||
_LOGGER.warning(f"invalid remote address {remote_addr} from JSON file")
|
|
||||||
"""
|
|
||||||
_LOGGER.info(f"Put request will be sent to remote destination {remote_addr}")
|
|
||||||
udp_server = UdpServer(
|
udp_server = UdpServer(
|
||||||
sleep_time=0.1,
|
sleep_time=0.1,
|
||||||
addr=(str(local_addr), PY_PORT),
|
addr=(str(local_addr), PY_PORT),
|
||||||
@ -631,6 +642,27 @@ def main():
|
|||||||
stop_signal=stop_signal,
|
stop_signal=stop_signal,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Prepare a put request / file copy operation if the user specifies it.
|
||||||
|
if args.file_copy:
|
||||||
|
_LOGGER.info("Performing file copy operation")
|
||||||
|
transmission_mode = None
|
||||||
|
if args.transmission_mode == "ack":
|
||||||
|
transmission_mode = TransmissionMode.ACKNOWLEDGED
|
||||||
|
elif args.transmission_mode == "nak":
|
||||||
|
transmission_mode = TransmissionMode.UNACKNOWLEDGED
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False) as srcfile:
|
||||||
|
srcfile.write(FILE_CONTENT.encode())
|
||||||
|
srcfile_path = srcfile.name
|
||||||
|
tempdir = tempfile.TemporaryDirectory()
|
||||||
|
put_req = PutRequest(
|
||||||
|
destination_id=RUST_ENTITY_ID,
|
||||||
|
source_file=Path(srcfile_path),
|
||||||
|
dest_file=Path(tempdir.name).joinpath("test.txt"),
|
||||||
|
closure_requested=args.closure_requested,
|
||||||
|
trans_mode=transmission_mode,
|
||||||
|
)
|
||||||
|
PUT_REQ_QUEUE.put(put_req)
|
||||||
|
|
||||||
source_entity_task.start()
|
source_entity_task.start()
|
||||||
dest_entity_task.start()
|
dest_entity_task.start()
|
||||||
udp_server.start()
|
udp_server.start()
|
||||||
|
@ -15,8 +15,9 @@ use cfdp::{
|
|||||||
source::SourceHandler,
|
source::SourceHandler,
|
||||||
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
|
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
|
||||||
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, PduProvider,
|
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, PduProvider,
|
||||||
RemoteEntityConfig, StdCheckTimerCreator, TransactionId, UserFaultHookProvider,
|
RemoteEntityConfig, StdTimerCreator, TransactionId, UserFaultHookProvider,
|
||||||
};
|
};
|
||||||
|
use clap::Parser;
|
||||||
use log::{debug, info, warn};
|
use log::{debug, info, warn};
|
||||||
use spacepackets::{
|
use spacepackets::{
|
||||||
cfdp::{
|
cfdp::{
|
||||||
@ -37,6 +38,23 @@ const LOG_LEVEL: log::LevelFilter = log::LevelFilter::Info;
|
|||||||
|
|
||||||
const FILE_DATA: &str = "Hello World!";
|
const FILE_DATA: &str = "Hello World!";
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, clap::ValueEnum)]
|
||||||
|
pub enum TransmissionModeCli {
|
||||||
|
Nak,
|
||||||
|
Ack,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Parser)]
|
||||||
|
#[command(about = "Arguments for executing a file copy operation")]
|
||||||
|
pub struct Cli {
|
||||||
|
#[arg(short, help = "Perform a file copy operation")]
|
||||||
|
file_copy: bool,
|
||||||
|
#[arg(short, default_value = "nak")]
|
||||||
|
mode: Option<TransmissionModeCli>,
|
||||||
|
#[arg(short)]
|
||||||
|
closure_requested: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ExampleFaultHandler {}
|
pub struct ExampleFaultHandler {}
|
||||||
|
|
||||||
@ -277,6 +295,7 @@ fn pdu_printout(pdu: &PduOwnedWithInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let cli_args = Cli::parse();
|
||||||
fern::Dispatch::new()
|
fern::Dispatch::new()
|
||||||
.format(|out, message, record| {
|
.format(|out, message, record| {
|
||||||
out.finish(format_args!(
|
out.finish(format_args!(
|
||||||
@ -327,6 +346,7 @@ fn main() {
|
|||||||
put_request_cacher,
|
put_request_cacher,
|
||||||
2048,
|
2048,
|
||||||
remote_cfg_python,
|
remote_cfg_python,
|
||||||
|
StdTimerCreator::default(),
|
||||||
seq_count_provider,
|
seq_count_provider,
|
||||||
);
|
);
|
||||||
let mut cfdp_user_source = ExampleCfdpUser::new(EntityType::Sending);
|
let mut cfdp_user_source = ExampleCfdpUser::new(EntityType::Sending);
|
||||||
@ -342,18 +362,27 @@ fn main() {
|
|||||||
dest_tm_tx,
|
dest_tm_tx,
|
||||||
NativeFilestore::default(),
|
NativeFilestore::default(),
|
||||||
remote_cfg_python,
|
remote_cfg_python,
|
||||||
StdCheckTimerCreator::default(),
|
StdTimerCreator::default(),
|
||||||
);
|
);
|
||||||
let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving);
|
let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving);
|
||||||
|
|
||||||
let put_request = PutRequestOwned::new_regular_request(
|
let put_request = if cli_args.file_copy {
|
||||||
|
Some(
|
||||||
|
PutRequestOwned::new_regular_request(
|
||||||
PYTHON_ID.into(),
|
PYTHON_ID.into(),
|
||||||
srcfile.to_str().expect("invaid path string"),
|
srcfile.to_str().expect("invaid path string"),
|
||||||
destfile.to_str().expect("invaid path string"),
|
destfile.to_str().expect("invaid path string"),
|
||||||
Some(TransmissionMode::Unacknowledged),
|
cli_args.mode.map(|m| match m {
|
||||||
Some(true),
|
TransmissionModeCli::Ack => TransmissionMode::Acknowledged,
|
||||||
|
TransmissionModeCli::Nak => TransmissionMode::Unacknowledged,
|
||||||
|
}),
|
||||||
|
cli_args.closure_requested,
|
||||||
)
|
)
|
||||||
.expect("put request creation failed");
|
.expect("put request creation failed"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let (source_tc_tx, source_tc_rx) = mpsc::channel();
|
let (source_tc_tx, source_tc_rx) = mpsc::channel();
|
||||||
let (dest_tc_tx, dest_tc_rx) = mpsc::channel();
|
let (dest_tc_tx, dest_tc_rx) = mpsc::channel();
|
||||||
@ -375,9 +404,12 @@ fn main() {
|
|||||||
.name("cfdp src entity".to_string())
|
.name("cfdp src entity".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
info!("Starting RUST SRC");
|
info!("Starting RUST SRC");
|
||||||
|
if let Some(put_request) = put_request {
|
||||||
|
info!("RUST SRC: Performing put request: {:?}", put_request);
|
||||||
source_handler
|
source_handler
|
||||||
.put_request(&put_request)
|
.put_request(&put_request)
|
||||||
.expect("put request failed");
|
.expect("put request failed");
|
||||||
|
}
|
||||||
loop {
|
loop {
|
||||||
let mut next_delay = None;
|
let mut next_delay = None;
|
||||||
let mut undelayed_call_count = 0;
|
let mut undelayed_call_count = 0;
|
||||||
@ -437,7 +469,9 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Source handler error: {}", e);
|
println!("Dest handler error: {}", e);
|
||||||
|
// TODO: I'd prefer a proper cancel request if a transfer is active..
|
||||||
|
dest_handler.reset();
|
||||||
next_delay = Some(Duration::from_millis(50));
|
next_delay = Some(Duration::from_millis(50));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1 @@
|
|||||||
cfdp-py @ git+https://github.com/us-irs/cfdp-py.git@main
|
cfdp-py @ git+https://github.com/us-irs/cfdp-py.git@main
|
||||||
tmtccmd == 8.0.2
|
|
||||||
|
669
src/dest.rs
669
src/dest.rs
File diff suppressed because it is too large
Load Diff
163
src/filestore.rs
163
src/filestore.rs
@ -1,90 +1,35 @@
|
|||||||
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, Clone)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[cfg_attr(all(feature = "defmt", not(feature = "std")), derive(defmt::Format))]
|
||||||
|
#[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,
|
||||||
ByteConversion(ByteConversionError),
|
#[error("byte conversion: {0}")]
|
||||||
Io {
|
ByteConversion(#[from] ByteConversionError),
|
||||||
raw_errno: Option<i32>,
|
#[error("IO error: {0})")]
|
||||||
string: String,
|
|
||||||
},
|
|
||||||
ChecksumTypeNotImplemented(ChecksumType),
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for FilestoreError {
|
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
||||||
match self {
|
|
||||||
FilestoreError::ByteConversion(e) => Some(e),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl From<std::io::Error> for FilestoreError {
|
Io(#[from] std::io::Error),
|
||||||
fn from(value: std::io::Error) -> Self {
|
#[error("checksum type not implemented: {0:?}")]
|
||||||
Self::Io {
|
ChecksumTypeNotImplemented(ChecksumType),
|
||||||
raw_errno: value.raw_os_error(),
|
#[error("utf8 error")]
|
||||||
string: value.to_string(),
|
Utf8Error,
|
||||||
}
|
#[error("other error")]
|
||||||
}
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait VirtualFilestore {
|
pub trait VirtualFilestore {
|
||||||
@ -111,14 +56,7 @@ 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>;
|
||||||
|
|
||||||
@ -128,6 +66,11 @@ pub trait VirtualFilestore {
|
|||||||
|
|
||||||
fn exists(&self, path: &str) -> Result<bool, FilestoreError>;
|
fn exists(&self, path: &str) -> Result<bool, FilestoreError>;
|
||||||
|
|
||||||
|
/// Extract the file name part of a full path.
|
||||||
|
///
|
||||||
|
/// This method should behave similarly to the [std::path::Path::file_name] method.
|
||||||
|
fn file_name<'a>(&self, full_path: &'a str) -> Result<Option<&'a str>, FilestoreError>;
|
||||||
|
|
||||||
fn file_size(&self, path: &str) -> Result<u64, FilestoreError>;
|
fn file_size(&self, path: &str) -> Result<u64, FilestoreError>;
|
||||||
|
|
||||||
/// This special function is the CFDP specific abstraction to calculate the checksum of a file.
|
/// This special function is the CFDP specific abstraction to calculate the checksum of a file.
|
||||||
@ -141,6 +84,7 @@ pub trait VirtualFilestore {
|
|||||||
&self,
|
&self,
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
checksum_type: ChecksumType,
|
checksum_type: ChecksumType,
|
||||||
|
size_to_verify: u64,
|
||||||
verification_buf: &mut [u8],
|
verification_buf: &mut [u8],
|
||||||
) -> Result<u32, FilestoreError>;
|
) -> Result<u32, FilestoreError>;
|
||||||
|
|
||||||
@ -153,13 +97,14 @@ pub trait VirtualFilestore {
|
|||||||
/// 4096 or 8192 bytes.
|
/// 4096 or 8192 bytes.
|
||||||
fn checksum_verify(
|
fn checksum_verify(
|
||||||
&self,
|
&self,
|
||||||
|
expected_checksum: u32,
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
checksum_type: ChecksumType,
|
checksum_type: ChecksumType,
|
||||||
expected_checksum: u32,
|
size_to_verify: u64,
|
||||||
verification_buf: &mut [u8],
|
verification_buf: &mut [u8],
|
||||||
) -> Result<bool, FilestoreError> {
|
) -> Result<bool, FilestoreError> {
|
||||||
Ok(
|
Ok(
|
||||||
self.calculate_checksum(file_path, checksum_type, verification_buf)?
|
self.calculate_checksum(file_path, checksum_type, size_to_verify, verification_buf)?
|
||||||
== expected_checksum,
|
== expected_checksum,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -176,6 +121,7 @@ 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)]
|
||||||
@ -201,6 +147,13 @@ pub mod std_mod {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn file_name<'a>(&self, full_path: &'a str) -> Result<Option<&'a str>, FilestoreError> {
|
||||||
|
let path = Path::new(full_path);
|
||||||
|
path.file_name()
|
||||||
|
.map(|s| s.to_str())
|
||||||
|
.ok_or(FilestoreError::Utf8Error)
|
||||||
|
}
|
||||||
|
|
||||||
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> {
|
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> {
|
||||||
if !self.exists(file_path)? {
|
if !self.exists(file_path)? {
|
||||||
return Err(FilestoreError::FileDoesNotExist);
|
return Err(FilestoreError::FileDoesNotExist);
|
||||||
@ -216,10 +169,7 @@ 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).map_err(|e| FilestoreError::Io {
|
fs::create_dir(dir_path)?;
|
||||||
raw_errno: e.raw_os_error(),
|
|
||||||
string: e.to_string(),
|
|
||||||
})?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,18 +255,23 @@ pub mod std_mod {
|
|||||||
&self,
|
&self,
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
checksum_type: ChecksumType,
|
checksum_type: ChecksumType,
|
||||||
|
size_to_verify: u64,
|
||||||
verification_buf: &mut [u8],
|
verification_buf: &mut [u8],
|
||||||
) -> Result<u32, FilestoreError> {
|
) -> Result<u32, FilestoreError> {
|
||||||
let mut calc_with_crc_lib = |crc: Crc<u32>| -> Result<u32, FilestoreError> {
|
let mut calc_with_crc_lib = |crc: Crc<u32>| -> Result<u32, FilestoreError> {
|
||||||
let mut digest = crc.digest();
|
let mut digest = crc.digest();
|
||||||
let file_to_check = File::open(file_path)?;
|
let mut buf_reader = BufReader::new(File::open(file_path)?);
|
||||||
let mut buf_reader = BufReader::new(file_to_check);
|
let mut remaining_bytes = size_to_verify;
|
||||||
loop {
|
while remaining_bytes > 0 {
|
||||||
let bytes_read = buf_reader.read(verification_buf)?;
|
// Read the smaller of the remaining bytes or the buffer size
|
||||||
|
let bytes_to_read = remaining_bytes.min(verification_buf.len() as u64) as usize;
|
||||||
|
let bytes_read = buf_reader.read(&mut verification_buf[0..bytes_to_read])?;
|
||||||
|
|
||||||
if bytes_read == 0 {
|
if bytes_read == 0 {
|
||||||
break;
|
break; // Reached end of file
|
||||||
}
|
}
|
||||||
digest.update(&verification_buf[0..bytes_read]);
|
digest.update(&verification_buf[0..bytes_read]);
|
||||||
|
remaining_bytes -= bytes_read as u64;
|
||||||
}
|
}
|
||||||
Ok(digest.finalize())
|
Ok(digest.finalize())
|
||||||
};
|
};
|
||||||
@ -328,6 +283,17 @@ 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 {
|
||||||
@ -363,7 +329,7 @@ pub mod std_mod {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{fs, path::Path, println};
|
use std::{fs, path::Path, println, string::ToString};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
@ -676,7 +642,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error.to_string(),
|
error.to_string(),
|
||||||
format!("filestore error: {}", byte_conv_error)
|
format!("byte conversion: {}", byte_conv_error)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected error");
|
panic!("unexpected error");
|
||||||
@ -755,9 +721,10 @@ mod tests {
|
|||||||
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
|
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
|
||||||
let mut verif_buf: [u8; 32] = [0; 32];
|
let mut verif_buf: [u8; 32] = [0; 32];
|
||||||
let result = NATIVE_FS.checksum_verify(
|
let result = NATIVE_FS.checksum_verify(
|
||||||
|
checksum,
|
||||||
file_path.to_str().unwrap(),
|
file_path.to_str().unwrap(),
|
||||||
ChecksumType::Modular,
|
ChecksumType::Modular,
|
||||||
checksum,
|
EXAMPLE_DATA_CFDP.len() as u64,
|
||||||
&mut verif_buf,
|
&mut verif_buf,
|
||||||
);
|
);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
@ -770,6 +737,7 @@ mod tests {
|
|||||||
// The file to check does not even need to exist, and the verification buffer can be
|
// The file to check does not even need to exist, and the verification buffer can be
|
||||||
// empty: the null checksum is always yields the same result.
|
// empty: the null checksum is always yields the same result.
|
||||||
let result = NATIVE_FS.checksum_verify(
|
let result = NATIVE_FS.checksum_verify(
|
||||||
|
0,
|
||||||
file_path.to_str().unwrap(),
|
file_path.to_str().unwrap(),
|
||||||
ChecksumType::NullChecksum,
|
ChecksumType::NullChecksum,
|
||||||
0,
|
0,
|
||||||
@ -786,6 +754,7 @@ mod tests {
|
|||||||
// The file to check does not even need to exist, and the verification buffer can be
|
// The file to check does not even need to exist, and the verification buffer can be
|
||||||
// empty: the null checksum is always yields the same result.
|
// empty: the null checksum is always yields the same result.
|
||||||
let result = NATIVE_FS.checksum_verify(
|
let result = NATIVE_FS.checksum_verify(
|
||||||
|
0,
|
||||||
file_path.to_str().unwrap(),
|
file_path.to_str().unwrap(),
|
||||||
ChecksumType::Crc32Proximity1,
|
ChecksumType::Crc32Proximity1,
|
||||||
0,
|
0,
|
||||||
@ -796,7 +765,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 {:?} not implemented", cksum_type)
|
format!("checksum type not implemented: {:?}", cksum_type)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected error");
|
panic!("unexpected error");
|
||||||
|
203
src/lib.rs
203
src/lib.rs
@ -1,5 +1,69 @@
|
|||||||
//! This module contains the implementation of the CFDP high level abstractions as specified in
|
//! This module contains the implementation of the CCSDS File Delivery Protocol (CFDP) high level
|
||||||
//! CCSDS 727.0-B-5.
|
//! abstractions as specified in CCSDS 727.0-B-5.
|
||||||
|
//!
|
||||||
|
//! The basic idea of CFDP is to convert files of any size into a stream of packets called packet
|
||||||
|
//! data units (PDU). CFPD has an unacknowledged and acknowledged mode, with the option to request
|
||||||
|
//! 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
|
||||||
|
//! 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 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
|
||||||
|
//! [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")]
|
||||||
@ -7,12 +71,11 @@ extern crate alloc;
|
|||||||
#[cfg(any(feature = "std", test))]
|
#[cfg(any(feature = "std", test))]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub mod dest;
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
|
pub mod dest;
|
||||||
pub mod filestore;
|
pub mod filestore;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "alloc")]
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
@ -20,11 +83,10 @@ 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::*;
|
||||||
|
use core::time::Duration;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use spacepackets::{
|
use spacepackets::{
|
||||||
@ -35,16 +97,19 @@ use spacepackets::{
|
|||||||
util::{UnsignedByteField, UnsignedEnum},
|
util::{UnsignedByteField, UnsignedEnum},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::time::Duration;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
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,
|
||||||
@ -52,10 +117,10 @@ pub enum TimerContext {
|
|||||||
entity_type: EntityType,
|
entity_type: EntityType,
|
||||||
},
|
},
|
||||||
NakActivity {
|
NakActivity {
|
||||||
expiry_time_seconds: f32,
|
expiry_time: Duration,
|
||||||
},
|
},
|
||||||
PositiveAck {
|
PositiveAck {
|
||||||
expiry_time_seconds: f32,
|
expiry_time: Duration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,10 +161,10 @@ pub enum TimerContext {
|
|||||||
/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in
|
/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in
|
||||||
/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer
|
/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer
|
||||||
/// interval of the remote entity configuration.
|
/// interval of the remote entity configuration.
|
||||||
pub trait CheckTimerProviderCreator {
|
pub trait TimerCreatorProvider {
|
||||||
type CheckTimer: CountdownProvider;
|
type Countdown: CountdownProvider;
|
||||||
|
|
||||||
fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer;
|
fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This structure models the remote entity configuration information as specified in chapter 8.3
|
/// This structure models the remote entity configuration information as specified in chapter 8.3
|
||||||
@ -162,6 +227,8 @@ pub trait CheckTimerProviderCreator {
|
|||||||
/// * `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,
|
||||||
@ -219,9 +286,12 @@ pub trait RemoteEntityConfigProvider {
|
|||||||
fn remove_config(&mut self, remote_id: u64) -> bool;
|
fn remove_config(&mut self, remote_id: u64) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
/// This is a thin wrapper around a [hashbrown::HashMap] to store remote entity configurations.
|
||||||
#[derive(Default)]
|
/// It implements the full [RemoteEntityConfigProvider] trait.
|
||||||
pub struct StdRemoteEntityConfigProvider(pub HashMap<u64, RemoteEntityConfig>);
|
#[cfg(feature = "alloc")]
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct StdRemoteEntityConfigProvider(pub hashbrown::HashMap<u64, RemoteEntityConfig>);
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
|
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
|
||||||
@ -239,8 +309,12 @@ 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")]
|
#[cfg(feature = "alloc")]
|
||||||
#[derive(Default)]
|
#[derive(Default, Debug)]
|
||||||
|
#[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")]
|
||||||
@ -273,6 +347,9 @@ impl RemoteEntityConfigProvider for VecRemoteEntityConfigProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A remote entity configurations also implements the [RemoteEntityConfigProvider], but the
|
||||||
|
/// [RemoteEntityConfigProvider::add_config] and [RemoteEntityConfigProvider::remove_config]
|
||||||
|
/// are no-ops and always returns [false].
|
||||||
impl RemoteEntityConfigProvider for RemoteEntityConfig {
|
impl RemoteEntityConfigProvider for RemoteEntityConfig {
|
||||||
fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
|
fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
|
||||||
if remote_id == self.entity_id.value() {
|
if remote_id == self.entity_id.value() {
|
||||||
@ -300,7 +377,7 @@ impl RemoteEntityConfigProvider for RemoteEntityConfig {
|
|||||||
/// This trait introduces some callbacks which will be called when a particular CFDP fault
|
/// This trait introduces some callbacks which will be called when a particular CFDP fault
|
||||||
/// handler is called.
|
/// handler is called.
|
||||||
///
|
///
|
||||||
/// It is passed into the CFDP handlers as part of the [DefaultFaultHandler] and the local entity
|
/// It is passed into the CFDP handlers as part of the [UserFaultHookProvider] and the local entity
|
||||||
/// configuration and provides a way to specify custom user error handlers. This allows to
|
/// configuration and provides a way to specify custom user error handlers. This allows to
|
||||||
/// implement some CFDP features like fault handler logging, which would not be possible
|
/// implement some CFDP features like fault handler logging, which would not be possible
|
||||||
/// generically otherwise.
|
/// generically otherwise.
|
||||||
@ -327,6 +404,8 @@ pub trait UserFaultHookProvider {
|
|||||||
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
|
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dummy fault hook which implements [UserFaultHookProvider] but only provides empty
|
||||||
|
/// implementations.
|
||||||
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct DummyFaultHook {}
|
pub struct DummyFaultHook {}
|
||||||
|
|
||||||
@ -364,7 +443,7 @@ impl UserFaultHookProvider for DummyFaultHook {
|
|||||||
/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler
|
/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler
|
||||||
/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used
|
/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used
|
||||||
/// to select the error handling inside the CFDP handler itself in addition to dispatching to a
|
/// to select the error handling inside the CFDP handler itself in addition to dispatching to a
|
||||||
/// user-provided callback function provided by the [UserFaultHandler].
|
/// user-provided callback function provided by the [UserFaultHookProvider].
|
||||||
///
|
///
|
||||||
/// Some note on the provided default settings:
|
/// Some note on the provided default settings:
|
||||||
///
|
///
|
||||||
@ -465,6 +544,9 @@ 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,
|
||||||
@ -487,6 +569,7 @@ impl Default for IndicationConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Each CFDP entity handler has a [LocalEntityConfig]uration.
|
||||||
pub struct LocalEntityConfig<UserFaultHook: UserFaultHookProvider> {
|
pub struct LocalEntityConfig<UserFaultHook: UserFaultHookProvider> {
|
||||||
pub id: UnsignedByteField,
|
pub id: UnsignedByteField,
|
||||||
pub indication_cfg: IndicationConfig,
|
pub indication_cfg: IndicationConfig,
|
||||||
@ -563,31 +646,29 @@ pub mod std_mod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime.
|
/// Simple implementation of the [CountdownProvider] trait assuming a standard runtime.
|
||||||
/// It also assumes that a second accuracy of the check timer period is sufficient.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StdCheckTimer {
|
pub struct StdCountdown {
|
||||||
expiry_time_seconds: u64,
|
expiry_time: Duration,
|
||||||
start_time: std::time::Instant,
|
start_time: std::time::Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdCheckTimer {
|
impl StdCountdown {
|
||||||
pub fn new(expiry_time_seconds: u64) -> Self {
|
pub fn new(expiry_time: Duration) -> Self {
|
||||||
Self {
|
Self {
|
||||||
expiry_time_seconds,
|
expiry_time,
|
||||||
start_time: std::time::Instant::now(),
|
start_time: std::time::Instant::now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expiry_time_seconds(&self) -> u64 {
|
pub fn expiry_time_seconds(&self) -> u64 {
|
||||||
self.expiry_time_seconds
|
self.expiry_time.as_secs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CountdownProvider for StdCheckTimer {
|
impl CountdownProvider for StdCountdown {
|
||||||
fn has_expired(&self) -> bool {
|
fn has_expired(&self) -> bool {
|
||||||
let elapsed_time = self.start_time.elapsed();
|
if self.start_time.elapsed() > self.expiry_time {
|
||||||
if elapsed_time.as_nanos() > self.expiry_time_seconds as u128 * 1_000_000_000 {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
@ -598,40 +679,36 @@ pub mod std_mod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StdCheckTimerCreator {
|
pub struct StdTimerCreator {
|
||||||
pub check_limit_timeout_secs: u64,
|
pub check_limit_timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdCheckTimerCreator {
|
impl StdTimerCreator {
|
||||||
pub const fn new(check_limit_timeout_secs: u64) -> Self {
|
pub const fn new(check_limit_timeout: Duration) -> Self {
|
||||||
Self {
|
Self {
|
||||||
check_limit_timeout_secs,
|
check_limit_timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StdCheckTimerCreator {
|
impl Default for StdTimerCreator {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(5)
|
Self::new(Duration::from_secs(5))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CheckTimerProviderCreator for StdCheckTimerCreator {
|
impl TimerCreatorProvider for StdTimerCreator {
|
||||||
type CheckTimer = StdCheckTimer;
|
type Countdown = StdCountdown;
|
||||||
|
|
||||||
fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer {
|
fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown {
|
||||||
match timer_context {
|
match timer_context {
|
||||||
TimerContext::CheckLimit {
|
TimerContext::CheckLimit {
|
||||||
local_id: _,
|
local_id: _,
|
||||||
remote_id: _,
|
remote_id: _,
|
||||||
entity_type: _,
|
entity_type: _,
|
||||||
} => StdCheckTimer::new(self.check_limit_timeout_secs),
|
} => StdCountdown::new(self.check_limit_timeout),
|
||||||
TimerContext::NakActivity {
|
TimerContext::NakActivity { expiry_time } => StdCountdown::new(expiry_time),
|
||||||
expiry_time_seconds,
|
TimerContext::PositiveAck { expiry_time } => StdCountdown::new(expiry_time),
|
||||||
} => StdCheckTimer::new(Duration::from_secs_f32(expiry_time_seconds).as_secs()),
|
|
||||||
TimerContext::PositiveAck {
|
|
||||||
expiry_time_seconds,
|
|
||||||
} => StdCheckTimer::new(Duration::from_secs_f32(expiry_time_seconds).as_secs()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -641,6 +718,7 @@ 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,
|
||||||
@ -682,11 +760,15 @@ pub enum State {
|
|||||||
Suspended = 2,
|
Suspended = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SANA registry entry: https://sanaregistry.org/r/checksum_identifiers/records/4
|
/// [crc::Crc] instance using [crc::CRC_32_ISO_HDLC].
|
||||||
/// Entry in CRC catalogue: https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32
|
///
|
||||||
|
/// SANA registry entry: <https://sanaregistry.org/r/checksum_identifiers/records/4>,
|
||||||
|
/// Entry in CRC catalogue: <https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32>
|
||||||
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
|
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
|
||||||
/// SANA registry entry: https://sanaregistry.org/r/checksum_identifiers/records/3
|
/// [crc::Crc] instance using [crc::CRC_32_ISCSI].
|
||||||
/// Entry in CRC catalogue: https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi
|
///
|
||||||
|
/// SANA registry entry: <https://sanaregistry.org/r/checksum_identifiers/records/3>,
|
||||||
|
/// Entry in CRC catalogue: <https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi>
|
||||||
pub const CRC_32C: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
|
pub const CRC_32C: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
@ -696,6 +778,8 @@ pub enum PacketTarget {
|
|||||||
DestEntity,
|
DestEntity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generic trait which models a raw CFDP packet data unit (PDU) block with some additional context
|
||||||
|
/// information.
|
||||||
pub trait PduProvider {
|
pub trait PduProvider {
|
||||||
fn pdu_type(&self) -> PduType;
|
fn pdu_type(&self) -> PduType;
|
||||||
fn file_directive_type(&self) -> Option<FileDirectiveType>;
|
fn file_directive_type(&self) -> Option<FileDirectiveType>;
|
||||||
@ -793,7 +877,7 @@ impl<'raw> PduRawWithInfo<'raw> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if pdu_header.pdu_datafield_len() < 1 {
|
if pdu_header.pdu_datafield_len() < 1 {
|
||||||
return Err(PduError::FormatError);
|
return Err(PduError::Format);
|
||||||
}
|
}
|
||||||
// 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).
|
||||||
@ -919,7 +1003,7 @@ pub(crate) mod tests {
|
|||||||
};
|
};
|
||||||
use user::{CfdpUser, OwnedMetadataRecvdParams, TransactionFinishedParams};
|
use user::{CfdpUser, OwnedMetadataRecvdParams, TransactionFinishedParams};
|
||||||
|
|
||||||
use crate::{PacketTarget, StdCheckTimer};
|
use crate::{PacketTarget, StdCountdown};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -1269,7 +1353,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_std_check_timer() {
|
fn test_std_check_timer() {
|
||||||
let mut std_check_timer = StdCheckTimer::new(1);
|
let mut std_check_timer = StdCountdown::new(Duration::from_secs(1));
|
||||||
assert!(!std_check_timer.has_expired());
|
assert!(!std_check_timer.has_expired());
|
||||||
assert_eq!(std_check_timer.expiry_time_seconds(), 1);
|
assert_eq!(std_check_timer.expiry_time_seconds(), 1);
|
||||||
std::thread::sleep(Duration::from_millis(800));
|
std::thread::sleep(Duration::from_millis(800));
|
||||||
@ -1282,10 +1366,9 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_std_check_timer_creator() {
|
fn test_std_check_timer_creator() {
|
||||||
let std_check_timer_creator = StdCheckTimerCreator::new(1);
|
let std_check_timer_creator = StdTimerCreator::new(Duration::from_secs(1));
|
||||||
let check_timer =
|
let check_timer = std_check_timer_creator.create_countdown(TimerContext::NakActivity {
|
||||||
std_check_timer_creator.create_check_timer_provider(TimerContext::NakActivity {
|
expiry_time: Duration::from_secs(1),
|
||||||
expiry_time_seconds: 1.0,
|
|
||||||
});
|
});
|
||||||
assert_eq!(check_timer.expiry_time_seconds(), 1);
|
assert_eq!(check_timer.expiry_time_seconds(), 1);
|
||||||
}
|
}
|
||||||
@ -1463,7 +1546,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 = HashMap::new();
|
let mut map = hashbrown::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,6 +10,8 @@ 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
|
||||||
@ -167,6 +169,8 @@ 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> {
|
||||||
@ -236,6 +240,7 @@ 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>,
|
||||||
|
702
src/source.rs
702
src/source.rs
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,8 @@ 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,
|
||||||
|
@ -14,7 +14,7 @@ use cfdp::{
|
|||||||
source::SourceHandler,
|
source::SourceHandler,
|
||||||
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
|
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
|
||||||
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, RemoteEntityConfig,
|
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, RemoteEntityConfig,
|
||||||
StdCheckTimerCreator, TransactionId, UserFaultHookProvider,
|
StdTimerCreator, TransactionId, UserFaultHookProvider,
|
||||||
};
|
};
|
||||||
use spacepackets::{
|
use spacepackets::{
|
||||||
cfdp::{ChecksumType, ConditionCode, TransmissionMode},
|
cfdp::{ChecksumType, ConditionCode, TransmissionMode},
|
||||||
@ -202,6 +202,7 @@ fn end_to_end_test(with_closure: bool) {
|
|||||||
put_request_cacher,
|
put_request_cacher,
|
||||||
2048,
|
2048,
|
||||||
remote_cfg_of_dest,
|
remote_cfg_of_dest,
|
||||||
|
StdTimerCreator::default(),
|
||||||
seq_count_provider,
|
seq_count_provider,
|
||||||
);
|
);
|
||||||
let mut cfdp_user_source = ExampleCfdpUser::new(EntityType::Sending, completion_signal_source);
|
let mut cfdp_user_source = ExampleCfdpUser::new(EntityType::Sending, completion_signal_source);
|
||||||
@ -225,7 +226,7 @@ fn end_to_end_test(with_closure: bool) {
|
|||||||
dest_tx,
|
dest_tx,
|
||||||
NativeFilestore::default(),
|
NativeFilestore::default(),
|
||||||
remote_cfg_of_source,
|
remote_cfg_of_source,
|
||||||
StdCheckTimerCreator::default(),
|
StdTimerCreator::default(),
|
||||||
);
|
);
|
||||||
let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving, completion_signal_dest);
|
let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving, completion_signal_dest);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user