start removing alloc dependency

This commit is contained in:
Robin Mueller
2025-09-24 20:12:05 +02:00
committed by Robin Mueller
parent f48267692c
commit b79d5d7de8
11 changed files with 277 additions and 252 deletions

View File

@@ -21,7 +21,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- name: Install nextest - name: Install nextest
uses: taiki-e/install-action@nextest uses: taiki-e/install-action@nextest
- run: cargo nextest run --all-features - run: cargo nextest run --features "serde, defmt"
- run: cargo test --doc - run: cargo test --doc
msrv: msrv:
@@ -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 "alloc" - run: cargo check --release --target=${{matrix.target}} --no-default-features --features "packet-buf-1k, defmt"
fmt: fmt:
name: Check formatting name: Check formatting
@@ -53,6 +53,8 @@ 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:
@@ -61,7 +63,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 --all-features - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features "serde, defmt"
clippy: clippy:
name: Clippy name: Clippy
@@ -69,4 +71,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy -- -D warnings - run: cargo clippy -- -D warnings

View File

@@ -27,7 +27,7 @@ serde = { version = "1", optional = true }
defmt = { version = "1", optional = true } defmt = { version = "1", optional = true }
[features] [features]
default = ["std"] default = ["std", "packet-buf-2k"]
std = [ std = [
"alloc", "alloc",
"thiserror/std", "thiserror/std",
@@ -40,6 +40,18 @@ alloc = [
serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde", "heapless/serde"] serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde", "heapless/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"
@@ -49,5 +61,5 @@ chrono = "0.4"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true features = ["serde", "defmt"]
rustdoc-args = ["--generate-link-to-definition"] rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -17,7 +17,7 @@ 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 side 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!
@@ -26,28 +26,8 @@ The following features have not been implemented yet. PRs or notifications for d
- 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
## Rust features Check out the [documentation](https://docs.rs/cfdp-rs) for more information on available
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 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
@@ -56,13 +36,11 @@ examples.
# Coverage # Coverage
Coverage was generated using [`grcov`](https://github.com/mozilla/grcov). If you have not done so Coverage can be generated using [`llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov). If you have not done so
already, install the `llvm-tools-preview`: already, install the tool:
```sh ```sh
rustup component add llvm-tools-preview cargo +stable install cargo-llvm-cov --locked
cargo install grcov --locked
``` ```
After that, you can simply run `coverage.py` to test the project with coverage. You can optionally After this, you can run `cargo llvm-cov nextest` to run all the tests and display coverage.
supply the `--open` flag to open the coverage report in your webbrowser.

View File

@@ -17,7 +17,7 @@ use cfdp::{
dest::DestinationHandler, dest::DestinationHandler,
filestore::NativeFilestore, filestore::NativeFilestore,
lost_segments::LostSegmentsList, lost_segments::LostSegmentsList,
request::{PutRequestOwned, StaticPutRequestCacher}, request::PutRequestOwned,
source::SourceHandler, source::SourceHandler,
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams}, user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
}; };
@@ -313,7 +313,6 @@ 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,
@@ -327,8 +326,6 @@ fn main() {
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,
@@ -342,7 +339,6 @@ 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,

View File

@@ -1,4 +1,4 @@
all: check build clippy fmt docs test coverage all: check build embedded clippy fmt docs test coverage
clippy: clippy:
cargo clippy -- -D warnings cargo clippy -- -D warnings
@@ -7,25 +7,25 @@ fmt:
cargo fmt --all -- --check cargo fmt --all -- --check
check: check:
cargo check --all-features cargo check --features "serde, defmt"
test: test:
cargo nextest r --all-features cargo nextest r --features "serde, defmt"
cargo test --doc cargo test --doc
build: build:
cargo build --all-features cargo build --features "serde, defmt"
embedded: embedded:
cargo build --target thumbv7em-none-eabihf --no-default-features --features "alloc" cargo build --target thumbv7em-none-eabihf --no-default-features --features "defmt, packet-buf-1k"
docs: docs:
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --all-features cargo +nightly doc --features "serde, defmt"
docs-html: docs-html:
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --all-features --open cargo +nightly doc --features "serde, defmt" --open
coverage: coverage:
cargo llvm-cov nextest cargo llvm-cov nextest

21
src/buf_len.rs Normal file
View File

@@ -0,0 +1,21 @@
#[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;

View File

@@ -327,9 +327,11 @@ pub enum DestError {
/// Prompt PDUs in addition to ACK PDUs where the acknowledged PDU is the Finished PDU. /// Prompt PDUs in addition to ACK PDUs where the acknowledged PDU is the Finished PDU.
/// All generated packets are sent using the user provided [PduSender]. /// All generated packets are sent using the user provided [PduSender].
/// ///
/// The handler requires the [alloc] feature but will allocated all required memory on construction ///
/// time. This means that the handler is still suitable for embedded systems where run-time /// The handler has an internal buffer for PDU generation and checksum generation. The size of this
/// allocation is prohibited. Furthermore, it uses the [VirtualFilestore] abstraction to allow /// buffer is select via Cargo features and defaults to 2048 bytes. It does not allocate
/// memory during run-time and thus is suitable for embedded systems where allocation is
/// not possible. Furthermore, it uses the [VirtualFilestore] abstraction to allow
/// usage on systems without a [std] filesystem. /// usage on systems without a [std] filesystem.
/// ///
/// This handler is able to deal with file copy operations to directories, similarly to how the /// This handler is able to deal with file copy operations to directories, similarly to how the
@@ -353,7 +355,7 @@ pub struct DestinationHandler<
step: core::cell::Cell<TransactionStep>, step: core::cell::Cell<TransactionStep>,
state: State, state: State,
transaction_params: TransactionParams<CountdownInstance>, transaction_params: TransactionParams<CountdownInstance>,
pdu_and_cksum_buffer: RefCell<alloc::vec::Vec<u8>>, pdu_and_cksum_buffer: RefCell<[u8; crate::buf_len::PACKET_BUF_LEN]>,
pub pdu_sender: PduSenderInstance, pub pdu_sender: PduSenderInstance,
pub vfs: VirtualFileStoreInstance, pub vfs: VirtualFileStoreInstance,
pub remote_cfg_table: RemoteConfigStoreInstance, pub remote_cfg_table: RemoteConfigStoreInstance,
@@ -379,12 +381,10 @@ impl<PduSenderInstance: PduSender, UserFaultHookInstance: UserFaultHook>
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn new_std( pub fn new_std(
local_cfg: LocalEntityConfig<UserFaultHookInstance>, local_cfg: LocalEntityConfig<UserFaultHookInstance>,
pdu_and_cksum_buf_size: usize,
pdu_sender: PduSenderInstance, pdu_sender: PduSenderInstance,
) -> Self { ) -> Self {
Self::new( Self::new(
local_cfg, local_cfg,
pdu_and_cksum_buf_size,
pdu_sender, pdu_sender,
crate::filestore::NativeFilestore::default(), crate::filestore::NativeFilestore::default(),
crate::RemoteConfigStoreStd::default(), crate::RemoteConfigStoreStd::default(),
@@ -435,7 +435,6 @@ impl<
/// where the standard time APIs might not be available. /// where the standard time APIs might not be available.
pub fn new( pub fn new(
local_cfg: LocalEntityConfig<UserFaultHookInstance>, local_cfg: LocalEntityConfig<UserFaultHookInstance>,
pdu_and_cksum_buf_size: usize,
pdu_sender: PduSenderInstance, pdu_sender: PduSenderInstance,
vfs: VirtualFilestoreInstance, vfs: VirtualFilestoreInstance,
remote_cfg_table: RemoteConfigStoreInstance, remote_cfg_table: RemoteConfigStoreInstance,
@@ -447,7 +446,7 @@ impl<
step: Cell::new(TransactionStep::Idle), step: Cell::new(TransactionStep::Idle),
state: State::Idle, state: State::Idle,
transaction_params: Default::default(), transaction_params: Default::default(),
pdu_and_cksum_buffer: core::cell::RefCell::new(alloc::vec![0; pdu_and_cksum_buf_size]), pdu_and_cksum_buffer: core::cell::RefCell::new([0; crate::buf_len::PACKET_BUF_LEN]),
pdu_sender, pdu_sender,
vfs, vfs,
remote_cfg_table, remote_cfg_table,
@@ -2328,7 +2327,6 @@ mod tests {
}; };
DestinationHandler::new( DestinationHandler::new(
local_entity_cfg, local_entity_cfg,
2048,
test_packet_sender, test_packet_sender,
NativeFilestore::default(), NativeFilestore::default(),
basic_remote_cfg_table(LOCAL_ID, 1024, true), basic_remote_cfg_table(LOCAL_ID, 1024, true),

View File

@@ -8,7 +8,7 @@
//! `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 side 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!
//! //!
@@ -32,18 +32,43 @@
//! The goal of this library is to be flexible enough to support the use-cases of both on-board //! 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 //! 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` //! as possible, but also has sufficient abstraction to allow for integration on `no_std`
//! environments and can be used on these systems as well as long as the [alloc] feature is used //! environments and can be used on these systems as well.
//! as well.
//! //!
//! Please note even though the [alloc] feature is required for the core handlers, these components //! The core handlers inside this library do not allocate memory dynamically. The internal buffer
//! will only allocate memory at initialization time and thus are still viable for systems where //! size used for PDU generation and checksum calculation is statically determined via a Rust
//! run-time allocation is prohibited. //! feature and defaults to 2048 bytes.
//! //!
//! The core of this library are the [crate::dest::DestinationHandler] and the //! The core of this library are the [crate::dest::DestinationHandler] and the
//! [crate::source::SourceHandler] components which model the CFDP destination and source entity //! [crate::source::SourceHandler] components which model the CFDP destination and source entity
//! respectively. You can find high-level and API documentation for both handlers in the respective //! respectively. You can find high-level and API documentation for both handlers in the respective
//! [crate::dest] and [crate::source] module. //! [crate::dest] and [crate::source] module.
//! //!
//! # Rust Features
//!
//! ## 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.
//!
//! ## Buffer size selection
//!
//! The following features can be used to select the internal buffer size used for PDU generation
//! and checksum calculation. Selection of this value possibly limits the size of the generated PDU
//! packets. Only one feature may be enabled.
//!
//! - `packet-buf-256` for 256 bytes
//! - `packet-buf-512` for 512 bytes
//! - `packet-buf-1k` for 1024 bytes
//! - `packet-buf-2k` for 2048 bytes. This is the default.
//! - `packet-buf-4k` for 4096 bytes
//!
//! # Examples //! # Examples
//! //!
//! This library currently features two example application which showcase how the provided //! This library currently features two example application which showcase how the provided
@@ -89,12 +114,11 @@ extern crate alloc;
#[cfg(any(feature = "std", test))] #[cfg(any(feature = "std", test))]
extern crate std; extern crate std;
#[cfg(feature = "alloc")] pub mod buf_len;
pub mod dest; pub mod dest;
pub mod filestore; pub mod filestore;
pub mod lost_segments; pub mod lost_segments;
pub mod request; pub mod request;
#[cfg(feature = "alloc")]
pub mod source; pub mod source;
pub mod time; pub mod time;
pub mod user; pub mod user;
@@ -374,6 +398,7 @@ impl RemoteConfigStore for RemoteConfigList {
} }
} }
#[cfg(feature = "alloc")]
impl RemoteConfigList { impl RemoteConfigList {
pub fn remove_config(&mut self, remote_id: u64) -> bool { pub fn remove_config(&mut self, remote_id: u64) -> bool {
for (idx, cfg) in self.0.iter().enumerate() { for (idx, cfg) in self.0.iter().enumerate() {
@@ -386,7 +411,7 @@ impl RemoteConfigList {
} }
} }
/// This is a thin wrapper around a [alloc::vec::Vec] to store remote entity configurations. /// This is a thin wrapper around a [heapless::vec::Vec] to store remote entity configurations.
/// It implements the full [RemoteEntityConfig] trait. /// It implements the full [RemoteEntityConfig] trait.
#[derive(Default, Debug)] #[derive(Default, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]

View File

@@ -1,7 +1,10 @@
use core::str::Utf8Error;
use spacepackets::{ use spacepackets::{
ByteConversionError,
cfdp::{ cfdp::{
SegmentationControl, TransmissionMode, SegmentationControl, TransmissionMode,
tlv::{GenericTlv, Tlv, TlvType}, tlv::{GenericTlv, ReadableTlv as _, Tlv, TlvType, WritableTlv as _},
}, },
util::UnsignedByteField, util::UnsignedByteField,
}; };
@@ -226,16 +229,171 @@ pub fn generic_tlv_list_type_check<TlvProvider: GenericTlv>(
true true
} }
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<const BUF_SIZE: usize> {
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> {
pub fn new() -> Self {
Self {
static_fields: StaticPutRequestFields::default(),
opts_buf: [0; BUF_SIZE],
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(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::{ use spacepackets::cfdp::tlv::{TlvOwned, msg_to_user::MsgToUserTlv};
ByteConversionError,
cfdp::tlv::{ReadableTlv, TlvOwned, WritableTlv, msg_to_user::MsgToUserTlv},
};
/// 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)]
@@ -395,161 +553,6 @@ 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)]
@@ -689,7 +692,7 @@ mod tests {
#[test] #[test]
fn test_put_request_cacher_basic() { fn test_put_request_cacher_basic() {
let put_request_cached = StaticPutRequestCacher::new(128); let put_request_cached = StaticPutRequestCacher::<128>::new();
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);
@@ -698,7 +701,7 @@ mod tests {
#[test] #[test]
fn test_put_request_cacher_set() { fn test_put_request_cacher_set() {
let mut put_request_cached = StaticPutRequestCacher::new(128); let mut put_request_cached = StaticPutRequestCacher::<128>::new();
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 =
@@ -720,7 +723,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::new(128); let mut put_request_cached = StaticPutRequestCacher::<128>::new();
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 =

View File

@@ -267,9 +267,10 @@ impl<CountdownInstance: Countdown> TransactionParams<CountdownInstance> {
/// ///
/// A put request will only be accepted if the handler is in the idle state. /// A put request will only be accepted if the handler is in the idle state.
/// ///
/// The handler requires the [alloc] feature but will allocated all required memory on construction /// The handler has an internal buffer for PDU generation and checksum generation. The size of this
/// time. This means that the handler is still suitable for embedded systems where run-time /// buffer is select via Cargo features and defaults to 2048 bytes. It does not allocate
/// allocation is prohibited. Furthermore, it uses the [VirtualFilestore] abstraction to allow /// memory during run-time and thus is suitable for embedded systems where allocation is
/// not possible. Furthermore, it uses the [VirtualFilestore] abstraction to allow
/// usage on systems without a [std] filesystem. /// usage on systems without a [std] filesystem.
/// This handler does not support concurrency out of the box. Instead, if concurrent handling /// This handler does not support concurrency out of the box. Instead, if concurrent handling
/// is required, it is recommended to create a new handler and run all active handlers inside a /// is required, it is recommended to create a new handler and run all active handlers inside a
@@ -285,8 +286,8 @@ pub struct SourceHandler<
> { > {
local_cfg: LocalEntityConfig<UserFaultHookInstance>, local_cfg: LocalEntityConfig<UserFaultHookInstance>,
pdu_sender: PduSenderInstance, pdu_sender: PduSenderInstance,
pdu_and_cksum_buffer: RefCell<alloc::vec::Vec<u8>>, pdu_and_cksum_buffer: RefCell<[u8; crate::buf_len::PACKET_BUF_LEN]>,
put_request_cacher: StaticPutRequestCacher, put_request_cacher: StaticPutRequestCacher<{ crate::buf_len::PACKET_BUF_LEN }>,
remote_cfg_table: RemoteConfigStoreInstance, remote_cfg_table: RemoteConfigStoreInstance,
vfs: Vfs, vfs: Vfs,
state_helper: StateHelper, state_helper: StateHelper,
@@ -326,10 +327,6 @@ impl<
/// for embedded systems where a standard runtime might not be available. /// for embedded systems where a standard runtime might not be available.
/// * `put_request_cacher` - The put request cacher is used cache put requests without /// * `put_request_cacher` - The put request cacher is used cache put requests without
/// requiring run-time allocation. /// requiring run-time allocation.
/// * `pdu_and_cksum_buf_size` - The handler requires a buffer to generate PDUs and perform
/// checksum calculations. The user can specify the size of this buffer, so this should be
/// set to the maximum expected PDU size or a conservative upper bound for this size, for
/// example 2048 or 4096 bytes.
/// * `remote_cfg_table` - The [RemoteEntityConfig] used to look up remote /// * `remote_cfg_table` - The [RemoteEntityConfig] used to look up remote
/// entities and target specific configuration for file copy operations. /// entities and target specific configuration for file copy operations.
/// * `timer_creator` - [TimerCreator] used by the CFDP handler to generate /// * `timer_creator` - [TimerCreator] used by the CFDP handler to generate
@@ -342,8 +339,6 @@ impl<
cfg: LocalEntityConfig<UserFaultHookInstance>, cfg: LocalEntityConfig<UserFaultHookInstance>,
pdu_sender: PduSenderInstance, pdu_sender: PduSenderInstance,
vfs: Vfs, vfs: Vfs,
put_request_cacher: StaticPutRequestCacher,
pdu_and_cksum_buf_size: usize,
remote_cfg_table: RemoteConfigStoreInstance, remote_cfg_table: RemoteConfigStoreInstance,
timer_creator: TimerCreatorInstance, timer_creator: TimerCreatorInstance,
seq_count_provider: SequenceCounterInstance, seq_count_provider: SequenceCounterInstance,
@@ -352,9 +347,9 @@ impl<
local_cfg: cfg, local_cfg: cfg,
remote_cfg_table, remote_cfg_table,
pdu_sender, pdu_sender,
pdu_and_cksum_buffer: RefCell::new(alloc::vec![0; pdu_and_cksum_buf_size]), pdu_and_cksum_buffer: RefCell::new([0; crate::buf_len::PACKET_BUF_LEN]),
vfs, vfs,
put_request_cacher, put_request_cacher: StaticPutRequestCacher::<{ crate::buf_len::PACKET_BUF_LEN }>::new(),
state_helper: Default::default(), state_helper: Default::default(),
transaction_params: Default::default(), transaction_params: Default::default(),
anomalies: Default::default(), anomalies: Default::default(),
@@ -802,7 +797,7 @@ impl<
.unwrap() .unwrap()
.default_crc_type, .default_crc_type,
self.transaction_params.file_params.file_size, self.transaction_params.file_params.file_size,
&mut self.pdu_and_cksum_buffer.borrow_mut(), self.pdu_and_cksum_buffer.borrow_mut().as_mut_slice(),
)?; )?;
self.transaction_params.file_params.checksum_completed_file = Some(checksum); self.transaction_params.file_params.checksum_completed_file = Some(checksum);
self.prepare_and_send_eof_pdu(user, checksum)?; self.prepare_and_send_eof_pdu(user, checksum)?;
@@ -1081,7 +1076,7 @@ impl<
fn pdu_send_helper(&self, pdu: &(impl WritablePduPacket + CfdpPdu)) -> Result<(), SourceError> { fn pdu_send_helper(&self, pdu: &(impl WritablePduPacket + CfdpPdu)) -> Result<(), SourceError> {
let mut pdu_buffer_mut = self.pdu_and_cksum_buffer.borrow_mut(); let mut pdu_buffer_mut = self.pdu_and_cksum_buffer.borrow_mut();
let written_len = pdu.write_to_bytes(&mut pdu_buffer_mut)?; let written_len = pdu.write_to_bytes(pdu_buffer_mut.as_mut_slice())?;
self.pdu_sender.send_pdu( self.pdu_sender.send_pdu(
pdu.pdu_type(), pdu.pdu_type(),
pdu.file_directive_type(), pdu.file_directive_type(),
@@ -1171,7 +1166,7 @@ impl<
.unwrap() .unwrap()
.default_crc_type, .default_crc_type,
self.transaction_params.file_params.progress, self.transaction_params.file_params.progress,
&mut self.pdu_and_cksum_buffer.borrow_mut(), self.pdu_and_cksum_buffer.borrow_mut().as_mut_slice(),
)?; )?;
self.prepare_and_send_eof_pdu(user, checksum)?; self.prepare_and_send_eof_pdu(user, checksum)?;
*sent_packets += 1; *sent_packets += 1;
@@ -1338,7 +1333,6 @@ mod tests {
indication_cfg: IndicationConfig::default(), indication_cfg: IndicationConfig::default(),
fault_handler: FaultHandler::new(TestFaultHandler::default()), fault_handler: FaultHandler::new(TestFaultHandler::default()),
}; };
let static_put_request_cacher = StaticPutRequestCacher::new(2048);
let (srcfile_handle, destfile) = init_full_filepaths_textfile(); let (srcfile_handle, destfile) = init_full_filepaths_textfile();
let srcfile = String::from(srcfile_handle.to_path_buf().to_str().unwrap()); let srcfile = String::from(srcfile_handle.to_path_buf().to_str().unwrap());
let expiry_control = TimerExpiryControl::default(); let expiry_control = TimerExpiryControl::default();
@@ -1348,8 +1342,6 @@ mod tests {
local_entity_cfg, local_entity_cfg,
sender, sender,
NativeFilestore::default(), NativeFilestore::default(),
static_put_request_cacher,
1024,
basic_remote_cfg_table( basic_remote_cfg_table(
REMOTE_ID, REMOTE_ID,
max_packet_len, max_packet_len,

View File

@@ -17,7 +17,7 @@ use cfdp::{
dest::DestinationHandler, dest::DestinationHandler,
filestore::NativeFilestore, filestore::NativeFilestore,
lost_segments::LostSegmentsList, lost_segments::LostSegmentsList,
request::{PutRequestOwned, StaticPutRequestCacher}, request::PutRequestOwned,
source::SourceHandler, source::SourceHandler,
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams}, user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
}; };
@@ -167,7 +167,6 @@ 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,
@@ -181,8 +180,6 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
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,
@@ -204,7 +201,6 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) {
); );
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,