Compare commits
No commits in common. "main" and "python-interop-example-squashed" have entirely different histories.
main
...
python-int
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@1.81.0
|
||||
- uses: dtolnay/rust-toolchain@1.75.0
|
||||
- run: cargo check --release
|
||||
|
||||
cross-check:
|
||||
|
@ -7,12 +7,3 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
- Bumped `thiserror` to v2
|
||||
- Bumped `spacepackets` to v0.13
|
||||
- The source and destination handlers can now be used without the `std` feature and only require
|
||||
the `alloc` feature.
|
||||
|
||||
# [v0.1.0] 2024-09-11
|
||||
|
||||
Initial release
|
||||
|
28
Cargo.toml
28
Cargo.toml
@ -18,49 +18,45 @@ name = "cfdp"
|
||||
[dependencies]
|
||||
crc = "3"
|
||||
smallvec = "1"
|
||||
derive-new = ">=0.6, <=0.7"
|
||||
|
||||
[dependencies.spacepackets]
|
||||
version = "0.13"
|
||||
default-features = false
|
||||
derive-new = "0.6"
|
||||
|
||||
[dependencies.thiserror]
|
||||
version = "2"
|
||||
default-features = false
|
||||
version = "1"
|
||||
optional = true
|
||||
|
||||
[dependencies.hashbrown]
|
||||
version = ">=0.14, <=0.15"
|
||||
version = "0.14"
|
||||
optional = true
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
optional = true
|
||||
|
||||
[dependencies.defmt]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
[dependencies.spacepackets]
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
|
||||
branch = "main"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"alloc",
|
||||
"thiserror/std",
|
||||
"thiserror",
|
||||
"spacepackets/std"
|
||||
]
|
||||
alloc = [
|
||||
"hashbrown",
|
||||
"spacepackets/alloc"
|
||||
]
|
||||
serde = ["dep:serde", "spacepackets/serde", "hashbrown/serde"]
|
||||
defmt = ["dep:defmt", "spacepackets/defmt"]
|
||||
serde = ["dep:serde", "spacepackets/serde"]
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
rand = "0.8"
|
||||
log = "0.4"
|
||||
fern = "0.7"
|
||||
fern = "0.6"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
12
README.md
12
README.md
@ -13,14 +13,10 @@ The underlying base packet library used to generate the packets to be sent is th
|
||||
|
||||
# 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.
|
||||
`cfdp-rs` supports various runtime environments and is also suitable for `no_std` environments.
|
||||
It is recommended to activate the `alloc` feature at the very least to allow using the primary
|
||||
components provided by this crate. These components will only allocate memory at initialization
|
||||
time and thus are still viable for systems where run-time allocation is prohibited.
|
||||
|
||||
## Default features
|
||||
|
||||
|
@ -20,7 +20,7 @@ def generate_cov_report(open_report: bool, format: str):
|
||||
out_path = "./target/debug/lcov.info"
|
||||
os.system(
|
||||
f"grcov . -s . --binary-path ./target/debug/ -t {format} --branch --ignore-not-existing "
|
||||
f"--ignore \"examples/*\" -o {out_path}"
|
||||
f"-o {out_path}"
|
||||
)
|
||||
if format == "lcov":
|
||||
os.system(
|
||||
|
@ -1,37 +0,0 @@
|
||||
Python Interoperability Example for cfdp-rs
|
||||
=======
|
||||
|
||||
This example application showcases the interoperability of the CFDP handlers written in Rust
|
||||
with a Python implementation which uses [cfdp-py](https://github.com/us-irs/cfdp-py) library.
|
||||
|
||||
Both the Rust and the Python app exchange packet data units via a UDP interface and launch
|
||||
both a destination and source handler. As such, they are both able to send and receive files.
|
||||
Both applications can be started with the command line argument `-f` to initiate a file transfer.
|
||||
You can run both applications with `-h` to get more information about the available options.
|
||||
|
||||
## Running the Python App
|
||||
|
||||
It is recommended to run the Python App in a dedicated virtual environment. For example, on a
|
||||
Unix system you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
|
||||
and activate a virtual environment.
|
||||
|
||||
After that, you can install the required dependencies using
|
||||
|
||||
```sh
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
and then run the application using `./main.py` or `python3 main.py`.
|
||||
|
||||
It is recommended to run `./main.py -h` first to get an overview of some possible options.
|
||||
Running the Python App with `./main.py -f` will cause the Python App to start a file copy operation
|
||||
with fixed temporary paths.
|
||||
|
||||
## Running the Rust App
|
||||
|
||||
You can run the Rust application using `cargo`, for example `cargo run --example python-interop`.
|
||||
It is recommended to run `cargo run --example python-interop -- -h` to get an overview of some
|
||||
possible launch options.
|
||||
|
||||
Running the Rust App with `cargo run --example python-interop -- -f` will cause the Rust app to
|
||||
start a file copy operation with fixed temporary paths.
|
@ -1,9 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
import os
|
||||
import ipaddress
|
||||
import tempfile
|
||||
import socket
|
||||
import select
|
||||
import threading
|
||||
@ -53,6 +51,8 @@ from spacepackets.cfdp.tlv.msg_to_user import ProxyPutResponseParams
|
||||
from spacepackets.countdown import Countdown
|
||||
from spacepackets.seqcount import SeqCountProvider
|
||||
from spacepackets.util import ByteFieldU16, UnsignedByteField
|
||||
from tmtccmd.config.cfdp import CfdpParams, generic_cfdp_params_to_put_request
|
||||
from tmtccmd.config.args import add_cfdp_procedure_arguments, cfdp_args_to_cfdp_params
|
||||
|
||||
|
||||
PYTHON_ENTITY_ID = ByteFieldU16(1)
|
||||
@ -544,44 +544,23 @@ class CustomCheckTimerProvider(CheckTimerProvider):
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="CFDP Local Entity Application",
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
)
|
||||
parser = argparse.ArgumentParser(prog="CFDP Local Entity Application")
|
||||
parser.add_argument("-v", "--verbose", action="count", default=0)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
help="Perform a file-copy operation",
|
||||
action="store_true",
|
||||
dest="file_copy",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--mode",
|
||||
dest="transmission_mode",
|
||||
help=(
|
||||
f"Specify the transfer type{os.linesep}"
|
||||
f' - "0" or "ack" for unacknowledged (Class 0) transfers{os.linesep}'
|
||||
f' - "1" or "nak" for acknowledged (Class 1) transfers. Default value'
|
||||
),
|
||||
default="nak",
|
||||
)
|
||||
# Optional Boolean argument where you can specify True/False
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
type=bool,
|
||||
nargs="?",
|
||||
const=True,
|
||||
default=None,
|
||||
dest="closure_requested",
|
||||
help="Request transaction closure for the unacknowledged mode",
|
||||
)
|
||||
add_cfdp_procedure_arguments(parser)
|
||||
args = parser.parse_args()
|
||||
stop_signal = threading.Event()
|
||||
|
||||
logging_level = logging.INFO
|
||||
if args.verbose >= 1:
|
||||
logging_level = logging.DEBUG
|
||||
if args.source is not None and args.target is not None:
|
||||
# Generate a put request from the CLI arguments.
|
||||
cfdp_params = CfdpParams()
|
||||
cfdp_args_to_cfdp_params(args, cfdp_params)
|
||||
put_req = generic_cfdp_params_to_put_request(
|
||||
cfdp_params, PYTHON_ENTITY_ID, RUST_ENTITY_ID, PYTHON_ENTITY_ID
|
||||
)
|
||||
PUT_REQ_QUEUE.put(put_req)
|
||||
|
||||
logging.basicConfig(level=logging_level)
|
||||
|
||||
@ -632,6 +611,16 @@ def main():
|
||||
local_addr = ipaddress.ip_address("0.0.0.0")
|
||||
# Localhost as default.
|
||||
remote_addr = ipaddress.ip_address("127.0.0.1")
|
||||
"""
|
||||
if Path(LOCAL_CFG_JSON_PATH).exists():
|
||||
addr_from_cfg = parse_remote_addr_from_json(Path(LOCAL_CFG_JSON_PATH))
|
||||
if addr_from_cfg is not None:
|
||||
try:
|
||||
remote_addr = ipaddress.ip_address(addr_from_cfg)
|
||||
except ValueError:
|
||||
_LOGGER.warning(f"invalid remote address {remote_addr} from JSON file")
|
||||
"""
|
||||
_LOGGER.info(f"Put request will be sent to remote destination {remote_addr}")
|
||||
udp_server = UdpServer(
|
||||
sleep_time=0.1,
|
||||
addr=(str(local_addr), PY_PORT),
|
||||
@ -642,27 +631,6 @@ def main():
|
||||
stop_signal=stop_signal,
|
||||
)
|
||||
|
||||
# Prepare a put request / file copy operation if the user specifies it.
|
||||
if args.file_copy:
|
||||
_LOGGER.info("Performing file copy operation")
|
||||
transmission_mode = None
|
||||
if args.transmission_mode == "ack":
|
||||
transmission_mode = TransmissionMode.ACKNOWLEDGED
|
||||
elif args.transmission_mode == "nak":
|
||||
transmission_mode = TransmissionMode.UNACKNOWLEDGED
|
||||
with tempfile.NamedTemporaryFile(delete=False) as srcfile:
|
||||
srcfile.write(FILE_CONTENT.encode())
|
||||
srcfile_path = srcfile.name
|
||||
tempdir = tempfile.TemporaryDirectory()
|
||||
put_req = PutRequest(
|
||||
destination_id=RUST_ENTITY_ID,
|
||||
source_file=Path(srcfile_path),
|
||||
dest_file=Path(tempdir.name).joinpath("test.txt"),
|
||||
closure_requested=args.closure_requested,
|
||||
trans_mode=transmission_mode,
|
||||
)
|
||||
PUT_REQ_QUEUE.put(put_req)
|
||||
|
||||
source_entity_task.start()
|
||||
dest_entity_task.start()
|
||||
udp_server.start()
|
||||
|
@ -15,9 +15,8 @@ use cfdp::{
|
||||
source::SourceHandler,
|
||||
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
|
||||
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, PduProvider,
|
||||
RemoteEntityConfig, StdTimerCreator, TransactionId, UserFaultHookProvider,
|
||||
RemoteEntityConfig, StdCheckTimerCreator, TransactionId, UserFaultHookProvider,
|
||||
};
|
||||
use clap::Parser;
|
||||
use log::{debug, info, warn};
|
||||
use spacepackets::{
|
||||
cfdp::{
|
||||
@ -38,23 +37,6 @@ const LOG_LEVEL: log::LevelFilter = log::LevelFilter::Info;
|
||||
|
||||
const FILE_DATA: &str = "Hello World!";
|
||||
|
||||
#[derive(Debug, Copy, Clone, clap::ValueEnum)]
|
||||
pub enum TransmissionModeCli {
|
||||
Nak,
|
||||
Ack,
|
||||
}
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[command(about = "Arguments for executing a file copy operation")]
|
||||
pub struct Cli {
|
||||
#[arg(short, help = "Perform a file copy operation")]
|
||||
file_copy: bool,
|
||||
#[arg(short, default_value = "nak")]
|
||||
mode: Option<TransmissionModeCli>,
|
||||
#[arg(short)]
|
||||
closure_requested: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExampleFaultHandler {}
|
||||
|
||||
@ -295,7 +277,6 @@ fn pdu_printout(pdu: &PduOwnedWithInfo) {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli_args = Cli::parse();
|
||||
fern::Dispatch::new()
|
||||
.format(|out, message, record| {
|
||||
out.finish(format_args!(
|
||||
@ -346,7 +327,6 @@ fn main() {
|
||||
put_request_cacher,
|
||||
2048,
|
||||
remote_cfg_python,
|
||||
StdTimerCreator::default(),
|
||||
seq_count_provider,
|
||||
);
|
||||
let mut cfdp_user_source = ExampleCfdpUser::new(EntityType::Sending);
|
||||
@ -362,27 +342,18 @@ fn main() {
|
||||
dest_tm_tx,
|
||||
NativeFilestore::default(),
|
||||
remote_cfg_python,
|
||||
StdTimerCreator::default(),
|
||||
StdCheckTimerCreator::default(),
|
||||
);
|
||||
let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving);
|
||||
|
||||
let put_request = if cli_args.file_copy {
|
||||
Some(
|
||||
PutRequestOwned::new_regular_request(
|
||||
let put_request = PutRequestOwned::new_regular_request(
|
||||
PYTHON_ID.into(),
|
||||
srcfile.to_str().expect("invaid path string"),
|
||||
destfile.to_str().expect("invaid path string"),
|
||||
cli_args.mode.map(|m| match m {
|
||||
TransmissionModeCli::Ack => TransmissionMode::Acknowledged,
|
||||
TransmissionModeCli::Nak => TransmissionMode::Unacknowledged,
|
||||
}),
|
||||
cli_args.closure_requested,
|
||||
Some(TransmissionMode::Unacknowledged),
|
||||
Some(true),
|
||||
)
|
||||
.expect("put request creation failed"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
.expect("put request creation failed");
|
||||
|
||||
let (source_tc_tx, source_tc_rx) = mpsc::channel();
|
||||
let (dest_tc_tx, dest_tc_rx) = mpsc::channel();
|
||||
@ -404,12 +375,9 @@ fn main() {
|
||||
.name("cfdp src entity".to_string())
|
||||
.spawn(move || {
|
||||
info!("Starting RUST SRC");
|
||||
if let Some(put_request) = put_request {
|
||||
info!("RUST SRC: Performing put request: {:?}", put_request);
|
||||
source_handler
|
||||
.put_request(&put_request)
|
||||
.expect("put request failed");
|
||||
}
|
||||
loop {
|
||||
let mut next_delay = None;
|
||||
let mut undelayed_call_count = 0;
|
||||
@ -469,9 +437,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Dest handler error: {}", e);
|
||||
// TODO: I'd prefer a proper cancel request if a transfer is active..
|
||||
dest_handler.reset();
|
||||
println!("Source handler error: {}", e);
|
||||
next_delay = Some(Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
cfdp-py @ git+https://github.com/us-irs/cfdp-py.git@main
|
||||
tmtccmd == 8.0.2
|
||||
|
669
src/dest.rs
669
src/dest.rs
File diff suppressed because it is too large
Load Diff
163
src/filestore.rs
163
src/filestore.rs
@ -1,35 +1,90 @@
|
||||
use alloc::string::{String, ToString};
|
||||
use core::fmt::Display;
|
||||
use spacepackets::cfdp::ChecksumType;
|
||||
use spacepackets::ByteConversionError;
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "std")]
|
||||
pub use std_mod::*;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[cfg_attr(all(feature = "defmt", not(feature = "std")), derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FilestoreError {
|
||||
#[error("file does not exist")]
|
||||
FileDoesNotExist,
|
||||
#[error("file already exists")]
|
||||
FileAlreadyExists,
|
||||
#[error("directory does not exist")]
|
||||
DirDoesNotExist,
|
||||
#[error("permission error")]
|
||||
Permission,
|
||||
#[error("is not a file")]
|
||||
IsNotFile,
|
||||
#[error("is not a directory")]
|
||||
IsNotDirectory,
|
||||
#[error("byte conversion: {0}")]
|
||||
ByteConversion(#[from] ByteConversionError),
|
||||
#[error("IO error: {0})")]
|
||||
#[cfg(feature = "std")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("checksum type not implemented: {0:?}")]
|
||||
ByteConversion(ByteConversionError),
|
||||
Io {
|
||||
raw_errno: Option<i32>,
|
||||
string: String,
|
||||
},
|
||||
ChecksumTypeNotImplemented(ChecksumType),
|
||||
#[error("utf8 error")]
|
||||
Utf8Error,
|
||||
#[error("other error")]
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<ByteConversionError> for FilestoreError {
|
||||
fn from(value: ByteConversionError) -> Self {
|
||||
Self::ByteConversion(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FilestoreError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
FilestoreError::FileDoesNotExist => {
|
||||
write!(f, "file does not exist")
|
||||
}
|
||||
FilestoreError::FileAlreadyExists => {
|
||||
write!(f, "file already exists")
|
||||
}
|
||||
FilestoreError::DirDoesNotExist => {
|
||||
write!(f, "directory does not exist")
|
||||
}
|
||||
FilestoreError::Permission => {
|
||||
write!(f, "permission error")
|
||||
}
|
||||
FilestoreError::IsNotFile => {
|
||||
write!(f, "is not a file")
|
||||
}
|
||||
FilestoreError::IsNotDirectory => {
|
||||
write!(f, "is not a directory")
|
||||
}
|
||||
FilestoreError::ByteConversion(e) => {
|
||||
write!(f, "filestore error: {e}")
|
||||
}
|
||||
FilestoreError::Io { raw_errno, string } => {
|
||||
write!(
|
||||
f,
|
||||
"filestore generic IO error with raw errno {:?}: {}",
|
||||
raw_errno, string
|
||||
)
|
||||
}
|
||||
FilestoreError::ChecksumTypeNotImplemented(checksum_type) => {
|
||||
write!(f, "checksum {:?} not implemented", checksum_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for FilestoreError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
FilestoreError::ByteConversion(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl From<std::io::Error> for FilestoreError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::Io {
|
||||
raw_errno: value.raw_os_error(),
|
||||
string: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VirtualFilestore {
|
||||
@ -56,7 +111,14 @@ pub trait VirtualFilestore {
|
||||
|
||||
fn filename_from_full_path(path: &str) -> Option<&str>
|
||||
where
|
||||
Self: Sized;
|
||||
Self: Sized,
|
||||
{
|
||||
// Convert the path string to a Path
|
||||
let path = Path::new(path);
|
||||
|
||||
// Extract the file name using the file_name() method
|
||||
path.file_name().and_then(|name| name.to_str())
|
||||
}
|
||||
|
||||
fn is_file(&self, path: &str) -> Result<bool, FilestoreError>;
|
||||
|
||||
@ -66,11 +128,6 @@ pub trait VirtualFilestore {
|
||||
|
||||
fn exists(&self, path: &str) -> Result<bool, FilestoreError>;
|
||||
|
||||
/// Extract the file name part of a full path.
|
||||
///
|
||||
/// This method should behave similarly to the [std::path::Path::file_name] method.
|
||||
fn file_name<'a>(&self, full_path: &'a str) -> Result<Option<&'a str>, FilestoreError>;
|
||||
|
||||
fn file_size(&self, path: &str) -> Result<u64, FilestoreError>;
|
||||
|
||||
/// This special function is the CFDP specific abstraction to calculate the checksum of a file.
|
||||
@ -84,7 +141,6 @@ pub trait VirtualFilestore {
|
||||
&self,
|
||||
file_path: &str,
|
||||
checksum_type: ChecksumType,
|
||||
size_to_verify: u64,
|
||||
verification_buf: &mut [u8],
|
||||
) -> Result<u32, FilestoreError>;
|
||||
|
||||
@ -97,14 +153,13 @@ pub trait VirtualFilestore {
|
||||
/// 4096 or 8192 bytes.
|
||||
fn checksum_verify(
|
||||
&self,
|
||||
expected_checksum: u32,
|
||||
file_path: &str,
|
||||
checksum_type: ChecksumType,
|
||||
size_to_verify: u64,
|
||||
expected_checksum: u32,
|
||||
verification_buf: &mut [u8],
|
||||
) -> Result<bool, FilestoreError> {
|
||||
Ok(
|
||||
self.calculate_checksum(file_path, checksum_type, size_to_verify, verification_buf)?
|
||||
self.calculate_checksum(file_path, checksum_type, verification_buf)?
|
||||
== expected_checksum,
|
||||
)
|
||||
}
|
||||
@ -121,7 +176,6 @@ pub mod std_mod {
|
||||
use std::{
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{BufReader, Read, Seek, SeekFrom, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@ -147,13 +201,6 @@ pub mod std_mod {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn file_name<'a>(&self, full_path: &'a str) -> Result<Option<&'a str>, FilestoreError> {
|
||||
let path = Path::new(full_path);
|
||||
path.file_name()
|
||||
.map(|s| s.to_str())
|
||||
.ok_or(FilestoreError::Utf8Error)
|
||||
}
|
||||
|
||||
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> {
|
||||
if !self.exists(file_path)? {
|
||||
return Err(FilestoreError::FileDoesNotExist);
|
||||
@ -169,7 +216,10 @@ pub mod std_mod {
|
||||
}
|
||||
|
||||
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> {
|
||||
fs::create_dir(dir_path)?;
|
||||
fs::create_dir(dir_path).map_err(|e| FilestoreError::Io {
|
||||
raw_errno: e.raw_os_error(),
|
||||
string: e.to_string(),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -255,23 +305,18 @@ pub mod std_mod {
|
||||
&self,
|
||||
file_path: &str,
|
||||
checksum_type: ChecksumType,
|
||||
size_to_verify: u64,
|
||||
verification_buf: &mut [u8],
|
||||
) -> Result<u32, FilestoreError> {
|
||||
let mut calc_with_crc_lib = |crc: Crc<u32>| -> Result<u32, FilestoreError> {
|
||||
let mut digest = crc.digest();
|
||||
let mut buf_reader = BufReader::new(File::open(file_path)?);
|
||||
let mut remaining_bytes = size_to_verify;
|
||||
while remaining_bytes > 0 {
|
||||
// Read the smaller of the remaining bytes or the buffer size
|
||||
let bytes_to_read = remaining_bytes.min(verification_buf.len() as u64) as usize;
|
||||
let bytes_read = buf_reader.read(&mut verification_buf[0..bytes_to_read])?;
|
||||
|
||||
let file_to_check = File::open(file_path)?;
|
||||
let mut buf_reader = BufReader::new(file_to_check);
|
||||
loop {
|
||||
let bytes_read = buf_reader.read(verification_buf)?;
|
||||
if bytes_read == 0 {
|
||||
break; // Reached end of file
|
||||
break;
|
||||
}
|
||||
digest.update(&verification_buf[0..bytes_read]);
|
||||
remaining_bytes -= bytes_read as u64;
|
||||
}
|
||||
Ok(digest.finalize())
|
||||
};
|
||||
@ -283,17 +328,6 @@ pub mod std_mod {
|
||||
_ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)),
|
||||
}
|
||||
}
|
||||
|
||||
fn filename_from_full_path(path: &str) -> Option<&str>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// Convert the path string to a Path
|
||||
let path = Path::new(path);
|
||||
|
||||
// Extract the file name using the file_name() method
|
||||
path.file_name().and_then(|name| name.to_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeFilestore {
|
||||
@ -329,7 +363,7 @@ pub mod std_mod {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs, path::Path, println, string::ToString};
|
||||
use std::{fs, path::Path, println};
|
||||
|
||||
use super::*;
|
||||
use alloc::format;
|
||||
@ -642,7 +676,7 @@ mod tests {
|
||||
}
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!("byte conversion: {}", byte_conv_error)
|
||||
format!("filestore error: {}", byte_conv_error)
|
||||
);
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
@ -721,10 +755,9 @@ mod tests {
|
||||
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
|
||||
let mut verif_buf: [u8; 32] = [0; 32];
|
||||
let result = NATIVE_FS.checksum_verify(
|
||||
checksum,
|
||||
file_path.to_str().unwrap(),
|
||||
ChecksumType::Modular,
|
||||
EXAMPLE_DATA_CFDP.len() as u64,
|
||||
checksum,
|
||||
&mut verif_buf,
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
@ -737,7 +770,6 @@ mod tests {
|
||||
// The file to check does not even need to exist, and the verification buffer can be
|
||||
// empty: the null checksum is always yields the same result.
|
||||
let result = NATIVE_FS.checksum_verify(
|
||||
0,
|
||||
file_path.to_str().unwrap(),
|
||||
ChecksumType::NullChecksum,
|
||||
0,
|
||||
@ -754,7 +786,6 @@ mod tests {
|
||||
// The file to check does not even need to exist, and the verification buffer can be
|
||||
// empty: the null checksum is always yields the same result.
|
||||
let result = NATIVE_FS.checksum_verify(
|
||||
0,
|
||||
file_path.to_str().unwrap(),
|
||||
ChecksumType::Crc32Proximity1,
|
||||
0,
|
||||
@ -765,7 +796,7 @@ mod tests {
|
||||
if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!("checksum type not implemented: {:?}", cksum_type)
|
||||
format!("checksum {:?} not implemented", cksum_type)
|
||||
);
|
||||
} else {
|
||||
panic!("unexpected error");
|
||||
|
203
src/lib.rs
203
src/lib.rs
@ -1,69 +1,5 @@
|
||||
//! This module contains the implementation of the CCSDS File Delivery Protocol (CFDP) high level
|
||||
//! abstractions as specified in CCSDS 727.0-B-5.
|
||||
//!
|
||||
//! The basic idea of CFDP is to convert files of any size into a stream of packets called packet
|
||||
//! data units (PDU). CFPD has an unacknowledged and acknowledged mode, with the option to request
|
||||
//! a transaction closure for the unacknowledged mode. Using the unacknowledged mode with no
|
||||
//! transaction closure is applicable for simplex communication paths, while the unacknowledged
|
||||
//! mode with closure is the easiest way to get a confirmation of a successful file transfer,
|
||||
//! including a CRC check on the remote side to verify file integrity. The acknowledged mode is
|
||||
//! the most complex mode which includes multiple mechanism to ensure succesfull packet transaction
|
||||
//! even for unreliable connections, including lost segment detection. As such, it can be compared
|
||||
//! to a specialized TCP for file transfers with remote systems.
|
||||
//!
|
||||
//! 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.
|
||||
//!
|
||||
//! 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 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.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! This library currently features two example application which showcase how the provided
|
||||
//! components could be used to provide CFDP services.
|
||||
//!
|
||||
//! The [end-to-end test](https://egit.irs.uni-stuttgart.de/rust/cfdp/src/branch/main/tests/end-to-end.rs)
|
||||
//! is an integration tests which spawns a CFDP source entity and a CFDP destination entity,
|
||||
//! moves them to separate threads and then performs a small file copy operation.
|
||||
//! You can run the integration test for a transfer with no closure and with printout to the
|
||||
//! standard console by running:
|
||||
//!
|
||||
//! ```sh
|
||||
//! cargo test end_to_end_test_no_closure -- --nocapture
|
||||
//! ```
|
||||
//!
|
||||
//! or with closure:
|
||||
//!
|
||||
//! ```sh
|
||||
//! cargo test end_to_end_test_with_closure -- --nocapture
|
||||
//! ```
|
||||
//!
|
||||
//! The [Python Interoperability](https://egit.irs.uni-stuttgart.de/rust/cfdp/src/branch/main/examples/python-interop)
|
||||
//! example showcases the interoperability of the CFDP handlers written in Rust with a Python
|
||||
//! implementation. The dedicated example documentation shows how to run this example.
|
||||
//!
|
||||
//! # Notes on the user hooks and scheduling
|
||||
//!
|
||||
//! Both examples feature implementations of the [UserFaultHookProvider] and the [user::CfdpUser]
|
||||
//! trait which simply print some information to the console to monitor the progress of a file
|
||||
//! copy operation. These implementations could be adapted for other handler integrations. For
|
||||
//! example, they could signal a GUI application to display some information for the user.
|
||||
//!
|
||||
//! Even though both examples move the newly spawned handlers to dedicated threads, this is not
|
||||
//! the only way they could be scheduled. For example, to support an arbitrary (or bounded)
|
||||
//! amount of file copy operations on either source or destination side, those handlers could be
|
||||
//! moved into a [std::collections::HashMap] structure which is then scheduled inside a thread, or
|
||||
//! you could schedule a fixed amount of handlers inside a
|
||||
//! [threadpool](https://docs.rs/threadpool/latest/threadpool/).
|
||||
//! This module contains the implementation of the CFDP high level abstractions as specified in
|
||||
//! CCSDS 727.0-B-5.
|
||||
#![no_std]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#[cfg(feature = "alloc")]
|
||||
@ -71,11 +7,12 @@ extern crate alloc;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
extern crate std;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg(feature = "std")]
|
||||
pub mod dest;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod filestore;
|
||||
pub mod request;
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg(feature = "std")]
|
||||
pub mod source;
|
||||
pub mod time;
|
||||
pub mod user;
|
||||
@ -83,10 +20,11 @@ pub mod user;
|
||||
use crate::time::CountdownProvider;
|
||||
use core::{cell::RefCell, fmt::Debug, hash::Hash};
|
||||
use crc::{Crc, CRC_32_ISCSI, CRC_32_ISO_HDLC};
|
||||
#[cfg(feature = "std")]
|
||||
use hashbrown::HashMap;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use alloc_mod::*;
|
||||
use core::time::Duration;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use spacepackets::{
|
||||
@ -97,19 +35,16 @@ use spacepackets::{
|
||||
util::{UnsignedByteField, UnsignedEnum},
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
use std::time::Duration;
|
||||
#[cfg(feature = "std")]
|
||||
pub use std_mod::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum EntityType {
|
||||
Sending,
|
||||
Receiving,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum TimerContext {
|
||||
CheckLimit {
|
||||
local_id: UnsignedByteField,
|
||||
@ -117,10 +52,10 @@ pub enum TimerContext {
|
||||
entity_type: EntityType,
|
||||
},
|
||||
NakActivity {
|
||||
expiry_time: Duration,
|
||||
expiry_time_seconds: f32,
|
||||
},
|
||||
PositiveAck {
|
||||
expiry_time: Duration,
|
||||
expiry_time_seconds: f32,
|
||||
},
|
||||
}
|
||||
|
||||
@ -161,10 +96,10 @@ pub enum TimerContext {
|
||||
/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in
|
||||
/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer
|
||||
/// interval of the remote entity configuration.
|
||||
pub trait TimerCreatorProvider {
|
||||
type Countdown: CountdownProvider;
|
||||
pub trait CheckTimerProviderCreator {
|
||||
type CheckTimer: CountdownProvider;
|
||||
|
||||
fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown;
|
||||
fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer;
|
||||
}
|
||||
|
||||
/// This structure models the remote entity configuration information as specified in chapter 8.3
|
||||
@ -227,8 +162,6 @@ pub trait TimerCreatorProvider {
|
||||
/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside
|
||||
/// the class documentation. Defaults to 2, so the timer may expire two times.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct RemoteEntityConfig {
|
||||
pub entity_id: UnsignedByteField,
|
||||
pub max_packet_len: usize,
|
||||
@ -286,12 +219,9 @@ pub trait RemoteEntityConfigProvider {
|
||||
fn remove_config(&mut self, remote_id: u64) -> bool;
|
||||
}
|
||||
|
||||
/// This is a thin wrapper around a [hashbrown::HashMap] to store remote entity configurations.
|
||||
/// It implements the full [RemoteEntityConfigProvider] trait.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Default, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct StdRemoteEntityConfigProvider(pub hashbrown::HashMap<u64, RemoteEntityConfig>);
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(Default)]
|
||||
pub struct StdRemoteEntityConfigProvider(pub HashMap<u64, RemoteEntityConfig>);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
|
||||
@ -309,12 +239,8 @@ impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a thin wrapper around a [alloc::vec::Vec] to store remote entity configurations.
|
||||
/// It implements the full [RemoteEntityConfigProvider] trait.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Default, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Default)]
|
||||
pub struct VecRemoteEntityConfigProvider(pub alloc::vec::Vec<RemoteEntityConfig>);
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
@ -347,9 +273,6 @@ impl RemoteEntityConfigProvider for VecRemoteEntityConfigProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/// A remote entity configurations also implements the [RemoteEntityConfigProvider], but the
|
||||
/// [RemoteEntityConfigProvider::add_config] and [RemoteEntityConfigProvider::remove_config]
|
||||
/// are no-ops and always returns [false].
|
||||
impl RemoteEntityConfigProvider for RemoteEntityConfig {
|
||||
fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
|
||||
if remote_id == self.entity_id.value() {
|
||||
@ -377,7 +300,7 @@ impl RemoteEntityConfigProvider for RemoteEntityConfig {
|
||||
/// This trait introduces some callbacks which will be called when a particular CFDP fault
|
||||
/// handler is called.
|
||||
///
|
||||
/// It is passed into the CFDP handlers as part of the [UserFaultHookProvider] and the local entity
|
||||
/// It is passed into the CFDP handlers as part of the [DefaultFaultHandler] and the local entity
|
||||
/// configuration and provides a way to specify custom user error handlers. This allows to
|
||||
/// implement some CFDP features like fault handler logging, which would not be possible
|
||||
/// generically otherwise.
|
||||
@ -404,8 +327,6 @@ pub trait UserFaultHookProvider {
|
||||
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
|
||||
}
|
||||
|
||||
/// Dummy fault hook which implements [UserFaultHookProvider] but only provides empty
|
||||
/// implementations.
|
||||
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct DummyFaultHook {}
|
||||
|
||||
@ -443,7 +364,7 @@ impl UserFaultHookProvider for DummyFaultHook {
|
||||
/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler
|
||||
/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used
|
||||
/// to select the error handling inside the CFDP handler itself in addition to dispatching to a
|
||||
/// user-provided callback function provided by the [UserFaultHookProvider].
|
||||
/// user-provided callback function provided by the [UserFaultHandler].
|
||||
///
|
||||
/// Some note on the provided default settings:
|
||||
///
|
||||
@ -544,9 +465,6 @@ impl<UserHandler: UserFaultHookProvider> FaultHandler<UserHandler> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct IndicationConfig {
|
||||
pub eof_sent: bool,
|
||||
pub eof_recv: bool,
|
||||
@ -569,7 +487,6 @@ impl Default for IndicationConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Each CFDP entity handler has a [LocalEntityConfig]uration.
|
||||
pub struct LocalEntityConfig<UserFaultHook: UserFaultHookProvider> {
|
||||
pub id: UnsignedByteField,
|
||||
pub indication_cfg: IndicationConfig,
|
||||
@ -646,29 +563,31 @@ pub mod std_mod {
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple implementation of the [CountdownProvider] trait assuming a standard runtime.
|
||||
/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime.
|
||||
/// It also assumes that a second accuracy of the check timer period is sufficient.
|
||||
#[derive(Debug)]
|
||||
pub struct StdCountdown {
|
||||
expiry_time: Duration,
|
||||
pub struct StdCheckTimer {
|
||||
expiry_time_seconds: u64,
|
||||
start_time: std::time::Instant,
|
||||
}
|
||||
|
||||
impl StdCountdown {
|
||||
pub fn new(expiry_time: Duration) -> Self {
|
||||
impl StdCheckTimer {
|
||||
pub fn new(expiry_time_seconds: u64) -> Self {
|
||||
Self {
|
||||
expiry_time,
|
||||
expiry_time_seconds,
|
||||
start_time: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expiry_time_seconds(&self) -> u64 {
|
||||
self.expiry_time.as_secs()
|
||||
self.expiry_time_seconds
|
||||
}
|
||||
}
|
||||
|
||||
impl CountdownProvider for StdCountdown {
|
||||
impl CountdownProvider for StdCheckTimer {
|
||||
fn has_expired(&self) -> bool {
|
||||
if self.start_time.elapsed() > self.expiry_time {
|
||||
let elapsed_time = self.start_time.elapsed();
|
||||
if elapsed_time.as_nanos() > self.expiry_time_seconds as u128 * 1_000_000_000 {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
@ -679,36 +598,40 @@ pub mod std_mod {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StdTimerCreator {
|
||||
pub check_limit_timeout: Duration,
|
||||
pub struct StdCheckTimerCreator {
|
||||
pub check_limit_timeout_secs: u64,
|
||||
}
|
||||
|
||||
impl StdTimerCreator {
|
||||
pub const fn new(check_limit_timeout: Duration) -> Self {
|
||||
impl StdCheckTimerCreator {
|
||||
pub const fn new(check_limit_timeout_secs: u64) -> Self {
|
||||
Self {
|
||||
check_limit_timeout,
|
||||
check_limit_timeout_secs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StdTimerCreator {
|
||||
impl Default for StdCheckTimerCreator {
|
||||
fn default() -> Self {
|
||||
Self::new(Duration::from_secs(5))
|
||||
Self::new(5)
|
||||
}
|
||||
}
|
||||
|
||||
impl TimerCreatorProvider for StdTimerCreator {
|
||||
type Countdown = StdCountdown;
|
||||
impl CheckTimerProviderCreator for StdCheckTimerCreator {
|
||||
type CheckTimer = StdCheckTimer;
|
||||
|
||||
fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown {
|
||||
fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer {
|
||||
match timer_context {
|
||||
TimerContext::CheckLimit {
|
||||
local_id: _,
|
||||
remote_id: _,
|
||||
entity_type: _,
|
||||
} => StdCountdown::new(self.check_limit_timeout),
|
||||
TimerContext::NakActivity { expiry_time } => StdCountdown::new(expiry_time),
|
||||
TimerContext::PositiveAck { expiry_time } => StdCountdown::new(expiry_time),
|
||||
} => StdCheckTimer::new(self.check_limit_timeout_secs),
|
||||
TimerContext::NakActivity {
|
||||
expiry_time_seconds,
|
||||
} => StdCheckTimer::new(Duration::from_secs_f32(expiry_time_seconds).as_secs()),
|
||||
TimerContext::PositiveAck {
|
||||
expiry_time_seconds,
|
||||
} => StdCheckTimer::new(Duration::from_secs_f32(expiry_time_seconds).as_secs()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -718,7 +641,6 @@ pub mod std_mod {
|
||||
/// number of that transfer which is also determined by the CFDP source entity.
|
||||
#[derive(Debug, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TransactionId {
|
||||
source_id: UnsignedByteField,
|
||||
seq_num: UnsignedByteField,
|
||||
@ -760,15 +682,11 @@ pub enum State {
|
||||
Suspended = 2,
|
||||
}
|
||||
|
||||
/// [crc::Crc] instance using [crc::CRC_32_ISO_HDLC].
|
||||
///
|
||||
/// SANA registry entry: <https://sanaregistry.org/r/checksum_identifiers/records/4>,
|
||||
/// Entry in CRC catalogue: <https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32>
|
||||
/// SANA registry entry: https://sanaregistry.org/r/checksum_identifiers/records/4
|
||||
/// Entry in CRC catalogue: https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32
|
||||
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
|
||||
/// [crc::Crc] instance using [crc::CRC_32_ISCSI].
|
||||
///
|
||||
/// SANA registry entry: <https://sanaregistry.org/r/checksum_identifiers/records/3>,
|
||||
/// Entry in CRC catalogue: <https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi>
|
||||
/// SANA registry entry: https://sanaregistry.org/r/checksum_identifiers/records/3
|
||||
/// Entry in CRC catalogue: https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi
|
||||
pub const CRC_32C: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
@ -778,8 +696,6 @@ pub enum PacketTarget {
|
||||
DestEntity,
|
||||
}
|
||||
|
||||
/// Generic trait which models a raw CFDP packet data unit (PDU) block with some additional context
|
||||
/// information.
|
||||
pub trait PduProvider {
|
||||
fn pdu_type(&self) -> PduType;
|
||||
fn file_directive_type(&self) -> Option<FileDirectiveType>;
|
||||
@ -877,7 +793,7 @@ impl<'raw> PduRawWithInfo<'raw> {
|
||||
});
|
||||
}
|
||||
if pdu_header.pdu_datafield_len() < 1 {
|
||||
return Err(PduError::Format);
|
||||
return Err(PduError::FormatError);
|
||||
}
|
||||
// Route depending on PDU type and directive type if applicable. Retrieve directive type
|
||||
// from the raw stream for better performance (with sanity and directive code check).
|
||||
@ -1003,7 +919,7 @@ pub(crate) mod tests {
|
||||
};
|
||||
use user::{CfdpUser, OwnedMetadataRecvdParams, TransactionFinishedParams};
|
||||
|
||||
use crate::{PacketTarget, StdCountdown};
|
||||
use crate::{PacketTarget, StdCheckTimer};
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -1353,7 +1269,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_std_check_timer() {
|
||||
let mut std_check_timer = StdCountdown::new(Duration::from_secs(1));
|
||||
let mut std_check_timer = StdCheckTimer::new(1);
|
||||
assert!(!std_check_timer.has_expired());
|
||||
assert_eq!(std_check_timer.expiry_time_seconds(), 1);
|
||||
std::thread::sleep(Duration::from_millis(800));
|
||||
@ -1366,9 +1282,10 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_std_check_timer_creator() {
|
||||
let std_check_timer_creator = StdTimerCreator::new(Duration::from_secs(1));
|
||||
let check_timer = std_check_timer_creator.create_countdown(TimerContext::NakActivity {
|
||||
expiry_time: Duration::from_secs(1),
|
||||
let std_check_timer_creator = StdCheckTimerCreator::new(1);
|
||||
let check_timer =
|
||||
std_check_timer_creator.create_check_timer_provider(TimerContext::NakActivity {
|
||||
expiry_time_seconds: 1.0,
|
||||
});
|
||||
assert_eq!(check_timer.expiry_time_seconds(), 1);
|
||||
}
|
||||
@ -1546,7 +1463,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn transaction_id_hashable_usable_as_map_key() {
|
||||
let mut map = hashbrown::HashMap::new();
|
||||
let mut map = HashMap::new();
|
||||
let transaction_id_0 = TransactionId::new(
|
||||
UnsignedByteFieldU8::new(1).into(),
|
||||
UnsignedByteFieldU8::new(2).into(),
|
||||
|
@ -10,8 +10,6 @@ use spacepackets::{
|
||||
pub use alloc_mod::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct FilePathTooLarge(pub usize);
|
||||
|
||||
/// This trait is an abstraction for different Put Request structures which can be used
|
||||
@ -169,8 +167,6 @@ impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TlvWithInvalidType(pub(crate) ());
|
||||
|
||||
impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static, 'static> {
|
||||
@ -240,7 +236,6 @@ pub mod alloc_mod {
|
||||
/// Owned variant of [PutRequest] with no lifetimes which is also [Clone]able.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PutRequestOwned {
|
||||
pub destination_id: UnsignedByteField,
|
||||
source_file: Option<alloc::string::String>,
|
||||
|
702
src/source.rs
702
src/source.rs
File diff suppressed because it is too large
Load Diff
@ -15,8 +15,6 @@ use spacepackets::{
|
||||
use super::TransactionId;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TransactionFinishedParams {
|
||||
pub id: TransactionId,
|
||||
pub condition_code: ConditionCode,
|
||||
|
@ -14,7 +14,7 @@ use cfdp::{
|
||||
source::SourceHandler,
|
||||
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams, TransactionFinishedParams},
|
||||
EntityType, IndicationConfig, LocalEntityConfig, PduOwnedWithInfo, RemoteEntityConfig,
|
||||
StdTimerCreator, TransactionId, UserFaultHookProvider,
|
||||
StdCheckTimerCreator, TransactionId, UserFaultHookProvider,
|
||||
};
|
||||
use spacepackets::{
|
||||
cfdp::{ChecksumType, ConditionCode, TransmissionMode},
|
||||
@ -202,7 +202,6 @@ fn end_to_end_test(with_closure: bool) {
|
||||
put_request_cacher,
|
||||
2048,
|
||||
remote_cfg_of_dest,
|
||||
StdTimerCreator::default(),
|
||||
seq_count_provider,
|
||||
);
|
||||
let mut cfdp_user_source = ExampleCfdpUser::new(EntityType::Sending, completion_signal_source);
|
||||
@ -226,7 +225,7 @@ fn end_to_end_test(with_closure: bool) {
|
||||
dest_tx,
|
||||
NativeFilestore::default(),
|
||||
remote_cfg_of_source,
|
||||
StdTimerCreator::default(),
|
||||
StdCheckTimerCreator::default(),
|
||||
);
|
||||
let mut cfdp_user_dest = ExampleCfdpUser::new(EntityType::Receiving, completion_signal_dest);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user