Compare commits
No commits in common. "main" and "satrs-shared-v0.2.0" have entirely different histories.
main
...
satrs-shar
@ -12,3 +12,4 @@ exclude = [
|
|||||||
"embedded-examples/stm32f3-disco-rtic",
|
"embedded-examples/stm32f3-disco-rtic",
|
||||||
"embedded-examples/stm32h7-rtic",
|
"embedded-examples/stm32h7-rtic",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -8,18 +8,18 @@ homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
|||||||
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
repository = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fern = "0.7"
|
fern = "0.6"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
delegate = "0.13"
|
delegate = "0.10"
|
||||||
zerocopy = "0.8"
|
zerocopy = "0.6"
|
||||||
csv = "1"
|
csv = "1"
|
||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
thiserror = "2"
|
thiserror = "1"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
derive-new = "0.7"
|
derive-new = "0.5"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
|
@ -132,7 +132,6 @@ pub struct MgmData {
|
|||||||
pub struct MpscModeLeafInterface {
|
pub struct MpscModeLeafInterface {
|
||||||
pub request_rx: mpsc::Receiver<GenericMessage<ModeRequest>>,
|
pub request_rx: mpsc::Receiver<GenericMessage<ModeRequest>>,
|
||||||
pub reply_to_pus_tx: mpsc::Sender<GenericMessage<ModeReply>>,
|
pub reply_to_pus_tx: mpsc::Sender<GenericMessage<ModeReply>>,
|
||||||
#[allow(dead_code)]
|
|
||||||
pub reply_to_parent_tx: mpsc::SyncSender<GenericMessage<ModeReply>>,
|
pub reply_to_parent_tx: mpsc::SyncSender<GenericMessage<ModeReply>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use crossbeam_channel::{bounded, Receiver, Sender};
|
use crossbeam_channel::{bounded, Receiver, Sender};
|
||||||
use std::sync::atomic::{AtomicU16, Ordering};
|
use std::sync::atomic::{AtomicU16, Ordering};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use zerocopy::{FromBytes, Immutable, IntoBytes, NetworkEndian, Unaligned, U16};
|
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
|
||||||
|
|
||||||
trait FieldDataProvider: Send {
|
trait FieldDataProvider: Send {
|
||||||
fn get_data(&self) -> &[u8];
|
fn get_data(&self) -> &[u8];
|
||||||
@ -35,7 +35,7 @@ struct ExampleMgmSet {
|
|||||||
temperature: u16,
|
temperature: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromBytes, IntoBytes, Immutable, Unaligned)]
|
#[derive(FromBytes, AsBytes, Unaligned)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct ExampleMgmSetZc {
|
struct ExampleMgmSetZc {
|
||||||
mgm_vec: [u8; 12],
|
mgm_vec: [u8; 12],
|
||||||
|
@ -88,7 +88,6 @@ impl PowerSwitcherCommandSender<PcduSwitch> for PowerSwitchHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(new)]
|
#[derive(new)]
|
||||||
pub struct SwitchRequestInfo {
|
pub struct SwitchRequestInfo {
|
||||||
pub requestor_info: MessageMetadata,
|
pub requestor_info: MessageMetadata,
|
||||||
@ -100,7 +99,6 @@ pub struct SwitchRequestInfo {
|
|||||||
pub struct TestSwitchHelper {
|
pub struct TestSwitchHelper {
|
||||||
pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
|
pub switch_requests: RefCell<VecDeque<SwitchRequestInfo>>,
|
||||||
pub switch_info_requests: RefCell<VecDeque<PcduSwitch>>,
|
pub switch_info_requests: RefCell<VecDeque<PcduSwitch>>,
|
||||||
#[allow(dead_code)]
|
|
||||||
pub switch_delay_request_count: u32,
|
pub switch_delay_request_count: u32,
|
||||||
pub next_switch_delay: Duration,
|
pub next_switch_delay: Duration,
|
||||||
pub switch_map: RefCell<SwitchMapWrapper>,
|
pub switch_map: RefCell<SwitchMapWrapper>,
|
||||||
|
@ -44,13 +44,11 @@ pub struct PusTcMpscRouter {
|
|||||||
pub event_tc_sender: Sender<EcssTcAndToken>,
|
pub event_tc_sender: Sender<EcssTcAndToken>,
|
||||||
pub sched_tc_sender: Sender<EcssTcAndToken>,
|
pub sched_tc_sender: Sender<EcssTcAndToken>,
|
||||||
pub hk_tc_sender: Sender<EcssTcAndToken>,
|
pub hk_tc_sender: Sender<EcssTcAndToken>,
|
||||||
#[allow(dead_code)]
|
|
||||||
pub action_tc_sender: Sender<EcssTcAndToken>,
|
pub action_tc_sender: Sender<EcssTcAndToken>,
|
||||||
pub mode_tc_sender: Sender<EcssTcAndToken>,
|
pub mode_tc_sender: Sender<EcssTcAndToken>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PusTcDistributor<TmSender: EcssTmSender> {
|
pub struct PusTcDistributor<TmSender: EcssTmSender> {
|
||||||
#[allow(dead_code)]
|
|
||||||
pub id: ComponentId,
|
pub id: ComponentId,
|
||||||
pub tm_sender: TmSender,
|
pub tm_sender: TmSender,
|
||||||
pub verif_reporter: VerificationReporter,
|
pub verif_reporter: VerificationReporter,
|
||||||
|
@ -26,7 +26,6 @@ pub enum CompositeRequest {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GenericRequestRouter {
|
pub struct GenericRequestRouter {
|
||||||
#[allow(dead_code)]
|
|
||||||
pub id: ComponentId,
|
pub id: ComponentId,
|
||||||
// All messages which do not have a dedicated queue.
|
// All messages which do not have a dedicated queue.
|
||||||
pub composite_router_map:
|
pub composite_router_map:
|
||||||
|
@ -4,19 +4,16 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use satrs::tmtc::{PacketAsVec, PacketInPool, SharedPacketPool};
|
||||||
use satrs::{
|
use satrs::{
|
||||||
pool::PoolProvider,
|
pool::PoolProvider,
|
||||||
|
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
|
||||||
spacepackets::{
|
spacepackets::{
|
||||||
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
||||||
seq_count::CcsdsSimpleSeqCountProvider,
|
|
||||||
time::cds::MIN_CDS_FIELD_LEN,
|
time::cds::MIN_CDS_FIELD_LEN,
|
||||||
CcsdsPacket,
|
CcsdsPacket,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use satrs::{
|
|
||||||
spacepackets::seq_count::SequenceCountProvider,
|
|
||||||
tmtc::{PacketAsVec, PacketInPool, SharedPacketPool},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::interface::tcp::SyncTcpTmSource;
|
use crate::interface::tcp::SyncTcpTmSource;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ version = "1"
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.satrs-shared]
|
[dependencies.satrs-shared]
|
||||||
version = ">=0.1.3, <=0.2"
|
version = ">=0.1.3, <0.2"
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
[dependencies.satrs-mib-codegen]
|
[dependencies.satrs-mib-codegen]
|
||||||
|
@ -28,7 +28,7 @@ features = ["full"]
|
|||||||
trybuild = { version = "1", features = ["diff"] }
|
trybuild = { version = "1", features = ["diff"] }
|
||||||
|
|
||||||
[dev-dependencies.satrs-shared]
|
[dev-dependencies.satrs-shared]
|
||||||
version = ">=0.1.3, <=0.2"
|
version = ">=0.1.3, <0.2"
|
||||||
|
|
||||||
[dev-dependencies.satrs-mib]
|
[dev-dependencies.satrs-mib]
|
||||||
path = ".."
|
path = ".."
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
extern crate alloc;
|
||||||
#[cfg(any(feature = "std", test))]
|
#[cfg(any(feature = "std", test))]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@ edition = "2021"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
thiserror = "2"
|
thiserror = "1"
|
||||||
fern = "0.7"
|
fern = "0.5"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
humantime = "2"
|
humantime = "2"
|
||||||
@ -25,4 +25,4 @@ features = ["serde"]
|
|||||||
path = "../satrs"
|
path = "../satrs"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
delegate = "0.13"
|
delegate = "0.12"
|
||||||
|
@ -31,7 +31,6 @@ const PHASE_Z: f32 = 0.2;
|
|||||||
/// might still be possible and is probably sufficient for many OBSW needs.
|
/// might still be possible and is probably sufficient for many OBSW needs.
|
||||||
pub struct MagnetometerModel<ReplyProvider: MgmReplyProvider> {
|
pub struct MagnetometerModel<ReplyProvider: MgmReplyProvider> {
|
||||||
pub switch_state: SwitchStateBinary,
|
pub switch_state: SwitchStateBinary,
|
||||||
#[allow(dead_code)]
|
|
||||||
pub periodicity: Duration,
|
pub periodicity: Duration,
|
||||||
pub external_mag_field: Option<MgmSensorValuesMicroTesla>,
|
pub external_mag_field: Option<MgmSensorValuesMicroTesla>,
|
||||||
pub reply_sender: mpsc::Sender<SimReply>,
|
pub reply_sender: mpsc::Sender<SimReply>,
|
||||||
|
@ -8,10 +8,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
# [unreleased]
|
# [unreleased]
|
||||||
|
|
||||||
# [v0.2.1] 2024-11-15
|
|
||||||
|
|
||||||
Increased allowed spacepackets to v0.13
|
|
||||||
|
|
||||||
# [v0.2.0] 2024-11-04
|
# [v0.2.0] 2024-11-04
|
||||||
|
|
||||||
Semver bump, due to added features in v0.1.4
|
Semver bump, due to added features in v0.1.4
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "satrs-shared"
|
name = "satrs-shared"
|
||||||
description = "Components shared by multiple sat-rs crates"
|
description = "Components shared by multiple sat-rs crates"
|
||||||
version = "0.2.1"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||||
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
|
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
|
||||||
@ -22,7 +22,7 @@ version = "0.3"
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.spacepackets]
|
[dependencies.spacepackets]
|
||||||
version = ">0.9, <=0.13"
|
version = ">0.9, <=0.12"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -12,14 +12,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
- Renamed `StaticPoolConfig::new` to `StaticPoolConfig::new_from_subpool_cfg_tuples`. The new
|
- Renamed `StaticPoolConfig::new` to `StaticPoolConfig::new_from_subpool_cfg_tuples`. The new
|
||||||
`new` implementation expects a type struct instead of tuples.
|
`new` implementation expects a type struct instead of tuples.
|
||||||
- Moved `cfdp` module to [dedicated crate](https://egit.irs.uni-stuttgart.de/rust/cfdp)
|
|
||||||
- Moved `seq_count` module to [spacepackets](https://egit.irs.uni-stuttgart.de/rust/spacepackets)
|
|
||||||
crate
|
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
- `StaticHeaplessMemoryPool` which can be grown with user-provided static buffers.
|
- `StaticHeaplessMemoryPool` which can be grown with user-provided static buffers.
|
||||||
- Scheduling table for systems with a standard runtime
|
|
||||||
|
|
||||||
# [v0.2.1] 2024-05-19
|
# [v0.2.1] 2024-05-19
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ keywords = ["no-std", "space", "aerospace"]
|
|||||||
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
|
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
delegate = ">0.7, <=0.13"
|
delegate = ">0.7, <=0.10"
|
||||||
paste = "1"
|
paste = "1"
|
||||||
derive-new = ">=0.6, <=0.7"
|
derive-new = "0.6"
|
||||||
smallvec = "1"
|
smallvec = "1"
|
||||||
crc = "3"
|
crc = "3"
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ version = ">0.5, <=0.7"
|
|||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.spacepackets]
|
[dependencies.spacepackets]
|
||||||
version = "0.13"
|
version = "0.12"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.cobs]
|
[dependencies.cobs]
|
||||||
@ -45,11 +45,11 @@ version = "1"
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.hashbrown]
|
[dependencies.hashbrown]
|
||||||
version = ">=0.14, <=0.15"
|
version = "0.14"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.heapless]
|
[dependencies.heapless]
|
||||||
version = "0.8"
|
version = "0.7"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.downcast-rs]
|
[dependencies.downcast-rs]
|
||||||
@ -67,8 +67,8 @@ default-features = false
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.thiserror]
|
[dependencies.thiserror]
|
||||||
version = "2"
|
version = "1"
|
||||||
default-features = false
|
optional = true
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1"
|
version = "1"
|
||||||
@ -81,7 +81,7 @@ features = ["all"]
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.mio]
|
[dependencies.mio]
|
||||||
version = "1"
|
version = "0.8"
|
||||||
features = ["os-poll", "net"]
|
features = ["os-poll", "net"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ optional = true
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde = "1"
|
serde = "1"
|
||||||
zerocopy = "0.8"
|
zerocopy = "0.7"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
@ -111,7 +111,7 @@ std = [
|
|||||||
"serde/std",
|
"serde/std",
|
||||||
"spacepackets/std",
|
"spacepackets/std",
|
||||||
"num_enum/std",
|
"num_enum/std",
|
||||||
"thiserror/std",
|
"thiserror",
|
||||||
"socket2",
|
"socket2",
|
||||||
"mio"
|
"mio"
|
||||||
]
|
]
|
||||||
@ -127,7 +127,8 @@ crossbeam = ["crossbeam-channel"]
|
|||||||
heapless = ["dep:heapless"]
|
heapless = ["dep:heapless"]
|
||||||
defmt = ["dep:defmt", "spacepackets/defmt"]
|
defmt = ["dep:defmt", "spacepackets/defmt"]
|
||||||
test_util = []
|
test_util = []
|
||||||
|
doc-images = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--generate-link-to-definition"]
|
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
|
||||||
cargo +nightly doc --all-features --open
|
|
@ -3,7 +3,8 @@ Checklist for new releases
|
|||||||
|
|
||||||
# Pre-Release
|
# Pre-Release
|
||||||
|
|
||||||
1. Make sure any new modules are documented sufficiently enough and check docs by running `docs.sh`.
|
1. Make sure any new modules are documented sufficiently enough and check docs with
|
||||||
|
`cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]' --open`.
|
||||||
2. Bump version specifier in `Cargo.toml`.
|
2. Bump version specifier in `Cargo.toml`.
|
||||||
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
|
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
|
||||||
`unreleased` section.
|
`unreleased` section.
|
||||||
|
1603
satrs/src/cfdp/dest.rs
Normal file
1603
satrs/src/cfdp/dest.rs
Normal file
File diff suppressed because it is too large
Load Diff
769
satrs/src/cfdp/filestore.rs
Normal file
769
satrs/src/cfdp/filestore.rs
Normal file
@ -0,0 +1,769 @@
|
|||||||
|
use alloc::string::{String, ToString};
|
||||||
|
use core::fmt::Display;
|
||||||
|
use crc::{Crc, CRC_32_CKSUM};
|
||||||
|
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::*;
|
||||||
|
|
||||||
|
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum FilestoreError {
|
||||||
|
FileDoesNotExist,
|
||||||
|
FileAlreadyExists,
|
||||||
|
DirDoesNotExist,
|
||||||
|
Permission,
|
||||||
|
IsNotFile,
|
||||||
|
IsNotDirectory,
|
||||||
|
ByteConversion(ByteConversionError),
|
||||||
|
Io {
|
||||||
|
raw_errno: Option<i32>,
|
||||||
|
string: String,
|
||||||
|
},
|
||||||
|
ChecksumTypeNotImplemented(ChecksumType),
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>;
|
||||||
|
|
||||||
|
fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError>;
|
||||||
|
|
||||||
|
/// Truncating a file means deleting all its data so the resulting file is empty.
|
||||||
|
/// This can be more efficient than removing and re-creating a file.
|
||||||
|
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError>;
|
||||||
|
|
||||||
|
fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError>;
|
||||||
|
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError>;
|
||||||
|
|
||||||
|
fn read_data(
|
||||||
|
&self,
|
||||||
|
file_path: &str,
|
||||||
|
offset: u64,
|
||||||
|
read_len: u64,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Result<(), FilestoreError>;
|
||||||
|
|
||||||
|
fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError>;
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_file(&self, path: &str) -> bool;
|
||||||
|
|
||||||
|
fn is_dir(&self, path: &str) -> bool {
|
||||||
|
!self.is_file(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists(&self, path: &str) -> bool;
|
||||||
|
|
||||||
|
/// This special function is the CFDP specific abstraction to verify the checksum of a file.
|
||||||
|
/// This allows to keep OS specific details like reading the whole file in the most efficient
|
||||||
|
/// manner inside the file system abstraction.
|
||||||
|
///
|
||||||
|
/// The passed verification buffer argument will be used by the specific implementation as
|
||||||
|
/// a buffer to read the file into. It is recommended to use common buffer sizes like
|
||||||
|
/// 4096 or 8192 bytes.
|
||||||
|
fn checksum_verify(
|
||||||
|
&self,
|
||||||
|
file_path: &str,
|
||||||
|
checksum_type: ChecksumType,
|
||||||
|
expected_checksum: u32,
|
||||||
|
verification_buf: &mut [u8],
|
||||||
|
) -> Result<bool, FilestoreError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub mod std_mod {
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
fs::{self, File, OpenOptions},
|
||||||
|
io::{BufReader, Read, Seek, SeekFrom, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NativeFilestore {}
|
||||||
|
|
||||||
|
impl VirtualFilestore for NativeFilestore {
|
||||||
|
fn create_file(&self, file_path: &str) -> Result<(), FilestoreError> {
|
||||||
|
if self.exists(file_path) {
|
||||||
|
return Err(FilestoreError::FileAlreadyExists);
|
||||||
|
}
|
||||||
|
File::create(file_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError> {
|
||||||
|
if !self.exists(file_path) {
|
||||||
|
return Err(FilestoreError::FileDoesNotExist);
|
||||||
|
}
|
||||||
|
if !self.is_file(file_path) {
|
||||||
|
return Err(FilestoreError::IsNotFile);
|
||||||
|
}
|
||||||
|
fs::remove_file(file_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> {
|
||||||
|
if !self.exists(file_path) {
|
||||||
|
return Err(FilestoreError::FileDoesNotExist);
|
||||||
|
}
|
||||||
|
if !self.is_file(file_path) {
|
||||||
|
return Err(FilestoreError::IsNotFile);
|
||||||
|
}
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(file_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> {
|
||||||
|
fs::create_dir(dir_path).map_err(|e| FilestoreError::Io {
|
||||||
|
raw_errno: e.raw_os_error(),
|
||||||
|
string: e.to_string(),
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError> {
|
||||||
|
if !self.exists(dir_path) {
|
||||||
|
return Err(FilestoreError::DirDoesNotExist);
|
||||||
|
}
|
||||||
|
if !self.is_dir(dir_path) {
|
||||||
|
return Err(FilestoreError::IsNotDirectory);
|
||||||
|
}
|
||||||
|
if !all {
|
||||||
|
fs::remove_dir(dir_path)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
fs::remove_dir_all(dir_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_data(
|
||||||
|
&self,
|
||||||
|
file_name: &str,
|
||||||
|
offset: u64,
|
||||||
|
read_len: u64,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Result<(), FilestoreError> {
|
||||||
|
if buf.len() < read_len as usize {
|
||||||
|
return Err(ByteConversionError::ToSliceTooSmall {
|
||||||
|
found: buf.len(),
|
||||||
|
expected: read_len as usize,
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
if !self.exists(file_name) {
|
||||||
|
return Err(FilestoreError::FileDoesNotExist);
|
||||||
|
}
|
||||||
|
if !self.is_file(file_name) {
|
||||||
|
return Err(FilestoreError::IsNotFile);
|
||||||
|
}
|
||||||
|
let mut file = File::open(file_name)?;
|
||||||
|
file.seek(SeekFrom::Start(offset))?;
|
||||||
|
file.read_exact(&mut buf[0..read_len as usize])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError> {
|
||||||
|
if !self.exists(file) {
|
||||||
|
return Err(FilestoreError::FileDoesNotExist);
|
||||||
|
}
|
||||||
|
if !self.is_file(file) {
|
||||||
|
return Err(FilestoreError::IsNotFile);
|
||||||
|
}
|
||||||
|
let mut file = OpenOptions::new().write(true).open(file)?;
|
||||||
|
file.seek(SeekFrom::Start(offset))?;
|
||||||
|
file.write_all(buf)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_file(&self, path: &str) -> bool {
|
||||||
|
let path = Path::new(path);
|
||||||
|
path.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists(&self, path: &str) -> bool {
|
||||||
|
let path = Path::new(path);
|
||||||
|
if !path.exists() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checksum_verify(
|
||||||
|
&self,
|
||||||
|
file_path: &str,
|
||||||
|
checksum_type: ChecksumType,
|
||||||
|
expected_checksum: u32,
|
||||||
|
verification_buf: &mut [u8],
|
||||||
|
) -> Result<bool, FilestoreError> {
|
||||||
|
match checksum_type {
|
||||||
|
ChecksumType::Modular => {
|
||||||
|
if self.calc_modular_checksum(file_path)? == expected_checksum {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
ChecksumType::Crc32 => {
|
||||||
|
let mut digest = CRC_32.digest();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
digest.update(&verification_buf[0..bytes_read]);
|
||||||
|
}
|
||||||
|
if digest.finalize() == expected_checksum {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
ChecksumType::NullChecksum => Ok(true),
|
||||||
|
_ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NativeFilestore {
|
||||||
|
pub fn calc_modular_checksum(&self, file_path: &str) -> Result<u32, FilestoreError> {
|
||||||
|
let mut checksum: u32 = 0;
|
||||||
|
let file = File::open(file_path)?;
|
||||||
|
let mut buf_reader = BufReader::new(file);
|
||||||
|
let mut buffer = [0; 4];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let bytes_read = buf_reader.read(&mut buffer)?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Perform padding directly in the buffer
|
||||||
|
(bytes_read..4).for_each(|i| {
|
||||||
|
buffer[i] = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
|
||||||
|
}
|
||||||
|
Ok(checksum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{fs, path::Path, println};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use alloc::format;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
const EXAMPLE_DATA_CFDP: [u8; 15] = [
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||||
|
];
|
||||||
|
|
||||||
|
const NATIVE_FS: NativeFilestore = NativeFilestore {};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_native_filestore_create() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
let result =
|
||||||
|
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let path = Path::new(&file_path);
|
||||||
|
assert!(path.exists());
|
||||||
|
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||||
|
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_native_fs_file_exists() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||||
|
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_native_fs_dir_exists() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let dir_path = tmpdir.path().join("testdir");
|
||||||
|
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||||
|
NATIVE_FS
|
||||||
|
.create_dir(dir_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||||
|
assert!(NATIVE_FS.is_dir(dir_path.as_path().to_str().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_native_fs_remove_file() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.expect("creating file failed");
|
||||||
|
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||||
|
NATIVE_FS
|
||||||
|
.remove_file(file_path.to_str().unwrap())
|
||||||
|
.expect("removing file failed");
|
||||||
|
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_native_fs_write() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||||
|
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
|
||||||
|
println!("{}", file_path.to_str().unwrap());
|
||||||
|
let write_data = "hello world\n";
|
||||||
|
NATIVE_FS
|
||||||
|
.write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
|
||||||
|
.expect("writing to file failed");
|
||||||
|
let read_back = fs::read_to_string(file_path).expect("reading back data failed");
|
||||||
|
assert_eq!(read_back, write_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_native_fs_read() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
assert!(NATIVE_FS.exists(file_path.to_str().unwrap()));
|
||||||
|
assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()));
|
||||||
|
println!("{}", file_path.to_str().unwrap());
|
||||||
|
let write_data = "hello world\n";
|
||||||
|
NATIVE_FS
|
||||||
|
.write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes())
|
||||||
|
.expect("writing to file failed");
|
||||||
|
let read_back = fs::read_to_string(file_path).expect("reading back data failed");
|
||||||
|
assert_eq!(read_back, write_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_truncate_file() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.expect("creating file failed");
|
||||||
|
fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
|
||||||
|
assert_eq!(fs::read(file_path.clone()).unwrap(), [1, 2, 3, 4]);
|
||||||
|
NATIVE_FS
|
||||||
|
.truncate_file(file_path.to_str().unwrap())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(fs::read(file_path.clone()).unwrap(), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_dir() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let dir_path = tmpdir.path().join("testdir");
|
||||||
|
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||||
|
NATIVE_FS
|
||||||
|
.create_dir(dir_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||||
|
NATIVE_FS
|
||||||
|
.remove_dir(dir_path.to_str().unwrap(), false)
|
||||||
|
.unwrap();
|
||||||
|
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_file() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.expect("creating file failed");
|
||||||
|
fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap();
|
||||||
|
let read_buf: &mut [u8] = &mut [0; 4];
|
||||||
|
NATIVE_FS
|
||||||
|
.read_data(file_path.to_str().unwrap(), 0, 4, read_buf)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!([1, 2, 3, 4], read_buf);
|
||||||
|
NATIVE_FS
|
||||||
|
.write_data(file_path.to_str().unwrap(), 4, &[5, 6, 7, 8])
|
||||||
|
.expect("writing to file failed");
|
||||||
|
NATIVE_FS
|
||||||
|
.read_data(file_path.to_str().unwrap(), 2, 4, read_buf)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!([3, 4, 5, 6], read_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_which_does_not_exist() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::FileDoesNotExist = error {
|
||||||
|
assert_eq!(error.to_string(), "file does not exist");
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_file_already_exists() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
let result =
|
||||||
|
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let result =
|
||||||
|
NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed"));
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::FileAlreadyExists = error {
|
||||||
|
assert_eq!(error.to_string(), "file already exists");
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_file_with_dir_api() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::IsNotDirectory = error {
|
||||||
|
assert_eq!(error.to_string(), "is not a directory");
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_dir_remove_all() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let dir_path = tmpdir.path().join("test");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_dir(dir_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
let file_path = dir_path.as_path().join("test.txt");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
let result = NATIVE_FS.remove_dir(dir_path.to_str().unwrap(), true);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_dir_with_file_api() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_dir(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::IsNotFile = error {
|
||||||
|
assert_eq!(error.to_string(), "is not a file");
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_dir_which_does_not_exist() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test");
|
||||||
|
let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::DirDoesNotExist = error {
|
||||||
|
assert_eq!(error.to_string(), "directory does not exist");
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_file_which_does_not_exist() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
let result = NATIVE_FS.remove_file(file_path.to_str().unwrap());
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::FileDoesNotExist = error {
|
||||||
|
assert_eq!(error.to_string(), "file does not exist");
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_truncate_file_which_does_not_exist() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::FileDoesNotExist = error {
|
||||||
|
assert_eq!(error.to_string(), "file does not exist");
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_truncate_file_on_directory() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test");
|
||||||
|
NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
|
||||||
|
let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap());
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::IsNotFile = error {
|
||||||
|
assert_eq!(error.to_string(), "is not a file");
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_byte_conversion_error_when_reading() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 2, &mut []);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::ByteConversion(byte_conv_error) = error {
|
||||||
|
if let ByteConversionError::ToSliceTooSmall { found, expected } = byte_conv_error {
|
||||||
|
assert_eq!(found, 0);
|
||||||
|
assert_eq!(expected, 2);
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
format!("filestore error: {}", byte_conv_error)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_file_on_dir() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let dir_path = tmpdir.path().join("test");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_dir(dir_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
let result = NATIVE_FS.read_data(dir_path.to_str().unwrap(), 0, 4, &mut [0; 4]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::IsNotFile = error {
|
||||||
|
assert_eq!(error.to_string(), "is not a file");
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_file_non_existing() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::FileDoesNotExist = error {
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_file_on_dir() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test");
|
||||||
|
NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap();
|
||||||
|
let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::IsNotFile = error {
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filename_extraction() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("test.txt");
|
||||||
|
NATIVE_FS
|
||||||
|
.create_file(file_path.to_str().expect("getting str for file failed"))
|
||||||
|
.unwrap();
|
||||||
|
NativeFilestore::filename_from_full_path(file_path.to_str().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_modular_checksum() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("mod-crc.bin");
|
||||||
|
fs::write(file_path.as_path(), EXAMPLE_DATA_CFDP).expect("writing test file failed");
|
||||||
|
// Kind of re-writing the modular checksum impl here which we are trying to test, but the
|
||||||
|
// numbers/correctness were verified manually using calculators, so this is okay.
|
||||||
|
let mut checksum: u32 = 0;
|
||||||
|
let mut buffer: [u8; 4] = [0; 4];
|
||||||
|
for i in 0..3 {
|
||||||
|
buffer = EXAMPLE_DATA_CFDP[i * 4..(i + 1) * 4].try_into().unwrap();
|
||||||
|
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
|
||||||
|
}
|
||||||
|
buffer[0..3].copy_from_slice(&EXAMPLE_DATA_CFDP[12..15]);
|
||||||
|
buffer[3] = 0;
|
||||||
|
checksum = checksum.wrapping_add(u32::from_be_bytes(buffer));
|
||||||
|
let mut verif_buf: [u8; 32] = [0; 32];
|
||||||
|
let result = NATIVE_FS.checksum_verify(
|
||||||
|
file_path.to_str().unwrap(),
|
||||||
|
ChecksumType::Modular,
|
||||||
|
checksum,
|
||||||
|
&mut verif_buf,
|
||||||
|
);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_null_checksum_impl() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("mod-crc.bin");
|
||||||
|
// 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(
|
||||||
|
file_path.to_str().unwrap(),
|
||||||
|
ChecksumType::NullChecksum,
|
||||||
|
0,
|
||||||
|
&mut [],
|
||||||
|
);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(result.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_checksum_not_implemented() {
|
||||||
|
let tmpdir = tempdir().expect("creating tmpdir failed");
|
||||||
|
let file_path = tmpdir.path().join("mod-crc.bin");
|
||||||
|
// 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(
|
||||||
|
file_path.to_str().unwrap(),
|
||||||
|
ChecksumType::Crc32Proximity1,
|
||||||
|
0,
|
||||||
|
&mut [],
|
||||||
|
);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error {
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
format!("checksum {:?} not implemented", cksum_type)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("unexpected error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
668
satrs/src/cfdp/mod.rs
Normal file
668
satrs/src/cfdp/mod.rs
Normal file
@ -0,0 +1,668 @@
|
|||||||
|
//! This module contains the implementation of the CFDP high level classes as specified in the
|
||||||
|
//! CCSDS 727.0-B-5.
|
||||||
|
use core::{cell::RefCell, fmt::Debug, hash::Hash};
|
||||||
|
|
||||||
|
use crc::{Crc, CRC_32_CKSUM};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use spacepackets::{
|
||||||
|
cfdp::{
|
||||||
|
pdu::{FileDirectiveType, PduError, PduHeader},
|
||||||
|
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
|
||||||
|
},
|
||||||
|
util::{UnsignedByteField, UnsignedEnum},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::time::CountdownProvider;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub mod dest;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub mod filestore;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub mod source;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum EntityType {
|
||||||
|
Sending,
|
||||||
|
Receiving,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TimerContext {
|
||||||
|
CheckLimit {
|
||||||
|
local_id: UnsignedByteField,
|
||||||
|
remote_id: UnsignedByteField,
|
||||||
|
entity_type: EntityType,
|
||||||
|
},
|
||||||
|
NakActivity {
|
||||||
|
expiry_time_seconds: f32,
|
||||||
|
},
|
||||||
|
PositiveAck {
|
||||||
|
expiry_time_seconds: f32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A generic trait which allows CFDP entities to create check timers which are required to
|
||||||
|
/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
|
||||||
|
/// and 4.6.3.3.
|
||||||
|
///
|
||||||
|
/// This trait also allows the creation of different check timers depending on context and purpose
|
||||||
|
/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or
|
||||||
|
/// other factors.
|
||||||
|
///
|
||||||
|
/// The countdown timer is used by 3 mechanisms of the CFDP protocol.
|
||||||
|
///
|
||||||
|
/// ## 1. Check limit handling
|
||||||
|
///
|
||||||
|
/// The first mechanism is the check limit handling for unacknowledged transfers as specified
|
||||||
|
/// in 4.6.3.2 and 4.6.3.3 of the CFDP standard.
|
||||||
|
/// For this mechanism, the timer has different functionality depending on whether
|
||||||
|
/// the using entity is the sending entity or the receiving entity for the unacknowledged
|
||||||
|
/// transmission mode.
|
||||||
|
///
|
||||||
|
/// For the sending entity, this timer determines the expiry period for declaring a check limit
|
||||||
|
/// fault after sending an EOF PDU with requested closure. This allows a timeout of the transfer.
|
||||||
|
/// Also see 4.6.3.2 of the CFDP standard.
|
||||||
|
///
|
||||||
|
/// For the receiving entity, this timer determines the expiry period for incrementing a check
|
||||||
|
/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
|
||||||
|
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard.
|
||||||
|
///
|
||||||
|
/// ## 2. NAK activity limit
|
||||||
|
///
|
||||||
|
/// The timer will be used to perform the NAK activity check as specified in 4.6.4.7 of the CFDP
|
||||||
|
/// standard. The expiration period will be provided by the NAK timer expiration limit of the
|
||||||
|
/// remote entity configuration.
|
||||||
|
///
|
||||||
|
/// ## 3. Positive ACK procedures
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub trait CheckTimerCreator {
|
||||||
|
fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box<dyn CountdownProvider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime.
|
||||||
|
/// It also assumes that a second accuracy of the check timer period is sufficient.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StdCheckTimer {
|
||||||
|
expiry_time_seconds: u64,
|
||||||
|
start_time: std::time::Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl StdCheckTimer {
|
||||||
|
pub fn new(expiry_time_seconds: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
expiry_time_seconds,
|
||||||
|
start_time: std::time::Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl CountdownProvider for StdCheckTimer {
|
||||||
|
fn has_expired(&self) -> bool {
|
||||||
|
let elapsed_time = self.start_time.elapsed();
|
||||||
|
if elapsed_time.as_secs() > self.expiry_time_seconds {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.start_time = std::time::Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This structure models the remote entity configuration information as specified in chapter 8.3
|
||||||
|
/// of the CFDP standard.
|
||||||
|
|
||||||
|
/// Some of the fields which were not considered necessary for the Rust implementation
|
||||||
|
/// were omitted. Some other fields which are not contained inside the standard but are considered
|
||||||
|
/// necessary for the Rust implementation are included.
|
||||||
|
///
|
||||||
|
/// ## Notes on Positive Acknowledgment Procedures
|
||||||
|
///
|
||||||
|
/// The `positive_ack_timer_interval_seconds` and `positive_ack_timer_expiration_limit` will
|
||||||
|
/// be used for positive acknowledgement procedures as specified in CFDP chapter 4.7. The sending
|
||||||
|
/// entity will start the timer for any PDUs where an acknowledgment is required (e.g. EOF PDU).
|
||||||
|
/// Once the expected ACK response has not been received for that interval, as counter will be
|
||||||
|
/// incremented and the timer will be reset. Once the counter exceeds the
|
||||||
|
/// `positive_ack_timer_expiration_limit`, a Positive ACK Limit Reached fault will be declared.
|
||||||
|
///
|
||||||
|
/// ## Notes on Deferred Lost Segment Procedures
|
||||||
|
///
|
||||||
|
/// This procedure will be active if an EOF (No Error) PDU is received in acknowledged mode. After
|
||||||
|
/// issuing the NAK sequence which has the whole file scope, a timer will be started. The timer is
|
||||||
|
/// reset when missing segments or missing metadata is received. The timer will be deactivated if
|
||||||
|
/// all missing data is received. If the timer expires, a new NAK sequence will be issued and a
|
||||||
|
/// counter will be incremented, which can lead to a NAK Limit Reached fault being declared.
|
||||||
|
///
|
||||||
|
/// ## Fields
|
||||||
|
///
|
||||||
|
/// * `entity_id` - The ID of the remote entity.
|
||||||
|
/// * `max_packet_len` - This determines of all PDUs generated for that remote entity in addition
|
||||||
|
/// to the `max_file_segment_len` attribute which also determines the size of file data PDUs.
|
||||||
|
/// * `max_file_segment_len` The maximum file segment length which determines the maximum size
|
||||||
|
/// of file data PDUs in addition to the `max_packet_len` attribute. If this field is set
|
||||||
|
/// to None, the maximum file segment length will be derived from the maximum packet length.
|
||||||
|
/// If this has some value which is smaller than the segment value derived from
|
||||||
|
/// `max_packet_len`, this value will be picked.
|
||||||
|
/// * `closure_requested_by_default` - If the closure requested field is not supplied as part of
|
||||||
|
/// the Put Request, it will be determined from this field in the remote configuration.
|
||||||
|
/// * `crc_on_transmission_by_default` - If the CRC option is not supplied as part of the Put
|
||||||
|
/// Request, it will be determined from this field in the remote configuration.
|
||||||
|
/// * `default_transmission_mode` - If the transmission mode is not supplied as part of the
|
||||||
|
/// Put Request, it will be determined from this field in the remote configuration.
|
||||||
|
/// * `disposition_on_cancellation` - Determines whether an incomplete received file is discard on
|
||||||
|
/// transaction cancellation. Defaults to False.
|
||||||
|
/// * `default_crc_type` - Default checksum type used to calculate for all file transmissions to
|
||||||
|
/// this remote entity.
|
||||||
|
/// * `check_limit` - This timer determines the expiry period for incrementing a check counter
|
||||||
|
/// after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
|
||||||
|
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. Defaults to
|
||||||
|
/// 2, so the check limit timer may expire twice.
|
||||||
|
/// * `positive_ack_timer_interval_seconds`- See the notes on the Positive Acknowledgment
|
||||||
|
/// Procedures inside the class documentation. Expected as floating point seconds. Defaults to
|
||||||
|
/// 10 seconds.
|
||||||
|
/// * `positive_ack_timer_expiration_limit` - See the notes on the Positive Acknowledgment
|
||||||
|
/// Procedures inside the class documentation. Defaults to 2, so the timer may expire twice.
|
||||||
|
/// * `immediate_nak_mode` - Specifies whether a NAK sequence should be issued immediately when a
|
||||||
|
/// file data gap or lost metadata is detected in the acknowledged mode. Defaults to True.
|
||||||
|
/// * `nak_timer_interval_seconds` - See the notes on the Deferred Lost Segment Procedure inside
|
||||||
|
/// the class documentation. Expected as floating point seconds. Defaults to 10 seconds.
|
||||||
|
/// * `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)]
|
||||||
|
pub struct RemoteEntityConfig {
|
||||||
|
pub entity_id: UnsignedByteField,
|
||||||
|
pub max_packet_len: usize,
|
||||||
|
pub max_file_segment_len: usize,
|
||||||
|
pub closure_requested_by_default: bool,
|
||||||
|
pub crc_on_transmission_by_default: bool,
|
||||||
|
pub default_transmission_mode: TransmissionMode,
|
||||||
|
pub default_crc_type: ChecksumType,
|
||||||
|
pub positive_ack_timer_interval_seconds: f32,
|
||||||
|
pub positive_ack_timer_expiration_limit: u32,
|
||||||
|
pub check_limit: u32,
|
||||||
|
pub disposition_on_cancellation: bool,
|
||||||
|
pub immediate_nak_mode: bool,
|
||||||
|
pub nak_timer_interval_seconds: f32,
|
||||||
|
pub nak_timer_expiration_limit: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteEntityConfig {
|
||||||
|
pub fn new_with_default_values(
|
||||||
|
entity_id: UnsignedByteField,
|
||||||
|
max_file_segment_len: usize,
|
||||||
|
max_packet_len: usize,
|
||||||
|
closure_requested_by_default: bool,
|
||||||
|
crc_on_transmission_by_default: bool,
|
||||||
|
default_transmission_mode: TransmissionMode,
|
||||||
|
default_crc_type: ChecksumType,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
entity_id,
|
||||||
|
max_file_segment_len,
|
||||||
|
max_packet_len,
|
||||||
|
closure_requested_by_default,
|
||||||
|
crc_on_transmission_by_default,
|
||||||
|
default_transmission_mode,
|
||||||
|
default_crc_type,
|
||||||
|
check_limit: 2,
|
||||||
|
positive_ack_timer_interval_seconds: 10.0,
|
||||||
|
positive_ack_timer_expiration_limit: 2,
|
||||||
|
disposition_on_cancellation: false,
|
||||||
|
immediate_nak_mode: true,
|
||||||
|
nak_timer_interval_seconds: 10.0,
|
||||||
|
nak_timer_expiration_limit: 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RemoteEntityConfigProvider {
|
||||||
|
/// Retrieve the remote entity configuration for the given remote ID.
|
||||||
|
fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig>;
|
||||||
|
fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>;
|
||||||
|
/// Add a new remote configuration. Return [true] if the configuration was
|
||||||
|
/// inserted successfully, and [false] if a configuration already exists.
|
||||||
|
fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool;
|
||||||
|
/// Remote a configuration. Returns [true] if the configuration was removed successfully,
|
||||||
|
/// and [false] if no configuration exists for the given remote ID.
|
||||||
|
fn remove_config(&mut self, remote_id: u64) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct StdRemoteEntityConfigProvider {
|
||||||
|
remote_cfg_table: HashMap<u64, RemoteEntityConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
|
||||||
|
fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
|
||||||
|
self.remote_cfg_table.get(&remote_id)
|
||||||
|
}
|
||||||
|
fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
|
||||||
|
self.remote_cfg_table.get_mut(&remote_id)
|
||||||
|
}
|
||||||
|
fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool {
|
||||||
|
self.remote_cfg_table
|
||||||
|
.insert(cfg.entity_id.value(), *cfg)
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
fn remove_config(&mut self, remote_id: u64) -> bool {
|
||||||
|
self.remote_cfg_table.remove(&remote_id).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 [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.
|
||||||
|
///
|
||||||
|
/// For each error reported by the [DefaultFaultHandler], the appropriate fault handler callback
|
||||||
|
/// will be called depending on the [FaultHandlerCode].
|
||||||
|
pub trait UserFaultHandler {
|
||||||
|
fn notice_of_suspension_cb(
|
||||||
|
&mut self,
|
||||||
|
transaction_id: TransactionId,
|
||||||
|
cond: ConditionCode,
|
||||||
|
progress: u64,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn notice_of_cancellation_cb(
|
||||||
|
&mut self,
|
||||||
|
transaction_id: TransactionId,
|
||||||
|
cond: ConditionCode,
|
||||||
|
progress: u64,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
|
||||||
|
|
||||||
|
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This structure is used to implement the fault handling as specified in chapter 4.8 of the CFDP
|
||||||
|
/// standard.
|
||||||
|
///
|
||||||
|
/// 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 [UserFaultHandler].
|
||||||
|
///
|
||||||
|
/// Some note on the provided default settings:
|
||||||
|
///
|
||||||
|
/// - Checksum failures will be ignored by default. This is because for unacknowledged transfers,
|
||||||
|
/// cancelling the transfer immediately would interfere with the check limit mechanism specified
|
||||||
|
/// in chapter 4.6.3.3.
|
||||||
|
/// - Unsupported checksum types will also be ignored by default. Even if the checksum type is
|
||||||
|
/// not supported the file transfer might still have worked properly.
|
||||||
|
///
|
||||||
|
/// For all other faults, the default fault handling operation will be to cancel the transaction.
|
||||||
|
/// These defaults can be overriden by using the [Self::set_fault_handler] method.
|
||||||
|
/// Please note that in any case, fault handler overrides can be specified by the sending CFDP
|
||||||
|
/// entity.
|
||||||
|
pub struct DefaultFaultHandler {
|
||||||
|
handler_array: [FaultHandlerCode; 10],
|
||||||
|
// Could also change the user fault handler trait to have non mutable methods, but that limits
|
||||||
|
// flexbility on the user side..
|
||||||
|
user_fault_handler: RefCell<Box<dyn UserFaultHandler + Send>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultFaultHandler {
|
||||||
|
fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option<usize> {
|
||||||
|
Some(match conditon_code {
|
||||||
|
ConditionCode::PositiveAckLimitReached => 0,
|
||||||
|
ConditionCode::KeepAliveLimitReached => 1,
|
||||||
|
ConditionCode::InvalidTransmissionMode => 2,
|
||||||
|
ConditionCode::FilestoreRejection => 3,
|
||||||
|
ConditionCode::FileChecksumFailure => 4,
|
||||||
|
ConditionCode::FileSizeError => 5,
|
||||||
|
ConditionCode::NakLimitReached => 6,
|
||||||
|
ConditionCode::InactivityDetected => 7,
|
||||||
|
ConditionCode::CheckLimitReached => 8,
|
||||||
|
ConditionCode::UnsupportedChecksumType => 9,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_fault_handler(
|
||||||
|
&mut self,
|
||||||
|
condition_code: ConditionCode,
|
||||||
|
fault_handler: FaultHandlerCode,
|
||||||
|
) {
|
||||||
|
let array_idx = Self::condition_code_to_array_index(condition_code);
|
||||||
|
if array_idx.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.handler_array[array_idx.unwrap()] = fault_handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(user_fault_handler: Box<dyn UserFaultHandler + Send>) -> Self {
|
||||||
|
let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10];
|
||||||
|
init_array
|
||||||
|
[Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] =
|
||||||
|
FaultHandlerCode::IgnoreError;
|
||||||
|
init_array[Self::condition_code_to_array_index(ConditionCode::UnsupportedChecksumType)
|
||||||
|
.unwrap()] = FaultHandlerCode::IgnoreError;
|
||||||
|
Self {
|
||||||
|
handler_array: init_array,
|
||||||
|
user_fault_handler: RefCell::new(user_fault_handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_fault_handler(&self, condition_code: ConditionCode) -> FaultHandlerCode {
|
||||||
|
let array_idx = Self::condition_code_to_array_index(condition_code);
|
||||||
|
if array_idx.is_none() {
|
||||||
|
return FaultHandlerCode::IgnoreError;
|
||||||
|
}
|
||||||
|
self.handler_array[array_idx.unwrap()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_fault(
|
||||||
|
&self,
|
||||||
|
transaction_id: TransactionId,
|
||||||
|
condition: ConditionCode,
|
||||||
|
progress: u64,
|
||||||
|
) -> FaultHandlerCode {
|
||||||
|
let array_idx = Self::condition_code_to_array_index(condition);
|
||||||
|
if array_idx.is_none() {
|
||||||
|
return FaultHandlerCode::IgnoreError;
|
||||||
|
}
|
||||||
|
let fh_code = self.handler_array[array_idx.unwrap()];
|
||||||
|
let mut handler_mut = self.user_fault_handler.borrow_mut();
|
||||||
|
match fh_code {
|
||||||
|
FaultHandlerCode::NoticeOfCancellation => {
|
||||||
|
handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress);
|
||||||
|
}
|
||||||
|
FaultHandlerCode::NoticeOfSuspension => {
|
||||||
|
handler_mut.notice_of_suspension_cb(transaction_id, condition, progress);
|
||||||
|
}
|
||||||
|
FaultHandlerCode::IgnoreError => {
|
||||||
|
handler_mut.ignore_cb(transaction_id, condition, progress);
|
||||||
|
}
|
||||||
|
FaultHandlerCode::AbandonTransaction => {
|
||||||
|
handler_mut.abandoned_cb(transaction_id, condition, progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fh_code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IndicationConfig {
|
||||||
|
pub eof_sent: bool,
|
||||||
|
pub eof_recv: bool,
|
||||||
|
pub file_segment_recv: bool,
|
||||||
|
pub transaction_finished: bool,
|
||||||
|
pub suspended: bool,
|
||||||
|
pub resumed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IndicationConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
eof_sent: true,
|
||||||
|
eof_recv: true,
|
||||||
|
file_segment_recv: true,
|
||||||
|
transaction_finished: true,
|
||||||
|
suspended: true,
|
||||||
|
resumed: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LocalEntityConfig {
|
||||||
|
pub id: UnsignedByteField,
|
||||||
|
pub indication_cfg: IndicationConfig,
|
||||||
|
pub default_fault_handler: DefaultFaultHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence
|
||||||
|
/// 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))]
|
||||||
|
pub struct TransactionId {
|
||||||
|
source_id: UnsignedByteField,
|
||||||
|
seq_num: UnsignedByteField,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransactionId {
|
||||||
|
pub fn new(source_id: UnsignedByteField, seq_num: UnsignedByteField) -> Self {
|
||||||
|
Self { source_id, seq_num }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn source_id(&self) -> &UnsignedByteField {
|
||||||
|
&self.source_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seq_num(&self) -> &UnsignedByteField {
|
||||||
|
&self.seq_num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for TransactionId {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.source_id.value().hash(state);
|
||||||
|
self.seq_num.value().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TransactionId {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.source_id.value() == other.source_id.value()
|
||||||
|
&& self.seq_num.value() == other.seq_num.value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum TransactionStep {
|
||||||
|
Idle = 0,
|
||||||
|
TransactionStart = 1,
|
||||||
|
ReceivingFileDataPdus = 2,
|
||||||
|
ReceivingFileDataPdusWithCheckLimitHandling = 3,
|
||||||
|
SendingAckPdu = 4,
|
||||||
|
TransferCompletion = 5,
|
||||||
|
SendingFinishedPdu = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum State {
|
||||||
|
Idle = 0,
|
||||||
|
Busy = 1,
|
||||||
|
Suspended = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum PacketTarget {
|
||||||
|
SourceEntity,
|
||||||
|
DestEntity,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a helper struct which contains base information about a particular PDU packet.
|
||||||
|
/// This is also necessary information for CFDP packet routing. For example, some packet types
|
||||||
|
/// like file data PDUs can only be used by CFDP source entities.
|
||||||
|
pub struct PacketInfo<'raw_packet> {
|
||||||
|
pdu_type: PduType,
|
||||||
|
pdu_directive: Option<FileDirectiveType>,
|
||||||
|
target: PacketTarget,
|
||||||
|
raw_packet: &'raw_packet [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'raw> PacketInfo<'raw> {
|
||||||
|
pub fn new(raw_packet: &'raw [u8]) -> Result<Self, PduError> {
|
||||||
|
let (pdu_header, header_len) = PduHeader::from_bytes(raw_packet)?;
|
||||||
|
if pdu_header.pdu_type() == PduType::FileData {
|
||||||
|
return Ok(Self {
|
||||||
|
pdu_type: pdu_header.pdu_type(),
|
||||||
|
pdu_directive: None,
|
||||||
|
target: PacketTarget::DestEntity,
|
||||||
|
raw_packet,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if pdu_header.pdu_datafield_len() < 1 {
|
||||||
|
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).
|
||||||
|
// The routing is based on section 4.5 of the CFDP standard which specifies the PDU forwarding
|
||||||
|
// procedure.
|
||||||
|
let directive = FileDirectiveType::try_from(raw_packet[header_len]).map_err(|_| {
|
||||||
|
PduError::InvalidDirectiveType {
|
||||||
|
found: raw_packet[header_len],
|
||||||
|
expected: None,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let packet_target = match directive {
|
||||||
|
// Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a.
|
||||||
|
// the source handler
|
||||||
|
FileDirectiveType::NakPdu
|
||||||
|
| FileDirectiveType::FinishedPdu
|
||||||
|
| FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity,
|
||||||
|
// Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a.
|
||||||
|
// the destination handler
|
||||||
|
FileDirectiveType::MetadataPdu
|
||||||
|
| FileDirectiveType::EofPdu
|
||||||
|
| FileDirectiveType::PromptPdu => PacketTarget::DestEntity,
|
||||||
|
// Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply
|
||||||
|
// extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to
|
||||||
|
// the source handler, for a Finished PDU, it is passed to the destination handler.
|
||||||
|
FileDirectiveType::AckPdu => {
|
||||||
|
let acked_directive = FileDirectiveType::try_from(raw_packet[header_len + 1])
|
||||||
|
.map_err(|_| PduError::InvalidDirectiveType {
|
||||||
|
found: raw_packet[header_len],
|
||||||
|
expected: None,
|
||||||
|
})?;
|
||||||
|
if acked_directive == FileDirectiveType::EofPdu {
|
||||||
|
PacketTarget::SourceEntity
|
||||||
|
} else if acked_directive == FileDirectiveType::FinishedPdu {
|
||||||
|
PacketTarget::DestEntity
|
||||||
|
} else {
|
||||||
|
// TODO: Maybe a better error? This might be confusing..
|
||||||
|
return Err(PduError::InvalidDirectiveType {
|
||||||
|
found: raw_packet[header_len + 1],
|
||||||
|
expected: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
pdu_type: pdu_header.pdu_type(),
|
||||||
|
pdu_directive: Some(directive),
|
||||||
|
target: packet_target,
|
||||||
|
raw_packet,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pdu_type(&self) -> PduType {
|
||||||
|
self.pdu_type
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pdu_directive(&self) -> Option<FileDirectiveType> {
|
||||||
|
self.pdu_directive
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target(&self) -> PacketTarget {
|
||||||
|
self.target
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw_packet(&self) -> &[u8] {
|
||||||
|
self.raw_packet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use spacepackets::cfdp::{
|
||||||
|
lv::Lv,
|
||||||
|
pdu::{
|
||||||
|
eof::EofPdu,
|
||||||
|
file_data::FileDataPdu,
|
||||||
|
metadata::{MetadataGenericParams, MetadataPduCreator},
|
||||||
|
CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket,
|
||||||
|
},
|
||||||
|
PduType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::cfdp::PacketTarget;
|
||||||
|
|
||||||
|
use super::PacketInfo;
|
||||||
|
|
||||||
|
fn generic_pdu_header() -> PduHeader {
|
||||||
|
let pdu_conf = CommonPduConfig::default();
|
||||||
|
PduHeader::new_no_file_data(pdu_conf, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_metadata_pdu_info() {
|
||||||
|
let mut buf: [u8; 128] = [0; 128];
|
||||||
|
let pdu_header = generic_pdu_header();
|
||||||
|
let metadata_params = MetadataGenericParams::default();
|
||||||
|
let src_file_name = "hello.txt";
|
||||||
|
let dest_file_name = "hello-dest.txt";
|
||||||
|
let src_lv = Lv::new_from_str(src_file_name).unwrap();
|
||||||
|
let dest_lv = Lv::new_from_str(dest_file_name).unwrap();
|
||||||
|
let metadata_pdu =
|
||||||
|
MetadataPduCreator::new_no_opts(pdu_header, metadata_params, src_lv, dest_lv);
|
||||||
|
metadata_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing metadata PDU failed");
|
||||||
|
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||||
|
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
|
||||||
|
assert!(packet_info.pdu_directive().is_some());
|
||||||
|
assert_eq!(
|
||||||
|
packet_info.pdu_directive().unwrap(),
|
||||||
|
FileDirectiveType::MetadataPdu
|
||||||
|
);
|
||||||
|
assert_eq!(packet_info.target(), PacketTarget::DestEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filedata_pdu_info() {
|
||||||
|
let mut buf: [u8; 128] = [0; 128];
|
||||||
|
let pdu_header = generic_pdu_header();
|
||||||
|
let file_data_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 0, &[]);
|
||||||
|
file_data_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing file data PDU failed");
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||||
|
assert_eq!(packet_info.pdu_type(), PduType::FileData);
|
||||||
|
assert!(packet_info.pdu_directive().is_none());
|
||||||
|
assert_eq!(packet_info.target(), PacketTarget::DestEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_eof_pdu_info() {
|
||||||
|
let mut buf: [u8; 128] = [0; 128];
|
||||||
|
let pdu_header = generic_pdu_header();
|
||||||
|
let eof_pdu = EofPdu::new_no_error(pdu_header, 0, 0);
|
||||||
|
eof_pdu
|
||||||
|
.write_to_bytes(&mut buf)
|
||||||
|
.expect("writing file data PDU failed");
|
||||||
|
let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
|
||||||
|
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
|
||||||
|
assert!(packet_info.pdu_directive().is_some());
|
||||||
|
assert_eq!(
|
||||||
|
packet_info.pdu_directive().unwrap(),
|
||||||
|
FileDirectiveType::EofPdu
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
15
satrs/src/cfdp/source.rs
Normal file
15
satrs/src/cfdp/source.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
use spacepackets::util::UnsignedByteField;
|
||||||
|
|
||||||
|
pub struct SourceHandler {
|
||||||
|
id: UnsignedByteField,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceHandler {
|
||||||
|
pub fn new(id: impl Into<UnsignedByteField>) -> Self {
|
||||||
|
Self { id: id.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {}
|
96
satrs/src/cfdp/user.rs
Normal file
96
satrs/src/cfdp/user.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use spacepackets::{
|
||||||
|
cfdp::{
|
||||||
|
pdu::{
|
||||||
|
file_data::SegmentMetadata,
|
||||||
|
finished::{DeliveryCode, FileStatus},
|
||||||
|
},
|
||||||
|
tlv::{msg_to_user::MsgToUserTlv, WritableTlv},
|
||||||
|
ConditionCode,
|
||||||
|
},
|
||||||
|
util::UnsignedByteField,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::TransactionId;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct TransactionFinishedParams {
|
||||||
|
pub id: TransactionId,
|
||||||
|
pub condition_code: ConditionCode,
|
||||||
|
pub delivery_code: DeliveryCode,
|
||||||
|
pub file_status: FileStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> {
|
||||||
|
pub id: TransactionId,
|
||||||
|
pub source_id: UnsignedByteField,
|
||||||
|
pub file_size: u64,
|
||||||
|
pub src_file_name: &'src_file str,
|
||||||
|
pub dest_file_name: &'dest_file str,
|
||||||
|
pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OwnedMetadataRecvdParams {
|
||||||
|
pub id: TransactionId,
|
||||||
|
pub source_id: UnsignedByteField,
|
||||||
|
pub file_size: u64,
|
||||||
|
pub src_file_name: alloc::string::String,
|
||||||
|
pub dest_file_name: alloc::string::String,
|
||||||
|
pub msgs_to_user: alloc::vec::Vec<alloc::vec::Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl From<MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
|
||||||
|
fn from(value: MetadataReceivedParams) -> Self {
|
||||||
|
Self::from(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl From<&MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
|
||||||
|
fn from(value: &MetadataReceivedParams) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
source_id: value.source_id,
|
||||||
|
file_size: value.file_size,
|
||||||
|
src_file_name: value.src_file_name.into(),
|
||||||
|
dest_file_name: value.dest_file_name.into(),
|
||||||
|
msgs_to_user: value.msgs_to_user.iter().map(|tlv| tlv.to_vec()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FileSegmentRecvdParams<'seg_meta> {
|
||||||
|
pub id: TransactionId,
|
||||||
|
pub offset: u64,
|
||||||
|
pub length: usize,
|
||||||
|
pub segment_metadata: Option<&'seg_meta SegmentMetadata<'seg_meta>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CfdpUser {
|
||||||
|
fn transaction_indication(&mut self, id: &TransactionId);
|
||||||
|
fn eof_sent_indication(&mut self, id: &TransactionId);
|
||||||
|
fn transaction_finished_indication(&mut self, finished_params: &TransactionFinishedParams);
|
||||||
|
fn metadata_recvd_indication(&mut self, md_recvd_params: &MetadataReceivedParams);
|
||||||
|
fn file_segment_recvd_indication(&mut self, segment_recvd_params: &FileSegmentRecvdParams);
|
||||||
|
// TODO: The standard does not strictly specify how the report information looks..
|
||||||
|
fn report_indication(&mut self, id: &TransactionId);
|
||||||
|
fn suspended_indication(&mut self, id: &TransactionId, condition_code: ConditionCode);
|
||||||
|
fn resumed_indication(&mut self, id: &TransactionId, progress: u64);
|
||||||
|
fn fault_indication(
|
||||||
|
&mut self,
|
||||||
|
id: &TransactionId,
|
||||||
|
condition_code: ConditionCode,
|
||||||
|
progress: u64,
|
||||||
|
);
|
||||||
|
fn abandoned_indication(
|
||||||
|
&mut self,
|
||||||
|
id: &TransactionId,
|
||||||
|
condition_code: ConditionCode,
|
||||||
|
progress: u64,
|
||||||
|
);
|
||||||
|
fn eof_recvd_indication(&mut self, id: &TransactionId);
|
||||||
|
}
|
@ -15,24 +15,20 @@ pub enum OpResult {
|
|||||||
TerminationRequested,
|
TerminationRequested,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ExecutionType {
|
pub enum ExecutionType {
|
||||||
Infinite,
|
Infinite,
|
||||||
Cycles(u32),
|
Cycles(u32),
|
||||||
OneShot,
|
OneShot,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Executable {
|
pub trait Executable: Send {
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
fn exec_type(&self) -> ExecutionType;
|
||||||
fn task_name(&self) -> &'static str;
|
fn task_name(&self) -> &'static str;
|
||||||
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, Self::Error>;
|
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ExecutableWithType: Executable {
|
|
||||||
fn exec_type(&self) -> ExecutionType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function allows executing one task which implements the [Executable] trait
|
/// This function allows executing one task which implements the [Executable] trait
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -43,10 +39,7 @@ pub trait ExecutableWithType: Executable {
|
|||||||
/// * `op_code`: Operation code which is passed to the executable task
|
/// * `op_code`: Operation code which is passed to the executable task
|
||||||
/// [operation call][Executable::periodic_op]
|
/// [operation call][Executable::periodic_op]
|
||||||
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
||||||
pub fn exec_sched_single<
|
pub fn exec_sched_single<T: Executable<Error = E> + Send + 'static + ?Sized, E: Send + 'static>(
|
||||||
T: ExecutableWithType<Error = E> + Send + 'static + ?Sized,
|
|
||||||
E: Send + 'static,
|
|
||||||
>(
|
|
||||||
mut executable: Box<T>,
|
mut executable: Box<T>,
|
||||||
task_freq: Option<Duration>,
|
task_freq: Option<Duration>,
|
||||||
op_code: i32,
|
op_code: i32,
|
||||||
@ -95,10 +88,7 @@ pub fn exec_sched_single<
|
|||||||
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks
|
/// * `task_freq`: Optional frequency of task. Required for periodic and fixed cycle tasks
|
||||||
/// * `op_code`: Operation code which is passed to the executable task [operation call][Executable::periodic_op]
|
/// * `op_code`: Operation code which is passed to the executable task [operation call][Executable::periodic_op]
|
||||||
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
/// * `termination`: Optional termination handler which can cancel threads with a broadcast
|
||||||
pub fn exec_sched_multi<
|
pub fn exec_sched_multi<T: Executable<Error = E> + Send + 'static + ?Sized, E: Send + 'static>(
|
||||||
T: ExecutableWithType<Error = E> + Send + 'static + ?Sized,
|
|
||||||
E: Send + 'static,
|
|
||||||
>(
|
|
||||||
task_name: &'static str,
|
task_name: &'static str,
|
||||||
mut executable_vec: Vec<Box<T>>,
|
mut executable_vec: Vec<Box<T>>,
|
||||||
task_freq: Option<Duration>,
|
task_freq: Option<Duration>,
|
||||||
@ -152,10 +142,7 @@ pub fn exec_sched_multi<
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{exec_sched_multi, exec_sched_single, Executable, ExecutionType, OpResult};
|
||||||
exec_sched_multi, exec_sched_single, Executable, ExecutableWithType, ExecutionType,
|
|
||||||
OpResult,
|
|
||||||
};
|
|
||||||
use bus::Bus;
|
use bus::Bus;
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@ -221,6 +208,10 @@ mod tests {
|
|||||||
impl Executable for OneShotTask {
|
impl Executable for OneShotTask {
|
||||||
type Error = ExampleError;
|
type Error = ExampleError;
|
||||||
|
|
||||||
|
fn exec_type(&self) -> ExecutionType {
|
||||||
|
ExecutionType::OneShot
|
||||||
|
}
|
||||||
|
|
||||||
fn task_name(&self) -> &'static str {
|
fn task_name(&self) -> &'static str {
|
||||||
ONE_SHOT_TASK_NAME
|
ONE_SHOT_TASK_NAME
|
||||||
}
|
}
|
||||||
@ -238,17 +229,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutableWithType for OneShotTask {
|
|
||||||
fn exec_type(&self) -> ExecutionType {
|
|
||||||
ExecutionType::OneShot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CYCLE_TASK_NAME: &str = "Fixed Cycles Task";
|
const CYCLE_TASK_NAME: &str = "Fixed Cycles Task";
|
||||||
|
|
||||||
impl Executable for FixedCyclesTask {
|
impl Executable for FixedCyclesTask {
|
||||||
type Error = ExampleError;
|
type Error = ExampleError;
|
||||||
|
|
||||||
|
fn exec_type(&self) -> ExecutionType {
|
||||||
|
ExecutionType::Cycles(self.cycles)
|
||||||
|
}
|
||||||
|
|
||||||
fn task_name(&self) -> &'static str {
|
fn task_name(&self) -> &'static str {
|
||||||
CYCLE_TASK_NAME
|
CYCLE_TASK_NAME
|
||||||
}
|
}
|
||||||
@ -266,17 +255,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutableWithType for FixedCyclesTask {
|
|
||||||
fn exec_type(&self) -> ExecutionType {
|
|
||||||
ExecutionType::Cycles(self.cycles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PERIODIC_TASK_NAME: &str = "Periodic Task";
|
const PERIODIC_TASK_NAME: &str = "Periodic Task";
|
||||||
|
|
||||||
impl Executable for PeriodicTask {
|
impl Executable for PeriodicTask {
|
||||||
type Error = ExampleError;
|
type Error = ExampleError;
|
||||||
|
|
||||||
|
fn exec_type(&self) -> ExecutionType {
|
||||||
|
ExecutionType::Infinite
|
||||||
|
}
|
||||||
|
|
||||||
fn task_name(&self) -> &'static str {
|
fn task_name(&self) -> &'static str {
|
||||||
PERIODIC_TASK_NAME
|
PERIODIC_TASK_NAME
|
||||||
}
|
}
|
||||||
@ -294,12 +281,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutableWithType for PeriodicTask {
|
|
||||||
fn exec_type(&self) -> ExecutionType {
|
|
||||||
ExecutionType::Infinite
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_one_shot() {
|
fn test_simple_one_shot() {
|
||||||
let expected_op_code = 42;
|
let expected_op_code = 42;
|
||||||
@ -442,7 +423,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
assert_eq!(cycled_task_0.task_name(), CYCLE_TASK_NAME);
|
assert_eq!(cycled_task_0.task_name(), CYCLE_TASK_NAME);
|
||||||
assert_eq!(one_shot_task.task_name(), ONE_SHOT_TASK_NAME);
|
assert_eq!(one_shot_task.task_name(), ONE_SHOT_TASK_NAME);
|
||||||
let task_vec: Vec<Box<dyn ExecutableWithType<Error = ExampleError> + Send>> =
|
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
||||||
vec![one_shot_task, cycled_task_0, cycled_task_1];
|
vec![one_shot_task, cycled_task_0, cycled_task_1];
|
||||||
let jh = exec_sched_multi(
|
let jh = exec_sched_multi(
|
||||||
"multi-task-name",
|
"multi-task-name",
|
||||||
@ -512,7 +493,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
assert_eq!(periodic_task_0.task_name(), PERIODIC_TASK_NAME);
|
assert_eq!(periodic_task_0.task_name(), PERIODIC_TASK_NAME);
|
||||||
assert_eq!(periodic_task_1.task_name(), PERIODIC_TASK_NAME);
|
assert_eq!(periodic_task_1.task_name(), PERIODIC_TASK_NAME);
|
||||||
let task_vec: Vec<Box<dyn ExecutableWithType<Error = ExampleError> + Send>> =
|
let task_vec: Vec<Box<dyn Executable<Error = ExampleError>>> =
|
||||||
vec![cycled_task, periodic_task_0, periodic_task_1];
|
vec![cycled_task, periodic_task_0, periodic_task_1];
|
||||||
let jh = exec_sched_multi(
|
let jh = exec_sched_multi(
|
||||||
"multi-task-name",
|
"multi-task-name",
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
//! - The [pus] module which provides special support for projects using
|
//! - The [pus] module which provides special support for projects using
|
||||||
//! the [ECSS PUS C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
//! the [ECSS PUS C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
@ -22,6 +22,8 @@ extern crate downcast_rs;
|
|||||||
#[cfg(any(feature = "std", test))]
|
#[cfg(any(feature = "std", test))]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub mod cfdp;
|
||||||
pub mod encoding;
|
pub mod encoding;
|
||||||
pub mod event_man;
|
pub mod event_man;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
@ -36,10 +38,9 @@ pub mod pus;
|
|||||||
pub mod queue;
|
pub mod queue;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod res_code;
|
pub mod res_code;
|
||||||
|
pub mod seq_count;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod tmtc;
|
pub mod tmtc;
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub mod scheduling;
|
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod hk;
|
pub mod hk;
|
||||||
|
@ -1584,21 +1584,23 @@ mod tests {
|
|||||||
mod heapless_tests {
|
mod heapless_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::static_subpool;
|
use crate::static_subpool;
|
||||||
use core::ptr::addr_of_mut;
|
use core::mem::MaybeUninit;
|
||||||
|
|
||||||
const SUBPOOL_1_BLOCK_SIZE: usize = 4;
|
const SUBPOOL_1_BLOCK_SIZE: usize = 4;
|
||||||
const SUBPOOL_1_NUM_ELEMENTS: u16 = 4;
|
const SUBPOOL_1_NUM_ELEMENTS: u16 = 4;
|
||||||
static mut SUBPOOL_1: [u8; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE] =
|
static mut SUBPOOL_1: MaybeUninit<
|
||||||
[0; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE];
|
[u8; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE],
|
||||||
static mut SUBPOOL_1_SIZES: [usize; SUBPOOL_1_NUM_ELEMENTS as usize] =
|
> = MaybeUninit::new([0; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE]);
|
||||||
[STORE_FREE; SUBPOOL_1_NUM_ELEMENTS as usize];
|
static mut SUBPOOL_1_SIZES: MaybeUninit<[usize; SUBPOOL_1_NUM_ELEMENTS as usize]> =
|
||||||
|
MaybeUninit::new([STORE_FREE; SUBPOOL_1_NUM_ELEMENTS as usize]);
|
||||||
|
|
||||||
const SUBPOOL_2_NUM_ELEMENTS: u16 = 2;
|
const SUBPOOL_2_NUM_ELEMENTS: u16 = 2;
|
||||||
const SUBPOOL_2_BLOCK_SIZE: usize = 8;
|
const SUBPOOL_2_BLOCK_SIZE: usize = 8;
|
||||||
static mut SUBPOOL_2: [u8; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE] =
|
static mut SUBPOOL_2: MaybeUninit<
|
||||||
[0; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE];
|
[u8; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE],
|
||||||
static mut SUBPOOL_2_SIZES: [usize; SUBPOOL_2_NUM_ELEMENTS as usize] =
|
> = MaybeUninit::new([0; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE]);
|
||||||
[STORE_FREE; SUBPOOL_2_NUM_ELEMENTS as usize];
|
static mut SUBPOOL_2_SIZES: MaybeUninit<[usize; SUBPOOL_2_NUM_ELEMENTS as usize]> =
|
||||||
|
MaybeUninit::new([STORE_FREE; SUBPOOL_2_NUM_ELEMENTS as usize]);
|
||||||
|
|
||||||
const SUBPOOL_3_NUM_ELEMENTS: u16 = 1;
|
const SUBPOOL_3_NUM_ELEMENTS: u16 = 1;
|
||||||
const SUBPOOL_3_BLOCK_SIZE: usize = 16;
|
const SUBPOOL_3_BLOCK_SIZE: usize = 16;
|
||||||
@ -1641,18 +1643,18 @@ mod tests {
|
|||||||
StaticHeaplessMemoryPool::new(false);
|
StaticHeaplessMemoryPool::new(false);
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
.grow(
|
.grow(
|
||||||
unsafe { &mut *addr_of_mut!(SUBPOOL_1) },
|
unsafe { SUBPOOL_1.assume_init_mut() },
|
||||||
unsafe { &mut *addr_of_mut!(SUBPOOL_1_SIZES) },
|
unsafe { SUBPOOL_1_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_1_NUM_ELEMENTS,
|
SUBPOOL_1_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
.grow(
|
.grow(
|
||||||
unsafe { &mut *addr_of_mut!(SUBPOOL_2) },
|
unsafe { SUBPOOL_2.assume_init_mut() },
|
||||||
unsafe { &mut *addr_of_mut!(SUBPOOL_2_SIZES) },
|
unsafe { SUBPOOL_2_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_2_NUM_ELEMENTS,
|
SUBPOOL_2_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
@ -1660,7 +1662,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_3.assume_init_mut() },
|
unsafe { SUBPOOL_3.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_3_NUM_ELEMENTS,
|
SUBPOOL_3_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
heapless_pool
|
heapless_pool
|
||||||
@ -1780,10 +1782,10 @@ mod tests {
|
|||||||
StaticHeaplessMemoryPool::new(true);
|
StaticHeaplessMemoryPool::new(true);
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
.grow(
|
.grow(
|
||||||
unsafe { &mut *addr_of_mut!(SUBPOOL_2) },
|
unsafe { SUBPOOL_2.assume_init_mut() },
|
||||||
unsafe { &mut *addr_of_mut!(SUBPOOL_2_SIZES) },
|
unsafe { SUBPOOL_2_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_2_NUM_ELEMENTS,
|
SUBPOOL_2_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
@ -1791,7 +1793,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_4.assume_init_mut() },
|
unsafe { SUBPOOL_4.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_4_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_4_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_4_NUM_ELEMENTS,
|
SUBPOOL_4_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
generic_test_spills_to_higher_subpools(&mut heapless_pool);
|
generic_test_spills_to_higher_subpools(&mut heapless_pool);
|
||||||
@ -1806,7 +1808,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_5.assume_init_mut() },
|
unsafe { SUBPOOL_5.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_5_NUM_ELEMENTS,
|
SUBPOOL_5_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
@ -1814,7 +1816,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_3.assume_init_mut() },
|
unsafe { SUBPOOL_3.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_3_NUM_ELEMENTS,
|
SUBPOOL_3_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
generic_test_spillage_fails_as_well(&mut heapless_pool);
|
generic_test_spillage_fails_as_well(&mut heapless_pool);
|
||||||
@ -1829,7 +1831,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_5.assume_init_mut() },
|
unsafe { SUBPOOL_5.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_5_NUM_ELEMENTS,
|
SUBPOOL_5_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
@ -1837,7 +1839,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_6.assume_init_mut() },
|
unsafe { SUBPOOL_6.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_6_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_6_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_6_NUM_ELEMENTS,
|
SUBPOOL_6_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
@ -1845,7 +1847,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_3.assume_init_mut() },
|
unsafe { SUBPOOL_3.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_3_NUM_ELEMENTS,
|
SUBPOOL_3_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
generic_test_spillage_works_across_multiple_subpools(&mut heapless_pool);
|
generic_test_spillage_works_across_multiple_subpools(&mut heapless_pool);
|
||||||
@ -1860,7 +1862,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_5.assume_init_mut() },
|
unsafe { SUBPOOL_5.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_5_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_5_NUM_ELEMENTS,
|
SUBPOOL_5_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
@ -1868,7 +1870,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_6.assume_init_mut() },
|
unsafe { SUBPOOL_6.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_6_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_6_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_6_NUM_ELEMENTS,
|
SUBPOOL_6_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(heapless_pool
|
assert!(heapless_pool
|
||||||
@ -1876,7 +1878,7 @@ mod tests {
|
|||||||
unsafe { SUBPOOL_3.assume_init_mut() },
|
unsafe { SUBPOOL_3.assume_init_mut() },
|
||||||
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
unsafe { SUBPOOL_3_SIZES.assume_init_mut() },
|
||||||
SUBPOOL_3_NUM_ELEMENTS,
|
SUBPOOL_3_NUM_ELEMENTS,
|
||||||
true
|
false
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
generic_test_spillage_fails_across_multiple_subpools(&mut heapless_pool);
|
generic_test_spillage_fails_across_multiple_subpools(&mut heapless_pool);
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
//! VerificationReportingProvider, VerificationReporterCfg, VerificationReporter
|
//! VerificationReportingProvider, VerificationReporterCfg, VerificationReporter
|
||||||
//! };
|
//! };
|
||||||
//! use satrs::tmtc::{SharedStaticMemoryPool, PacketSenderWithSharedPool};
|
//! use satrs::tmtc::{SharedStaticMemoryPool, PacketSenderWithSharedPool};
|
||||||
//! use satrs::spacepackets::seq_count::SeqCountProviderSimple;
|
//! use satrs::seq_count::SeqCountProviderSimple;
|
||||||
//! use satrs::request::UniqueApidTargetId;
|
//! use satrs::request::UniqueApidTargetId;
|
||||||
//! use spacepackets::ecss::PusPacket;
|
//! use spacepackets::ecss::PusPacket;
|
||||||
//! use spacepackets::SpHeader;
|
//! use spacepackets::SpHeader;
|
||||||
@ -97,8 +97,8 @@ use spacepackets::ecss::EcssEnumeration;
|
|||||||
use spacepackets::{ByteConversionError, CcsdsPacket, PacketId, PacketSequenceCtrl};
|
use spacepackets::{ByteConversionError, CcsdsPacket, PacketId, PacketSequenceCtrl};
|
||||||
use spacepackets::{SpHeader, MAX_APID};
|
use spacepackets::{SpHeader, MAX_APID};
|
||||||
|
|
||||||
|
pub use crate::seq_count::SeqCountProviderSimple;
|
||||||
pub use spacepackets::ecss::verification::*;
|
pub use spacepackets::ecss::verification::*;
|
||||||
pub use spacepackets::seq_count::SeqCountProviderSimple;
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub use alloc_mod::*;
|
pub use alloc_mod::*;
|
||||||
@ -1702,7 +1702,7 @@ pub mod tests {
|
|||||||
};
|
};
|
||||||
use crate::pus::{ChannelWithId, PusTmVariant};
|
use crate::pus::{ChannelWithId, PusTmVariant};
|
||||||
use crate::request::MessageMetadata;
|
use crate::request::MessageMetadata;
|
||||||
use crate::spacepackets::seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProvider};
|
use crate::seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore};
|
||||||
use crate::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
|
use crate::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
|
||||||
use crate::ComponentId;
|
use crate::ComponentId;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
|
@ -1,416 +0,0 @@
|
|||||||
use core::{convert::Infallible, fmt::Debug, time::Duration};
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::executable::Executable;
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub use std_mod::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct SchedulingTable {
|
|
||||||
execution_frequency: Duration,
|
|
||||||
pub table: alloc::vec::Vec<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum InvalidSlotError {
|
|
||||||
#[error("slot time is larger than the execution frequency")]
|
|
||||||
SlotTimeLargerThanFrequency,
|
|
||||||
#[error("slot time is smaller than previous slot")]
|
|
||||||
SmallerThanPreviousSlot {
|
|
||||||
slot_time_ms: u32,
|
|
||||||
prev_slot_time_ms: u32,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SchedulingTable {
|
|
||||||
pub fn new(execution_frequency: Duration) -> Self {
|
|
||||||
Self {
|
|
||||||
execution_frequency,
|
|
||||||
table: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_slot(&mut self, relative_execution_time_ms: u32) -> Result<(), InvalidSlotError> {
|
|
||||||
if relative_execution_time_ms > self.execution_frequency.as_millis() as u32 {
|
|
||||||
return Err(InvalidSlotError::SlotTimeLargerThanFrequency);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.table.is_empty() {
|
|
||||||
let prev_slot_ms = *self.table.last().unwrap();
|
|
||||||
if relative_execution_time_ms < prev_slot_ms {
|
|
||||||
return Err(InvalidSlotError::SmallerThanPreviousSlot {
|
|
||||||
slot_time_ms: relative_execution_time_ms,
|
|
||||||
prev_slot_time_ms: *self.table.last().unwrap(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.table.push(relative_execution_time_ms);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum TaskWithSchedulingTableError {
|
|
||||||
#[error("scheudlig table error: {0}")]
|
|
||||||
InvalidSlot(#[from] InvalidSlotError),
|
|
||||||
#[error("task lock error")]
|
|
||||||
LockError,
|
|
||||||
#[error("task borrow error")]
|
|
||||||
BorrowError,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DeadlineMissedHandler {
|
|
||||||
fn deadline_missed_callback(&mut self, task_name: &'static str, op_code: i32);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TaskExecutor {
|
|
||||||
fn with_task<F: FnOnce(&mut dyn Executable<Error = Infallible>)>(&self, f: F);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub mod std_mod {
|
|
||||||
use core::cell::RefCell;
|
|
||||||
use std::{
|
|
||||||
rc::Rc,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl TaskExecutor for Arc<Mutex<dyn Executable<Error = Infallible> + Send>> {
|
|
||||||
fn with_task<F: FnOnce(&mut dyn Executable<Error = Infallible>)>(&self, f: F) {
|
|
||||||
let mut task = self.lock().unwrap();
|
|
||||||
f(&mut *task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskExecutor for Rc<RefCell<dyn Executable<Error = Infallible>>> {
|
|
||||||
fn with_task<F: FnOnce(&mut dyn Executable<Error = Infallible>)>(&self, f: F) {
|
|
||||||
let mut task = self.borrow_mut();
|
|
||||||
f(&mut *task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TaskWithOpCode<T: TaskExecutor> {
|
|
||||||
task: T,
|
|
||||||
op_code: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TaskWithSchedulingTable<T: TaskExecutor> {
|
|
||||||
start_of_slot: Instant,
|
|
||||||
end_of_slot: Instant,
|
|
||||||
deadline_missed_ms_count: u32,
|
|
||||||
table: SchedulingTable,
|
|
||||||
tasks: Vec<TaskWithOpCode<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskWithSchedulingTable<Rc<RefCell<dyn Executable<Error = Infallible>>>> {
|
|
||||||
/// Add a new task to the scheduling table
|
|
||||||
///
|
|
||||||
/// The task needs to be wrapped inside [Rc] and [RefCell]. The task is not sendable and
|
|
||||||
/// needs to be created inside the target thread.
|
|
||||||
pub fn add_task(
|
|
||||||
&mut self,
|
|
||||||
relative_execution_time_ms: u32,
|
|
||||||
task: Rc<RefCell<dyn Executable<Error = Infallible>>>,
|
|
||||||
op_code: i32,
|
|
||||||
) -> Result<(), TaskWithSchedulingTableError> {
|
|
||||||
self.table.add_slot(relative_execution_time_ms)?;
|
|
||||||
self.tasks.push(TaskWithOpCode { task, op_code });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskWithSchedulingTable<Arc<Mutex<dyn Executable<Error = Infallible> + Send>>> {
|
|
||||||
/// Add a new task to the scheduling table
|
|
||||||
///
|
|
||||||
/// The task needs to be wrapped inside [Arc] and [Mutex], but the task can be sent to
|
|
||||||
/// a different thread.
|
|
||||||
pub fn add_task_sendable(
|
|
||||||
&mut self,
|
|
||||||
relative_execution_time_ms: u32,
|
|
||||||
task: Arc<Mutex<dyn Executable<Error = Infallible> + Send>>,
|
|
||||||
op_code: i32,
|
|
||||||
) -> Result<(), TaskWithSchedulingTableError> {
|
|
||||||
self.table.add_slot(relative_execution_time_ms)?;
|
|
||||||
self.tasks.push(TaskWithOpCode { task, op_code });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TaskExecutor> TaskWithSchedulingTable<T> {
|
|
||||||
pub fn new(execution_frequency: Duration) -> Self {
|
|
||||||
Self {
|
|
||||||
start_of_slot: Instant::now(),
|
|
||||||
end_of_slot: Instant::now(),
|
|
||||||
deadline_missed_ms_count: 10,
|
|
||||||
table: SchedulingTable::new(execution_frequency),
|
|
||||||
tasks: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Can be used to set the start of the slot to the current time. This is useful if a custom
|
|
||||||
/// runner implementation is used instead of the [Self::start] method.
|
|
||||||
pub fn init_start_of_slot(&mut self) {
|
|
||||||
self.start_of_slot = Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_one_task_cycle(
|
|
||||||
&mut self,
|
|
||||||
deadline_missed_cb: &mut impl DeadlineMissedHandler,
|
|
||||||
) -> Result<(), TaskWithSchedulingTableError> {
|
|
||||||
self.end_of_slot = self.start_of_slot + self.table.execution_frequency;
|
|
||||||
|
|
||||||
for (&relative_execution_time_ms, task_with_op_code) in
|
|
||||||
self.table.table.iter().zip(self.tasks.iter_mut())
|
|
||||||
{
|
|
||||||
let scheduled_execution_time = self.start_of_slot
|
|
||||||
+ core::time::Duration::from_millis(relative_execution_time_ms as u64);
|
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
if now < scheduled_execution_time {
|
|
||||||
std::thread::sleep(scheduled_execution_time - now);
|
|
||||||
} else if (now - scheduled_execution_time).as_millis()
|
|
||||||
> self.deadline_missed_ms_count.into()
|
|
||||||
{
|
|
||||||
task_with_op_code.task.with_task(|task| {
|
|
||||||
deadline_missed_cb
|
|
||||||
.deadline_missed_callback(task.task_name(), task_with_op_code.op_code);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
task_with_op_code.task.with_task(|task| {
|
|
||||||
// Unwrapping is okay here because we constrain the tasks to be infallible.
|
|
||||||
task.periodic_op(task_with_op_code.op_code).unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let now = Instant::now();
|
|
||||||
if now <= self.end_of_slot {
|
|
||||||
let diff = self.end_of_slot - now;
|
|
||||||
std::thread::sleep(diff);
|
|
||||||
self.start_of_slot = self.end_of_slot;
|
|
||||||
} else if now > self.end_of_slot + self.table.execution_frequency {
|
|
||||||
// We're getting strongly out of sync. Set the new start timt to now.
|
|
||||||
self.start_of_slot = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use core::{cell::RefCell, convert::Infallible, time::Duration};
|
|
||||||
use std::{
|
|
||||||
println,
|
|
||||||
rc::Rc,
|
|
||||||
sync::{
|
|
||||||
mpsc::{self, TryRecvError},
|
|
||||||
Arc, Mutex,
|
|
||||||
},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::executable::{Executable, OpResult};
|
|
||||||
|
|
||||||
use super::{DeadlineMissedHandler, TaskWithSchedulingTable};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CallInfo {
|
|
||||||
time: std::time::Instant,
|
|
||||||
op_code: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Task1 {
|
|
||||||
called_queue: mpsc::Sender<CallInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Executable for Task1 {
|
|
||||||
type Error = Infallible;
|
|
||||||
fn task_name(&self) -> &'static str {
|
|
||||||
"Task1"
|
|
||||||
}
|
|
||||||
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, Self::Error> {
|
|
||||||
self.called_queue
|
|
||||||
.send(CallInfo {
|
|
||||||
time: Instant::now(),
|
|
||||||
op_code,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
Ok(OpResult::Ok)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Task2 {
|
|
||||||
called_queue: mpsc::Sender<CallInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Executable for Task2 {
|
|
||||||
type Error = Infallible;
|
|
||||||
fn task_name(&self) -> &'static str {
|
|
||||||
"Task2"
|
|
||||||
}
|
|
||||||
fn periodic_op(&mut self, op_code: i32) -> Result<OpResult, Self::Error> {
|
|
||||||
self.called_queue
|
|
||||||
.send(CallInfo {
|
|
||||||
time: Instant::now(),
|
|
||||||
op_code,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
Ok(OpResult::Ok)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DeadlineMissed {
|
|
||||||
call_count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeadlineMissedHandler for DeadlineMissed {
|
|
||||||
fn deadline_missed_callback(&mut self, task_name: &'static str, _op_code: i32) {
|
|
||||||
println!("task name {task_name} missed the deadline");
|
|
||||||
self.call_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn basic_test() {
|
|
||||||
let (tx_t1, rx_t1) = mpsc::channel();
|
|
||||||
let (tx_t2, rx_t2) = mpsc::channel();
|
|
||||||
let t1 = Task1 {
|
|
||||||
called_queue: tx_t1,
|
|
||||||
};
|
|
||||||
let t2 = Task2 {
|
|
||||||
called_queue: tx_t2,
|
|
||||||
};
|
|
||||||
let mut deadline_missed_cb = DeadlineMissed::default();
|
|
||||||
let mut exec_task = TaskWithSchedulingTable::new(Duration::from_millis(200));
|
|
||||||
let t1_first_slot = Rc::new(RefCell::new(t1));
|
|
||||||
let t1_second_slot = t1_first_slot.clone();
|
|
||||||
let t2_first_slot = Rc::new(RefCell::new(t2));
|
|
||||||
let t2_second_slot = t2_first_slot.clone();
|
|
||||||
|
|
||||||
exec_task.add_task(0, t1_first_slot, 0).unwrap();
|
|
||||||
exec_task.add_task(50, t1_second_slot, -1).unwrap();
|
|
||||||
exec_task.add_task(100, t2_first_slot, 1).unwrap();
|
|
||||||
exec_task.add_task(150, t2_second_slot, 2).unwrap();
|
|
||||||
let now = Instant::now();
|
|
||||||
exec_task.init_start_of_slot();
|
|
||||||
exec_task
|
|
||||||
.run_one_task_cycle(&mut deadline_missed_cb)
|
|
||||||
.unwrap();
|
|
||||||
let mut call_info = rx_t1.try_recv().unwrap();
|
|
||||||
assert_eq!(call_info.op_code, 0);
|
|
||||||
let diff_call_to_start = call_info.time - now;
|
|
||||||
assert!(diff_call_to_start.as_millis() < 30);
|
|
||||||
call_info = rx_t1.try_recv().unwrap();
|
|
||||||
assert_eq!(call_info.op_code, -1);
|
|
||||||
let diff_call_to_start = call_info.time - now;
|
|
||||||
assert!(diff_call_to_start.as_millis() < 80);
|
|
||||||
assert!(diff_call_to_start.as_millis() >= 50);
|
|
||||||
matches!(rx_t1.try_recv().unwrap_err(), TryRecvError::Empty);
|
|
||||||
|
|
||||||
call_info = rx_t2.try_recv().unwrap();
|
|
||||||
assert_eq!(call_info.op_code, 1);
|
|
||||||
let diff_call_to_start = call_info.time - now;
|
|
||||||
assert!(diff_call_to_start.as_millis() < 120);
|
|
||||||
assert!(diff_call_to_start.as_millis() >= 100);
|
|
||||||
call_info = rx_t2.try_recv().unwrap();
|
|
||||||
assert_eq!(call_info.op_code, 2);
|
|
||||||
let diff_call_to_start = call_info.time - now;
|
|
||||||
assert!(diff_call_to_start.as_millis() < 180);
|
|
||||||
assert!(diff_call_to_start.as_millis() >= 150);
|
|
||||||
matches!(rx_t2.try_recv().unwrap_err(), TryRecvError::Empty);
|
|
||||||
assert_eq!(deadline_missed_cb.call_count, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn basic_test_with_arc_mutex() {
|
|
||||||
let (tx_t1, rx_t1) = mpsc::channel();
|
|
||||||
let (tx_t2, rx_t2) = mpsc::channel();
|
|
||||||
let t1 = Task1 {
|
|
||||||
called_queue: tx_t1,
|
|
||||||
};
|
|
||||||
let t2 = Task2 {
|
|
||||||
called_queue: tx_t2,
|
|
||||||
};
|
|
||||||
let mut deadline_missed_cb = DeadlineMissed::default();
|
|
||||||
let mut exec_task = TaskWithSchedulingTable::new(Duration::from_millis(200));
|
|
||||||
let t1_first_slot = Arc::new(Mutex::new(t1));
|
|
||||||
let t1_second_slot = t1_first_slot.clone();
|
|
||||||
let t2_first_slot = Arc::new(Mutex::new(t2));
|
|
||||||
let t2_second_slot = t2_first_slot.clone();
|
|
||||||
|
|
||||||
exec_task.add_task_sendable(0, t1_first_slot, 0).unwrap();
|
|
||||||
exec_task.add_task_sendable(50, t1_second_slot, -1).unwrap();
|
|
||||||
exec_task.add_task_sendable(100, t2_first_slot, 1).unwrap();
|
|
||||||
exec_task.add_task_sendable(150, t2_second_slot, 2).unwrap();
|
|
||||||
let now = Instant::now();
|
|
||||||
exec_task.init_start_of_slot();
|
|
||||||
exec_task
|
|
||||||
.run_one_task_cycle(&mut deadline_missed_cb)
|
|
||||||
.unwrap();
|
|
||||||
let mut call_info = rx_t1.try_recv().unwrap();
|
|
||||||
assert_eq!(call_info.op_code, 0);
|
|
||||||
let diff_call_to_start = call_info.time - now;
|
|
||||||
assert!(diff_call_to_start.as_millis() < 30);
|
|
||||||
call_info = rx_t1.try_recv().unwrap();
|
|
||||||
assert_eq!(call_info.op_code, -1);
|
|
||||||
let diff_call_to_start = call_info.time - now;
|
|
||||||
assert!(diff_call_to_start.as_millis() < 80);
|
|
||||||
assert!(diff_call_to_start.as_millis() >= 50);
|
|
||||||
matches!(rx_t1.try_recv().unwrap_err(), TryRecvError::Empty);
|
|
||||||
|
|
||||||
call_info = rx_t2.try_recv().unwrap();
|
|
||||||
assert_eq!(call_info.op_code, 1);
|
|
||||||
let diff_call_to_start = call_info.time - now;
|
|
||||||
assert!(diff_call_to_start.as_millis() < 120);
|
|
||||||
assert!(diff_call_to_start.as_millis() >= 100);
|
|
||||||
call_info = rx_t2.try_recv().unwrap();
|
|
||||||
assert_eq!(call_info.op_code, 2);
|
|
||||||
let diff_call_to_start = call_info.time - now;
|
|
||||||
assert!(diff_call_to_start.as_millis() < 180);
|
|
||||||
assert!(diff_call_to_start.as_millis() >= 150);
|
|
||||||
matches!(rx_t2.try_recv().unwrap_err(), TryRecvError::Empty);
|
|
||||||
assert_eq!(deadline_missed_cb.call_count, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn basic_test_in_thread() {
|
|
||||||
let mut deadline_missed_cb = DeadlineMissed::default();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
let (tx_t1, _rx_t1) = mpsc::channel();
|
|
||||||
let t1 = Task1 {
|
|
||||||
called_queue: tx_t1,
|
|
||||||
};
|
|
||||||
// Need to construct this in the thread, the task table in not [Send]
|
|
||||||
let mut exec_task = TaskWithSchedulingTable::new(Duration::from_millis(200));
|
|
||||||
let t1_wrapper = Rc::new(RefCell::new(t1));
|
|
||||||
exec_task.add_task(0, t1_wrapper, 0).unwrap();
|
|
||||||
exec_task
|
|
||||||
.run_one_task_cycle(&mut deadline_missed_cb)
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut deadline_missed_cb = DeadlineMissed::default();
|
|
||||||
let (tx_t1, _rx_t1) = mpsc::channel();
|
|
||||||
let t1 = Task1 {
|
|
||||||
called_queue: tx_t1,
|
|
||||||
};
|
|
||||||
let mut exec_task_sendable = TaskWithSchedulingTable::new(Duration::from_millis(200));
|
|
||||||
exec_task_sendable
|
|
||||||
.add_task_sendable(0, Arc::new(Mutex::new(t1)), 1)
|
|
||||||
.unwrap();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
exec_task_sendable
|
|
||||||
.run_one_task_cycle(&mut deadline_missed_cb)
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
250
satrs/src/seq_count.rs
Normal file
250
satrs/src/seq_count.rs
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
use core::cell::Cell;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use dyn_clone::DynClone;
|
||||||
|
use paste::paste;
|
||||||
|
use spacepackets::MAX_SEQ_COUNT;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use stdmod::*;
|
||||||
|
|
||||||
|
/// Core trait for objects which can provide a sequence count.
|
||||||
|
///
|
||||||
|
/// The core functions are not mutable on purpose to allow easier usage with
|
||||||
|
/// static structs when using the interior mutability pattern. This can be achieved by using
|
||||||
|
/// [Cell], [core::cell::RefCell] or atomic types.
|
||||||
|
pub trait SequenceCountProviderCore<Raw> {
|
||||||
|
fn get(&self) -> Raw;
|
||||||
|
|
||||||
|
fn increment(&self);
|
||||||
|
|
||||||
|
fn get_and_increment(&self) -> Raw {
|
||||||
|
let val = self.get();
|
||||||
|
self.increment();
|
||||||
|
val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait which allows cloning a sequence count provider after it was turned into
|
||||||
|
/// a trait object.
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub trait SequenceCountProvider<Raw>: SequenceCountProviderCore<Raw> + DynClone {}
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
dyn_clone::clone_trait_object!(SequenceCountProvider<u16>);
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl<T, Raw> SequenceCountProvider<Raw> for T where T: SequenceCountProviderCore<Raw> + Clone {}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SeqCountProviderSimple<T: Copy> {
|
||||||
|
seq_count: Cell<T>,
|
||||||
|
max_val: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_for_primitives {
|
||||||
|
($($ty: ident,)+) => {
|
||||||
|
$(
|
||||||
|
paste! {
|
||||||
|
impl SeqCountProviderSimple<$ty> {
|
||||||
|
pub fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self {
|
||||||
|
Self {
|
||||||
|
seq_count: Cell::new(0),
|
||||||
|
max_val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn [<new_ $ty>]() -> Self {
|
||||||
|
Self {
|
||||||
|
seq_count: Cell::new(0),
|
||||||
|
max_val: $ty::MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SeqCountProviderSimple<$ty> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::[<new_ $ty>]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SequenceCountProviderCore<$ty> for SeqCountProviderSimple<$ty> {
|
||||||
|
fn get(&self) -> $ty {
|
||||||
|
self.seq_count.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment(&self) {
|
||||||
|
self.get_and_increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_and_increment(&self) -> $ty {
|
||||||
|
let curr_count = self.seq_count.get();
|
||||||
|
|
||||||
|
if curr_count == self.max_val {
|
||||||
|
self.seq_count.set(0);
|
||||||
|
} else {
|
||||||
|
self.seq_count.set(curr_count + 1);
|
||||||
|
}
|
||||||
|
curr_count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_for_primitives!(u8, u16, u32, u64,);
|
||||||
|
|
||||||
|
/// This is a sequence count provider which wraps around at [MAX_SEQ_COUNT].
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CcsdsSimpleSeqCountProvider {
|
||||||
|
provider: SeqCountProviderSimple<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CcsdsSimpleSeqCountProvider {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
provider: SeqCountProviderSimple::new_custom_max_val_u16(MAX_SEQ_COUNT),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SequenceCountProviderCore<u16> for CcsdsSimpleSeqCountProvider {
|
||||||
|
delegate::delegate! {
|
||||||
|
to self.provider {
|
||||||
|
fn get(&self) -> u16;
|
||||||
|
fn increment(&self);
|
||||||
|
fn get_and_increment(&self) -> u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub mod stdmod {
|
||||||
|
use super::*;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
macro_rules! sync_clonable_seq_counter_impl {
|
||||||
|
($($ty: ident,)+) => {
|
||||||
|
$(paste! {
|
||||||
|
/// These sequence counters can be shared between threads and can also be
|
||||||
|
/// configured to wrap around at specified maximum values. Please note that
|
||||||
|
/// that the API provided by this class will not panic und [Mutex] lock errors,
|
||||||
|
/// but it will yield 0 for the getter functions.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct [<SeqCountProviderSync $ty:upper>] {
|
||||||
|
seq_count: Arc<Mutex<$ty>>,
|
||||||
|
max_val: $ty
|
||||||
|
}
|
||||||
|
|
||||||
|
impl [<SeqCountProviderSync $ty:upper>] {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::new_with_max_val($ty::MAX)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_max_val(max_val: $ty) -> Self {
|
||||||
|
Self {
|
||||||
|
seq_count: Arc::default(),
|
||||||
|
max_val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SequenceCountProviderCore<$ty> for [<SeqCountProviderSync $ty:upper>] {
|
||||||
|
fn get(&self) -> $ty {
|
||||||
|
match self.seq_count.lock() {
|
||||||
|
Ok(counter) => *counter,
|
||||||
|
Err(_) => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment(&self) {
|
||||||
|
self.get_and_increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_and_increment(&self) -> $ty {
|
||||||
|
match self.seq_count.lock() {
|
||||||
|
Ok(mut counter) => {
|
||||||
|
let val = *counter;
|
||||||
|
if val == self.max_val {
|
||||||
|
*counter = 0;
|
||||||
|
} else {
|
||||||
|
*counter += 1;
|
||||||
|
}
|
||||||
|
val
|
||||||
|
}
|
||||||
|
Err(_) => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sync_clonable_seq_counter_impl!(u8, u16, u32, u64,);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::seq_count::{
|
||||||
|
CcsdsSimpleSeqCountProvider, SeqCountProviderSimple, SeqCountProviderSyncU8,
|
||||||
|
SequenceCountProviderCore,
|
||||||
|
};
|
||||||
|
use spacepackets::MAX_SEQ_COUNT;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u8_counter() {
|
||||||
|
let u8_counter = SeqCountProviderSimple::<u8>::default();
|
||||||
|
assert_eq!(u8_counter.get(), 0);
|
||||||
|
assert_eq!(u8_counter.get_and_increment(), 0);
|
||||||
|
assert_eq!(u8_counter.get_and_increment(), 1);
|
||||||
|
assert_eq!(u8_counter.get(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u8_counter_overflow() {
|
||||||
|
let u8_counter = SeqCountProviderSimple::new_u8();
|
||||||
|
for _ in 0..256 {
|
||||||
|
u8_counter.increment();
|
||||||
|
}
|
||||||
|
assert_eq!(u8_counter.get(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ccsds_counter() {
|
||||||
|
let ccsds_counter = CcsdsSimpleSeqCountProvider::default();
|
||||||
|
assert_eq!(ccsds_counter.get(), 0);
|
||||||
|
assert_eq!(ccsds_counter.get_and_increment(), 0);
|
||||||
|
assert_eq!(ccsds_counter.get_and_increment(), 1);
|
||||||
|
assert_eq!(ccsds_counter.get(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ccsds_counter_overflow() {
|
||||||
|
let ccsds_counter = CcsdsSimpleSeqCountProvider::default();
|
||||||
|
for _ in 0..MAX_SEQ_COUNT + 1 {
|
||||||
|
ccsds_counter.increment();
|
||||||
|
}
|
||||||
|
assert_eq!(ccsds_counter.get(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_atomic_ref_counters() {
|
||||||
|
let sync_u8_counter = SeqCountProviderSyncU8::new();
|
||||||
|
assert_eq!(sync_u8_counter.get(), 0);
|
||||||
|
assert_eq!(sync_u8_counter.get_and_increment(), 0);
|
||||||
|
assert_eq!(sync_u8_counter.get_and_increment(), 1);
|
||||||
|
assert_eq!(sync_u8_counter.get(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_atomic_ref_counters_overflow() {
|
||||||
|
let sync_u8_counter = SeqCountProviderSyncU8::new();
|
||||||
|
for _ in 0..u8::MAX as u16 + 1 {
|
||||||
|
sync_u8_counter.increment();
|
||||||
|
}
|
||||||
|
assert_eq!(sync_u8_counter.get(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_atomic_ref_counters_overflow_custom_max_val() {
|
||||||
|
let sync_u8_counter = SeqCountProviderSyncU8::new_with_max_val(128);
|
||||||
|
for _ in 0..129 {
|
||||||
|
sync_u8_counter.increment();
|
||||||
|
}
|
||||||
|
assert_eq!(sync_u8_counter.get(), 0);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user