1 Commits

Author SHA1 Message Date
c6df24b947 readme update 2025-08-13 18:18:52 +02:00
21 changed files with 1927 additions and 6391 deletions

View File

@@ -21,7 +21,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- name: Install nextest - name: Install nextest
uses: taiki-e/install-action@nextest uses: taiki-e/install-action@nextest
- run: cargo nextest run --features "serde, defmt" - run: cargo nextest run --all-features
- run: cargo test --doc - run: cargo test --doc
msrv: msrv:
@@ -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.87 - uses: dtolnay/rust-toolchain@1.81.0
- run: cargo check --release - run: cargo check --release
cross-check: cross-check:
@@ -45,7 +45,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf" 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: fmt:
name: Check formatting name: Check formatting
@@ -53,8 +53,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check - run: cargo fmt --all -- --check
docs: docs:
@@ -63,7 +61,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly - 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: clippy:
name: Clippy name: Clippy
@@ -71,6 +69,4 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy -- -D warnings - run: cargo clippy -- -D warnings

View File

@@ -8,18 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
- Bumped `spacepackets` to v0.17 - Bumped `spacepackets` to v0.15
# [v0.3.0] 2025-09-25
- Bumped `spacepackets` to v0.16
- Bumped `defmt` to v1 - 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 # [v0.2.0] 2024-11-26
- Bumped `thiserror` to v2 - Bumped `thiserror` to v2
@@ -30,7 +21,3 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [v0.1.0] 2024-09-11 # [v0.1.0] 2024-09-11
Initial release 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

View File

