Compare commits
1 Commits
HEAD
...
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
|
- 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
|
||||||
|
|||||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -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
|
|
||||||
|
|||||||
27
Cargo.toml
27
Cargo.toml
@@ -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"]
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -1,8 +1,7 @@
|
|||||||
[](https://crates.io/crates/cfdp-rs)
|
[](https://crates.io/crates/cfdp-rs)
|
||||||
[](https://docs.rs/cfdp-rs)
|
[](https://docs.rs/cfdp-rs)
|
||||||
[](https://github.com/us-irs/cfdp-rs/actions/workflows/ci.yml)
|
[](https://github.com/us-irs/cfdp-rs/actions/workflows/ci.yml)
|
||||||
[](https://matrix.to/#/#sat-rs:matrix.org)
|
[](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. [](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
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
|
## 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`.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)) => {
|
||||||
|
|||||||
@@ -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::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]
|
||||||
|
|||||||
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::{
|
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 =
|
||||||
|
|||||||
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;
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/user.rs
53
src/user.rs
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user