diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52ed3b7..57dab2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Install nextest uses: taiki-e/install-action@nextest - - run: cargo nextest run --all-features + - run: cargo nextest run --features "serde, defmt" - run: cargo test --doc msrv: @@ -45,7 +45,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf" - - run: cargo check --release --target=${{matrix.target}} --no-default-features --features "alloc" + - run: cargo check --release --target=${{matrix.target}} --no-default-features --features "packet-buf-1k, defmt" fmt: name: Check formatting @@ -53,6 +53,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt - run: cargo fmt --all -- --check docs: @@ -61,7 +63,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features + - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features "serde, defmt" clippy: name: Clippy @@ -69,4 +71,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: + components: clippy - run: cargo clippy -- -D warnings diff --git a/Cargo.toml b/Cargo.toml index 5026b50..0811c54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ serde = { version = "1", optional = true } defmt = { version = "1", optional = true } [features] -default = ["std"] +default = ["std", "packet-buf-2k"] std = [ "alloc", "thiserror/std", @@ -40,6 +40,18 @@ alloc = [ serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde", "heapless/serde"] defmt = ["dep:defmt", "spacepackets/defmt"] +# Available packet buffer sizes. Only one should be enabled. +# 256 bytes +packet-buf-256 = [] +# 512 bytes +packet-buf-512 = [] +# 1024 bytes +packet-buf-1k = [] +# 2048 bytes +packet-buf-2k = [] +# 4096 bytes +packet-buf-4k = [] + [dev-dependencies] tempfile = "3" rand = "0.9" @@ -49,5 +61,5 @@ chrono = "0.4" clap = { version = "4", features = ["derive"] } [package.metadata.docs.rs] -all-features = true +features = ["serde", "defmt"] rustdoc-args = ["--generate-link-to-definition"] diff --git a/README.md b/README.md index 690aa3f..efede66 100644 --- a/README.md +++ b/README.md @@ -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: - 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! @@ -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 - Keep Alive and Prompt PDU handling -## 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. +Check out the [documentation](https://docs.rs/cfdp-rs) for more information on available +Rust features. # Examples @@ -56,13 +36,11 @@ examples. # Coverage -Coverage was generated using [`grcov`](https://github.com/mozilla/grcov). If you have not done so -already, install the `llvm-tools-preview`: +Coverage can be generated using [`llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov). If you have not done so +already, install the tool: ```sh -rustup component add llvm-tools-preview -cargo install grcov --locked +cargo +stable install cargo-llvm-cov --locked ``` -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. +After this, you can run `cargo llvm-cov nextest` to run all the tests and display coverage. diff --git a/examples/python-interop/main.rs b/examples/python-interop/main.rs index 8b4a0e1..0f3b0bc 100644 --- a/examples/python-interop/main.rs +++ b/examples/python-interop/main.rs @@ -17,7 +17,7 @@ use cfdp::{ dest::DestinationHandler, filestore::NativeFilestore, lost_segments::LostSegmentsList, - request::{PutRequestOwned, StaticPutRequestCacher}, + request::PutRequestOwned, source::SourceHandler, user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams}, }; @@ -313,7 +313,6 @@ fn main() { ); let (source_tm_tx, source_tm_rx) = mpsc::channel::(); let (dest_tm_tx, dest_tm_rx) = mpsc::channel::(); - let put_request_cacher = StaticPutRequestCacher::new(2048); let remote_cfg_python = RemoteEntityConfig::new_with_default_values( PYTHON_ID.into(), 1024, @@ -327,8 +326,6 @@ fn main() { local_cfg_source, source_tm_tx, NativeFilestore::default(), - put_request_cacher, - 2048, remote_cfg_python, StdTimerCreator::default(), seq_count_provider, @@ -342,7 +339,6 @@ fn main() { ); let mut dest_handler = DestinationHandler::new( local_cfg_dest, - 1024, dest_tm_tx, NativeFilestore::default(), remote_cfg_python, diff --git a/justfile b/justfile index 8f68ad3..e257a0c 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -all: check build clippy fmt docs test coverage +all: check build embedded clippy fmt docs test coverage clippy: cargo clippy -- -D warnings @@ -7,25 +7,25 @@ fmt: cargo fmt --all -- --check check: - cargo check --all-features + cargo check --features "serde, defmt" test: - cargo nextest r --all-features + cargo nextest r --features "serde, defmt" cargo test --doc build: - cargo build --all-features + cargo build --features "serde, defmt" 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: export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" - cargo +nightly doc --all-features + cargo +nightly doc --features "serde, defmt" docs-html: 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: cargo llvm-cov nextest diff --git a/src/buf_len.rs b/src/buf_len.rs new file mode 100644 index 0000000..52b2d7d --- /dev/null +++ b/src/buf_len.rs @@ -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; diff --git a/src/dest.rs b/src/dest.rs index 58a6967..ec8380d 100644 --- a/src/dest.rs +++ b/src/dest.rs @@ -327,9 +327,11 @@ pub enum DestError { /// 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]. /// -/// 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 -/// allocation is prohibited. Furthermore, it uses the [VirtualFilestore] abstraction to allow +/// +/// The handler has an internal buffer for PDU generation and checksum generation. The size of this +/// 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. /// /// 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, state: State, transaction_params: TransactionParams, - pdu_and_cksum_buffer: RefCell>, + pdu_and_cksum_buffer: RefCell<[u8; crate::buf_len::PACKET_BUF_LEN]>, pub pdu_sender: PduSenderInstance, pub vfs: VirtualFileStoreInstance, pub remote_cfg_table: RemoteConfigStoreInstance, @@ -379,12 +381,10 @@ impl #[cfg(feature = "std")] pub fn new_std( local_cfg: LocalEntityConfig, - pdu_and_cksum_buf_size: usize, pdu_sender: PduSenderInstance, ) -> Self { Self::new( local_cfg, - pdu_and_cksum_buf_size, pdu_sender, crate::filestore::NativeFilestore::default(), crate::RemoteConfigStoreStd::default(), @@ -435,7 +435,6 @@ impl< /// where the standard time APIs might not be available. pub fn new( local_cfg: LocalEntityConfig, - pdu_and_cksum_buf_size: usize, pdu_sender: PduSenderInstance, vfs: VirtualFilestoreInstance, remote_cfg_table: RemoteConfigStoreInstance, @@ -447,7 +446,7 @@ impl< step: Cell::new(TransactionStep::Idle), state: State::Idle, 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, vfs, remote_cfg_table, @@ -2328,7 +2327,6 @@ mod tests { }; DestinationHandler::new( local_entity_cfg, - 2048, test_packet_sender, NativeFilestore::default(), basic_remote_cfg_table(LOCAL_ID, 1024, true), diff --git a/src/lib.rs b/src/lib.rs index e899094..08118ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! `cfdp-rs` currently supports following high-level features: //! //! - Unacknowledged (class 1) file transfers for both source and destination side. -//! - Acknowledged (class 2) file transfers for both source 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! //! @@ -32,18 +32,43 @@ //! 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 used -//! as well. +//! environments and can be used on these systems as well. //! -//! 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. +//! The core handlers inside this library do not allocate memory dynamically. The internal buffer +//! size used for PDU generation and checksum calculation is statically determined via a Rust +//! feature and defaults to 2048 bytes. //! //! The core of this library are the [crate::dest::DestinationHandler] and the //! [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 //! [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 //! //! This library currently features two example application which showcase how the provided @@ -89,12 +114,11 @@ extern crate alloc; #[cfg(any(feature = "std", test))] extern crate std; -#[cfg(feature = "alloc")] +pub mod buf_len; pub mod dest; pub mod filestore; pub mod lost_segments; pub mod request; -#[cfg(feature = "alloc")] pub mod source; pub mod time; pub mod user; @@ -374,6 +398,7 @@ impl RemoteConfigStore for RemoteConfigList { } } +#[cfg(feature = "alloc")] impl RemoteConfigList { pub fn remove_config(&mut self, remote_id: u64) -> bool { 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. #[derive(Default, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/src/request.rs b/src/request.rs index 244186f..0d812ce 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,7 +1,10 @@ +use core::str::Utf8Error; + use spacepackets::{ + ByteConversionError, cfdp::{ SegmentationControl, TransmissionMode, - tlv::{GenericTlv, Tlv, TlvType}, + tlv::{GenericTlv, ReadableTlv as _, Tlv, TlvType, WritableTlv as _}, }, util::UnsignedByteField, }; @@ -226,16 +229,171 @@ pub fn generic_tlv_list_type_check( 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, + pub closure_requested: Option, + pub seg_ctrl: Option, +} + +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: [u8; BUF_SIZE], + opts_len: usize, +} + +impl Default for StaticPutRequestCacher { + fn default() -> Self { + Self::new() + } +} + +impl StaticPutRequestCacher { + 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")] pub mod alloc_mod { - use core::str::Utf8Error; use super::*; use alloc::string::ToString; - use spacepackets::{ - ByteConversionError, - cfdp::tlv::{ReadableTlv, TlvOwned, WritableTlv, msg_to_user::MsgToUserTlv}, - }; + use spacepackets::cfdp::tlv::{TlvOwned, msg_to_user::MsgToUserTlv}; /// Owned variant of [PutRequest] with no lifetimes which is also [Clone]able. #[derive(Debug, Clone, PartialEq, Eq)] @@ -395,161 +553,6 @@ pub mod alloc_mod { None } } - - pub struct StaticPutRequestFields { - pub destination_id: UnsignedByteField, - /// Static buffer to store source file path. - pub source_file_buf: [u8; u8::MAX as usize], - /// Current source path length. - pub source_file_len: usize, - /// Static buffer to store dest file path. - pub dest_file_buf: [u8; u8::MAX as usize], - /// Current destination path length. - pub dest_file_len: usize, - pub trans_mode: Option, - pub closure_requested: Option, - pub seg_ctrl: Option, - } - - 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, - 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)] @@ -689,7 +692,7 @@ mod tests { #[test] 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.dest_file_len, 0); assert_eq!(put_request_cached.opts_len(), 0); @@ -698,7 +701,7 @@ mod tests { #[test] 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 dest_file = "/tmp/hello2.txt"; let put_request = @@ -720,7 +723,7 @@ mod tests { #[test] 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 dest_file = "/tmp/hello2.txt"; let put_request = diff --git a/src/source.rs b/src/source.rs index 2bcdb6a..e640a7d 100644 --- a/src/source.rs +++ b/src/source.rs @@ -267,9 +267,10 @@ impl TransactionParams { /// /// 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 -/// time. This means that the handler is still suitable for embedded systems where run-time -/// allocation is prohibited. Furthermore, it uses the [VirtualFilestore] abstraction to allow +/// The handler has an internal buffer for PDU generation and checksum generation. The size of this +/// 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. /// 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 @@ -285,8 +286,8 @@ pub struct SourceHandler< > { local_cfg: LocalEntityConfig, pdu_sender: PduSenderInstance, - pdu_and_cksum_buffer: RefCell>, - put_request_cacher: StaticPutRequestCacher, + pdu_and_cksum_buffer: RefCell<[u8; crate::buf_len::PACKET_BUF_LEN]>, + put_request_cacher: StaticPutRequestCacher<{ crate::buf_len::PACKET_BUF_LEN }>, remote_cfg_table: RemoteConfigStoreInstance, vfs: Vfs, state_helper: StateHelper, @@ -326,10 +327,6 @@ impl< /// for embedded systems where a standard runtime might not be available. /// * `put_request_cacher` - The put request cacher is used cache put requests without /// 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 /// entities and target specific configuration for file copy operations. /// * `timer_creator` - [TimerCreator] used by the CFDP handler to generate @@ -342,8 +339,6 @@ impl< cfg: LocalEntityConfig, pdu_sender: PduSenderInstance, vfs: Vfs, - put_request_cacher: StaticPutRequestCacher, - pdu_and_cksum_buf_size: usize, remote_cfg_table: RemoteConfigStoreInstance, timer_creator: TimerCreatorInstance, seq_count_provider: SequenceCounterInstance, @@ -352,9 +347,9 @@ impl< local_cfg: cfg, remote_cfg_table, 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, - put_request_cacher, + put_request_cacher: StaticPutRequestCacher::<{ crate::buf_len::PACKET_BUF_LEN }>::new(), state_helper: Default::default(), transaction_params: Default::default(), anomalies: Default::default(), @@ -802,7 +797,7 @@ impl< .unwrap() .default_crc_type, 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.prepare_and_send_eof_pdu(user, checksum)?; @@ -1081,7 +1076,7 @@ impl< fn pdu_send_helper(&self, pdu: &(impl WritablePduPacket + CfdpPdu)) -> Result<(), SourceError> { 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( pdu.pdu_type(), pdu.file_directive_type(), @@ -1171,7 +1166,7 @@ impl< .unwrap() .default_crc_type, 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)?; *sent_packets += 1; @@ -1338,7 +1333,6 @@ mod tests { indication_cfg: IndicationConfig::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 = String::from(srcfile_handle.to_path_buf().to_str().unwrap()); let expiry_control = TimerExpiryControl::default(); @@ -1348,8 +1342,6 @@ mod tests { local_entity_cfg, sender, NativeFilestore::default(), - static_put_request_cacher, - 1024, basic_remote_cfg_table( REMOTE_ID, max_packet_len, diff --git a/tests/end-to-end.rs b/tests/end-to-end.rs index 1d3afaa..d80dc05 100644 --- a/tests/end-to-end.rs +++ b/tests/end-to-end.rs @@ -17,7 +17,7 @@ use cfdp::{ dest::DestinationHandler, filestore::NativeFilestore, lost_segments::LostSegmentsList, - request::{PutRequestOwned, StaticPutRequestCacher}, + request::PutRequestOwned, source::SourceHandler, 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::(); let (dest_tx, dest_rx) = mpsc::channel::(); - let put_request_cacher = StaticPutRequestCacher::new(2048); let remote_cfg_of_dest = RemoteEntityConfig::new_with_default_values( REMOTE_ID.into(), 1024, @@ -181,8 +180,6 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) { local_cfg_source, source_tx, NativeFilestore::default(), - put_request_cacher, - 2048, remote_cfg_of_dest, StdTimerCreator::default(), seq_count_provider, @@ -204,7 +201,6 @@ fn end_to_end_test(transmission_mode: TransmissionMode, with_closure: bool) { ); let mut dest_handler = DestinationHandler::new( local_cfg_dest, - 1024, dest_tx, NativeFilestore::default(), remote_cfg_of_source,