@@ -1,8 +1,8 @@
[package] [package]
name = "cfdp-rs" name = "cfdp-rs"
version = "0.3.0" version = "0.2.0"
edition = "2024" edition = "2021"
rust-version = "1.87" rust-version = "1.81.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "High level CCSDS File Delivery Protocol components" description = "High level CCSDS File Delivery Protocol components"
homepage = "https://egit.irs.uni-stuttgart.de/rust/cfdp" homepage = "https://egit.irs.uni-stuttgart.de/rust/cfdp"
@@ -20,14 +20,13 @@ crc = "3"
smallvec = "1" smallvec = "1"
derive-new = ">=0.6, <=0.7" derive-new = ">=0.6, <=0.7"
hashbrown = { version = ">=0.14, <=0.15", optional = true } hashbrown = { version = ">=0.14, <=0.15", optional = true }
spacepackets = { version = "0.17", default-features = false } spacepackets = { version = "0.15", default-features = false }
thiserror = { version = "2", default-features = false } thiserror = { version = "2", default-features = false }
heapless = "0.9"
serde = { version = "1", optional = true } serde = { version = "1", optional = true }
defmt = { version = "1", optional = true } defmt = { version = "1", optional = true }
[features] [features]
default = ["std", "packet-buf-2k"] default = ["std"]
std = [ std = [
"alloc", "alloc",
"thiserror/std", "thiserror/std",
@@ -37,21 +36,9 @@ alloc = [
"hashbrown", "hashbrown",
"spacepackets/alloc" "spacepackets/alloc"
] ]
serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde", "heapless/serde"] serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde"]
defmt = ["dep:defmt", "spacepackets/defmt"] 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 = []
[dev-dependencies] [dev-dependencies]
tempfile = "3" tempfile = "3"
rand = "0.9" rand = "0.9"
@@ -61,5 +48,5 @@ chrono = "0.4"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["serde", "defmt"] all-features = true
rustdoc-args = ["--generate-link-to-definition"] rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -1,8 +1,7 @@
[![Crates.io](https://img.shields.io/crates/v/cfdp-rs)](https://crates.io/crates/cfdp-rs) [![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) [![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) [![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) [![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)
<!-- 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) -->
cfdp-rs - High level Rust crate for CFDP components cfdp-rs - High level Rust crate for CFDP components
====================== ======================
@@ -17,17 +16,37 @@ The underlying base packet library used to generate the packets to be sent is th
`cfdp-rs` currently supports following high-level features: `cfdp-rs` currently supports following high-level features:
- Unacknowledged (class 1) file transfers for both source and destination side. - 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! 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 - Suspending transfers
- Inactivity handling - Inactivity handling
- Start and end of transmission and reception opportunity handling - Start and end of transmission and reception opportunity handling
- Keep Alive and Prompt PDU 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.
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 activated.
Please note that 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
- [`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 # Examples
@@ -36,11 +55,13 @@ examples.
# Coverage # Coverage
Coverage can be generated using [`llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov). If you have not done so Coverage was generated using [`grcov`](https://github.com/mozilla/grcov). If you have not done so
already, install the tool: already, install the `llvm-tools-preview`:
```sh ```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.

3
docs.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --all-features --open

View File

@@ -12,13 +12,13 @@ You can run both applications with `-h` to get more information about the availa
## Running the Python App ## Running the Python App
It is recommended to run the Python App in a dedicated virtual environment. For example, on a 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 Unix system you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
and activate a virtual environment. and activate a virtual environment.
After that, you can install the required dependencies using After that, you can install the required dependencies using
```sh ```sh
uv pip install -r requirements.txt pip install -r requirements.txt
``` ```
and then run the application using `./main.py` or `python3 main.py`. and then run the application using `./main.py` or `python3 main.py`.

View File

@@ -16,11 +16,11 @@ from typing import Any, Dict, List, Tuple, Optional
from multiprocessing import Queue from multiprocessing import Queue
from queue import Empty 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.exceptions import InvalidDestinationId, SourceFileDoesNotExist
from cfdppy import ( from cfdppy import (
CfdpUserBase, CfdpUserBase,
LocalEntityConfig, LocalEntityCfg,
PacketDestination, PacketDestination,
PutRequest, PutRequest,
TransactionId, TransactionId,
@@ -31,8 +31,8 @@ from cfdppy.mib import (
CheckTimerProvider, CheckTimerProvider,
DefaultFaultHandlerBase, DefaultFaultHandlerBase,
EntityType, EntityType,
IndicationConfig, IndicationCfg,
RemoteEntityConfig, RemoteEntityCfg,
) )
from cfdppy.user import ( from cfdppy.user import (
FileSegmentRecvdParams, FileSegmentRecvdParams,
@@ -58,7 +58,7 @@ from spacepackets.util import ByteFieldU16, UnsignedByteField
PYTHON_ENTITY_ID = ByteFieldU16(1) PYTHON_ENTITY_ID = ByteFieldU16(1)
RUST_ENTITY_ID = ByteFieldU16(2) RUST_ENTITY_ID = ByteFieldU16(2)
# Enable all indications for both local and remote entity. # Enable all indications for both local and remote entity.
INDICATION_CFG = IndicationConfig() INDICATION_CFG = IndicationCfg()
BASE_STR_SRC = "PY SRC" BASE_STR_SRC = "PY SRC"
BASE_STR_DEST = "PY DEST" BASE_STR_DEST = "PY DEST"
@@ -79,7 +79,7 @@ DEST_ENTITY_QUEUE = Queue()
# be sent by the UDP server. # be sent by the UDP server.
TM_QUEUE = Queue() TM_QUEUE = Queue()
REMOTE_CFG_OF_PY_ENTITY = RemoteEntityConfig( REMOTE_CFG_OF_PY_ENTITY = RemoteEntityCfg(
entity_id=PYTHON_ENTITY_ID, entity_id=PYTHON_ENTITY_ID,
max_packet_len=MAX_PACKET_LEN, max_packet_len=MAX_PACKET_LEN,
max_file_segment_len=FILE_SEGMENT_SIZE, max_file_segment_len=FILE_SEGMENT_SIZE,
@@ -585,7 +585,7 @@ def main():
logging.basicConfig(level=logging_level) logging.basicConfig(level=logging_level)
remote_cfg_table = RemoteEntityConfigTable() remote_cfg_table = RemoteEntityCfgTable()
remote_cfg_table.add_config(REMOTE_CFG_OF_REMOTE_ENTITY) remote_cfg_table.add_config(REMOTE_CFG_OF_REMOTE_ENTITY)
src_fault_handler = CfdpFaultHandler(BASE_STR_SRC) src_fault_handler = CfdpFaultHandler(BASE_STR_SRC)
@@ -594,7 +594,7 @@ def main():
src_user = CfdpUser(BASE_STR_SRC, PUT_REQ_QUEUE) src_user = CfdpUser(BASE_STR_SRC, PUT_REQ_QUEUE)
check_timer_provider = CustomCheckTimerProvider() check_timer_provider = CustomCheckTimerProvider()
source_handler = SourceHandler( 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, seq_num_provider=src_seq_count_provider,
remote_cfg_table=remote_cfg_table, remote_cfg_table=remote_cfg_table,
user=src_user, user=src_user,
@@ -614,7 +614,7 @@ def main():
dest_fault_handler = CfdpFaultHandler(BASE_STR_DEST) dest_fault_handler = CfdpFaultHandler(BASE_STR_DEST)
dest_user = CfdpUser(BASE_STR_DEST, PUT_REQ_QUEUE) dest_user = CfdpUser(BASE_STR_DEST, PUT_REQ_QUEUE)
dest_handler = DestHandler( 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, user=dest_user,
remote_cfg_table=remote_cfg_table, remote_cfg_table=remote_cfg_table,
check_timer_provider=check_timer_provider, check_timer_provider=check_timer_provider,

View File

@@ -3,36 +3,31 @@ use std::{
fs::OpenOptions, fs::OpenOptions,
io::{self, ErrorKind, Write}, io::{self, ErrorKind, Write},
net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket}, net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket},
sync::{ sync::mpsc,
atomic::{AtomicBool, AtomicU16},
mpsc,
},
thread, thread,
time::Duration, time::Duration,
}; };
use cfdp::{ use cfdp::{
EntityType, FaultInfo, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, PduProvider,
RemoteEntityConfig, StdTimerCreator, TransactionId, UserFaultHook,
dest::DestinationHandler, dest::DestinationHandler,
filestore::NativeFilestore, filestore::NativeFilestore,
lost_segments::LostSegmentsList, request::{PutRequestOwned, StaticPutRequestCacher},
request::PutRequestOwned,
source::SourceHandler, source::SourceHandler,
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams}, user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, PduProvider,
RemoteEntityConfig, StdTimerCreator, TransactionId, UserFaultHookProvider,
}; };
use clap::Parser; use clap::Parser;
use log::{debug, info, warn}; use log::{debug, info, warn};
use spacepackets::{ use spacepackets::{
cfdp::{ cfdp::{
pdu::{file_data::FileDataPdu, metadata::MetadataPduReader, PduError},
ChecksumType, ConditionCode, TransmissionMode, 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 PYTHON_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1);
const RUST_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2); const RUST_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2);
@@ -63,21 +58,43 @@ pub struct Cli {
#[derive(Default)] #[derive(Default)]
pub struct ExampleFaultHandler {} pub struct ExampleFaultHandler {}
impl UserFaultHook for ExampleFaultHandler { impl UserFaultHookProvider for ExampleFaultHandler {
fn notice_of_suspension_cb(&mut self, fault_info: FaultInfo) { fn notice_of_suspension_cb(
panic!("unexpected suspension, {:?}", fault_info); &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) { fn notice_of_cancellation_cb(
panic!("unexpected cancellation, {:?}", fault_info); &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) { fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
panic!("unexpected abandonment, {:?}", fault_info); panic!(
"unexpected abandonment of transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
} }
fn ignore_cb(&mut self, fault_info: FaultInfo) { fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
panic!("unexpected ignore, {:?}", fault_info); panic!(
"ignoring unexpected error in transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
} }
} }
@@ -214,7 +231,7 @@ impl UdpServer {
Ok(None) Ok(None)
} else { } else {
Err(e.into()) Err(e.into())
}; }
} }
}; };
let (_, from) = res; let (_, from) = res;
@@ -240,7 +257,7 @@ impl UdpServer {
while let Ok(tm) = receiver.try_recv() { while let Ok(tm) = receiver.try_recv() {
debug!("Sending PDU: {:?}", tm); debug!("Sending PDU: {:?}", tm);
pdu_printout(&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 { if let Err(e) = result {
warn!("Sending TM with UDP socket failed: {e}") warn!("Sending TM with UDP socket failed: {e}")
} }
@@ -258,21 +275,20 @@ impl UdpServer {
fn pdu_printout(pdu: &PduOwnedWithInfo) { fn pdu_printout(pdu: &PduOwnedWithInfo) {
match pdu.pdu_type() { match pdu.pdu_type() {
spacepackets::cfdp::PduType::FileDirective => match pdu.file_directive_type().unwrap() { spacepackets::cfdp::PduType::FileDirective => match pdu.file_directive_type().unwrap() {
spacepackets::cfdp::pdu::FileDirectiveType::Eof => (), spacepackets::cfdp::pdu::FileDirectiveType::EofPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::Finished => (), spacepackets::cfdp::pdu::FileDirectiveType::FinishedPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::Ack => (), spacepackets::cfdp::pdu::FileDirectiveType::AckPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::Metadata => { spacepackets::cfdp::pdu::FileDirectiveType::MetadataPdu => {
let meta_pdu = 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) debug!("Metadata PDU: {:?}", meta_pdu)
} }
spacepackets::cfdp::pdu::FileDirectiveType::Nak => (), spacepackets::cfdp::pdu::FileDirectiveType::NakPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::Prompt => (), spacepackets::cfdp::pdu::FileDirectiveType::PromptPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::KeepAlive => (), spacepackets::cfdp::pdu::FileDirectiveType::KeepAlivePdu => (),
}, },
spacepackets::cfdp::PduType::FileData => { spacepackets::cfdp::PduType::FileData => {
let fd_pdu = let fd_pdu = FileDataPdu::from_bytes(pdu.pdu()).expect("creating file data pdu failed");
FileDataPdu::from_bytes(pdu.raw_pdu()).expect("creating file data pdu failed");
debug!("File data PDU: {:?}", fd_pdu); debug!("File data PDU: {:?}", fd_pdu);
} }
} }
@@ -313,6 +329,7 @@ fn main() {
); );
let (source_tm_tx, source_tm_rx) = mpsc::channel::<PduOwnedWithInfo>(); let (source_tm_tx, source_tm_rx) = mpsc::channel::<PduOwnedWithInfo>();
let (dest_tm_tx, dest_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( let remote_cfg_python = RemoteEntityConfig::new_with_default_values(
PYTHON_ID.into(), PYTHON_ID.into(),
1024, 1024,
@@ -321,11 +338,13 @@ fn main() {
spacepackets::cfdp::TransmissionMode::Unacknowledged, spacepackets::cfdp::TransmissionMode::Unacknowledged,
ChecksumType::Crc32C, ChecksumType::Crc32C,
); );
let seq_count_provider = AtomicU16::default(); let seq_count_provider = SeqCountProviderSyncU16::default();
let mut source_handler = SourceHandler::new( let mut source_handler = SourceHandler::new(
local_cfg_source, local_cfg_source,
source_tm_tx, source_tm_tx,
NativeFilestore::default(), NativeFilestore::default(),
put_request_cacher,
2048,
remote_cfg_python, remote_cfg_python,
StdTimerCreator::default(), StdTimerCreator::default(),
seq_count_provider, seq_count_provider,
@@ -339,11 +358,11 @@ fn main() {
); );
let mut dest_handler = DestinationHandler::new( let mut dest_handler = DestinationHandler::new(
local_cfg_dest, local_cfg_dest,
1024,
dest_tm_tx, dest_tm_tx,
NativeFilestore::default(), NativeFilestore::default(),
remote_cfg_python, remote_cfg_python,
StdTimerCreator::default(), StdTimerCreator::default(),
LostSegmentsList::default(),
); );
let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving); let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving);
@@ -392,9 +411,6 @@ fn main() {
.expect("put request failed"); .expect("put request failed");
} }
loop { loop {
if KILL_APP.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
let mut next_delay = None; let mut next_delay = None;
let mut undelayed_call_count = 0; let mut undelayed_call_count = 0;
let packet_info = match source_tc_rx.try_recv() { let packet_info = match source_tc_rx.try_recv() {
@@ -437,9 +453,6 @@ fn main() {
loop { loop {
let mut next_delay = None; let mut next_delay = None;
let mut undelayed_call_count = 0; 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() { let packet_info = match dest_tc_rx.try_recv() {
Ok(pdu_with_info) => Some(pdu_with_info), Ok(pdu_with_info) => Some(pdu_with_info),
Err(e) => match e { Err(e) => match e {
@@ -481,9 +494,6 @@ fn main() {
info!("Starting UDP server on {}", remote_addr); info!("Starting UDP server on {}", remote_addr);
loop { loop {
loop { loop {
if KILL_APP.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
match udp_server.try_recv_tc() { match udp_server.try_recv_tc() {
Ok(result) => match result { Ok(result) => match result {
Some((pdu, _addr)) => { Some((pdu, _addr)) => {

View File

@@ -1 +1 @@
cfdp-py == 0.6.0 cfdp-py @ git+https://github.com/us-irs/cfdp-py.git@main

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

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;

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
use spacepackets::ByteConversionError;
use spacepackets::cfdp::ChecksumType; use spacepackets::cfdp::ChecksumType;
use spacepackets::ByteConversionError;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use std_mod::*; pub use std_mod::*;
@@ -375,11 +375,9 @@ mod tests {
.create_dir(dir_path.to_str().expect("getting str for file failed")) .create_dir(dir_path.to_str().expect("getting str for file failed"))
.unwrap(); .unwrap();
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap()); assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap());
assert!( assert!(NATIVE_FS
NATIVE_FS .is_dir(dir_path.as_path().to_str().unwrap())
.is_dir(dir_path.as_path().to_str().unwrap()) .unwrap());
.unwrap()
);
} }
#[test] #[test]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,7 @@
//! # Request module
#![deny(missing_docs)]
use core::str::Utf8Error;
use spacepackets::{ use spacepackets::{
ByteConversionError,
cfdp::{ cfdp::{
tlv::{GenericTlv, Tlv, TlvType},
SegmentationControl, TransmissionMode, SegmentationControl, TransmissionMode,
tlv::{GenericTlv, ReadableTlv as _, Tlv, TlvType, WritableTlv as _},
}, },
util::UnsignedByteField, util::UnsignedByteField,
}; };
@@ -14,9 +9,6 @@ use spacepackets::{
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub use alloc_mod::*; pub use alloc_mod::*;
/// File path is too large.
///
/// The file path length is limited to 255 bytes.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -25,56 +17,36 @@ 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
/// by Put Request consumers. /// by Put Request consumers.
pub trait ReadablePutRequest { pub trait ReadablePutRequest {
/// Destination entity ID.
fn destination_id(&self) -> UnsignedByteField; fn destination_id(&self) -> UnsignedByteField;
/// Source file path.
fn source_file(&self) -> Option<&str>; fn source_file(&self) -> Option<&str>;
/// Destination file path.
fn dest_file(&self) -> Option<&str>; fn dest_file(&self) -> Option<&str>;
/// Transmission mode if explicitely specified.
fn trans_mode(&self) -> Option<TransmissionMode>; fn trans_mode(&self) -> Option<TransmissionMode>;
/// Closure is requested for unacknowledged file transfer.
fn closure_requested(&self) -> Option<bool>; fn closure_requested(&self) -> Option<bool>;
/// Segmentation control.
fn seg_ctrl(&self) -> Option<SegmentationControl>; 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<'_>>>; 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<'_>>>; fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv<'_>>>;
/// Flow label TLV, if it is supplied.
fn flow_label(&self) -> Option<Tlv<'_>>; 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 fs_requests(&self) -> Option<impl Iterator<Item = Tlv<'_>>>;
} }
/// Put request structure.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> { pub struct PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> {
/// Destination entity ID.
pub destination_id: UnsignedByteField, pub destination_id: UnsignedByteField,
source_file: Option<&'src_file str>, source_file: Option<&'src_file str>,
dest_file: Option<&'dest_file str>, dest_file: Option<&'dest_file str>,
/// Transmission mode.
pub trans_mode: Option<TransmissionMode>, pub trans_mode: Option<TransmissionMode>,
/// Closure requested flag for unacknowledged file transfer.
pub closure_requested: Option<bool>, pub closure_requested: Option<bool>,
/// Segmentation control.
pub seg_ctrl: Option<SegmentationControl>, pub seg_ctrl: Option<SegmentationControl>,
/// Messages to user TLVs.
pub msgs_to_user: Option<&'msgs_to_user [Tlv<'msgs_to_user>]>, 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>]>, pub fault_handler_overrides: Option<&'fh_ovrds [Tlv<'fh_ovrds>]>,
/// Flow label TLV.
pub flow_label: Option<Tlv<'flow_label>>, pub flow_label: Option<Tlv<'flow_label>>,
/// Filestore request TLVs.
pub fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>, pub fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>,
} }
impl<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, '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> 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)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
destination_id: UnsignedByteField, destination_id: UnsignedByteField,
@@ -155,9 +127,6 @@ impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> {
} }
} }
/// Generic path checks.
///
/// This only checks the length of the paths.
pub fn generic_path_checks( pub fn generic_path_checks(
source_file: Option<&str>, source_file: Option<&str>,
dest_file: Option<&str>, dest_file: Option<&str>,
@@ -176,7 +145,6 @@ pub fn generic_path_checks(
} }
impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static, 'static, 'static> { 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( pub fn new_regular_request(
dest_id: UnsignedByteField, dest_id: UnsignedByteField,
source_file: &'src_file str, source_file: &'src_file str,
@@ -200,14 +168,12 @@ impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static,
} }
} }
/// TLV has invalid type.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[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> {
/// New put request which only contains messages to the user TLVs.
pub fn new_msgs_to_user_only( pub fn new_msgs_to_user_only(
dest_id: UnsignedByteField, dest_id: UnsignedByteField,
msgs_to_user: &'msgs_to_user [Tlv<'msgs_to_user>], msgs_to_user: &'msgs_to_user [Tlv<'msgs_to_user>],
@@ -243,7 +209,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>( pub fn generic_tlv_list_type_check<TlvProvider: GenericTlv>(
opt_tlvs: Option<&[TlvProvider]>, opt_tlvs: Option<&[TlvProvider]>,
tlv_type: TlvType, tlv_type: TlvType,
@@ -261,216 +226,35 @@ pub fn generic_tlv_list_type_check<TlvProvider: GenericTlv>(
true 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")] #[cfg(feature = "alloc")]
pub mod alloc_mod { pub mod alloc_mod {
use core::str::Utf8Error;
use super::*; use super::*;
use alloc::string::ToString; 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. /// 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))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PutRequestOwned { pub struct PutRequestOwned {
/// Destination entity ID.
pub destination_id: UnsignedByteField, pub destination_id: UnsignedByteField,
source_file: Option<alloc::string::String>, source_file: Option<alloc::string::String>,
dest_file: Option<alloc::string::String>, dest_file: Option<alloc::string::String>,
/// Transmission mode.
pub trans_mode: Option<TransmissionMode>, pub trans_mode: Option<TransmissionMode>,
/// Closure requested flag for unacknowledged file transfer.
pub closure_requested: Option<bool>, pub closure_requested: Option<bool>,
/// Segmentation control.
pub seg_ctrl: Option<SegmentationControl>, pub seg_ctrl: Option<SegmentationControl>,
/// Messages to user TLVs.
pub msgs_to_user: Option<alloc::vec::Vec<TlvOwned>>, pub msgs_to_user: Option<alloc::vec::Vec<TlvOwned>>,
/// Fault handler override TLVs.
pub fault_handler_overrides: Option<alloc::vec::Vec<TlvOwned>>, pub fault_handler_overrides: Option<alloc::vec::Vec<TlvOwned>>,
/// Flow label TLV.
pub flow_label: Option<TlvOwned>, pub flow_label: Option<TlvOwned>,
/// Filestore request TLVs.
pub fs_requests: Option<alloc::vec::Vec<TlvOwned>>, pub fs_requests: Option<alloc::vec::Vec<TlvOwned>>,
} }
impl PutRequestOwned { impl PutRequestOwned {
/// New regular put request with no additional TLVs.
pub fn new_regular_request( pub fn new_regular_request(
dest_id: UnsignedByteField, dest_id: UnsignedByteField,
source_file: &str, source_file: &str,
@@ -498,7 +282,6 @@ pub mod alloc_mod {
}) })
} }
/// New put request which only contains messages to the user TLVs.
pub fn new_msgs_to_user_only( pub fn new_msgs_to_user_only(
dest_id: UnsignedByteField, dest_id: UnsignedByteField,
msgs_to_user: &[MsgToUserTlv<'_>], msgs_to_user: &[MsgToUserTlv<'_>],
@@ -612,6 +395,161 @@ pub mod alloc_mod {
None 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)] #[cfg(test)]
@@ -619,7 +557,7 @@ mod tests {
use std::string::String; use std::string::String;
use spacepackets::{ use spacepackets::{
cfdp::tlv::{ReadableTlv, msg_to_user::MsgToUserTlv}, cfdp::tlv::{msg_to_user::MsgToUserTlv, ReadableTlv},
util::UbfU16, util::UbfU16,
}; };
@@ -751,7 +689,7 @@ mod tests {
#[test] #[test]
fn test_put_request_cacher_basic() { 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.source_file_len, 0);
assert_eq!(put_request_cached.static_fields.dest_file_len, 0); assert_eq!(put_request_cached.static_fields.dest_file_len, 0);
assert_eq!(put_request_cached.opts_len(), 0); assert_eq!(put_request_cached.opts_len(), 0);
@@ -760,7 +698,7 @@ mod tests {
#[test] #[test]
fn test_put_request_cacher_set() { 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 src_file = "/tmp/hello.txt";
let dest_file = "/tmp/hello2.txt"; let dest_file = "/tmp/hello2.txt";
let put_request = let put_request =
@@ -782,7 +720,7 @@ mod tests {
#[test] #[test]
fn test_put_request_cacher_set_and_clear() { 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 src_file = "/tmp/hello.txt";
let dest_file = "/tmp/hello2.txt"; let dest_file = "/tmp/hello2.txt";
let put_request = let put_request =

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,7 @@
//! # Time support module.
#![deny(missing_docs)]
use core::fmt::Debug; use core::fmt::Debug;
/// Generic abstraction for a check/countdown timer. Should also be cheap to copy and clone. /// Generic abstraction for a check/countdown timer.
pub trait Countdown: Debug { pub trait CountdownProvider: Debug {
/// The countdown has expired.
fn has_expired(&self) -> bool; fn has_expired(&self) -> bool;
/// Reset the countdown to its initial state.
fn reset(&mut self); fn reset(&mut self);
} }

View File

@@ -1,68 +1,47 @@
//! # User support and hooks module
#![deny(missing_docs)]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use spacepackets::cfdp::tlv::WritableTlv; use spacepackets::cfdp::tlv::WritableTlv;
use spacepackets::{ use spacepackets::{
cfdp::{ cfdp::{
ConditionCode,
pdu::{ pdu::{
file_data::SegmentMetadata, file_data::SegmentMetadata,
finished::{DeliveryCode, FileStatus}, finished::{DeliveryCode, FileStatus},
}, },
tlv::msg_to_user::MsgToUserTlv, tlv::msg_to_user::MsgToUserTlv,
ConditionCode,
}, },
util::UnsignedByteField, util::UnsignedByteField,
}; };
use super::TransactionId; use super::TransactionId;
/// Parameters related to a finished transfer.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TransactionFinishedParams { pub struct TransactionFinishedParams {
/// ID of the transfer.
pub id: TransactionId, pub id: TransactionId,
/// Condition code.
pub condition_code: ConditionCode, pub condition_code: ConditionCode,
/// Delivery code.
pub delivery_code: DeliveryCode, pub delivery_code: DeliveryCode,
/// File status.
pub file_status: FileStatus, pub file_status: FileStatus,
} }
/// Parameters related to the reception of a metadata PDU, which might start file reception.
#[derive(Debug)] #[derive(Debug)]
pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> { pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> {
/// ID of the transfer.
pub id: TransactionId, pub id: TransactionId,
/// Source entity ID.
pub source_id: UnsignedByteField, pub source_id: UnsignedByteField,
/// File size.
pub file_size: u64, pub file_size: u64,
/// Source file name.
pub src_file_name: &'src_file str, pub src_file_name: &'src_file str,
/// Destination file name.
pub dest_file_name: &'dest_file str, pub dest_file_name: &'dest_file str,
/// Messages to user TLVs.
pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>], pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>],
} }
/// Owned variant of [MetadataReceivedParams].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct OwnedMetadataRecvdParams { pub struct OwnedMetadataRecvdParams {
/// ID of the transfer.
pub id: TransactionId, pub id: TransactionId,
/// Source entity ID.
pub source_id: UnsignedByteField, pub source_id: UnsignedByteField,
/// File size.
pub file_size: u64, pub file_size: u64,
/// Source file name.
pub src_file_name: alloc::string::String, pub src_file_name: alloc::string::String,
/// Destination file name.
pub dest_file_name: alloc::string::String, pub dest_file_name: alloc::string::String,
/// Messages to user TLVs.
pub msgs_to_user: alloc::vec::Vec<alloc::vec::Vec<u8>>, pub msgs_to_user: alloc::vec::Vec<alloc::vec::Vec<u8>>,
} }
@@ -87,63 +66,35 @@ impl From<&MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
} }
} }
/// Parameters related to the reception of a file segment PDU.
#[derive(Debug)] #[derive(Debug)]
pub struct FileSegmentRecvdParams<'seg_meta> { pub struct FileSegmentRecvdParams<'seg_meta> {
/// ID of the transfer.
pub id: TransactionId, pub id: TransactionId,
/// Offset of the segment.
pub offset: u64, pub offset: u64,
/// Length of the segment.
pub length: usize, pub length: usize,
/// Segment metadata, if present.
pub segment_metadata: Option<&'seg_meta SegmentMetadata<'seg_meta>>, 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 { pub trait CfdpUser {
/// Indication that a new transaction has started.
fn transaction_indication(&mut self, id: &TransactionId); fn transaction_indication(&mut self, id: &TransactionId);
/// Indication that an EOF PDU has been sent.
fn eof_sent_indication(&mut self, id: &TransactionId); fn eof_sent_indication(&mut self, id: &TransactionId);
/// Indication that a transaction has finished.
fn transaction_finished_indication(&mut self, finished_params: &TransactionFinishedParams); 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); 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); fn file_segment_recvd_indication(&mut self, segment_recvd_params: &FileSegmentRecvdParams);
// TODO: The standard does not strictly specify how the report information looks.. // TODO: The standard does not strictly specify how the report information looks..
/// Report information indication.
fn report_indication(&mut self, id: &TransactionId); fn report_indication(&mut self, id: &TransactionId);
/// Indication that a transfer has been suspended.
fn suspended_indication(&mut self, id: &TransactionId, condition_code: ConditionCode); 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); fn resumed_indication(&mut self, id: &TransactionId, progress: u64);
/// Indication that a fault has occured.
fn fault_indication( fn fault_indication(
&mut self, &mut self,
id: &TransactionId, id: &TransactionId,
condition_code: ConditionCode, condition_code: ConditionCode,
progress: u64, progress: u64,
); );
/// Indication that a transfer has been abandoned.
fn abandoned_indication( fn abandoned_indication(
&mut self, &mut self,
id: &TransactionId, id: &TransactionId,
condition_code: ConditionCode, condition_code: ConditionCode,
progress: u64, progress: u64,
); );
/// Indication that an EOF PDU has been received.
fn eof_recvd_indication(&mut self, id: &TransactionId); fn eof_recvd_indication(&mut self, id: &TransactionId);
} }

View File

@@ -2,27 +2,23 @@
use std::{ use std::{
fs::OpenOptions, fs::OpenOptions,
io::Write, io::Write,
sync::{ sync::{atomic::AtomicBool, mpsc, Arc},
Arc,
atomic::{AtomicBool, AtomicU16},
mpsc,
},
thread, thread,
time::Duration, time::Duration,
}; };
use cfdp::{ use cfdp::{
EntityType, FaultInfo, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo,
RemoteEntityConfig, StdTimerCreator, TransactionId, UserFaultHook,
dest::DestinationHandler, dest::DestinationHandler,
filestore::NativeFilestore, filestore::NativeFilestore,
lost_segments::LostSegmentsList, request::{PutRequestOwned, StaticPutRequestCacher},
request::PutRequestOwned,
source::SourceHandler, source::SourceHandler,
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams}, user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, RemoteEntityConfig,
StdTimerCreator, TransactionId, UserFaultHookProvider,
}; };
use spacepackets::{ use spacepackets::{
cfdp::{ChecksumType, ConditionCode, TransmissionMode}, cfdp::{ChecksumType, ConditionCode, TransmissionMode},
seq_count::SeqCountProviderSyncU16,
util::UnsignedByteFieldU16, util::UnsignedByteFieldU16,
}; };
@@ -34,21 +30,43 @@ const FILE_DATA: &str = "Hello World!";
#[derive(Default)] #[derive(Default)]
pub struct ExampleFaultHandler {} pub struct ExampleFaultHandler {}
impl UserFaultHook for ExampleFaultHandler { impl UserFaultHookProvider for ExampleFaultHandler {
fn notice_of_suspension_cb(&mut self, fault_info: FaultInfo) { fn notice_of_suspension_cb(
panic!("unexpected suspension, {:?}", fault_info); &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) { fn notice_of_cancellation_cb(
panic!("unexpected cancellation, {:?}", fault_info); &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) { fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
panic!("unexpected abandonment, {:?}", fault_info); panic!(
"unexpected abandonment of transaction {:?}, condition code {:?}, progress {}",
transaction_id, cond, progress
);
} }
fn ignore_cb(&mut self, fault_info: FaultInfo) { fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
panic!("unexpected ignore, {:?}", fault_info); 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. // Simplified event handling using atomic signals.
let stop_signal_source = Arc::new(AtomicBool::new(false)); let stop_signal_source = Arc::new(AtomicBool::new(false));
let stop_signal_dest = stop_signal_source.clone(); 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 (source_tx, source_rx) = mpsc::channel::<PduOwnedWithInfo>();
let (dest_tx, dest_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( let remote_cfg_of_dest = RemoteEntityConfig::new_with_default_values(
REMOTE_ID.into(), REMOTE_ID.into(),
1024, 1024,
with_closure, with_closure,
false, false,
transmission_mode, spacepackets::cfdp::TransmissionMode::Unacknowledged,
ChecksumType::Crc32, ChecksumType::Crc32,
); );
let seq_count_provider = AtomicU16::default(); let seq_count_provider = SeqCountProviderSyncU16::default();
let mut source_handler = SourceHandler::new( let mut source_handler = SourceHandler::new(
local_cfg_source, local_cfg_source,
source_tx, source_tx,
NativeFilestore::default(), NativeFilestore::default(),
put_request_cacher,
2048,
remote_cfg_of_dest, remote_cfg_of_dest,
StdTimerCreator::default(), StdTimerCreator::default(),
seq_count_provider, seq_count_provider,
@@ -196,16 +217,16 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
1024, 1024,
true, true,
false, false,
transmission_mode, spacepackets::cfdp::TransmissionMode::Unacknowledged,
ChecksumType::Crc32, ChecksumType::Crc32,
); );
let mut dest_handler = DestinationHandler::new( let mut dest_handler = DestinationHandler::new(
local_cfg_dest, local_cfg_dest,
1024,
dest_tx, dest_tx,
NativeFilestore::default(), NativeFilestore::default(),
remote_cfg_of_source, remote_cfg_of_source,
StdTimerCreator::default(), StdTimerCreator::default(),
LostSegmentsList::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);
@@ -213,7 +234,7 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
REMOTE_ID.into(), REMOTE_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(transmission_mode), Some(TransmissionMode::Unacknowledged),
Some(with_closure), Some(with_closure),
) )
.expect("put request creation failed"); .expect("put request creation failed");
@@ -322,16 +343,11 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
} }
#[test] #[test]
fn end_to_end_unacknowledged_no_closure() { fn end_to_end_test_no_closure() {
end_to_end_test(TransmissionMode::Unacknowledged, false); end_to_end_test(false);
} }
#[test] #[test]
fn end_to_end_unacknowledged_with_closure() { fn end_to_end_test_with_closure() {
end_to_end_test(TransmissionMode::Unacknowledged, true); end_to_end_test(true);
}
#[test]
fn end_to_end_acknowledged() {
end_to_end_test(TransmissionMode::Acknowledged, true);
} }