Compare commits
1 Commits
main
...
impl-acked
| Author | SHA1 | Date | |
|---|---|---|---|
| c6df24b947 |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -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.81.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
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -8,18 +8,9 @@ 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 `spacepackets` to v0.15
|
||||
- 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
|
||||
@@ -30,7 +21,3 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
# [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
|
||||
|
||||
27
Cargo.toml
27
Cargo.toml
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "cfdp-rs"
|
||||
version = "0.3.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.87"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.81.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"
|
||||
@@ -20,14 +20,13 @@ 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 }
|
||||
spacepackets = { version = "0.15", default-features = false }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
heapless = "0.9"
|
||||
serde = { version = "1", optional = true }
|
||||
defmt = { version = "1", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["std", "packet-buf-2k"]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"alloc",
|
||||
"thiserror/std",
|
||||
@@ -37,21 +36,9 @@ alloc = [
|
||||
"hashbrown",
|
||||
"spacepackets/alloc"
|
||||
]
|
||||
serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde", "heapless/serde"]
|
||||
serde = ["dep:serde", "spacepackets/serde", "hashbrown/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 = []
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
rand = "0.9"
|
||||
@@ -61,5 +48,5 @@ chrono = "0.4"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["serde", "defmt"]
|
||||
all-features = true
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
39
README.md
39
README.md
@@ -1,8 +1,7 @@
|
||||
[](https://crates.io/crates/cfdp-rs)
|
||||
[](https://docs.rs/cfdp-rs)
|
||||
[](https://github.com/us-irs/cfdp-rs/actions/workflows/ci.yml)
|
||||
[](https://matrix.to/#/#sat-rs:matrix.org)
|
||||
<!-- Does not work right now, I'd need to host that myself. [](https://absatsw.irs.uni-stuttgart.de/projects/cfdp/coverage-rs/latest/index.html) -->
|
||||
[](https://absatsw.irs.uni-stuttgart.de/projects/cfdp/coverage-rs/latest/index.html)
|
||||
|
||||
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:
|
||||
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
@@ -36,11 +55,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.
|
||||
|
||||
3
docs.sh
Executable file
3
docs.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
||||
cargo +nightly doc --all-features --open
|
||||
@@ -12,13 +12,13 @@ You can run both applications with `-h` to get more information about the availa
|
||||
## 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
|
||||
Unix system you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
|
||||
and activate a virtual environment.
|
||||
|
||||
After that, you can install the required dependencies using
|
||||
|
||||
```sh
|
||||
uv pip install -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
and then run the application using `./main.py` or `python3 main.py`.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 @@
|
||||
cfdp-py == 0.6.0
|
||||
cfdp-py @ git+https://github.com/us-irs/cfdp-py.git@main
|
||||
|
||||
35
justfile
35
justfile
@@ -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
|
||||
@@ -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;
|
||||
3330
src/dest.rs
3330
src/dest.rs
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
use spacepackets::ByteConversionError;
|
||||
use spacepackets::cfdp::ChecksumType;
|
||||
use spacepackets::ByteConversionError;
|
||||
#[cfg(feature = "std")]
|
||||
pub use std_mod::*;
|
||||
|
||||
@@ -375,11 +375,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]
|
||||
|
||||
745
src/lib.rs
745
src/lib.rs
File diff suppressed because it is too large
Load Diff
1361
src/lost_segments.rs
1361
src/lost_segments.rs
File diff suppressed because it is too large
Load Diff
392
src/request.rs
392
src/request.rs
@@ -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,9 +9,6 @@ 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))]
|
||||
@@ -25,56 +17,36 @@ 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<'_>>>;
|
||||
}
|
||||
|
||||
/// 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,
|
||||
@@ -155,9 +127,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 +145,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 +168,12 @@ 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 +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>(
|
||||
opt_tlvs: Option<&[TlvProvider]>,
|
||||
tlv_type: TlvType,
|
||||
@@ -261,216 +226,35 @@ 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 +282,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<'_>],
|
||||
@@ -612,6 +395,161 @@ pub mod alloc_mod {
|
||||
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 +557,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 +689,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 +698,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 +720,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 =
|
||||
|
||||
2063
src/source.rs
2063
src/source.rs
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
53
src/user.rs
53
src/user.rs
@@ -1,68 +1,47 @@
|
||||
//! # 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 +66,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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user