18 Commits

Author SHA1 Message Date
muellerr 70d8915c3f Update README feature list 2025-08-13 17:42:04 +02:00
muellerr 4f391d1139 add first cancellation tests for dest handler
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-10 12:27:19 +02:00
muellerr 82f8e22bd9 fix another test 2024-09-10 11:43:32 +02:00
muellerr cd03e5d18a dest handler cancel handling
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-09 21:19:52 +02:00
muellerr df97b38cc5 add additional test for dest handler
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-09 10:59:53 +02:00
muellerr a0b863f7d6 source handler test
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-07 11:59:18 +02:00
muellerr 467037c126 test stub
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-06 09:28:36 +02:00
muellerr 3575f331d4 doc corrections
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:58:40 +02:00
muellerr 8a331c2971 use better timer types
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:57:15 +02:00
muellerr a51ab5e878 some more renaming
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:49:50 +02:00
muellerr 80c91b59d3 some renaming
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:46:06 +02:00
muellerr 0766a0e6c9 add more error handling
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 14:44:56 +02:00
muellerr 69eed4a46d complete basic documentation
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 12:34:47 +02:00
muellerr 45163ff8aa dooocs
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 12:10:40 +02:00
muellerr 512abdd98a optimization and test stub 2024-09-05 00:34:36 +02:00
muellerr 08b9bf99a5 remove last std dependency 2024-09-05 00:25:02 +02:00
muellerr 5a4bd9710a remove one more path dependency
Rust/cfdp/pipeline/head There was a failure building this commit
2024-09-05 00:06:40 +02:00
muellerr befe3d66e0 init commit
Rust/cfdp/pipeline/head There was a failure building this commit
2024-08-29 15:23:47 +02:00
20 changed files with 2074 additions and 6795 deletions
+4 -8
View File
@@ -21,7 +21,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- name: Install nextest
uses: taiki-e/install-action@nextest
- run: cargo nextest run --features "serde, defmt"
- run: cargo nextest run --all-features
- run: cargo test --doc
msrv:
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.87
- uses: dtolnay/rust-toolchain@1.75.0
- run: cargo check --release
cross-check:
@@ -45,7 +45,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf"
- run: cargo check --release --target=${{matrix.target}} --no-default-features --features "packet-buf-1k, defmt"
- run: cargo check --release --target=${{matrix.target}} --no-default-features
fmt:
name: Check formatting
@@ -53,8 +53,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
docs:
@@ -63,7 +61,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features "serde, defmt" --no-deps
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features
clippy:
name: Clippy
@@ -71,6 +69,4 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy -- -D warnings
-27
View File
@@ -7,30 +7,3 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
- Bumped `spacepackets` to v0.17
# [v0.3.0] 2025-09-25
- Bumped `spacepackets` to v0.16
- Bumped `defmt` to v1
## Added
- Acknowledged mode support for both source and destination handler.
- `FaultInfo` structure which is passed to user fault callbacks.
# [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
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/cfdp/compare/v0.3.0...HEAD
[v0.3.0]: https://egit.irs.uni-stuttgart.de/rust/cfdp/compare/v0.2.0...v0.3.0
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/cfdp/compare/v0.1.0...v0.2.0
+28 -29
View File
@@ -1,8 +1,8 @@
[package]
name = "cfdp-rs"
version = "0.3.0"
edition = "2024"
rust-version = "1.87"
version = "0.1.0"
edition = "2021"
rust-version = "1.75.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "High level CCSDS File Delivery Protocol components"
homepage = "https://egit.irs.uni-stuttgart.de/rust/cfdp"
@@ -18,48 +18,47 @@ name = "cfdp"
[dependencies]
crc = "3"
smallvec = "1"
derive-new = ">=0.6, <=0.7"
hashbrown = { version = ">=0.14, <=0.15", optional = true }
spacepackets = { version = "0.17", default-features = false }
thiserror = { version = "2", default-features = false }
heapless = "0.9"
serde = { version = "1", optional = true }
defmt = { version = "1", optional = true }
derive-new = "0.6"
[dependencies.thiserror]
version = "1"
optional = true
[dependencies.hashbrown]
version = "0.14"
optional = true
[dependencies.serde]
version = "1"
optional = true
[dependencies.spacepackets]
version = "0.12"
default-features = false
git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
branch = "main"
[features]
default = ["std", "packet-buf-2k"]
default = ["std"]
std = [
"alloc",
"thiserror/std",
"thiserror",
"spacepackets/std"
]
alloc = [
"hashbrown",
"spacepackets/alloc"
]
serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde", "heapless/serde"]
defmt = ["dep:defmt", "spacepackets/defmt"]
# Available packet buffer sizes. Only one should be enabled.
# 256 bytes
packet-buf-256 = []
# 512 bytes
packet-buf-512 = []
# 1024 bytes
packet-buf-1k = []
# 2048 bytes
packet-buf-2k = []
# 4096 bytes
packet-buf-4k = []
serde = ["dep:serde", "spacepackets/serde"]
[dev-dependencies]
tempfile = "3"
rand = "0.9"
rand = "0.8"
log = "0.4"
fern = "0.7"
fern = "0.6"
chrono = "0.4"
clap = { version = "4", features = ["derive"] }
[package.metadata.docs.rs]
features = ["serde", "defmt"]
all-features = true
rustdoc-args = ["--generate-link-to-definition"]
+27 -10
View File
@@ -1,8 +1,7 @@
[![Crates.io](https://img.shields.io/crates/v/cfdp-rs)](https://crates.io/crates/cfdp-rs)
[![docs.rs](https://img.shields.io/docsrs/cfdp-rs)](https://docs.rs/cfdp-rs)
[![ci](https://github.com/us-irs/cfdp-rs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/us-irs/cfdp-rs/actions/workflows/ci.yml)
[![matrix chat](https://img.shields.io/matrix/sat-rs%3Amatrix.org)](https://matrix.to/#/#sat-rs:matrix.org)
<!-- Does not work right now, I'd need to host that myself. [![coverage](https://shields.io/endpoint?url=https://absatsw.irs.uni-stuttgart.de/projects/cfdp/coverage-rs/latest/coverage.json)](https://absatsw.irs.uni-stuttgart.de/projects/cfdp/coverage-rs/latest/index.html) -->
[![coverage](https://shields.io/endpoint?url=https://absatsw.irs.uni-stuttgart.de/projects/cfdp/coverage-rs/latest/coverage.json)](https://absatsw.irs.uni-stuttgart.de/projects/cfdp/coverage-rs/latest/index.html)
cfdp-rs - High level Rust crate for CFDP components
======================
@@ -14,20 +13,36 @@ The underlying base packet library used to generate the packets to be sent is th
# Features
`cfdp-rs` currently supports following high-level features:
`cfdp-rs` currently supports following features:
- Unacknowledged (class 1) file transfers for both source and destination side.
- Acknowledged (class 2) file transfers for both source and destination side.
The following features have not been implemented yet. PRs or notifications for demand are welcome!
- Acknowledged (class 2) file transfers for both source and destination side.
- Suspending transfers
- Inactivity handling
- Start and end of transmission and reception opportunity handling
- Keep Alive and Prompt PDU handling
Check out the [documentation](https://docs.rs/cfdp-rs) for more information on available
Rust features.
## 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.
- [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which require allocation support.
Enabled by the `std` feature.
### Optional Features
- [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s
- [`defmt`](https://defmt.ferrous-systems.com/): Add support for the `defmt` by adding the
[`defmt::Format`](https://defmt.ferrous-systems.com/format) derive on many types.
# Examples
@@ -36,11 +51,13 @@ examples.
# Coverage
Coverage can be generated using [`llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov). If you have not done so
already, install the tool:
Coverage was generated using [`grcov`](https://github.com/mozilla/grcov). If you have not done so
already, install the `llvm-tools-preview`:
```sh
cargo +stable install cargo-llvm-cov --locked
rustup component add llvm-tools-preview
cargo install grcov --locked
```
After this, you can run `cargo llvm-cov nextest` to run all the tests and display coverage.
After that, you can simply run `coverage.py` to test the project with coverage. You can optionally
supply the `--open` flag to open the coverage report in your webbrowser.
+1 -1
View File
@@ -20,7 +20,7 @@ def generate_cov_report(open_report: bool, format: str):
out_path = "./target/debug/lcov.info"
os.system(
f"grcov . -s . --binary-path ./target/debug/ -t {format} --branch --ignore-not-existing "
f"--ignore \"examples/*\" -o {out_path}"
f"-o {out_path}"
)
if format == "lcov":
os.system(
-37
View File
@@ -1,37 +0,0 @@
Python Interoperability Example for cfdp-rs
=======
This example application showcases the interoperability of the CFDP handlers written in Rust
with a Python implementation which uses [cfdp-py](https://github.com/us-irs/cfdp-py) library.
Both the Rust and the Python app exchange packet data units via a UDP interface and launch
both a destination and source handler. As such, they are both able to send and receive files.
Both applications can be started with the command line argument `-f` to initiate a file transfer.
You can run both applications with `-h` to get more information about the available options.
## Running the Python App
It is recommended to run the Python App in a dedicated virtual environment. For example, on a
Unix system you can use `uv venv` and then `source .venv/bin/activate` to create
and activate a virtual environment.
After that, you can install the required dependencies using
```sh
uv 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.
+9 -9
View File
@@ -16,11 +16,11 @@ from typing import Any, Dict, List, Tuple, Optional
from multiprocessing import Queue
from queue import Empty
from cfdppy.handler import DestHandler, RemoteEntityConfigTable, SourceHandler
from cfdppy.handler import DestHandler, RemoteEntityCfgTable, SourceHandler
from cfdppy.exceptions import InvalidDestinationId, SourceFileDoesNotExist
from cfdppy import (
CfdpUserBase,
LocalEntityConfig,
LocalEntityCfg,
PacketDestination,
PutRequest,
TransactionId,
@@ -31,8 +31,8 @@ from cfdppy.mib import (
CheckTimerProvider,
DefaultFaultHandlerBase,
EntityType,
IndicationConfig,
RemoteEntityConfig,
IndicationCfg,
RemoteEntityCfg,
)
from cfdppy.user import (
FileSegmentRecvdParams,
@@ -58,7 +58,7 @@ from spacepackets.util import ByteFieldU16, UnsignedByteField
PYTHON_ENTITY_ID = ByteFieldU16(1)
RUST_ENTITY_ID = ByteFieldU16(2)
# Enable all indications for both local and remote entity.
INDICATION_CFG = IndicationConfig()
INDICATION_CFG = IndicationCfg()
BASE_STR_SRC = "PY SRC"
BASE_STR_DEST = "PY DEST"
@@ -79,7 +79,7 @@ DEST_ENTITY_QUEUE = Queue()
# be sent by the UDP server.
TM_QUEUE = Queue()
REMOTE_CFG_OF_PY_ENTITY = RemoteEntityConfig(
REMOTE_CFG_OF_PY_ENTITY = RemoteEntityCfg(
entity_id=PYTHON_ENTITY_ID,
max_packet_len=MAX_PACKET_LEN,
max_file_segment_len=FILE_SEGMENT_SIZE,
@@ -585,7 +585,7 @@ def main():
logging.basicConfig(level=logging_level)
remote_cfg_table = RemoteEntityConfigTable()
remote_cfg_table = RemoteEntityCfgTable()
remote_cfg_table.add_config(REMOTE_CFG_OF_REMOTE_ENTITY)
src_fault_handler = CfdpFaultHandler(BASE_STR_SRC)
@@ -594,7 +594,7 @@ def main():
src_user = CfdpUser(BASE_STR_SRC, PUT_REQ_QUEUE)
check_timer_provider = CustomCheckTimerProvider()
source_handler = SourceHandler(
cfg=LocalEntityConfig(PYTHON_ENTITY_ID, INDICATION_CFG, src_fault_handler),
cfg=LocalEntityCfg(PYTHON_ENTITY_ID, INDICATION_CFG, src_fault_handler),
seq_num_provider=src_seq_count_provider,
remote_cfg_table=remote_cfg_table,
user=src_user,
@@ -614,7 +614,7 @@ def main():
dest_fault_handler = CfdpFaultHandler(BASE_STR_DEST)
dest_user = CfdpUser(BASE_STR_DEST, PUT_REQ_QUEUE)
dest_handler = DestHandler(
cfg=LocalEntityConfig(PYTHON_ENTITY_ID, INDICATION_CFG, dest_fault_handler),
cfg=LocalEntityCfg(PYTHON_ENTITY_ID, INDICATION_CFG, dest_fault_handler),
user=dest_user,
remote_cfg_table=remote_cfg_table,
check_timer_provider=check_timer_provider,
+54 -44
View File
@@ -3,36 +3,31 @@ use std::{
fs::OpenOptions,
io::{self, ErrorKind, Write},
net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket},
sync::{
atomic::{AtomicBool, AtomicU16},
mpsc,
},
sync::mpsc,
thread,
time::Duration,
};
use cfdp::{
EntityType, FaultInfo, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, PduProvider,
RemoteEntityConfig, StdTimerCreator, TransactionId, UserFaultHook,
dest::DestinationHandler,
filestore::NativeFilestore,
lost_segments::LostSegmentsList,
request::PutRequestOwned,
request::{PutRequestOwned, StaticPutRequestCacher},
source::SourceHandler,
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, PduProvider,
RemoteEntityConfig, StdTimerCreator, TransactionId, UserFaultHookProvider,
};
use clap::Parser;
use log::{debug, info, warn};
use spacepackets::{
cfdp::{
pdu::{file_data::FileDataPdu, metadata::MetadataPduReader, PduError},
ChecksumType, ConditionCode, TransmissionMode,
pdu::{PduError, file_data::FileDataPdu, metadata::MetadataPduReader},
},
util::UnsignedByteFieldU16,
seq_count::SeqCountProviderSyncU16,
util::{UnsignedByteFieldU16, UnsignedEnum},
};
static KILL_APP: AtomicBool = AtomicBool::new(false);
const PYTHON_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1);
const RUST_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2);
@@ -63,21 +58,43 @@ pub struct Cli {
#[derive(Default)]
pub struct ExampleFaultHandler {}
impl UserFaultHook for ExampleFaultHandler {
fn notice_of_suspension_cb(&mut self, fault_info: FaultInfo) {
panic!("unexpected suspension, {:?}", fault_info);
impl UserFaultHookProvider for ExampleFaultHandler {
fn notice_of_suspension_cb(
&mut self,
transaction_id: TransactionId,
cond: ConditionCode,
progress: u64,
) {
panic!(
"unexpected suspension of transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
}
fn notice_of_cancellation_cb(&mut self, fault_info: FaultInfo) {
panic!("unexpected cancellation, {:?}", fault_info);
fn notice_of_cancellation_cb(
&mut self,
transaction_id: TransactionId,
cond: ConditionCode,
progress: u64,
) {
panic!(
"unexpected cancellation of transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
}
fn abandoned_cb(&mut self, fault_info: FaultInfo) {
panic!("unexpected abandonment, {:?}", fault_info);
fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
panic!(
"unexpected abandonment of transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
}
fn ignore_cb(&mut self, fault_info: FaultInfo) {
panic!("unexpected ignore, {:?}", fault_info);
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
panic!(
"ignoring unexpected error in transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
}
}
@@ -214,7 +231,7 @@ impl UdpServer {
Ok(None)
} else {
Err(e.into())
};
}
}
};
let (_, from) = res;
@@ -240,7 +257,7 @@ impl UdpServer {
while let Ok(tm) = receiver.try_recv() {
debug!("Sending PDU: {:?}", tm);
pdu_printout(&tm);
let result = self.socket.send_to(tm.raw_pdu(), self.remote_addr());
let result = self.socket.send_to(tm.pdu(), self.remote_addr());
if let Err(e) = result {
warn!("Sending TM with UDP socket failed: {e}")
}
@@ -258,21 +275,20 @@ impl UdpServer {
fn pdu_printout(pdu: &PduOwnedWithInfo) {
match pdu.pdu_type() {
spacepackets::cfdp::PduType::FileDirective => match pdu.file_directive_type().unwrap() {
spacepackets::cfdp::pdu::FileDirectiveType::Eof => (),
spacepackets::cfdp::pdu::FileDirectiveType::Finished => (),
spacepackets::cfdp::pdu::FileDirectiveType::Ack => (),
spacepackets::cfdp::pdu::FileDirectiveType::Metadata => {
spacepackets::cfdp::pdu::FileDirectiveType::EofPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::FinishedPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::AckPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::MetadataPdu => {
let meta_pdu =
MetadataPduReader::new(pdu.raw_pdu()).expect("creating metadata pdu failed");
MetadataPduReader::new(pdu.pdu()).expect("creating metadata pdu failed");
debug!("Metadata PDU: {:?}", meta_pdu)
}
spacepackets::cfdp::pdu::FileDirectiveType::Nak => (),
spacepackets::cfdp::pdu::FileDirectiveType::Prompt => (),
spacepackets::cfdp::pdu::FileDirectiveType::KeepAlive => (),
spacepackets::cfdp::pdu::FileDirectiveType::NakPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::PromptPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::KeepAlivePdu => (),
},
spacepackets::cfdp::PduType::FileData => {
let fd_pdu =
FileDataPdu::from_bytes(pdu.raw_pdu()).expect("creating file data pdu failed");
let fd_pdu = FileDataPdu::from_bytes(pdu.pdu()).expect("creating file data pdu failed");
debug!("File data PDU: {:?}", fd_pdu);
}
}
@@ -313,6 +329,7 @@ fn main() {
);
let (source_tm_tx, source_tm_rx) = mpsc::channel::<PduOwnedWithInfo>();
let (dest_tm_tx, dest_tm_rx) = mpsc::channel::<PduOwnedWithInfo>();
let put_request_cacher = StaticPutRequestCacher::new(2048);
let remote_cfg_python = RemoteEntityConfig::new_with_default_values(
PYTHON_ID.into(),
1024,
@@ -321,11 +338,13 @@ fn main() {
spacepackets::cfdp::TransmissionMode::Unacknowledged,
ChecksumType::Crc32C,
);
let seq_count_provider = AtomicU16::default();
let seq_count_provider = SeqCountProviderSyncU16::default();
let mut source_handler = SourceHandler::new(
local_cfg_source,
source_tm_tx,
NativeFilestore::default(),
put_request_cacher,
2048,
remote_cfg_python,
StdTimerCreator::default(),
seq_count_provider,
@@ -339,11 +358,11 @@ fn main() {
);
let mut dest_handler = DestinationHandler::new(
local_cfg_dest,
1024,
dest_tm_tx,
NativeFilestore::default(),
remote_cfg_python,
StdTimerCreator::default(),
LostSegmentsList::default(),
);
let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving);
@@ -392,9 +411,6 @@ fn main() {
.expect("put request failed");
}
loop {
if KILL_APP.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
let mut next_delay = None;
let mut undelayed_call_count = 0;
let packet_info = match source_tc_rx.try_recv() {
@@ -437,9 +453,6 @@ fn main() {
loop {
let mut next_delay = None;
let mut undelayed_call_count = 0;
if KILL_APP.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
let packet_info = match dest_tc_rx.try_recv() {
Ok(pdu_with_info) => Some(pdu_with_info),
Err(e) => match e {
@@ -481,9 +494,6 @@ fn main() {
info!("Starting UDP server on {}", remote_addr);
loop {
loop {
if KILL_APP.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
match udp_server.try_recv_tc() {
Ok(result) => match result {
Some((pdu, _addr)) => {
+1 -1
View File
@@ -1 +1 @@
cfdp-py == 0.6.0
cfdp-py @ git+https://github.com/us-irs/cfdp-py.git@main
-35
View File
@@ -1,35 +0,0 @@
all: check build embedded clippy check-fmt docs test coverage
clippy:
cargo clippy -- -D warnings
fmt:
cargo fmt --all
check-fmt:
cargo fmt --all -- --check
check:
cargo check --features "serde, defmt"
test:
cargo nextest r --features "serde, defmt"
cargo test --doc
build:
cargo build --features "serde, defmt"
embedded:
cargo build --target thumbv7em-none-eabihf --no-default-features --features "defmt, packet-buf-1k"
docs:
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features "serde, defmt" --no-deps
docs-html:
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features "serde, defmt" --open
coverage:
cargo llvm-cov nextest
coverage-html:
cargo llvm-cov nextest --html --open
-21
View File
@@ -1,21 +0,0 @@
#[cfg(not(any(
feature = "packet-buf-256",
feature = "packet-buf-512",
feature = "packet-buf-1k",
feature = "packet-buf-2k",
feature = "packet-buf-4k"
)))]
compile_error!(
"One of the features `packet-buf-256`, `packet-buf-512`, `packet-buf-1k`, `packet-buf-2k`, or `packet-buf-4k` must be enabled."
);
#[cfg(feature = "packet-buf-256")]
pub const PACKET_BUF_LEN: usize = 256;
#[cfg(feature = "packet-buf-512")]
pub const PACKET_BUF_LEN: usize = 512;
#[cfg(feature = "packet-buf-1k")]
pub const PACKET_BUF_LEN: usize = 1024;
#[cfg(feature = "packet-buf-2k")]
pub const PACKET_BUF_LEN: usize = 2048;
#[cfg(feature = "packet-buf-4k")]
pub const PACKET_BUF_LEN: usize = 4096;
+618 -2881
View File
File diff suppressed because it is too large Load Diff
+99 -39
View File
@@ -1,37 +1,101 @@
use spacepackets::ByteConversionError;
use alloc::string::{String, ToString};
use core::fmt::Display;
use spacepackets::cfdp::ChecksumType;
use spacepackets::ByteConversionError;
#[cfg(feature = "std")]
use std::error::Error;
use std::path::Path;
#[cfg(feature = "std")]
pub use std_mod::*;
#[derive(Debug, thiserror::Error)]
#[cfg_attr(all(feature = "defmt", not(feature = "std")), derive(defmt::Format))]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum FilestoreError {
#[error("file does not exist")]
FileDoesNotExist,
#[error("file already exists")]
FileAlreadyExists,
#[error("directory does not exist")]
DirDoesNotExist,
#[error("permission error")]
Permission,
#[error("is not a file")]
IsNotFile,
#[error("is not a directory")]
IsNotDirectory,
#[error("byte conversion: {0}")]
ByteConversion(#[from] ByteConversionError),
#[error("IO error: {0})")]
#[cfg(feature = "std")]
Io(#[from] std::io::Error),
#[error("checksum type not implemented: {0:?}")]
ByteConversion(ByteConversionError),
Io {
raw_errno: Option<i32>,
string: String,
},
ChecksumTypeNotImplemented(ChecksumType),
#[error("utf8 error")]
Utf8Error,
#[error("other error")]
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 {
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>
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>;
@@ -121,7 +192,6 @@ pub mod std_mod {
use std::{
fs::{self, File, OpenOptions},
io::{BufReader, Read, Seek, SeekFrom, Write},
path::Path,
};
#[derive(Default)]
@@ -169,7 +239,10 @@ pub mod std_mod {
}
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(())
}
@@ -283,17 +356,6 @@ pub mod std_mod {
_ => 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 {
@@ -329,7 +391,7 @@ pub mod std_mod {
#[cfg(test)]
mod tests {
use std::{fs, path::Path, println, string::ToString};
use std::{fs, path::Path, println};
use super::*;
use alloc::format;
@@ -375,11 +437,9 @@ mod tests {
.create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap();
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
assert!(
NATIVE_FS
.is_dir(dir_path.as_path().to_str().unwrap())
.unwrap()
);
assert!(NATIVE_FS
.is_dir(dir_path.as_path().to_str().unwrap())
.unwrap());
}
#[test]
@@ -644,7 +704,7 @@ mod tests {
}
assert_eq!(
error.to_string(),
format!("byte conversion: {}", byte_conv_error)
format!("filestore error: {}", byte_conv_error)
);
} else {
panic!("unexpected error");
@@ -767,7 +827,7 @@ mod tests {
if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
assert_eq!(
error.to_string(),
format!("checksum type not implemented: {:?}", cksum_type)
format!("checksum {:?} not implemented", cksum_type)
);
} else {
panic!("unexpected error");
+252 -603
View File
File diff suppressed because it is too large Load Diff
-1361
View File
File diff suppressed because it is too large Load Diff
+177 -244
View File
@@ -1,12 +1,7 @@
//! # Request module
#![deny(missing_docs)]
use core::str::Utf8Error;
use spacepackets::{
ByteConversionError,
cfdp::{
tlv::{GenericTlv, Tlv, TlvType},
SegmentationControl, TransmissionMode,
tlv::{GenericTlv, ReadableTlv as _, Tlv, TlvType, WritableTlv as _},
},
util::UnsignedByteField,
};
@@ -14,67 +9,42 @@ use spacepackets::{
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
/// File path is too large.
///
/// The file path length is limited to 255 bytes.
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FilePathTooLarge(pub usize);
/// This trait is an abstraction for different Put Request structures which can be used
/// by Put Request consumers.
pub trait ReadablePutRequest {
/// Destination entity ID.
fn destination_id(&self) -> UnsignedByteField;
/// Source file path.
fn source_file(&self) -> Option<&str>;
/// Destination file path.
fn dest_file(&self) -> Option<&str>;
/// Transmission mode if explicitely specified.
fn trans_mode(&self) -> Option<TransmissionMode>;
/// Closure is requested for unacknowledged file transfer.
fn closure_requested(&self) -> Option<bool>;
/// Segmentation control.
fn seg_ctrl(&self) -> Option<SegmentationControl>;
/// Iterator over Messages to User TLVs, if any are supplied.
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv<'_>>>;
/// Iterator over fault handler override TLVs, if any are supplied.
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv<'_>>>;
/// Flow label TLV, if it is supplied.
fn flow_label(&self) -> Option<Tlv<'_>>;
/// Iterator over filestore request TLVs, if any are supplied.
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv<'_>>>;
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv>>;
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv>>;
fn flow_label(&self) -> Option<Tlv>;
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv>>;
}
/// Put request structure.
#[derive(Debug, PartialEq, Eq)]
pub struct PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> {
/// Destination entity ID.
pub destination_id: UnsignedByteField,
source_file: Option<&'src_file str>,
dest_file: Option<&'dest_file str>,
/// Transmission mode.
pub trans_mode: Option<TransmissionMode>,
/// Closure requested flag for unacknowledged file transfer.
pub closure_requested: Option<bool>,
/// Segmentation control.
pub seg_ctrl: Option<SegmentationControl>,
/// Messages to user TLVs.
pub msgs_to_user: Option<&'msgs_to_user [Tlv<'msgs_to_user>]>,
/// Fault handler override TLVs.
pub fault_handler_overrides: Option<&'fh_ovrds [Tlv<'fh_ovrds>]>,
/// Flow label TLV.
pub flow_label: Option<Tlv<'flow_label>>,
/// Filestore request TLVs.
pub fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>,
}
impl<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests>
PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests>
{
/// Create a new put request with all possible fields.
#[allow(clippy::too_many_arguments)]
pub fn new(
destination_id: UnsignedByteField,
@@ -129,25 +99,25 @@ impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> {
self.seg_ctrl
}
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv<'_>>> {
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(msgs_to_user) = self.msgs_to_user {
return Some(msgs_to_user.iter().copied());
}
None
}
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv<'_>>> {
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(fh_overrides) = self.fault_handler_overrides {
return Some(fh_overrides.iter().copied());
}
None
}
fn flow_label(&self) -> Option<Tlv<'_>> {
fn flow_label(&self) -> Option<Tlv> {
self.flow_label
}
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv<'_>>> {
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(fs_requests) = self.msgs_to_user {
return Some(fs_requests.iter().copied());
}
@@ -155,9 +125,6 @@ impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> {
}
}
/// Generic path checks.
///
/// This only checks the length of the paths.
pub fn generic_path_checks(
source_file: Option<&str>,
dest_file: Option<&str>,
@@ -176,7 +143,6 @@ pub fn generic_path_checks(
}
impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static, 'static, 'static> {
/// New regular put request with no additional TLVs.
pub fn new_regular_request(
dest_id: UnsignedByteField,
source_file: &'src_file str,
@@ -200,14 +166,10 @@ impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static,
}
}
/// TLV has invalid type.
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TlvWithInvalidType(pub(crate) ());
impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static, 'static> {
/// New put request which only contains messages to the user TLVs.
pub fn new_msgs_to_user_only(
dest_id: UnsignedByteField,
msgs_to_user: &'msgs_to_user [Tlv<'msgs_to_user>],
@@ -243,7 +205,6 @@ impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static
}
}
/// Generic check of the TLV list type.
pub fn generic_tlv_list_type_check<TlvProvider: GenericTlv>(
opt_tlvs: Option<&[TlvProvider]>,
tlv_type: TlvType,
@@ -261,216 +222,34 @@ pub fn generic_tlv_list_type_check<TlvProvider: GenericTlv>(
true
}
/// Structure for all static put request fields.
pub struct StaticPutRequestFields {
/// Destination entity ID.
pub destination_id: UnsignedByteField,
/// Static buffer to store source file path.
pub source_file_buf: [u8; u8::MAX as usize],
/// Current source path length.
pub source_file_len: usize,
/// Static buffer to store dest file path.
pub dest_file_buf: [u8; u8::MAX as usize],
/// Current destination path length.
pub dest_file_len: usize,
/// Transmission mode.
pub trans_mode: Option<TransmissionMode>,
/// Closure requested flag for unacknowledged file transfer.
pub closure_requested: Option<bool>,
/// Segmentation control.
pub seg_ctrl: Option<SegmentationControl>,
}
impl Default for StaticPutRequestFields {
fn default() -> Self {
Self {
destination_id: UnsignedByteField::new(0, 0),
source_file_buf: [0; u8::MAX as usize],
source_file_len: Default::default(),
dest_file_buf: [0; u8::MAX as usize],
dest_file_len: Default::default(),
trans_mode: Default::default(),
closure_requested: Default::default(),
seg_ctrl: Default::default(),
}
}
}
impl StaticPutRequestFields {
/// Clears and resets the fields.
pub fn clear(&mut self) {
self.destination_id = UnsignedByteField::new(0, 0);
self.source_file_len = 0;
self.dest_file_len = 0;
self.trans_mode = None;
self.closure_requested = None;
self.seg_ctrl = None;
}
}
/// This is a put request cache structure which can be used to cache [ReadablePutRequest]s
/// without requiring run-time allocation.
///
/// The user must specify the static buffer sizes used to store TLVs or list of TLVs.
pub struct StaticPutRequestCacher<const BUF_SIZE: usize> {
/// Static fields.
pub static_fields: StaticPutRequestFields,
opts_buf: [u8; BUF_SIZE],
opts_len: usize,
}
impl<const BUF_SIZE: usize> Default for StaticPutRequestCacher<BUF_SIZE> {
fn default() -> Self {
Self::new()
}
}
impl<const BUF_SIZE: usize> StaticPutRequestCacher<BUF_SIZE> {
/// Constructor.
pub fn new() -> Self {
Self {
static_fields: StaticPutRequestFields::default(),
opts_buf: [0; BUF_SIZE],
opts_len: 0,
}
}
/// Set and update with using any generic [ReadablePutRequest].
pub fn set(
&mut self,
put_request: &impl ReadablePutRequest,
) -> Result<(), ByteConversionError> {
self.static_fields.destination_id = put_request.destination_id();
if let Some(source_file) = put_request.source_file() {
if source_file.len() > u8::MAX as usize {
return Err(ByteConversionError::ToSliceTooSmall {
found: self.static_fields.source_file_buf.len(),
expected: source_file.len(),
});
}
self.static_fields.source_file_buf[..source_file.len()]
.copy_from_slice(source_file.as_bytes());
self.static_fields.source_file_len = source_file.len();
}
if let Some(dest_file) = put_request.dest_file() {
if dest_file.len() > u8::MAX as usize {
return Err(ByteConversionError::ToSliceTooSmall {
found: self.static_fields.source_file_buf.len(),
expected: dest_file.len(),
});
}
self.static_fields.dest_file_buf[..dest_file.len()]
.copy_from_slice(dest_file.as_bytes());
self.static_fields.dest_file_len = dest_file.len();
}
self.static_fields.trans_mode = put_request.trans_mode();
self.static_fields.closure_requested = put_request.closure_requested();
self.static_fields.seg_ctrl = put_request.seg_ctrl();
let mut current_idx = 0;
let mut store_tlv = |tlv: &Tlv| {
if current_idx + tlv.len_full() > self.opts_buf.len() {
return Err(ByteConversionError::ToSliceTooSmall {
found: self.opts_buf.len(),
expected: current_idx + tlv.len_full(),
});
}
// We checked the buffer lengths, so this should never fail.
tlv.write_to_bytes(&mut self.opts_buf[current_idx..current_idx + tlv.len_full()])
.unwrap();
current_idx += tlv.len_full();
Ok(())
};
if let Some(fs_req) = put_request.fs_requests() {
for fs_req in fs_req {
store_tlv(&fs_req)?;
}
}
if let Some(msgs_to_user) = put_request.msgs_to_user() {
for msg_to_user in msgs_to_user {
store_tlv(&msg_to_user)?;
}
}
self.opts_len = current_idx;
Ok(())
}
/// Does the put request have a source file?
pub fn has_source_file(&self) -> bool {
self.static_fields.source_file_len > 0
}
/// Does the put request have a destination file?
pub fn has_dest_file(&self) -> bool {
self.static_fields.dest_file_len > 0
}
/// Source file path.
pub fn source_file(&self) -> Result<&str, Utf8Error> {
core::str::from_utf8(
&self.static_fields.source_file_buf[0..self.static_fields.source_file_len],
)
}
/// Destination file path.
pub fn dest_file(&self) -> Result<&str, Utf8Error> {
core::str::from_utf8(&self.static_fields.dest_file_buf[0..self.static_fields.dest_file_len])
}
/// Length of stored options TLVs.
pub fn opts_len(&self) -> usize {
self.opts_len
}
/// Raw options slice.
pub fn opts_slice(&self) -> &[u8] {
&self.opts_buf[0..self.opts_len]
}
/// This clears the cacher structure. This is a cheap operation because it only
/// sets [Option]al values to [None] and the length of stores TLVs to 0.
///
/// Please note that this method will not set the values in the buffer to 0.
pub fn clear(&mut self) {
self.static_fields.clear();
self.opts_len = 0;
}
}
/// [alloc] support module.
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use core::str::Utf8Error;
use super::*;
use alloc::string::ToString;
use spacepackets::cfdp::tlv::{TlvOwned, msg_to_user::MsgToUserTlv};
use spacepackets::{
cfdp::tlv::{msg_to_user::MsgToUserTlv, ReadableTlv, TlvOwned, WritableTlv},
ByteConversionError,
};
/// Owned variant of [PutRequest] with no lifetimes which is also [Clone]able.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PutRequestOwned {
/// Destination entity ID.
pub destination_id: UnsignedByteField,
source_file: Option<alloc::string::String>,
dest_file: Option<alloc::string::String>,
/// Transmission mode.
pub trans_mode: Option<TransmissionMode>,
/// Closure requested flag for unacknowledged file transfer.
pub closure_requested: Option<bool>,
/// Segmentation control.
pub seg_ctrl: Option<SegmentationControl>,
/// Messages to user TLVs.
pub msgs_to_user: Option<alloc::vec::Vec<TlvOwned>>,
/// Fault handler override TLVs.
pub fault_handler_overrides: Option<alloc::vec::Vec<TlvOwned>>,
/// Flow label TLV.
pub flow_label: Option<TlvOwned>,
/// Filestore request TLVs.
pub fs_requests: Option<alloc::vec::Vec<TlvOwned>>,
}
impl PutRequestOwned {
/// New regular put request with no additional TLVs.
pub fn new_regular_request(
dest_id: UnsignedByteField,
source_file: &str,
@@ -498,7 +277,6 @@ pub mod alloc_mod {
})
}
/// New put request which only contains messages to the user TLVs.
pub fn new_msgs_to_user_only(
dest_id: UnsignedByteField,
msgs_to_user: &[MsgToUserTlv<'_>],
@@ -587,31 +365,186 @@ pub mod alloc_mod {
self.seg_ctrl
}
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv<'_>>> {
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(msgs_to_user) = &self.msgs_to_user {
return Some(msgs_to_user.iter().map(|tlv_owned| tlv_owned.as_tlv()));
}
None
}
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv<'_>>> {
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(fh_overrides) = &self.fault_handler_overrides {
return Some(fh_overrides.iter().map(|tlv_owned| tlv_owned.as_tlv()));
}
None
}
fn flow_label(&self) -> Option<Tlv<'_>> {
fn flow_label(&self) -> Option<Tlv> {
self.flow_label.as_ref().map(|tlv| tlv.as_tlv())
}
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv<'_>>> {
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv>> {
if let Some(requests) = &self.fs_requests {
return Some(requests.iter().map(|tlv_owned| tlv_owned.as_tlv()));
}
None
}
}
pub struct StaticPutRequestFields {
pub destination_id: UnsignedByteField,
/// Static buffer to store source file path.
pub source_file_buf: [u8; u8::MAX as usize],
/// Current source path length.
pub source_file_len: usize,
/// Static buffer to store dest file path.
pub dest_file_buf: [u8; u8::MAX as usize],
/// Current destination path length.
pub dest_file_len: usize,
pub trans_mode: Option<TransmissionMode>,
pub closure_requested: Option<bool>,
pub seg_ctrl: Option<SegmentationControl>,
}
impl Default for StaticPutRequestFields {
fn default() -> Self {
Self {
destination_id: UnsignedByteField::new(0, 0),
source_file_buf: [0; u8::MAX as usize],
source_file_len: Default::default(),
dest_file_buf: [0; u8::MAX as usize],
dest_file_len: Default::default(),
trans_mode: Default::default(),
closure_requested: Default::default(),
seg_ctrl: Default::default(),
}
}
}
impl StaticPutRequestFields {
pub fn clear(&mut self) {
self.destination_id = UnsignedByteField::new(0, 0);
self.source_file_len = 0;
self.dest_file_len = 0;
self.trans_mode = None;
self.closure_requested = None;
self.seg_ctrl = None;
}
}
/// This is a put request cache structure which can be used to cache [ReadablePutRequest]s
/// without requiring run-time allocation. The user must specify the static buffer sizes used
/// to store TLVs or list of TLVs.
pub struct StaticPutRequestCacher {
pub static_fields: StaticPutRequestFields,
opts_buf: alloc::vec::Vec<u8>,
opts_len: usize, // fs_request_start_end_pos: Option<(usize, usize)>
}
impl StaticPutRequestCacher {
pub fn new(max_len_opts_buf: usize) -> Self {
Self {
static_fields: StaticPutRequestFields::default(),
opts_buf: alloc::vec![0; max_len_opts_buf],
opts_len: 0,
}
}
pub fn set(
&mut self,
put_request: &impl ReadablePutRequest,
) -> Result<(), ByteConversionError> {
self.static_fields.destination_id = put_request.destination_id();
if let Some(source_file) = put_request.source_file() {
if source_file.len() > u8::MAX as usize {
return Err(ByteConversionError::ToSliceTooSmall {
found: self.static_fields.source_file_buf.len(),
expected: source_file.len(),
});
}
self.static_fields.source_file_buf[..source_file.len()]
.copy_from_slice(source_file.as_bytes());
self.static_fields.source_file_len = source_file.len();
}
if let Some(dest_file) = put_request.dest_file() {
if dest_file.len() > u8::MAX as usize {
return Err(ByteConversionError::ToSliceTooSmall {
found: self.static_fields.source_file_buf.len(),
expected: dest_file.len(),
});
}
self.static_fields.dest_file_buf[..dest_file.len()]
.copy_from_slice(dest_file.as_bytes());
self.static_fields.dest_file_len = dest_file.len();
}
self.static_fields.trans_mode = put_request.trans_mode();
self.static_fields.closure_requested = put_request.closure_requested();
self.static_fields.seg_ctrl = put_request.seg_ctrl();
let mut current_idx = 0;
let mut store_tlv = |tlv: &Tlv| {
if current_idx + tlv.len_full() > self.opts_buf.len() {
return Err(ByteConversionError::ToSliceTooSmall {
found: self.opts_buf.len(),
expected: current_idx + tlv.len_full(),
});
}
// We checked the buffer lengths, so this should never fail.
tlv.write_to_bytes(&mut self.opts_buf[current_idx..current_idx + tlv.len_full()])
.unwrap();
current_idx += tlv.len_full();
Ok(())
};
if let Some(fs_req) = put_request.fs_requests() {
for fs_req in fs_req {
store_tlv(&fs_req)?;
}
}
if let Some(msgs_to_user) = put_request.msgs_to_user() {
for msg_to_user in msgs_to_user {
store_tlv(&msg_to_user)?;
}
}
self.opts_len = current_idx;
Ok(())
}
pub fn has_source_file(&self) -> bool {
self.static_fields.source_file_len > 0
}
pub fn has_dest_file(&self) -> bool {
self.static_fields.dest_file_len > 0
}
pub fn source_file(&self) -> Result<&str, Utf8Error> {
core::str::from_utf8(
&self.static_fields.source_file_buf[0..self.static_fields.source_file_len],
)
}
pub fn dest_file(&self) -> Result<&str, Utf8Error> {
core::str::from_utf8(
&self.static_fields.dest_file_buf[0..self.static_fields.dest_file_len],
)
}
pub fn opts_len(&self) -> usize {
self.opts_len
}
pub fn opts_slice(&self) -> &[u8] {
&self.opts_buf[0..self.opts_len]
}
/// This clears the cacher structure. This is a cheap operation because it only
/// sets [Option]al values to [None] and the length of stores TLVs to 0.
///
/// Please note that this method will not set the values in the buffer to 0.
pub fn clear(&mut self) {
self.static_fields.clear();
self.opts_len = 0;
}
}
}
#[cfg(test)]
@@ -619,7 +552,7 @@ mod tests {
use std::string::String;
use spacepackets::{
cfdp::tlv::{ReadableTlv, msg_to_user::MsgToUserTlv},
cfdp::tlv::{msg_to_user::MsgToUserTlv, ReadableTlv},
util::UbfU16,
};
@@ -751,7 +684,7 @@ mod tests {
#[test]
fn test_put_request_cacher_basic() {
let put_request_cached = StaticPutRequestCacher::<128>::new();
let put_request_cached = StaticPutRequestCacher::new(128);
assert_eq!(put_request_cached.static_fields.source_file_len, 0);
assert_eq!(put_request_cached.static_fields.dest_file_len, 0);
assert_eq!(put_request_cached.opts_len(), 0);
@@ -760,7 +693,7 @@ mod tests {
#[test]
fn test_put_request_cacher_set() {
let mut put_request_cached = StaticPutRequestCacher::<128>::new();
let mut put_request_cached = StaticPutRequestCacher::new(128);
let src_file = "/tmp/hello.txt";
let dest_file = "/tmp/hello2.txt";
let put_request =
@@ -782,7 +715,7 @@ mod tests {
#[test]
fn test_put_request_cacher_set_and_clear() {
let mut put_request_cached = StaticPutRequestCacher::<128>::new();
let mut put_request_cached = StaticPutRequestCacher::new(128);
let src_file = "/tmp/hello.txt";
let dest_file = "/tmp/hello2.txt";
let put_request =
+751 -1353
View File
File diff suppressed because it is too large Load Diff
+2 -6
View File
@@ -1,11 +1,7 @@
//! # Time support module.
#![deny(missing_docs)]
use core::fmt::Debug;
/// Generic abstraction for a check/countdown timer. Should also be cheap to copy and clone.
pub trait Countdown: Debug {
/// The countdown has expired.
/// Generic abstraction for a check/countdown timer.
pub trait CountdownProvider: Debug {
fn has_expired(&self) -> bool;
/// Reset the countdown to its initial state.
fn reset(&mut self);
}
+2 -53
View File
@@ -1,68 +1,45 @@
//! # User support and hooks module
#![deny(missing_docs)]
#[cfg(feature = "alloc")]
use spacepackets::cfdp::tlv::WritableTlv;
use spacepackets::{
cfdp::{
ConditionCode,
pdu::{
file_data::SegmentMetadata,
finished::{DeliveryCode, FileStatus},
},
tlv::msg_to_user::MsgToUserTlv,
ConditionCode,
},
util::UnsignedByteField,
};
use super::TransactionId;
/// Parameters related to a finished transfer.
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TransactionFinishedParams {
/// ID of the transfer.
pub id: TransactionId,
/// Condition code.
pub condition_code: ConditionCode,
/// Delivery code.
pub delivery_code: DeliveryCode,
/// File status.
pub file_status: FileStatus,
}
/// Parameters related to the reception of a metadata PDU, which might start file reception.
#[derive(Debug)]
pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> {
/// ID of the transfer.
pub id: TransactionId,
/// Source entity ID.
pub source_id: UnsignedByteField,
/// File size.
pub file_size: u64,
/// Source file name.
pub src_file_name: &'src_file str,
/// Destination file name.
pub dest_file_name: &'dest_file str,
/// Messages to user TLVs.
pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>],
}
/// Owned variant of [MetadataReceivedParams].
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct OwnedMetadataRecvdParams {
/// ID of the transfer.
pub id: TransactionId,
/// Source entity ID.
pub source_id: UnsignedByteField,
/// File size.
pub file_size: u64,
/// Source file name.
pub src_file_name: alloc::string::String,
/// Destination file name.
pub dest_file_name: alloc::string::String,
/// Messages to user TLVs.
pub msgs_to_user: alloc::vec::Vec<alloc::vec::Vec<u8>>,
}
@@ -87,63 +64,35 @@ impl From<&MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
}
}
/// Parameters related to the reception of a file segment PDU.
#[derive(Debug)]
pub struct FileSegmentRecvdParams<'seg_meta> {
/// ID of the transfer.
pub id: TransactionId,
/// Offset of the segment.
pub offset: u64,
/// Length of the segment.
pub length: usize,
/// Segment metadata, if present.
pub segment_metadata: Option<&'seg_meta SegmentMetadata<'seg_meta>>,
}
/// Generic CFDP user as specified in the CFDP standard.
///
/// This trait declares all indications which are possible.
pub trait CfdpUser {
/// Indication that a new transaction has started.
fn transaction_indication(&mut self, id: &TransactionId);
/// Indication that an EOF PDU has been sent.
fn eof_sent_indication(&mut self, id: &TransactionId);
/// Indication that a transaction has finished.
fn transaction_finished_indication(&mut self, finished_params: &TransactionFinishedParams);
/// Indication that metadata has been received.
fn metadata_recvd_indication(&mut self, md_recvd_params: &MetadataReceivedParams);
/// Indication that a file segment has been received.
fn file_segment_recvd_indication(&mut self, segment_recvd_params: &FileSegmentRecvdParams);
// TODO: The standard does not strictly specify how the report information looks..
/// Report information indication.
fn report_indication(&mut self, id: &TransactionId);
/// Indication that a transfer has been suspended.
fn suspended_indication(&mut self, id: &TransactionId, condition_code: ConditionCode);
/// Indication that a transfer has been resumed.
fn resumed_indication(&mut self, id: &TransactionId, progress: u64);
/// Indication that a fault has occured.
fn fault_indication(
&mut self,
id: &TransactionId,
condition_code: ConditionCode,
progress: u64,
);
/// Indication that a transfer has been abandoned.
fn abandoned_indication(
&mut self,
id: &TransactionId,
condition_code: ConditionCode,
progress: u64,
);
/// Indication that an EOF PDU has been received.
fn eof_recvd_indication(&mut self, id: &TransactionId);
}
+49 -33
View File
@@ -2,27 +2,23 @@
use std::{
fs::OpenOptions,
io::Write,
sync::{
Arc,
atomic::{AtomicBool, AtomicU16},
mpsc,
},
sync::{atomic::AtomicBool, mpsc, Arc},
thread,
time::Duration,
};
use cfdp::{
EntityType, FaultInfo, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo,
RemoteEntityConfig, StdTimerCreator, TransactionId, UserFaultHook,
dest::DestinationHandler,
filestore::NativeFilestore,
lost_segments::LostSegmentsList,
request::PutRequestOwned,
request::{PutRequestOwned, StaticPutRequestCacher},
source::SourceHandler,
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, RemoteEntityConfig,
StdTimerCreator, TransactionId, UserFaultHookProvider,
};
use spacepackets::{
cfdp::{ChecksumType, ConditionCode, TransmissionMode},
seq_count::SeqCountProviderSyncU16,
util::UnsignedByteFieldU16,
};
@@ -34,21 +30,43 @@ const FILE_DATA: &str = "Hello World!";
#[derive(Default)]
pub struct ExampleFaultHandler {}
impl UserFaultHook for ExampleFaultHandler {
fn notice_of_suspension_cb(&mut self, fault_info: FaultInfo) {
panic!("unexpected suspension, {:?}", fault_info);
impl UserFaultHookProvider for ExampleFaultHandler {
fn notice_of_suspension_cb(
&mut self,
transaction_id: TransactionId,
cond: ConditionCode,
progress: u64,
) {
panic!(
"unexpected suspension of transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
}
fn notice_of_cancellation_cb(&mut self, fault_info: FaultInfo) {
panic!("unexpected cancellation, {:?}", fault_info);
fn notice_of_cancellation_cb(
&mut self,
transaction_id: TransactionId,
cond: ConditionCode,
progress: u64,
) {
panic!(
"unexpected cancellation of transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
}
fn abandoned_cb(&mut self, fault_info: FaultInfo) {
panic!("unexpected abandonment, {:?}", fault_info);
fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
panic!(
"unexpected abandonment of transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
}
fn ignore_cb(&mut self, fault_info: FaultInfo) {
panic!("unexpected ignore, {:?}", fault_info);
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
panic!(
"ignoring unexpected error in transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
}
}
@@ -138,7 +156,7 @@ impl CfdpUser for ExampleCfdpUser {
}
}
fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
fn end_to_end_test(with_closure: bool) {
// Simplified event handling using atomic signals.
let stop_signal_source = Arc::new(AtomicBool::new(false));
let stop_signal_dest = stop_signal_source.clone();
@@ -167,19 +185,22 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
);
let (source_tx, source_rx) = mpsc::channel::<PduOwnedWithInfo>();
let (dest_tx, dest_rx) = mpsc::channel::<PduOwnedWithInfo>();
let put_request_cacher = StaticPutRequestCacher::new(2048);
let remote_cfg_of_dest = RemoteEntityConfig::new_with_default_values(
REMOTE_ID.into(),
1024,
with_closure,
false,
transmission_mode,
spacepackets::cfdp::TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
let seq_count_provider = AtomicU16::default();
let seq_count_provider = SeqCountProviderSyncU16::default();
let mut source_handler = SourceHandler::new(
local_cfg_source,
source_tx,
NativeFilestore::default(),
put_request_cacher,
2048,
remote_cfg_of_dest,
StdTimerCreator::default(),
seq_count_provider,
@@ -196,16 +217,16 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
1024,
true,
false,
transmission_mode,
spacepackets::cfdp::TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
let mut dest_handler = DestinationHandler::new(
local_cfg_dest,
1024,
dest_tx,
NativeFilestore::default(),
remote_cfg_of_source,
StdTimerCreator::default(),
LostSegmentsList::default(),
);
let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving, completion_signal_dest);
@@ -213,7 +234,7 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
REMOTE_ID.into(),
srcfile.to_str().expect("invaid path string"),
destfile.to_str().expect("invaid path string"),
Some(transmission_mode),
Some(TransmissionMode::Unacknowledged),
Some(with_closure),
)
.expect("put request creation failed");
@@ -322,16 +343,11 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
}
#[test]
fn end_to_end_unacknowledged_no_closure() {
end_to_end_test(TransmissionMode::Unacknowledged, false);
fn end_to_end_test_no_closure() {
end_to_end_test(false);
}
#[test]
fn end_to_end_unacknowledged_with_closure() {
end_to_end_test(TransmissionMode::Unacknowledged, true);
}
#[test]
fn end_to_end_acknowledged() {
end_to_end_test(TransmissionMode::Acknowledged, true);
fn end_to_end_test_with_closure() {
end_to_end_test(true);
}