diff --git a/README.md b/README.md
index 6d5669f..c8415e4 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,8 @@
+[![Crates.io](https://img.shields.io/crates/v/satrs)](https://crates.io/crates/satrs)
+[![docs.rs](https://img.shields.io/docsrs/satrs)](https://docs.rs/satrs)
+
sat-rs
=========
diff --git a/coverage.py b/coverage.py
index b7efbe9..126a101 100755
--- a/coverage.py
+++ b/coverage.py
@@ -43,8 +43,8 @@ def main():
parser.add_argument(
"-p",
"--package",
- choices=["satrs-core"],
- default="satrs-core",
+ choices=["satrs"],
+ default="satrs",
help="Choose project to generate coverage for",
)
parser.add_argument(
diff --git a/satrs-example/Cargo.toml b/satrs-example/Cargo.toml
index 4ea2768..4814001 100644
--- a/satrs-example/Cargo.toml
+++ b/satrs-example/Cargo.toml
@@ -20,12 +20,12 @@ thiserror = "1"
derive-new = "0.5"
[dependencies.satrs]
-version = "0.1.1"
-# path = "../satrs"
+# version = "0.1.1"
+path = "../satrs"
[dependencies.satrs-mib]
-version = "0.1.0"
-# path = "../satrs-mib"
+# version = "0.1.0"
+path = "../satrs-mib"
[features]
dyn_tmtc = []
diff --git a/satrs-example/src/acs.rs b/satrs-example/src/acs.rs
index dc7763b..5c400f7 100644
--- a/satrs-example/src/acs.rs
+++ b/satrs-example/src/acs.rs
@@ -1,8 +1,9 @@
use std::sync::mpsc::{self, TryRecvError};
use log::{info, warn};
-use satrs::pus::verification::VerificationReporterWithSender;
+use satrs::pus::verification::{VerificationReporterWithSender, VerificationReportingProvider};
use satrs::pus::{EcssTmSender, PusTmWrapper};
+use satrs::request::TargetAndApidId;
use satrs::spacepackets::ecss::hk::Subservice as HkSubservice;
use satrs::{
hk::HkRequest,
@@ -70,12 +71,12 @@ impl AcsTask {
"ACS thread: Received HK request {:?}",
request.targeted_request
);
+ let target_and_apid_id = TargetAndApidId::from(request.targeted_request.target_id);
match request.targeted_request.request {
Request::Hk(hk_req) => match hk_req {
- HkRequest::OneShot(unique_id) => self.handle_hk_request(
- request.targeted_request.target_id_with_apid.target_id(),
- unique_id,
- ),
+ HkRequest::OneShot(unique_id) => {
+ self.handle_hk_request(target_and_apid_id.target(), unique_id)
+ }
HkRequest::Enable(_) => {}
HkRequest::Disable(_) => {}
HkRequest::ModifyCollectionInterval(_, _) => {}
@@ -89,10 +90,10 @@ impl AcsTask {
}
let started_token = self
.verif_reporter
- .start_success(request.token, Some(&self.timestamp))
+ .start_success(request.token, &self.timestamp)
.expect("Sending start success failed");
self.verif_reporter
- .completion_success(started_token, Some(&self.timestamp))
+ .completion_success(started_token, &self.timestamp)
.expect("Sending completion success failed");
true
}
diff --git a/satrs-example/src/config.rs b/satrs-example/src/config.rs
index 4cc960f..9d04403 100644
--- a/satrs-example/src/config.rs
+++ b/satrs-example/src/config.rs
@@ -48,7 +48,11 @@ pub mod tmtc_err {
#[resultcode]
pub const PUS_SERVICE_NOT_IMPLEMENTED: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 2);
#[resultcode]
- pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 3);
+ pub const PUS_SUBSERVICE_NOT_IMPLEMENTED: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 3);
+ #[resultcode]
+ pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 4);
+ #[resultcode]
+ pub const ROUTING_ERROR: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 5);
#[resultcode(
info = "Not enough data inside the TC application data field. Optionally includes: \
@@ -60,6 +64,9 @@ pub mod tmtc_err {
pub const TMTC_RESULTS: &[ResultU16Info] = &[
INVALID_PUS_SERVICE_EXT,
INVALID_PUS_SUBSERVICE_EXT,
+ PUS_SERVICE_NOT_IMPLEMENTED_EXT,
+ UNKNOWN_TARGET_ID_EXT,
+ ROUTING_ERROR_EXT,
NOT_ENOUGH_APP_DATA_EXT,
];
}
@@ -76,6 +83,13 @@ pub mod hk_err {
pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::new(GroupId::Hk as u8, 2);
#[resultcode]
pub const COLLECTION_INTERVAL_MISSING: ResultU16 = ResultU16::new(GroupId::Hk as u8, 3);
+
+ pub const HK_ERR_RESULTS: &[ResultU16Info] = &[
+ TARGET_ID_MISSING_EXT,
+ UNKNOWN_TARGET_ID_EXT,
+ UNKNOWN_TARGET_ID_EXT,
+ COLLECTION_INTERVAL_MISSING_EXT,
+ ];
}
#[allow(clippy::enum_variant_names)]
diff --git a/satrs-example/src/events.rs b/satrs-example/src/events.rs
index 81f7d1e..46ba6bb 100644
--- a/satrs-example/src/events.rs
+++ b/satrs-example/src/events.rs
@@ -12,7 +12,10 @@ use satrs::{
DefaultPusMgmtBackendProvider, EventReporter, EventRequest, EventRequestWithToken,
PusEventDispatcher,
},
- verification::{TcStateStarted, VerificationReporterWithSender, VerificationToken},
+ verification::{
+ TcStateStarted, VerificationReporterWithSender, VerificationReportingProvider,
+ VerificationToken,
+ },
EcssTmSender,
},
spacepackets::time::cds::{self, TimeProvider},
@@ -73,7 +76,7 @@ impl PusEventHandler {
.try_into()
.expect("expected start verification token");
self.verif_handler
- .completion_success(started_token, Some(timestamp))
+ .completion_success(started_token, timestamp)
.expect("Sending completion success failed");
};
// handle event requests
diff --git a/satrs-example/src/lib.rs b/satrs-example/src/lib.rs
index dac8ab0..ef68c36 100644
--- a/satrs-example/src/lib.rs
+++ b/satrs-example/src/lib.rs
@@ -1,59 +1 @@
-use derive_new::new;
-use satrs::spacepackets::ecss::tc::IsPusTelecommand;
-use satrs::spacepackets::ecss::PusPacket;
-use satrs::spacepackets::{ByteConversionError, CcsdsPacket};
-use satrs::tmtc::TargetId;
-use std::fmt;
-use thiserror::Error;
-
pub mod config;
-
-pub type Apid = u16;
-
-#[derive(Debug, Error)]
-pub enum TargetIdCreationError {
- #[error("byte conversion")]
- ByteConversion(#[from] ByteConversionError),
- #[error("not enough app data to generate target ID")]
- NotEnoughAppData(usize),
-}
-
-// TODO: can these stay pub?
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, new)]
-pub struct TargetIdWithApid {
- pub apid: Apid,
- pub target: TargetId,
-}
-
-impl fmt::Display for TargetIdWithApid {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{}, {}", self.apid, self.target)
- }
-}
-
-impl TargetIdWithApid {
- pub fn apid(&self) -> Apid {
- self.apid
- }
- pub fn target_id(&self) -> TargetId {
- self.target
- }
-}
-
-impl TargetIdWithApid {
- pub fn from_tc(
- tc: &(impl CcsdsPacket + PusPacket + IsPusTelecommand),
- ) -> Result {
- if tc.user_data().len() < 4 {
- return Err(ByteConversionError::FromSliceTooSmall {
- found: tc.user_data().len(),
- expected: 8,
- }
- .into());
- }
- Ok(Self {
- apid: tc.apid(),
- target: u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap()),
- })
- }
-}
diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs
index 32dbfcb..76f2ffd 100644
--- a/satrs-example/src/main.rs
+++ b/satrs-example/src/main.rs
@@ -17,6 +17,7 @@ use log::info;
use pus::test::create_test_service_dynamic;
use satrs::hal::std::tcp_server::ServerConfig;
use satrs::hal::std::udp_server::UdpTcServer;
+use satrs::request::TargetAndApidId;
use satrs::tmtc::tm_helper::SharedTmPool;
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
use satrs_example::config::tasks::{
@@ -35,7 +36,7 @@ use crate::pus::hk::{create_hk_service_dynamic, create_hk_service_static};
use crate::pus::scheduler::{create_scheduler_service_dynamic, create_scheduler_service_static};
use crate::pus::test::create_test_service_static;
use crate::pus::{PusReceiver, PusTcMpscRouter};
-use crate::requests::RequestWithToken;
+use crate::requests::{GenericRequestRouter, RequestWithToken};
use crate::tcp::{SyncTcpTmSource, TcpTask};
use crate::tmtc::{
PusTcSourceProviderSharedPool, SharedTcPool, TcSourceTaskDynamic, TcSourceTaskStatic,
@@ -45,10 +46,8 @@ use satrs::pus::event_man::EventRequestWithToken;
use satrs::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender};
use satrs::pus::{EcssTmSender, MpscTmAsVecSender, MpscTmInSharedPoolSender};
use satrs::spacepackets::{time::cds::TimeProvider, time::TimeWriter};
-use satrs::tmtc::{CcsdsDistributor, TargetId};
+use satrs::tmtc::CcsdsDistributor;
use satrs::ChannelId;
-use satrs_example::TargetIdWithApid;
-use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use std::sync::mpsc::{self, channel};
use std::sync::{Arc, RwLock};
@@ -82,11 +81,11 @@ fn static_tmtc_pool_main() {
tm_funnel_tx.clone(),
));
- let acs_target_id = TargetIdWithApid::new(PUS_APID, RequestTargetId::AcsSubsystem as TargetId);
+ let acs_target_id = TargetAndApidId::new(PUS_APID, RequestTargetId::AcsSubsystem as u32);
let (acs_thread_tx, acs_thread_rx) = channel::();
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
- let mut request_map = HashMap::new();
- request_map.insert(acs_target_id, acs_thread_tx);
+ let mut request_map = GenericRequestRouter::default();
+ request_map.0.insert(acs_target_id.into(), acs_thread_tx);
// This helper structure is used by all telecommand providers which need to send telecommands
// to the TC source.
@@ -310,11 +309,11 @@ fn dyn_tmtc_pool_main() {
tm_funnel_tx.clone(),
));
- let acs_target_id = TargetIdWithApid::new(PUS_APID, RequestTargetId::AcsSubsystem as TargetId);
+ let acs_target_id = TargetAndApidId::new(PUS_APID, RequestTargetId::AcsSubsystem as u32);
let (acs_thread_tx, acs_thread_rx) = channel::();
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
- let mut request_map = HashMap::new();
- request_map.insert(acs_target_id, acs_thread_tx);
+ let mut request_map = GenericRequestRouter::default();
+ request_map.0.insert(acs_target_id.into(), acs_thread_tx);
let tc_source = PusTcSourceProviderDynamic(tc_source_tx);
diff --git a/satrs-example/src/pus/action.rs b/satrs-example/src/pus/action.rs
index a791506..653fe80 100644
--- a/satrs-example/src/pus/action.rs
+++ b/satrs-example/src/pus/action.rs
@@ -1,22 +1,76 @@
-use crate::requests::{ActionRequest, Request, RequestWithToken};
use log::{error, warn};
+use satrs::action::ActionRequest;
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
+use satrs::pus::action::{PusActionToRequestConverter, PusService8ActionHandler};
use satrs::pus::verification::{
- FailParams, TcStateAccepted, VerificationReporterWithSender, VerificationToken,
+ FailParams, TcStateAccepted, VerificationReporterWithSender, VerificationReportingProvider,
+ VerificationToken,
};
use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
- EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender,
- PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper,
+ MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
+ PusPacketHandlingError, PusServiceHelper,
};
+use satrs::request::TargetAndApidId;
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusPacket;
use satrs::tmtc::tm_helper::SharedTmPool;
-use satrs::ChannelId;
+use satrs::{ChannelId, TargetId};
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
-use satrs_example::TargetIdWithApid;
-use std::collections::HashMap;
-use std::sync::mpsc::{self, Sender};
+use std::sync::mpsc::{self};
+
+use crate::requests::GenericRequestRouter;
+
+use super::GenericRoutingErrorHandler;
+
+#[derive(Default)]
+pub struct ExampleActionRequestConverter {}
+
+impl PusActionToRequestConverter for ExampleActionRequestConverter {
+ type Error = PusPacketHandlingError;
+
+ fn convert(
+ &mut self,
+ token: VerificationToken,
+ tc: &PusTcReader,
+ time_stamp: &[u8],
+ verif_reporter: &impl VerificationReportingProvider,
+ ) -> Result<(TargetId, ActionRequest), Self::Error> {
+ let subservice = tc.subservice();
+ let user_data = tc.user_data();
+ if user_data.len() < 8 {
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new_no_fail_data(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA),
+ )
+ .expect("Sending start failure failed");
+ return Err(PusPacketHandlingError::NotEnoughAppData {
+ expected: 8,
+ found: user_data.len(),
+ });
+ }
+ let target_id = TargetAndApidId::from_pus_tc(tc).unwrap();
+ let action_id = u32::from_be_bytes(user_data[4..8].try_into().unwrap());
+ if subservice == 128 {
+ Ok((
+ target_id.raw(),
+ ActionRequest::UnsignedIdAndVecData {
+ action_id,
+ data: user_data[8..].to_vec(),
+ },
+ ))
+ } else {
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new_no_fail_data(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE),
+ )
+ .expect("Sending start failure failed");
+ Err(PusPacketHandlingError::InvalidSubservice(subservice))
+ }
+ }
+}
pub fn create_action_service_static(
shared_tm_store: SharedTmPool,
@@ -24,7 +78,7 @@ pub fn create_action_service_static(
verif_reporter: VerificationReporterWithSender,
tc_pool: SharedStaticMemoryPool,
pus_action_rx: mpsc::Receiver,
- request_map: HashMap>,
+ action_router: GenericRequestRouter,
) -> Pus8Wrapper {
let action_srv_tm_sender = MpscTmInSharedPoolSender::new(
TmSenderId::PusAction as ChannelId,
@@ -38,12 +92,16 @@ pub fn create_action_service_static(
pus_action_rx,
);
let pus_8_handler = PusService8ActionHandler::new(
- Box::new(action_srv_receiver),
- Box::new(action_srv_tm_sender),
- PUS_APID,
- verif_reporter.clone(),
- EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
- request_map.clone(),
+ PusServiceHelper::new(
+ Box::new(action_srv_receiver),
+ Box::new(action_srv_tm_sender),
+ PUS_APID,
+ verif_reporter.clone(),
+ EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
+ ),
+ ExampleActionRequestConverter::default(),
+ action_router,
+ GenericRoutingErrorHandler::<8>::default(),
);
Pus8Wrapper { pus_8_handler }
}
@@ -52,7 +110,7 @@ pub fn create_action_service_dynamic(
tm_funnel_tx: mpsc::Sender>,
verif_reporter: VerificationReporterWithSender,
pus_action_rx: mpsc::Receiver,
- request_map: HashMap>,
+ action_router: GenericRequestRouter,
) -> Pus8Wrapper {
let action_srv_tm_sender = MpscTmAsVecSender::new(
TmSenderId::PusAction as ChannelId,
@@ -65,146 +123,28 @@ pub fn create_action_service_dynamic(
pus_action_rx,
);
let pus_8_handler = PusService8ActionHandler::new(
- Box::new(action_srv_receiver),
- Box::new(action_srv_tm_sender),
- PUS_APID,
- verif_reporter.clone(),
- EcssTcInVecConverter::default(),
- request_map.clone(),
+ PusServiceHelper::new(
+ Box::new(action_srv_receiver),
+ Box::new(action_srv_tm_sender),
+ PUS_APID,
+ verif_reporter.clone(),
+ EcssTcInVecConverter::default(),
+ ),
+ ExampleActionRequestConverter::default(),
+ action_router,
+ GenericRoutingErrorHandler::<8>::default(),
);
Pus8Wrapper { pus_8_handler }
}
-pub struct PusService8ActionHandler {
- service_helper: PusServiceHelper,
- request_handlers: HashMap>,
-}
-
-impl PusService8ActionHandler {
- pub fn new(
- tc_receiver: Box,
- tm_sender: Box,
- tm_apid: u16,
- verification_handler: VerificationReporterWithSender,
- tc_in_mem_converter: TcInMemConverter,
- request_handlers: HashMap>,
- ) -> Self {
- Self {
- service_helper: PusServiceHelper::new(
- tc_receiver,
- tm_sender,
- tm_apid,
- verification_handler,
- tc_in_mem_converter,
- ),
- request_handlers,
- }
- }
-
- fn handle_action_request_with_id(
- &self,
- token: VerificationToken,
- tc: &PusTcReader,
- time_stamp: &[u8],
- ) -> Result<(), PusPacketHandlingError> {
- let user_data = tc.user_data();
- if user_data.len() < 8 {
- self.service_helper
- .common
- .verification_handler
- .borrow_mut()
- .start_failure(
- token,
- FailParams::new(Some(time_stamp), &tmtc_err::NOT_ENOUGH_APP_DATA, None),
- )
- .expect("Sending start failure failed");
- return Err(PusPacketHandlingError::NotEnoughAppData(
- "Expected at least 4 bytes".into(),
- ));
- }
- //let target_id = u32::from_be_bytes(user_data[0..4].try_into().unwrap());
- let target_id = TargetIdWithApid::from_tc(tc).unwrap();
- let action_id = u32::from_be_bytes(user_data[4..8].try_into().unwrap());
- if let Some(sender) = self.request_handlers.get(&target_id) {
- sender
- .send(RequestWithToken::new(
- target_id,
- Request::Action(ActionRequest::CmdWithU32Id((
- action_id,
- Vec::from(&user_data[8..]),
- ))),
- token,
- ))
- .expect("Forwarding action request failed");
- } else {
- let mut fail_data: [u8; 4] = [0; 4];
- fail_data.copy_from_slice(&target_id.target.to_be_bytes());
- self.service_helper
- .common
- .verification_handler
- .borrow_mut()
- .start_failure(
- token,
- FailParams::new(
- Some(time_stamp),
- &tmtc_err::UNKNOWN_TARGET_ID,
- Some(&fail_data),
- ),
- )
- .expect("Sending start failure failed");
- return Err(PusPacketHandlingError::Other(format!(
- "Unknown target ID {target_id}"
- )));
- }
- Ok(())
- }
-
- fn handle_one_tc(&mut self) -> Result {
- let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
- if possible_packet.is_none() {
- return Ok(PusPacketHandlerResult::Empty);
- }
- let ecss_tc_and_token = possible_packet.unwrap();
- self.service_helper
- .tc_in_mem_converter
- .cache_ecss_tc_in_memory(&ecss_tc_and_token.tc_in_memory)?;
- let tc = PusTcReader::new(self.service_helper.tc_in_mem_converter.tc_slice_raw())?.0;
- let subservice = tc.subservice();
- let mut partial_error = None;
- let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
- match subservice {
- 128 => {
- self.handle_action_request_with_id(ecss_tc_and_token.token, &tc, &time_stamp)?;
- }
- _ => {
- let fail_data = [subservice];
- self.service_helper
- .common
- .verification_handler
- .get_mut()
- .start_failure(
- ecss_tc_and_token.token,
- FailParams::new(
- Some(&time_stamp),
- &tmtc_err::INVALID_PUS_SUBSERVICE,
- Some(&fail_data),
- ),
- )
- .expect("Sending start failure failed");
- return Err(PusPacketHandlingError::InvalidSubservice(subservice));
- }
- }
- if let Some(partial_error) = partial_error {
- return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess(
- partial_error,
- ));
- }
- Ok(PusPacketHandlerResult::RequestHandled)
- }
-}
-
pub struct Pus8Wrapper {
- pub(crate) pus_8_handler: PusService8ActionHandler,
+ pub(crate) pus_8_handler: PusService8ActionHandler<
+ TcInMemConverter,
+ VerificationReporterWithSender,
+ ExampleActionRequestConverter,
+ GenericRequestRouter,
+ GenericRoutingErrorHandler<8>,
+ >,
}
impl Pus8Wrapper {
diff --git a/satrs-example/src/pus/event.rs b/satrs-example/src/pus/event.rs
index 0355d9c..8256658 100644
--- a/satrs-example/src/pus/event.rs
+++ b/satrs-example/src/pus/event.rs
@@ -76,7 +76,7 @@ pub fn create_event_service_dynamic(
}
pub struct Pus5Wrapper {
- pub pus_5_handler: PusService5EventHandler,
+ pub pus_5_handler: PusService5EventHandler,
}
impl Pus5Wrapper {
diff --git a/satrs-example/src/pus/hk.rs b/satrs-example/src/pus/hk.rs
index 41c98b9..035bb86 100644
--- a/satrs-example/src/pus/hk.rs
+++ b/satrs-example/src/pus/hk.rs
@@ -1,22 +1,145 @@
-use crate::requests::{Request, RequestWithToken};
use log::{error, warn};
use satrs::hk::{CollectionIntervalFactor, HkRequest};
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
+use satrs::pus::hk::{PusHkToRequestConverter, PusService3HkHandler};
use satrs::pus::verification::{
- FailParams, StdVerifReporterWithSender, VerificationReporterWithSender,
+ FailParams, TcStateAccepted, VerificationReporterWithSender, VerificationReportingProvider,
+ VerificationToken,
};
use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
- EcssTcReceiver, EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender,
- PusPacketHandlerResult, PusPacketHandlingError, PusServiceBase, PusServiceHelper,
+ MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
+ PusPacketHandlingError, PusServiceHelper,
};
+use satrs::request::TargetAndApidId;
+use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::{hk, PusPacket};
use satrs::tmtc::tm_helper::SharedTmPool;
-use satrs::ChannelId;
+use satrs::{ChannelId, TargetId};
use satrs_example::config::{hk_err, tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
-use satrs_example::TargetIdWithApid;
-use std::collections::HashMap;
-use std::sync::mpsc::{self, Sender};
+use std::sync::mpsc::{self};
+
+use crate::requests::GenericRequestRouter;
+
+use super::GenericRoutingErrorHandler;
+
+#[derive(Default)]
+pub struct ExampleHkRequestConverter {}
+
+impl PusHkToRequestConverter for ExampleHkRequestConverter {
+ type Error = PusPacketHandlingError;
+
+ fn convert(
+ &mut self,
+ token: VerificationToken,
+ tc: &PusTcReader,
+ time_stamp: &[u8],
+ verif_reporter: &impl VerificationReportingProvider,
+ ) -> Result<(TargetId, HkRequest), Self::Error> {
+ let user_data = tc.user_data();
+ if user_data.is_empty() {
+ let user_data_len = user_data.len() as u32;
+ let user_data_len_raw = user_data_len.to_be_bytes();
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new(
+ time_stamp,
+ &tmtc_err::NOT_ENOUGH_APP_DATA,
+ &user_data_len_raw,
+ ),
+ )
+ .expect("Sending start failure TM failed");
+ return Err(PusPacketHandlingError::NotEnoughAppData {
+ expected: 4,
+ found: 0,
+ });
+ }
+ if user_data.len() < 8 {
+ let err = if user_data.len() < 4 {
+ &hk_err::TARGET_ID_MISSING
+ } else {
+ &hk_err::UNIQUE_ID_MISSING
+ };
+ let user_data_len = user_data.len() as u32;
+ let user_data_len_raw = user_data_len.to_be_bytes();
+ verif_reporter
+ .start_failure(token, FailParams::new(time_stamp, err, &user_data_len_raw))
+ .expect("Sending start failure TM failed");
+ return Err(PusPacketHandlingError::NotEnoughAppData {
+ expected: 8,
+ found: 4,
+ });
+ }
+ let subservice = tc.subservice();
+ let target_id = TargetAndApidId::from_pus_tc(tc).expect("invalid tc format");
+ let unique_id = u32::from_be_bytes(tc.user_data()[4..8].try_into().unwrap());
+
+ let standard_subservice = hk::Subservice::try_from(subservice);
+ if standard_subservice.is_err() {
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE, &[subservice]),
+ )
+ .expect("Sending start failure TM failed");
+ return Err(PusPacketHandlingError::InvalidSubservice(subservice));
+ }
+ Ok((
+ target_id.into(),
+ match standard_subservice.unwrap() {
+ hk::Subservice::TcEnableHkGeneration | hk::Subservice::TcEnableDiagGeneration => {
+ HkRequest::Enable(unique_id)
+ }
+ hk::Subservice::TcDisableHkGeneration | hk::Subservice::TcDisableDiagGeneration => {
+ HkRequest::Disable(unique_id)
+ }
+ hk::Subservice::TcReportHkReportStructures => todo!(),
+ hk::Subservice::TmHkPacket => todo!(),
+ hk::Subservice::TcGenerateOneShotHk | hk::Subservice::TcGenerateOneShotDiag => {
+ HkRequest::OneShot(unique_id)
+ }
+ hk::Subservice::TcModifyDiagCollectionInterval
+ | hk::Subservice::TcModifyHkCollectionInterval => {
+ if user_data.len() < 12 {
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new_no_fail_data(
+ time_stamp,
+ &tmtc_err::NOT_ENOUGH_APP_DATA,
+ ),
+ )
+ .expect("Sending start failure TM failed");
+ return Err(PusPacketHandlingError::NotEnoughAppData {
+ expected: 12,
+ found: user_data.len(),
+ });
+ }
+ HkRequest::ModifyCollectionInterval(
+ unique_id,
+ CollectionIntervalFactor::from_be_bytes(
+ user_data[8..12].try_into().unwrap(),
+ ),
+ )
+ }
+ _ => {
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new(
+ time_stamp,
+ &tmtc_err::PUS_SUBSERVICE_NOT_IMPLEMENTED,
+ &[subservice],
+ ),
+ )
+ .expect("Sending start failure TM failed");
+ return Err(PusPacketHandlingError::InvalidSubservice(subservice));
+ }
+ },
+ ))
+ }
+}
pub fn create_hk_service_static(
shared_tm_store: SharedTmPool,
@@ -24,7 +147,7 @@ pub fn create_hk_service_static(
verif_reporter: VerificationReporterWithSender,
tc_pool: SharedStaticMemoryPool,
pus_hk_rx: mpsc::Receiver,
- request_map: HashMap>,
+ request_router: GenericRequestRouter,
) -> Pus3Wrapper {
let hk_srv_tm_sender = MpscTmInSharedPoolSender::new(
TmSenderId::PusHk as ChannelId,
@@ -35,12 +158,16 @@ pub fn create_hk_service_static(
let hk_srv_receiver =
MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx);
let pus_3_handler = PusService3HkHandler::new(
- Box::new(hk_srv_receiver),
- Box::new(hk_srv_tm_sender),
- PUS_APID,
- verif_reporter.clone(),
- EcssTcInSharedStoreConverter::new(tc_pool, 2048),
- request_map,
+ PusServiceHelper::new(
+ Box::new(hk_srv_receiver),
+ Box::new(hk_srv_tm_sender),
+ PUS_APID,
+ verif_reporter.clone(),
+ EcssTcInSharedStoreConverter::new(tc_pool, 2048),
+ ),
+ ExampleHkRequestConverter::default(),
+ request_router,
+ GenericRoutingErrorHandler::default(),
);
Pus3Wrapper { pus_3_handler }
}
@@ -49,7 +176,7 @@ pub fn create_hk_service_dynamic(
tm_funnel_tx: mpsc::Sender>,
verif_reporter: VerificationReporterWithSender,
pus_hk_rx: mpsc::Receiver,
- request_map: HashMap>,
+ request_router: GenericRequestRouter,
) -> Pus3Wrapper {
let hk_srv_tm_sender = MpscTmAsVecSender::new(
TmSenderId::PusHk as ChannelId,
@@ -59,154 +186,28 @@ pub fn create_hk_service_dynamic(
let hk_srv_receiver =
MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx);
let pus_3_handler = PusService3HkHandler::new(
- Box::new(hk_srv_receiver),
- Box::new(hk_srv_tm_sender),
- PUS_APID,
- verif_reporter.clone(),
- EcssTcInVecConverter::default(),
- request_map,
+ PusServiceHelper::new(
+ Box::new(hk_srv_receiver),
+ Box::new(hk_srv_tm_sender),
+ PUS_APID,
+ verif_reporter.clone(),
+ EcssTcInVecConverter::default(),
+ ),
+ ExampleHkRequestConverter::default(),
+ request_router,
+ GenericRoutingErrorHandler::default(),
);
Pus3Wrapper { pus_3_handler }
}
-pub struct PusService3HkHandler {
- psb: PusServiceHelper,
- request_handlers: HashMap>,
-}
-
-impl PusService3HkHandler {
- pub fn new(
- tc_receiver: Box,
- tm_sender: Box,
- tm_apid: u16,
- verification_handler: StdVerifReporterWithSender,
- tc_in_mem_converter: TcInMemConverter,
- request_handlers: HashMap>,
- ) -> Self {
- Self {
- psb: PusServiceHelper::new(
- tc_receiver,
- tm_sender,
- tm_apid,
- verification_handler,
- tc_in_mem_converter,
- ),
- request_handlers,
- }
- }
-
- fn handle_one_tc(&mut self) -> Result {
- let possible_packet = self.psb.retrieve_and_accept_next_packet()?;
- if possible_packet.is_none() {
- return Ok(PusPacketHandlerResult::Empty);
- }
- let ecss_tc_and_token = possible_packet.unwrap();
- let tc = self
- .psb
- .tc_in_mem_converter
- .convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
- let subservice = tc.subservice();
- let mut partial_error = None;
- let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
- let user_data = tc.user_data();
- if user_data.is_empty() {
- self.psb
- .common
- .verification_handler
- .borrow_mut()
- .start_failure(
- ecss_tc_and_token.token,
- FailParams::new(Some(&time_stamp), &tmtc_err::NOT_ENOUGH_APP_DATA, None),
- )
- .expect("Sending start failure TM failed");
- return Err(PusPacketHandlingError::NotEnoughAppData(
- "Expected at least 8 bytes of app data".into(),
- ));
- }
- if user_data.len() < 8 {
- let err = if user_data.len() < 4 {
- &hk_err::TARGET_ID_MISSING
- } else {
- &hk_err::UNIQUE_ID_MISSING
- };
- self.psb
- .common
- .verification_handler
- .borrow_mut()
- .start_failure(
- ecss_tc_and_token.token,
- FailParams::new(Some(&time_stamp), err, None),
- )
- .expect("Sending start failure TM failed");
- return Err(PusPacketHandlingError::NotEnoughAppData(
- "Expected at least 8 bytes of app data".into(),
- ));
- }
- let target_id = TargetIdWithApid::from_tc(&tc).expect("invalid tc format");
- let unique_id = u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap());
- if !self.request_handlers.contains_key(&target_id) {
- self.psb
- .common
- .verification_handler
- .borrow_mut()
- .start_failure(
- ecss_tc_and_token.token,
- FailParams::new(Some(&time_stamp), &hk_err::UNKNOWN_TARGET_ID, None),
- )
- .expect("Sending start failure TM failed");
- return Err(PusPacketHandlingError::NotEnoughAppData(format!(
- "Unknown target ID {target_id}"
- )));
- }
- let send_request = |target: TargetIdWithApid, request: HkRequest| {
- let sender = self.request_handlers.get(&target).unwrap();
- sender
- .send(RequestWithToken::new(
- target,
- Request::Hk(request),
- ecss_tc_and_token.token,
- ))
- .unwrap_or_else(|_| panic!("Sending HK request {request:?} failed"));
- };
- if subservice == hk::Subservice::TcEnableHkGeneration as u8 {
- send_request(target_id, HkRequest::Enable(unique_id));
- } else if subservice == hk::Subservice::TcDisableHkGeneration as u8 {
- send_request(target_id, HkRequest::Disable(unique_id));
- } else if subservice == hk::Subservice::TcGenerateOneShotHk as u8 {
- send_request(target_id, HkRequest::OneShot(unique_id));
- } else if subservice == hk::Subservice::TcModifyHkCollectionInterval as u8 {
- if user_data.len() < 12 {
- self.psb
- .common
- .verification_handler
- .borrow_mut()
- .start_failure(
- ecss_tc_and_token.token,
- FailParams::new(
- Some(&time_stamp),
- &hk_err::COLLECTION_INTERVAL_MISSING,
- None,
- ),
- )
- .expect("Sending start failure TM failed");
- return Err(PusPacketHandlingError::NotEnoughAppData(
- "Collection interval missing".into(),
- ));
- }
- send_request(
- target_id,
- HkRequest::ModifyCollectionInterval(
- unique_id,
- CollectionIntervalFactor::from_be_bytes(user_data[8..12].try_into().unwrap()),
- ),
- );
- }
- Ok(PusPacketHandlerResult::RequestHandled)
- }
-}
-
pub struct Pus3Wrapper {
- pub(crate) pus_3_handler: PusService3HkHandler,
+ pub(crate) pus_3_handler: PusService3HkHandler<
+ TcInMemConverter,
+ VerificationReporterWithSender,
+ ExampleHkRequestConverter,
+ GenericRequestRouter,
+ GenericRoutingErrorHandler<3>,
+ >,
}
impl Pus3Wrapper {
diff --git a/satrs-example/src/pus/mod.rs b/satrs-example/src/pus/mod.rs
index ec63abe..b903cb4 100644
--- a/satrs-example/src/pus/mod.rs
+++ b/satrs-example/src/pus/mod.rs
@@ -1,7 +1,11 @@
use crate::tmtc::MpscStoreAndSendError;
use log::warn;
-use satrs::pus::verification::{FailParams, StdVerifReporterWithSender};
-use satrs::pus::{EcssTcAndToken, PusPacketHandlerResult, TcInMemory};
+use satrs::pus::verification::{
+ FailParams, StdVerifReporterWithSender, VerificationReportingProvider,
+};
+use satrs::pus::{
+ EcssTcAndToken, GenericRoutingError, PusPacketHandlerResult, PusRoutingErrorHandler, TcInMemory,
+};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusServiceId;
use satrs::spacepackets::time::cds::TimeProvider;
@@ -78,7 +82,7 @@ impl PusReceiver {
self.stamp_helper.update_from_now();
let accepted_token = self
.verif_reporter
- .acceptance_success(init_token, Some(self.stamp_helper.stamp()))
+ .acceptance_success(init_token, self.stamp_helper.stamp())
.expect("Acceptance success failure");
let service = PusServiceId::try_from(service);
match service {
@@ -115,9 +119,9 @@ impl PusReceiver {
let result = self.verif_reporter.start_failure(
accepted_token,
FailParams::new(
- Some(self.stamp_helper.stamp()),
+ self.stamp_helper.stamp(),
&tmtc_err::PUS_SERVICE_NOT_IMPLEMENTED,
- Some(&[standard_service as u8]),
+ &[standard_service as u8],
),
);
if result.is_err() {
@@ -139,9 +143,9 @@ impl PusReceiver {
.start_failure(
accepted_token,
FailParams::new(
- Some(self.stamp_helper.stamp()),
+ self.stamp_helper.stamp(),
&tmtc_err::INVALID_PUS_SUBSERVICE,
- Some(&[e.number]),
+ &[e.number],
),
)
.expect("Start failure verification failed")
@@ -151,3 +155,56 @@ impl PusReceiver {
Ok(PusPacketHandlerResult::RequestHandled)
}
}
+
+#[derive(Default)]
+pub struct GenericRoutingErrorHandler {}
+
+impl PusRoutingErrorHandler for GenericRoutingErrorHandler {
+ type Error = satrs::pus::GenericRoutingError;
+
+ fn handle_error(
+ &self,
+ target_id: satrs::TargetId,
+ token: satrs::pus::verification::VerificationToken<
+ satrs::pus::verification::TcStateAccepted,
+ >,
+ _tc: &PusTcReader,
+ error: Self::Error,
+ time_stamp: &[u8],
+ verif_reporter: &impl VerificationReportingProvider,
+ ) {
+ warn!("Routing request for service {SERVICE_ID} failed: {error:?}");
+ match error {
+ GenericRoutingError::UnknownTargetId(id) => {
+ let mut fail_data: [u8; 8] = [0; 8];
+ fail_data.copy_from_slice(&id.to_be_bytes());
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new(time_stamp, &tmtc_err::UNKNOWN_TARGET_ID, &fail_data),
+ )
+ .expect("Sending start failure failed");
+ }
+ GenericRoutingError::SendError(_) => {
+ let mut fail_data: [u8; 8] = [0; 8];
+ fail_data.copy_from_slice(&target_id.to_be_bytes());
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new(time_stamp, &tmtc_err::ROUTING_ERROR, &fail_data),
+ )
+ .expect("Sending start failure failed");
+ }
+ GenericRoutingError::NotEnoughAppData { expected, found } => {
+ let mut context_info = (found as u32).to_be_bytes().to_vec();
+ context_info.extend_from_slice(&(expected as u32).to_be_bytes());
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA, &context_info),
+ )
+ .expect("Sending start failure failed");
+ }
+ }
+ }
+}
diff --git a/satrs-example/src/pus/scheduler.rs b/satrs-example/src/pus/scheduler.rs
index c3bc7d3..8b5ec47 100644
--- a/satrs-example/src/pus/scheduler.rs
+++ b/satrs-example/src/pus/scheduler.rs
@@ -52,7 +52,8 @@ impl TcReleaser for mpsc::Sender> {
}
pub struct Pus11Wrapper {
- pub pus_11_handler: PusService11SchedHandler,
+ pub pus_11_handler:
+ PusService11SchedHandler,
pub sched_tc_pool: StaticMemoryPool,
pub releaser_buf: [u8; 4096],
pub tc_releaser: Box,
diff --git a/satrs-example/src/pus/test.rs b/satrs-example/src/pus/test.rs
index 45670cc..fe52af7 100644
--- a/satrs-example/src/pus/test.rs
+++ b/satrs-example/src/pus/test.rs
@@ -2,7 +2,9 @@ use log::{info, warn};
use satrs::params::Params;
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
use satrs::pus::test::PusService17TestHandler;
-use satrs::pus::verification::{FailParams, VerificationReporterWithSender};
+use satrs::pus::verification::{
+ FailParams, VerificationReporterWithSender, VerificationReportingProvider,
+};
use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender,
MpscTmInSharedPoolSender, PusPacketHandlerResult, PusServiceHelper,
@@ -79,7 +81,7 @@ pub fn create_test_service_dynamic(
}
pub struct Service17CustomWrapper {
- pub pus17_handler: PusService17TestHandler,
+ pub pus17_handler: PusService17TestHandler,
pub test_srv_event_sender: Sender<(EventU32, Option)>,
}
@@ -125,15 +127,13 @@ impl Service17CustomWrapper Service17CustomWrapper),
+}
+
+impl Display for GenericSendError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ match self {
+ GenericSendError::RxDisconnected => {
+ write!(f, "rx side has disconnected")
+ }
+ GenericSendError::QueueFull(max_cap) => {
+ write!(f, "queue with max capacity of {max_cap:?} is full")
+ }
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl Error for GenericSendError {}
+
+/// Generic error type for sending something via a message queue.
+#[derive(Debug, Copy, Clone)]
+pub enum GenericRecvError {
+ Empty,
+ TxDisconnected,
+}
+
+impl Display for GenericRecvError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Self::TxDisconnected => {
+ write!(f, "tx side has disconnected")
+ }
+ Self::Empty => {
+ write!(f, "nothing to receive")
+ }
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl Error for GenericRecvError {}
diff --git a/satrs-example/src/requests.rs b/satrs-example/src/requests.rs
index 1d42b55..6703d93 100644
--- a/satrs-example/src/requests.rs
+++ b/satrs-example/src/requests.rs
@@ -1,15 +1,16 @@
+use std::collections::HashMap;
+use std::sync::mpsc;
+
use derive_new::new;
+use satrs::action::ActionRequest;
use satrs::hk::HkRequest;
use satrs::mode::ModeRequest;
+use satrs::pus::action::PusActionRequestRouter;
+use satrs::pus::hk::PusHkRequestRouter;
use satrs::pus::verification::{TcStateAccepted, VerificationToken};
-use satrs_example::TargetIdWithApid;
-
-#[allow(dead_code)]
-#[derive(Clone, Eq, PartialEq, Debug)]
-pub enum ActionRequest {
- CmdWithU32Id((u32, Vec)),
- CmdWithStringId((String, Vec)),
-}
+use satrs::pus::GenericRoutingError;
+use satrs::queue::GenericSendError;
+use satrs::TargetId;
#[allow(dead_code)]
#[derive(Clone, Eq, PartialEq, Debug)]
@@ -22,7 +23,7 @@ pub enum Request {
#[derive(Clone, Eq, PartialEq, Debug, new)]
pub struct TargetedRequest {
- pub(crate) target_id_with_apid: TargetIdWithApid,
+ pub(crate) target_id: TargetId,
pub(crate) request: Request,
}
@@ -34,7 +35,7 @@ pub struct RequestWithToken {
impl RequestWithToken {
pub fn new(
- target_id: TargetIdWithApid,
+ target_id: TargetId,
request: Request,
token: VerificationToken,
) -> Self {
@@ -44,3 +45,50 @@ impl RequestWithToken {
}
}
}
+
+#[derive(Default, Clone)]
+pub struct GenericRequestRouter(pub HashMap>);
+
+impl PusHkRequestRouter for GenericRequestRouter {
+ type Error = GenericRoutingError;
+
+ fn route(
+ &self,
+ target_id: TargetId,
+ hk_request: HkRequest,
+ token: VerificationToken,
+ ) -> Result<(), Self::Error> {
+ if let Some(sender) = self.0.get(&target_id) {
+ sender
+ .send(RequestWithToken::new(
+ target_id,
+ Request::Hk(hk_request),
+ token,
+ ))
+ .map_err(|_| GenericRoutingError::SendError(GenericSendError::RxDisconnected))?;
+ }
+ Ok(())
+ }
+}
+
+impl PusActionRequestRouter for GenericRequestRouter {
+ type Error = GenericRoutingError;
+
+ fn route(
+ &self,
+ target_id: TargetId,
+ action_request: ActionRequest,
+ token: VerificationToken,
+ ) -> Result<(), Self::Error> {
+ if let Some(sender) = self.0.get(&target_id) {
+ sender
+ .send(RequestWithToken::new(
+ target_id,
+ Request::Action(action_request),
+ token,
+ ))
+ .map_err(|_| GenericRoutingError::SendError(GenericSendError::RxDisconnected))?;
+ }
+ Ok(())
+ }
+}
diff --git a/satrs-mib/CHANGELOG.md b/satrs-mib/CHANGELOG.md
index 3afde6b..2b03e74 100644
--- a/satrs-mib/CHANGELOG.md
+++ b/satrs-mib/CHANGELOG.md
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
+# [v0.1.1] 2024-02-17
+
+- Bumped `spacepackets` to v0.10.0
+
# [v0.1.0] 2024-02-12
Initial release containing the `resultcode` macro.
diff --git a/satrs-mib/Cargo.toml b/satrs-mib/Cargo.toml
index e701fc8..eeca52f 100644
--- a/satrs-mib/Cargo.toml
+++ b/satrs-mib/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "satrs-mib"
-version = "0.1.0"
+version = "0.1.1"
edition = "2021"
rust-version = "1.61"
authors = ["Robin Mueller "]
@@ -23,12 +23,12 @@ version = "1"
optional = true
[dependencies.satrs-shared]
-version = "0.1.1"
+version = "0.1.2"
features = ["serde"]
[dependencies.satrs-mib-codegen]
path = "codegen"
-version = "0.1.0"
+version = "0.1.1"
[dependencies.serde]
version = "1"
diff --git a/satrs-mib/codegen/Cargo.toml b/satrs-mib/codegen/Cargo.toml
index b3adaf2..0d9522e 100644
--- a/satrs-mib/codegen/Cargo.toml
+++ b/satrs-mib/codegen/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "satrs-mib-codegen"
-version = "0.1.0"
+version = "0.1.1"
edition = "2021"
description = "satrs-mib proc macro implementation"
homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
@@ -26,9 +26,7 @@ features = ["full"]
[dev-dependencies]
trybuild = { version = "1", features = ["diff"] }
+satrs-shared = "0.1.2"
[dev-dependencies.satrs-mib]
path = ".."
-
-[dev-dependencies.satrs-shared]
-version = "0.1.1"
diff --git a/satrs-mib/codegen/src/lib.rs b/satrs-mib/codegen/src/lib.rs
index 118f59a..f4e7a48 100644
--- a/satrs-mib/codegen/src/lib.rs
+++ b/satrs-mib/codegen/src/lib.rs
@@ -3,10 +3,12 @@ use syn::{parse_macro_input, ItemConst, LitStr};
/// This macro can be used to automatically generate introspection information for return codes.
///
-/// For example, it can be applied to types like the [satrs_shared::res_code::ResultU16] type
-/// to automatically generate [satrs_mib::res_code::ResultU16Info] instances. These instances
-/// can then be used for tasks like generating CSVs or YAML files with the list of all result
-/// codes. This information is valuable for both operators and developers.
+/// For example, it can be applied to types like the
+/// [`satrs_mib::res_code::ResultU16`](https://docs.rs/satrs-mib/latest/satrs_mib/res_code/struct.ResultU16.html#) type
+/// to automatically generate
+/// [`satrs_mib::res_code::ResultU16Info`](https://docs.rs/satrs-mib/latest/satrs_mib/res_code/struct.ResultU16Info.html)
+/// instances. These instances can then be used for tasks like generating CSVs or YAML files with
+/// the list of all result codes. This information is valuable for both operators and developers.
#[proc_macro_attribute]
pub fn resultcode(
args: proc_macro::TokenStream,
diff --git a/satrs-shared/CHANGELOG.md b/satrs-shared/CHANGELOG.md
index 0392a1c..b1656ea 100644
--- a/satrs-shared/CHANGELOG.md
+++ b/satrs-shared/CHANGELOG.md
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
+# [v0.1.2] 2024-02-17
+
+- Bumped `spacepackets` to v0.10.0 for `UnsignedEnum` trait change.
+
# [v0.1.1] 2024-02-12
- Added missing `#![no_std]` attribute for library
diff --git a/satrs-shared/Cargo.toml b/satrs-shared/Cargo.toml
index e1eb77b..e2998e9 100644
--- a/satrs-shared/Cargo.toml
+++ b/satrs-shared/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "satrs-shared"
description = "Components shared by multiple sat-rs crates"
-version = "0.1.1"
+version = "0.1.2"
edition = "2021"
authors = ["Robin Mueller "]
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
@@ -18,7 +18,7 @@ default-features = false
optional = true
[dependencies.spacepackets]
-version = "0.9"
+version = "0.10"
default-features = false
[features]
diff --git a/satrs-shared/src/res_code.rs b/satrs-shared/src/res_code.rs
index 0603adf..e7816f3 100644
--- a/satrs-shared/src/res_code.rs
+++ b/satrs-shared/src/res_code.rs
@@ -52,6 +52,10 @@ impl UnsignedEnum for ResultU16 {
buf[1] = self.unique_id;
Ok(self.size())
}
+
+ fn value(&self) -> u64 {
+ self.raw() as u64
+ }
}
impl EcssEnumeration for ResultU16 {
diff --git a/satrs/CHANGELOG.md b/satrs/CHANGELOG.md
index cc788fa..156e328 100644
--- a/satrs/CHANGELOG.md
+++ b/satrs/CHANGELOG.md
@@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
+# [v0.2.0]
+
+## Added
+
+- New PUS service abstractions for HK (PUS 3) and actions (PUS 8). Introducing new abstractions
+ allows to move some boilerplate code into the framework.
+- New `VerificationReportingProvider` abstraction to avoid relying on a concrete verification
+ reporting provider.
+
+## Changed
+
+- Verification reporter API timestamp arguments are not `Option`al anymore. Empty timestamps
+ can be passed by simply specifying the `&[]` empty slice argument.
+
# [v0.1.1] 2024-02-12
- Minor fixes for crate config `homepage` entries and links in documentation.
diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml
index b7501cc..021c593 100644
--- a/satrs/Cargo.toml
+++ b/satrs/Cargo.toml
@@ -17,7 +17,7 @@ delegate = ">0.7, <=0.10"
paste = "1"
smallvec = "1"
crc = "3"
-satrs-shared = "0.1.1"
+satrs-shared = "0.1.2"
[dependencies.num_enum]
version = ">0.5, <=0.7"
@@ -68,7 +68,7 @@ features = ["all"]
optional = true
[dependencies.spacepackets]
-version = "0.9"
+version = "0.10"
default-features = false
[dependencies.cobs]
diff --git a/satrs/src/action.rs b/satrs/src/action.rs
new file mode 100644
index 0000000..e073fea
--- /dev/null
+++ b/satrs/src/action.rs
@@ -0,0 +1,42 @@
+use crate::{pool::StoreAddr, TargetId};
+
+pub type ActionId = u32;
+
+#[non_exhaustive]
+#[derive(Clone, Eq, PartialEq, Debug)]
+pub enum ActionRequest {
+ UnsignedIdAndStoreData {
+ action_id: ActionId,
+ data_addr: StoreAddr,
+ },
+ #[cfg(feature = "alloc")]
+ UnsignedIdAndVecData {
+ action_id: ActionId,
+ data: alloc::vec::Vec,
+ },
+ #[cfg(feature = "alloc")]
+ StringIdAndVecData {
+ action_id: alloc::string::String,
+ data: alloc::vec::Vec,
+ },
+ #[cfg(feature = "alloc")]
+ StringIdAndStoreData {
+ action_id: alloc::string::String,
+ data: StoreAddr,
+ },
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct TargetedActionRequest {
+ target: TargetId,
+ action_request: ActionRequest,
+}
+
+impl TargetedActionRequest {
+ pub fn new(target: TargetId, action_request: ActionRequest) -> Self {
+ Self {
+ target,
+ action_request,
+ }
+ }
+}
diff --git a/satrs/src/cfdp/dest.rs b/satrs/src/cfdp/dest.rs
index 1899abd..b42df3a 100644
--- a/satrs/src/cfdp/dest.rs
+++ b/satrs/src/cfdp/dest.rs
@@ -23,7 +23,7 @@ use spacepackets::{
tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, GenericTlv, TlvType},
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
},
- util::UnsignedByteField,
+ util::{UnsignedByteField, UnsignedEnum},
};
use thiserror::Error;
diff --git a/satrs/src/cfdp/mod.rs b/satrs/src/cfdp/mod.rs
index c3bda16..8c88fda 100644
--- a/satrs/src/cfdp/mod.rs
+++ b/satrs/src/cfdp/mod.rs
@@ -9,7 +9,7 @@ use spacepackets::{
pdu::{FileDirectiveType, PduError, PduHeader},
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
},
- util::UnsignedByteField,
+ util::{UnsignedByteField, UnsignedEnum},
};
#[cfg(feature = "alloc")]
diff --git a/satrs/src/events.rs b/satrs/src/events.rs
index 2b23815..2732726 100644
--- a/satrs/src/events.rs
+++ b/satrs/src/events.rs
@@ -412,6 +412,10 @@ impl UnsignedEnum for EventU32 {
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result {
self.base.write_to_bytes(self.raw(), buf, self.size())
}
+
+ fn value(&self) -> u64 {
+ self.raw().into()
+ }
}
impl EcssEnumeration for EventU32 {
@@ -425,6 +429,7 @@ impl UnsignedEnum for EventU32TypedSev {
delegate!(to self.event {
fn size(&self) -> usize;
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result;
+ fn value(&self) -> u64;
});
}
@@ -560,6 +565,10 @@ impl UnsignedEnum for EventU16 {
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result {
self.base.write_to_bytes(self.raw(), buf, self.size())
}
+
+ fn value(&self) -> u64 {
+ self.raw().into()
+ }
}
impl EcssEnumeration for EventU16 {
#[inline]
@@ -573,6 +582,7 @@ impl UnsignedEnum for EventU16TypedSev {
delegate!(to self.event {
fn size(&self) -> usize;
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result;
+ fn value(&self) -> u64;
});
}
diff --git a/satrs/src/hk.rs b/satrs/src/hk.rs
index dc6c72e..8033e15 100644
--- a/satrs/src/hk.rs
+++ b/satrs/src/hk.rs
@@ -1,3 +1,8 @@
+use crate::{
+ pus::verification::{TcStateAccepted, VerificationToken},
+ TargetId,
+};
+
pub type CollectionIntervalFactor = u32;
pub type UniqueId = u32;
@@ -11,6 +16,25 @@ pub enum HkRequest {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TargetedHkRequest {
- target: u32,
- hk_request: HkRequest,
+ pub target_id: TargetId,
+ pub hk_request: HkRequest,
+}
+
+impl TargetedHkRequest {
+ pub fn new(target_id: TargetId, hk_request: HkRequest) -> Self {
+ Self {
+ target_id,
+ hk_request,
+ }
+ }
+}
+
+pub trait PusHkRequestRouter {
+ type Error;
+ fn route(
+ &self,
+ target_id: TargetId,
+ hk_request: HkRequest,
+ token: VerificationToken,
+ ) -> Result<(), Self::Error>;
}
diff --git a/satrs/src/lib.rs b/satrs/src/lib.rs
index 8babd6c..c31df8d 100644
--- a/satrs/src/lib.rs
+++ b/satrs/src/lib.rs
@@ -34,19 +34,25 @@ pub mod events;
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod executable;
pub mod hal;
-pub mod hk;
-pub mod mode;
pub mod objects;
-pub mod params;
pub mod pool;
pub mod power;
pub mod pus;
+pub mod queue;
pub mod request;
pub mod res_code;
pub mod seq_count;
pub mod tmtc;
+pub mod action;
+pub mod hk;
+pub mod mode;
+pub mod params;
+
pub use spacepackets;
-// Generic channel ID type.
+/// Generic channel ID type.
pub type ChannelId = u32;
+
+/// Generic target ID type.
+pub type TargetId = u64;
diff --git a/satrs/src/mode.rs b/satrs/src/mode.rs
index cdd8f8d..c5968b4 100644
--- a/satrs/src/mode.rs
+++ b/satrs/src/mode.rs
@@ -1,9 +1,10 @@
-use crate::tmtc::TargetId;
use core::mem::size_of;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use spacepackets::ByteConversionError;
+use crate::TargetId;
+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModeAndSubmode {
@@ -47,12 +48,12 @@ impl ModeAndSubmode {
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-pub struct ModeCommand {
+pub struct TargetedModeCommand {
pub address: TargetId,
pub mode_submode: ModeAndSubmode,
}
-impl ModeCommand {
+impl TargetedModeCommand {
pub const fn new(address: TargetId, mode_submode: ModeAndSubmode) -> Self {
Self {
address,
diff --git a/satrs/src/objects.rs b/satrs/src/objects.rs
index f16b567..a9b6881 100644
--- a/satrs/src/objects.rs
+++ b/satrs/src/objects.rs
@@ -51,7 +51,6 @@
//! assert_eq!(example_obj.id, obj_id);
//! assert_eq!(example_obj.dummy, 42);
//! ```
-use crate::tmtc::TargetId;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
@@ -63,6 +62,8 @@ use hashbrown::HashMap;
#[cfg(feature = "std")]
use std::error::Error;
+use crate::TargetId;
+
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct ObjectId {
pub id: TargetId,
diff --git a/satrs/src/pus/action.rs b/satrs/src/pus/action.rs
new file mode 100644
index 0000000..cd6de15
--- /dev/null
+++ b/satrs/src/pus/action.rs
@@ -0,0 +1,385 @@
+use crate::{action::ActionRequest, TargetId};
+
+use super::verification::{TcStateAccepted, VerificationToken};
+
+#[cfg(feature = "std")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
+pub use std_mod::*;
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
+pub use alloc_mod::*;
+
+/// This trait is an abstraction for the routing of PUS service 8 action requests to a dedicated
+/// recipient using the generic [TargetId].
+pub trait PusActionRequestRouter {
+ type Error;
+ fn route(
+ &self,
+ target_id: TargetId,
+ hk_request: ActionRequest,
+ token: VerificationToken,
+ ) -> Result<(), Self::Error>;
+}
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
+pub mod alloc_mod {
+ use spacepackets::ecss::tc::PusTcReader;
+
+ use crate::pus::verification::VerificationReportingProvider;
+
+ use super::*;
+
+ /// This trait is an abstraction for the conversion of a PUS service 8 action telecommand into
+ /// an [ActionRequest].
+ ///
+ /// Having a dedicated trait for this allows maximum flexiblity and tailoring of the standard.
+ /// The only requirement is that a valid [TargetId] and an [ActionRequest] are returned by the
+ /// core conversion function.
+ ///
+ /// The user should take care of performing the error handling as well. Some of the following
+ /// aspects might be relevant:
+ ///
+ /// - Checking the validity of the APID, service ID, subservice ID.
+ /// - Checking the validity of the user data.
+ ///
+ /// A [VerificationReporterWithSender] instance is passed to the user to also allow handling
+ /// of the verification process as part of the PUS standard requirements.
+ pub trait PusActionToRequestConverter {
+ type Error;
+ fn convert(
+ &mut self,
+ token: VerificationToken,
+ tc: &PusTcReader,
+ time_stamp: &[u8],
+ verif_reporter: &impl VerificationReportingProvider,
+ ) -> Result<(TargetId, ActionRequest), Self::Error>;
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
+pub mod std_mod {
+ use crate::pus::{
+ verification::VerificationReportingProvider, EcssTcInMemConverter, GenericRoutingError,
+ PusPacketHandlerResult, PusPacketHandlingError, PusRoutingErrorHandler, PusServiceBase,
+ PusServiceHelper,
+ };
+
+ use super::*;
+
+ /// This is a high-level handler for the PUS service 8 action service.
+ ///
+ /// It performs the following handling steps:
+ ///
+ /// 1. Retrieve the next TC packet from the [PusServiceHelper]. The [EcssTcInMemConverter]
+ /// allows to configure the used telecommand memory backend.
+ /// 2. Convert the TC to a targeted action request using the provided
+ /// [PusActionToRequestConverter]. The generic error type is constrained to the
+ /// [PusPacketHandlingError] for the concrete implementation which offers a packet handler.
+ /// 3. Route the action request using the provided [PusActionRequestRouter].
+ /// 4. Handle all routing errors using the provided [PusRoutingErrorHandler].
+ pub struct PusService8ActionHandler<
+ TcInMemConverter: EcssTcInMemConverter,
+ VerificationReporter: VerificationReportingProvider,
+ RequestConverter: PusActionToRequestConverter,
+ RequestRouter: PusActionRequestRouter,
+ RoutingErrorHandler: PusRoutingErrorHandler,
+ RoutingError = GenericRoutingError,
+ > {
+ service_helper: PusServiceHelper,
+ pub request_converter: RequestConverter,
+ pub request_router: RequestRouter,
+ pub routing_error_handler: RoutingErrorHandler,
+ }
+
+ impl<
+ TcInMemConverter: EcssTcInMemConverter,
+ VerificationReporter: VerificationReportingProvider,
+ RequestConverter: PusActionToRequestConverter,
+ RequestRouter: PusActionRequestRouter,
+ RoutingErrorHandler: PusRoutingErrorHandler,
+ RoutingError: Clone,
+ >
+ PusService8ActionHandler<
+ TcInMemConverter,
+ VerificationReporter,
+ RequestConverter,
+ RequestRouter,
+ RoutingErrorHandler,
+ RoutingError,
+ >
+ where
+ PusPacketHandlingError: From,
+ {
+ pub fn new(
+ service_helper: PusServiceHelper,
+ request_converter: RequestConverter,
+ request_router: RequestRouter,
+ routing_error_handler: RoutingErrorHandler,
+ ) -> Self {
+ Self {
+ service_helper,
+ request_converter,
+ request_router,
+ routing_error_handler,
+ }
+ }
+
+ /// Core function to poll the next TC packet and try to handle it.
+ pub fn handle_one_tc(&mut self) -> Result {
+ let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
+ if possible_packet.is_none() {
+ return Ok(PusPacketHandlerResult::Empty);
+ }
+ let ecss_tc_and_token = possible_packet.unwrap();
+ let tc = self
+ .service_helper
+ .tc_in_mem_converter
+ .convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
+ let mut partial_error = None;
+ let time_stamp =
+ PusServiceBase::::get_current_cds_short_timestamp(
+ &mut partial_error,
+ );
+ let (target_id, action_request) = self.request_converter.convert(
+ ecss_tc_and_token.token,
+ &tc,
+ &time_stamp,
+ &self.service_helper.common.verification_handler,
+ )?;
+ if let Err(e) =
+ self.request_router
+ .route(target_id, action_request, ecss_tc_and_token.token)
+ {
+ self.routing_error_handler.handle_error(
+ target_id,
+ ecss_tc_and_token.token,
+ &tc,
+ e.clone(),
+ &time_stamp,
+ &self.service_helper.common.verification_handler,
+ );
+ return Err(e.into());
+ }
+ Ok(PusPacketHandlerResult::RequestHandled)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use delegate::delegate;
+
+ use spacepackets::{
+ ecss::{
+ tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader},
+ tm::PusTmReader,
+ PusPacket,
+ },
+ CcsdsPacket, SequenceFlags, SpHeader,
+ };
+
+ use crate::pus::{
+ tests::{
+ PusServiceHandlerWithVecCommon, PusTestHarness, SimplePusPacketHandler, TestConverter,
+ TestRouter, TestRoutingErrorHandler, APP_DATA_TOO_SHORT, TEST_APID,
+ },
+ verification::{
+ tests::TestVerificationReporter, FailParams, RequestId, VerificationReportingProvider,
+ },
+ EcssTcInVecConverter, GenericRoutingError, PusPacketHandlerResult, PusPacketHandlingError,
+ };
+
+ use super::*;
+
+ impl PusActionRequestRouter for TestRouter {
+ type Error = GenericRoutingError;
+
+ fn route(
+ &self,
+ target_id: TargetId,
+ hk_request: ActionRequest,
+ _token: VerificationToken,
+ ) -> Result<(), Self::Error> {
+ self.routing_requests
+ .borrow_mut()
+ .push_back((target_id, hk_request));
+ self.check_for_injected_error()
+ }
+ }
+
+ impl PusActionToRequestConverter for TestConverter<8> {
+ type Error = PusPacketHandlingError;
+ fn convert(
+ &mut self,
+ token: VerificationToken,
+ tc: &PusTcReader,
+ time_stamp: &[u8],
+ verif_reporter: &impl VerificationReportingProvider,
+ ) -> Result<(TargetId, ActionRequest), Self::Error> {
+ self.conversion_request.push_back(tc.raw_data().to_vec());
+ self.check_service(tc)?;
+ let target_id = tc.apid();
+ if tc.user_data().len() < 4 {
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new(
+ time_stamp,
+ &APP_DATA_TOO_SHORT,
+ (tc.user_data().len() as u32).to_be_bytes().as_ref(),
+ ),
+ )
+ .expect("start success failure");
+ return Err(PusPacketHandlingError::NotEnoughAppData {
+ expected: 4,
+ found: tc.user_data().len(),
+ });
+ }
+ if tc.subservice() == 1 {
+ verif_reporter
+ .start_success(token, time_stamp)
+ .expect("start success failure");
+ return Ok((
+ target_id.into(),
+ ActionRequest::UnsignedIdAndVecData {
+ action_id: u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap()),
+ data: tc.user_data()[4..].to_vec(),
+ },
+ ));
+ }
+ Err(PusPacketHandlingError::InvalidAppData(
+ "unexpected app data".into(),
+ ))
+ }
+ }
+
+ struct Pus8HandlerWithVecTester {
+ common: PusServiceHandlerWithVecCommon,
+ handler: PusService8ActionHandler<
+ EcssTcInVecConverter,
+ TestVerificationReporter,
+ TestConverter<8>,
+ TestRouter,
+ TestRoutingErrorHandler,
+ >,
+ }
+
+ impl Pus8HandlerWithVecTester {
+ pub fn new() -> Self {
+ let (common, srv_handler) =
+ PusServiceHandlerWithVecCommon::new_with_test_verif_sender();
+ Self {
+ common,
+ handler: PusService8ActionHandler::new(
+ srv_handler,
+ TestConverter::default(),
+ TestRouter::default(),
+ TestRoutingErrorHandler::default(),
+ ),
+ }
+ }
+
+ delegate! {
+ to self.handler.request_converter {
+ pub fn check_next_conversion(&mut self, tc: &PusTcCreator);
+ }
+ }
+ delegate! {
+ to self.handler.request_router {
+ pub fn retrieve_next_request(&mut self) -> (TargetId, ActionRequest);
+ }
+ }
+ delegate! {
+ to self.handler.routing_error_handler {
+ pub fn retrieve_next_error(&mut self) -> (TargetId, GenericRoutingError);
+ }
+ }
+ }
+
+ impl PusTestHarness for Pus8HandlerWithVecTester {
+ delegate! {
+ to self.common {
+ fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken;
+ fn read_next_tm(&mut self) -> PusTmReader<'_>;
+ fn check_no_tm_available(&self) -> bool;
+ fn check_next_verification_tm(
+ &self,
+ subservice: u8,
+ expected_request_id: RequestId,
+ );
+ }
+ }
+ }
+ impl SimplePusPacketHandler for Pus8HandlerWithVecTester {
+ delegate! {
+ to self.handler {
+ fn handle_one_tc(&mut self) -> Result;
+ }
+ }
+ }
+
+ #[test]
+ fn basic_test() {
+ let mut action_handler = Pus8HandlerWithVecTester::new();
+ let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
+ let sec_header = PusTcSecondaryHeader::new_simple(8, 1);
+ let action_id: u32 = 1;
+ let action_id_raw = action_id.to_be_bytes();
+ let tc = PusTcCreator::new(&mut sp_header, sec_header, action_id_raw.as_ref(), true);
+ action_handler.send_tc(&tc);
+ let result = action_handler.handle_one_tc();
+ assert!(result.is_ok());
+ action_handler.check_next_conversion(&tc);
+ let (target_id, action_req) = action_handler.retrieve_next_request();
+ assert_eq!(target_id, TEST_APID.into());
+ if let ActionRequest::UnsignedIdAndVecData { action_id, data } = action_req {
+ assert_eq!(action_id, 1);
+ assert_eq!(data, &[]);
+ }
+ }
+
+ #[test]
+ fn test_routing_error() {
+ let mut action_handler = Pus8HandlerWithVecTester::new();
+ let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
+ let sec_header = PusTcSecondaryHeader::new_simple(8, 1);
+ let action_id: u32 = 1;
+ let action_id_raw = action_id.to_be_bytes();
+ let tc = PusTcCreator::new(&mut sp_header, sec_header, action_id_raw.as_ref(), true);
+ let error = GenericRoutingError::UnknownTargetId(25);
+ action_handler
+ .handler
+ .request_router
+ .inject_routing_error(error);
+ action_handler.send_tc(&tc);
+ let result = action_handler.handle_one_tc();
+ assert!(result.is_err());
+ let check_error = |routing_error: GenericRoutingError| {
+ if let GenericRoutingError::UnknownTargetId(id) = routing_error {
+ assert_eq!(id, 25);
+ } else {
+ panic!("unexpected error type");
+ }
+ };
+ if let PusPacketHandlingError::RequestRoutingError(routing_error) = result.unwrap_err() {
+ check_error(routing_error);
+ } else {
+ panic!("unexpected error type");
+ }
+
+ action_handler.check_next_conversion(&tc);
+ let (target_id, action_req) = action_handler.retrieve_next_request();
+ assert_eq!(target_id, TEST_APID.into());
+ if let ActionRequest::UnsignedIdAndVecData { action_id, data } = action_req {
+ assert_eq!(action_id, 1);
+ assert_eq!(data, &[]);
+ }
+
+ let (target_id, found_error) = action_handler.retrieve_next_error();
+ assert_eq!(target_id, TEST_APID.into());
+ check_error(found_error);
+ }
+}
diff --git a/satrs/src/pus/event_srv.rs b/satrs/src/pus/event_srv.rs
index 49a080c..b55a5c9 100644
--- a/satrs/src/pus/event_srv.rs
+++ b/satrs/src/pus/event_srv.rs
@@ -6,16 +6,24 @@ use spacepackets::ecss::event::Subservice;
use spacepackets::ecss::PusPacket;
use std::sync::mpsc::Sender;
+use super::verification::VerificationReportingProvider;
use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper};
-pub struct PusService5EventHandler {
- pub service_helper: PusServiceHelper,
+pub struct PusService5EventHandler<
+ TcInMemConverter: EcssTcInMemConverter,
+ VerificationReporter: VerificationReportingProvider,
+> {
+ pub service_helper: PusServiceHelper,
event_request_tx: Sender,
}
-impl PusService5EventHandler {
+impl<
+ TcInMemConverter: EcssTcInMemConverter,
+ VerificationReporter: VerificationReportingProvider,
+ > PusService5EventHandler
+{
pub fn new(
- service_handler: PusServiceHelper,
+ service_handler: PusServiceHelper,
event_request_tx: Sender,
) -> Self {
Self {
@@ -44,9 +52,10 @@ impl PusService5EventHandler PusService5EventHandler PusService5EventHandler::get_current_cds_short_timestamp(
+ &mut partial_error,
+ );
match srv.unwrap() {
Subservice::TmInfoReport
| Subservice::TmLowSeverityReport
@@ -128,7 +138,7 @@ mod tests {
use crate::pus::event_man::EventRequest;
use crate::pus::tests::SimplePusPacketHandler;
- use crate::pus::verification::RequestId;
+ use crate::pus::verification::{RequestId, VerificationReporterWithSender};
use crate::{
events::EventU32,
pus::{
@@ -145,7 +155,8 @@ mod tests {
struct Pus5HandlerWithStoreTester {
common: PusServiceHandlerWithSharedStoreCommon,
- handler: PusService5EventHandler,
+ handler:
+ PusService5EventHandler,
}
impl Pus5HandlerWithStoreTester {
@@ -271,8 +282,9 @@ mod tests {
let result = test_harness.handle_one_tc();
assert!(result.is_err());
let result = result.unwrap_err();
- if let PusPacketHandlingError::NotEnoughAppData(string) = result {
- assert_eq!(string, "at least 4 bytes event ID expected");
+ if let PusPacketHandlingError::NotEnoughAppData { expected, found } = result {
+ assert_eq!(expected, 4);
+ assert_eq!(found, 3);
} else {
panic!("unexpected result type {result:?}")
}
diff --git a/satrs/src/pus/hk.rs b/satrs/src/pus/hk.rs
index 50dbc86..a2a5354 100644
--- a/satrs/src/pus/hk.rs
+++ b/satrs/src/pus/hk.rs
@@ -1 +1,394 @@
pub use spacepackets::ecss::hk::*;
+
+#[cfg(feature = "std")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
+pub use std_mod::*;
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
+pub use alloc_mod::*;
+
+use crate::{hk::HkRequest, TargetId};
+
+use super::verification::{TcStateAccepted, VerificationToken};
+
+/// This trait is an abstraction for the routing of PUS service 3 housekeeping requests to a
+/// dedicated recipient using the generic [TargetId].
+pub trait PusHkRequestRouter {
+ type Error;
+ fn route(
+ &self,
+ target_id: TargetId,
+ hk_request: HkRequest,
+ token: VerificationToken,
+ ) -> Result<(), Self::Error>;
+}
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
+pub mod alloc_mod {
+ use spacepackets::ecss::tc::PusTcReader;
+
+ use crate::pus::verification::VerificationReportingProvider;
+
+ use super::*;
+
+ /// This trait is an abstraction for the conversion of a PUS service 8 action telecommand into
+ /// a [HkRequest].
+ ///
+ /// Having a dedicated trait for this allows maximum flexiblity and tailoring of the standard.
+ /// The only requirement is that a valid [TargetId] and a [HkRequest] are returned by the
+ /// core conversion function.
+ ///
+ /// The user should take care of performing the error handling as well. Some of the following
+ /// aspects might be relevant:
+ ///
+ /// - Checking the validity of the APID, service ID, subservice ID.
+ /// - Checking the validity of the user data.
+ ///
+ /// A [VerificationReporterWithSender] instance is passed to the user to also allow handling
+ /// of the verification process as part of the PUS standard requirements.
+ pub trait PusHkToRequestConverter {
+ type Error;
+ fn convert(
+ &mut self,
+ token: VerificationToken,
+ tc: &PusTcReader,
+ time_stamp: &[u8],
+ verif_reporter: &impl VerificationReportingProvider,
+ ) -> Result<(TargetId, HkRequest), Self::Error>;
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
+pub mod std_mod {
+ use crate::pus::{
+ verification::VerificationReportingProvider, EcssTcInMemConverter, GenericRoutingError,
+ PusPacketHandlerResult, PusPacketHandlingError, PusRoutingErrorHandler, PusServiceBase,
+ PusServiceHelper,
+ };
+
+ use super::*;
+
+ /// This is a generic high-level handler for the PUS service 3 housekeeping service.
+ ///
+ /// It performs the following handling steps:
+ ///
+ /// 1. Retrieve the next TC packet from the [PusServiceHelper]. The [EcssTcInMemConverter]
+ /// allows to configure the used telecommand memory backend.
+ /// 2. Convert the TC to a targeted action request using the provided
+ /// [PusActionToRequestConverter]. The generic error type is constrained to the
+ /// [PusPacketHandlerResult] for the concrete implementation which offers a packet handler.
+ /// 3. Route the action request using the provided [PusActionRequestRouter]. The generic error
+ /// type is constrained to the [GenericRoutingError] for the concrete implementation.
+ /// 4. Handle all routing errors using the provided [PusRoutingErrorHandler]. The generic error
+ /// type is constrained to the [GenericRoutingError] for the concrete implementation.
+ pub struct PusService3HkHandler<
+ TcInMemConverter: EcssTcInMemConverter,
+ VerificationReporter: VerificationReportingProvider,
+ RequestConverter: PusHkToRequestConverter,
+ RequestRouter: PusHkRequestRouter,
+ RoutingErrorHandler: PusRoutingErrorHandler,
+ RoutingError = GenericRoutingError,
+ > {
+ service_helper: PusServiceHelper,
+ pub request_converter: RequestConverter,
+ pub request_router: RequestRouter,
+ pub routing_error_handler: RoutingErrorHandler,
+ }
+
+ impl<
+ TcInMemConverter: EcssTcInMemConverter,
+ VerificationReporter: VerificationReportingProvider,
+ RequestConverter: PusHkToRequestConverter,
+ RequestRouter: PusHkRequestRouter,
+ RoutingErrorHandler: PusRoutingErrorHandler,
+ RoutingError: Clone,
+ >
+ PusService3HkHandler<
+ TcInMemConverter,
+ VerificationReporter,
+ RequestConverter,
+ RequestRouter,
+ RoutingErrorHandler,
+ RoutingError,
+ >
+ where
+ PusPacketHandlingError: From,
+ {
+ pub fn new(
+ service_helper: PusServiceHelper,
+ request_converter: RequestConverter,
+ request_router: RequestRouter,
+ routing_error_handler: RoutingErrorHandler,
+ ) -> Self {
+ Self {
+ service_helper,
+ request_converter,
+ request_router,
+ routing_error_handler,
+ }
+ }
+
+ pub fn handle_one_tc(&mut self) -> Result {
+ let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
+ if possible_packet.is_none() {
+ return Ok(PusPacketHandlerResult::Empty);
+ }
+ let ecss_tc_and_token = possible_packet.unwrap();
+ let tc = self
+ .service_helper
+ .tc_in_mem_converter
+ .convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
+ let mut partial_error = None;
+ let time_stamp =
+ PusServiceBase::::get_current_cds_short_timestamp(
+ &mut partial_error,
+ );
+ let (target_id, hk_request) = self.request_converter.convert(
+ ecss_tc_and_token.token,
+ &tc,
+ &time_stamp,
+ &self.service_helper.common.verification_handler,
+ )?;
+ if let Err(e) =
+ self.request_router
+ .route(target_id, hk_request, ecss_tc_and_token.token)
+ {
+ self.routing_error_handler.handle_error(
+ target_id,
+ ecss_tc_and_token.token,
+ &tc,
+ e.clone(),
+ &time_stamp,
+ &self.service_helper.common.verification_handler,
+ );
+ return Err(e.into());
+ }
+ Ok(PusPacketHandlerResult::RequestHandled)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use delegate::delegate;
+ use spacepackets::ecss::hk::Subservice;
+
+ use spacepackets::{
+ ecss::{
+ tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader},
+ tm::PusTmReader,
+ PusPacket,
+ },
+ CcsdsPacket, SequenceFlags, SpHeader,
+ };
+
+ use crate::{
+ hk::HkRequest,
+ pus::{
+ tests::{
+ PusServiceHandlerWithVecCommon, PusTestHarness, SimplePusPacketHandler,
+ TestConverter, TestRouter, TestRoutingErrorHandler, APP_DATA_TOO_SHORT, TEST_APID,
+ },
+ verification::{
+ tests::TestVerificationReporter, FailParams, RequestId, TcStateAccepted,
+ VerificationReportingProvider, VerificationToken,
+ },
+ EcssTcInVecConverter, GenericRoutingError, PusPacketHandlerResult,
+ PusPacketHandlingError,
+ },
+ TargetId,
+ };
+
+ use super::{PusHkRequestRouter, PusHkToRequestConverter, PusService3HkHandler};
+
+ impl PusHkRequestRouter for TestRouter {
+ type Error = GenericRoutingError;
+
+ fn route(
+ &self,
+ target_id: TargetId,
+ hk_request: HkRequest,
+ _token: VerificationToken,
+ ) -> Result<(), Self::Error> {
+ self.routing_requests
+ .borrow_mut()
+ .push_back((target_id, hk_request));
+ self.check_for_injected_error()
+ }
+ }
+
+ impl PusHkToRequestConverter for TestConverter<3> {
+ type Error = PusPacketHandlingError;
+ fn convert(
+ &mut self,
+ token: VerificationToken,
+ tc: &PusTcReader,
+ time_stamp: &[u8],
+ verif_reporter: &impl VerificationReportingProvider,
+ ) -> Result<(TargetId, HkRequest), Self::Error> {
+ self.conversion_request.push_back(tc.raw_data().to_vec());
+ self.check_service(tc)?;
+ let target_id = tc.apid();
+ if tc.user_data().len() < 4 {
+ verif_reporter
+ .start_failure(
+ token,
+ FailParams::new(
+ time_stamp,
+ &APP_DATA_TOO_SHORT,
+ (tc.user_data().len() as u32).to_be_bytes().as_ref(),
+ ),
+ )
+ .expect("start success failure");
+ return Err(PusPacketHandlingError::NotEnoughAppData {
+ expected: 4,
+ found: tc.user_data().len(),
+ });
+ }
+ if tc.subservice() == Subservice::TcGenerateOneShotHk as u8 {
+ verif_reporter
+ .start_success(token, time_stamp)
+ .expect("start success failure");
+ return Ok((
+ target_id.into(),
+ HkRequest::OneShot(u32::from_be_bytes(
+ tc.user_data()[0..4].try_into().unwrap(),
+ )),
+ ));
+ }
+ Err(PusPacketHandlingError::InvalidAppData(
+ "unexpected app data".into(),
+ ))
+ }
+ }
+
+ struct Pus3HandlerWithVecTester {
+ common: PusServiceHandlerWithVecCommon,
+ handler: PusService3HkHandler<
+ EcssTcInVecConverter,
+ TestVerificationReporter,
+ TestConverter<3>,
+ TestRouter,
+ TestRoutingErrorHandler,
+ >,
+ }
+
+ impl Pus3HandlerWithVecTester {
+ pub fn new() -> Self {
+ let (common, srv_handler) =
+ PusServiceHandlerWithVecCommon::new_with_test_verif_sender();
+ Self {
+ common,
+ handler: PusService3HkHandler::new(
+ srv_handler,
+ TestConverter::default(),
+ TestRouter::default(),
+ TestRoutingErrorHandler::default(),
+ ),
+ }
+ }
+
+ delegate! {
+ to self.handler.request_converter {
+ pub fn check_next_conversion(&mut self, tc: &PusTcCreator);
+ }
+ }
+ delegate! {
+ to self.handler.request_router {
+ pub fn retrieve_next_request(&mut self) -> (TargetId, HkRequest);
+ }
+ }
+ delegate! {
+ to self.handler.routing_error_handler {
+ pub fn retrieve_next_error(&mut self) -> (TargetId, GenericRoutingError);
+ }
+ }
+ }
+
+ impl PusTestHarness for Pus3HandlerWithVecTester {
+ delegate! {
+ to self.common {
+ fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken;
+ fn read_next_tm(&mut self) -> PusTmReader<'_>;
+ fn check_no_tm_available(&self) -> bool;
+ fn check_next_verification_tm(
+ &self,
+ subservice: u8,
+ expected_request_id: RequestId,
+ );
+ }
+ }
+ }
+ impl SimplePusPacketHandler for Pus3HandlerWithVecTester {
+ delegate! {
+ to self.handler {
+ fn handle_one_tc(&mut self) -> Result;
+ }
+ }
+ }
+
+ #[test]
+ fn basic_test() {
+ let mut hk_handler = Pus3HandlerWithVecTester::new();
+ let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
+ let sec_header = PusTcSecondaryHeader::new_simple(3, Subservice::TcGenerateOneShotHk as u8);
+ let unique_id: u32 = 1;
+ let unique_id_raw = unique_id.to_be_bytes();
+ let tc = PusTcCreator::new(&mut sp_header, sec_header, unique_id_raw.as_ref(), true);
+ hk_handler.send_tc(&tc);
+ let result = hk_handler.handle_one_tc();
+ assert!(result.is_ok());
+ hk_handler.check_next_conversion(&tc);
+ let (target_id, hk_request) = hk_handler.retrieve_next_request();
+ assert_eq!(target_id, TEST_APID.into());
+ if let HkRequest::OneShot(id) = hk_request {
+ assert_eq!(id, unique_id);
+ } else {
+ panic!("unexpected request");
+ }
+ }
+
+ #[test]
+ fn test_routing_error() {
+ let mut hk_handler = Pus3HandlerWithVecTester::new();
+ let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
+ let sec_header = PusTcSecondaryHeader::new_simple(3, Subservice::TcGenerateOneShotHk as u8);
+ let unique_id: u32 = 1;
+ let unique_id_raw = unique_id.to_be_bytes();
+ let tc = PusTcCreator::new(&mut sp_header, sec_header, unique_id_raw.as_ref(), true);
+ let error = GenericRoutingError::UnknownTargetId(25);
+ hk_handler
+ .handler
+ .request_router
+ .inject_routing_error(error);
+ hk_handler.send_tc(&tc);
+ let result = hk_handler.handle_one_tc();
+ assert!(result.is_err());
+ let check_error = |routing_error: GenericRoutingError| {
+ if let GenericRoutingError::UnknownTargetId(id) = routing_error {
+ assert_eq!(id, 25);
+ } else {
+ panic!("unexpected error type");
+ }
+ };
+ if let PusPacketHandlingError::RequestRoutingError(routing_error) = result.unwrap_err() {
+ check_error(routing_error);
+ } else {
+ panic!("unexpected error type");
+ }
+
+ hk_handler.check_next_conversion(&tc);
+ let (target_id, hk_req) = hk_handler.retrieve_next_request();
+ assert_eq!(target_id, TEST_APID.into());
+ if let HkRequest::OneShot(unique_id) = hk_req {
+ assert_eq!(unique_id, 1);
+ }
+
+ let (target_id, found_error) = hk_handler.retrieve_next_error();
+ assert_eq!(target_id, TEST_APID.into());
+ check_error(found_error);
+ }
+}
diff --git a/satrs/src/pus/mod.rs b/satrs/src/pus/mod.rs
index 9001f8b..91be068 100644
--- a/satrs/src/pus/mod.rs
+++ b/satrs/src/pus/mod.rs
@@ -2,6 +2,7 @@
//!
//! This module contains structures to make working with the PUS C standard easier.
//! The satrs-example application contains various usage examples of these components.
+use crate::queue::{GenericRecvError, GenericSendError};
use crate::ChannelId;
use core::fmt::{Display, Formatter};
#[cfg(feature = "alloc")]
@@ -16,6 +17,7 @@ use spacepackets::ecss::tm::PusTmCreator;
use spacepackets::ecss::PusError;
use spacepackets::{ByteConversionError, SpHeader};
+pub mod action;
pub mod event;
pub mod event_man;
#[cfg(feature = "std")]
@@ -55,52 +57,6 @@ impl<'tm> From> for PusTmWrapper<'tm> {
}
}
-/// Generic error type for sending something via a message queue.
-#[derive(Debug, Copy, Clone)]
-pub enum GenericSendError {
- RxDisconnected,
- QueueFull(Option),
-}
-
-impl Display for GenericSendError {
- fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
- match self {
- GenericSendError::RxDisconnected => {
- write!(f, "rx side has disconnected")
- }
- GenericSendError::QueueFull(max_cap) => {
- write!(f, "queue with max capacity of {max_cap:?} is full")
- }
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl Error for GenericSendError {}
-
-/// Generic error type for sending something via a message queue.
-#[derive(Debug, Copy, Clone)]
-pub enum GenericRecvError {
- Empty,
- TxDisconnected,
-}
-
-impl Display for GenericRecvError {
- fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
- match self {
- Self::TxDisconnected => {
- write!(f, "tx side has disconnected")
- }
- Self::Empty => {
- write!(f, "nothing to receive")
- }
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl Error for GenericRecvError {}
-
#[derive(Debug, Clone)]
pub enum EcssTmtcError {
StoreLock,
@@ -304,8 +260,12 @@ pub trait ReceivesEcssPusTc {
#[cfg(feature = "alloc")]
mod alloc_mod {
+ use crate::TargetId;
+
use super::*;
+ use crate::pus::verification::VerificationReportingProvider;
+
/// Extension trait for [EcssTmSenderCore].
///
/// It provides additional functionality, for example by implementing the [Downcast] trait
@@ -385,22 +345,33 @@ mod alloc_mod {
impl EcssTcReceiver for T where T: EcssTcReceiverCore + 'static {}
impl_downcast!(EcssTcReceiver);
+
+ pub trait PusRoutingErrorHandler {
+ type Error;
+ fn handle_error(
+ &self,
+ target_id: TargetId,
+ token: VerificationToken,
+ tc: &PusTcReader,
+ error: Self::Error,
+ time_stamp: &[u8],
+ verif_reporter: &impl VerificationReportingProvider,
+ );
+ }
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std_mod {
use crate::pool::{PoolProvider, PoolProviderWithGuards, SharedStaticMemoryPool, StoreAddr};
- use crate::pus::verification::{
- StdVerifReporterWithSender, TcStateAccepted, VerificationToken,
- };
+ use crate::pus::verification::{TcStateAccepted, VerificationToken};
use crate::pus::{
EcssChannel, EcssTcAndToken, EcssTcReceiver, EcssTcReceiverCore, EcssTmSender,
EcssTmSenderCore, EcssTmtcError, GenericRecvError, GenericSendError, PusTmWrapper,
TryRecvTmtcError,
};
use crate::tmtc::tm_helper::SharedTmPool;
- use crate::ChannelId;
+ use crate::{ChannelId, TargetId};
use alloc::boxed::Box;
use alloc::vec::Vec;
use crossbeam_channel as cb;
@@ -410,13 +381,12 @@ pub mod std_mod {
use spacepackets::time::cds::TimeProvider;
use spacepackets::time::StdTimestampError;
use spacepackets::time::TimeWriter;
- use std::cell::RefCell;
use std::string::String;
use std::sync::mpsc;
use std::sync::mpsc::TryRecvError;
use thiserror::Error;
- use super::verification::VerificationReporterWithSender;
+ use super::verification::VerificationReportingProvider;
use super::{AcceptedEcssTcAndToken, TcInMemory};
impl From> for EcssTmtcError {
@@ -662,6 +632,20 @@ pub mod std_mod {
}
}
+ // TODO: All these types could probably be no_std if we implemented error handling ourselves..
+ // but thiserror is really nice, so keep it like this for simplicity for now. Maybe thiserror
+ // will be no_std soon, see https://github.com/rust-lang/rust/issues/103765 .
+
+ #[derive(Debug, Clone, Error)]
+ pub enum GenericRoutingError {
+ #[error("not enough application data, expected at least {expected}, found {found}")]
+ NotEnoughAppData { expected: usize, found: usize },
+ #[error("Unknown target ID {0}")]
+ UnknownTargetId(TargetId),
+ #[error("Sending action request failed: {0}")]
+ SendError(GenericSendError),
+ }
+
#[derive(Debug, Clone, Error)]
pub enum PusPacketHandlingError {
#[error("generic PUS error: {0}")]
@@ -670,8 +654,8 @@ pub mod std_mod {
WrongService(u8),
#[error("invalid subservice {0}")]
InvalidSubservice(u8),
- #[error("not enough application data available: {0}")]
- NotEnoughAppData(String),
+ #[error("not enough application data, expected at least {expected}, found {found}")]
+ NotEnoughAppData { expected: usize, found: usize },
#[error("PUS packet too large, does not fit in buffer: {0}")]
PusPacketTooLarge(usize),
#[error("invalid application data")]
@@ -682,6 +666,8 @@ pub mod std_mod {
EcssTmtc(#[from] EcssTmtcError),
#[error("invalid verification token")]
InvalidVerificationToken,
+ #[error("request routing error: {0}")]
+ RequestRoutingError(#[from] GenericRoutingError),
#[error("other error {0}")]
Other(String),
}
@@ -825,18 +811,18 @@ pub mod std_mod {
}
}
- pub struct PusServiceBase {
+ pub struct PusServiceBase {
pub tc_receiver: Box,
pub tm_sender: Box,
pub tm_apid: u16,
/// The verification handler is wrapped in a [RefCell] to allow the interior mutability
/// pattern. This makes writing methods which are not mutable a lot easier.
- pub verification_handler: RefCell,
+ pub verification_handler: VerificationReporter,
}
- impl PusServiceBase {
+ impl PusServiceBase {
#[cfg(feature = "std")]
- pub fn get_current_timestamp(
+ pub fn get_current_cds_short_timestamp(
partial_error: &mut Option,
) -> [u8; 7] {
let mut time_stamp: [u8; 7] = [0; 7];
@@ -850,11 +836,10 @@ pub mod std_mod {
}
time_stamp
}
-
#[cfg(feature = "std")]
pub fn get_current_timestamp_ignore_error() -> [u8; 7] {
let mut dummy = None;
- Self::get_current_timestamp(&mut dummy)
+ Self::get_current_cds_short_timestamp(&mut dummy)
}
}
@@ -867,17 +852,24 @@ pub mod std_mod {
/// This base class can handle PUS telecommands backed by different memory storage machanisms
/// by using the [EcssTcInMemConverter] abstraction. This object provides some convenience
/// methods to make the generic parts of TC handling easier.
- pub struct PusServiceHelper {
- pub common: PusServiceBase,
+ pub struct PusServiceHelper<
+ TcInMemConverter: EcssTcInMemConverter,
+ VerificationReporter: VerificationReportingProvider,
+ > {
+ pub common: PusServiceBase,
pub tc_in_mem_converter: TcInMemConverter,
}
- impl PusServiceHelper {
+ impl<
+ TcInMemConverter: EcssTcInMemConverter,
+ VerificationReporter: VerificationReportingProvider,
+ > PusServiceHelper
+ {
pub fn new(
tc_receiver: Box,
tm_sender: Box,
tm_apid: u16,
- verification_handler: VerificationReporterWithSender,
+ verification_handler: VerificationReporter,
tc_in_mem_converter: TcInMemConverter,
) -> Self {
Self {
@@ -885,7 +877,7 @@ pub mod std_mod {
tc_receiver,
tm_sender,
tm_apid,
- verification_handler: RefCell::new(verification_handler),
+ verification_handler,
},
tc_in_mem_converter,
}
@@ -939,12 +931,15 @@ pub(crate) fn source_buffer_large_enough(cap: usize, len: usize) -> Result<(), E
#[cfg(test)]
pub mod tests {
+ use core::cell::RefCell;
use std::sync::mpsc::TryRecvError;
use std::sync::{mpsc, RwLock};
use alloc::boxed::Box;
- use alloc::vec;
- use spacepackets::ecss::tc::PusTcCreator;
+ use alloc::collections::VecDeque;
+ use alloc::vec::Vec;
+ use satrs_shared::res_code::ResultU16;
+ use spacepackets::ecss::tc::{PusTcCreator, PusTcReader};
use spacepackets::ecss::tm::{GenericPusTmSecondaryHeader, PusTmCreator, PusTmReader};
use spacepackets::ecss::{PusPacket, WritablePusPacket};
use spacepackets::CcsdsPacket;
@@ -954,14 +949,17 @@ pub mod tests {
};
use crate::pus::verification::RequestId;
use crate::tmtc::tm_helper::SharedTmPool;
+ use crate::TargetId;
+ use super::verification::tests::{SharedVerificationMap, TestVerificationReporter};
use super::verification::{
- TcStateAccepted, VerificationReporterCfg, VerificationReporterWithSender, VerificationToken,
+ TcStateAccepted, VerificationReporterCfg, VerificationReporterWithSender,
+ VerificationReportingProvider, VerificationToken,
};
use super::{
- EcssTcAndToken, EcssTcInSharedStoreConverter, EcssTcInVecConverter, MpscTcReceiver,
- MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
- PusPacketHandlingError, PusServiceHelper, TcInMemory,
+ EcssTcAndToken, EcssTcInSharedStoreConverter, EcssTcInVecConverter, GenericRoutingError,
+ MpscTcReceiver, MpscTmAsVecSender, MpscTmInSharedPoolSender, PusPacketHandlerResult,
+ PusPacketHandlingError, PusRoutingErrorHandler, PusServiceHelper, TcInMemory,
};
pub const TEST_APID: u16 = 0x101;
@@ -1016,8 +1014,11 @@ pub mod tests {
/// [PusServiceHandler] which might be required for a specific PUS service handler.
///
/// The PUS service handler is instantiated with a [EcssTcInStoreConverter].
- pub fn new() -> (Self, PusServiceHelper) {
- let pool_cfg = StaticPoolConfig::new(vec![(16, 16), (8, 32), (4, 64)], false);
+ pub fn new() -> (
+ Self,
+ PusServiceHelper,
+ ) {
+ let pool_cfg = StaticPoolConfig::new(alloc::vec![(16, 16), (8, 32), (4, 64)], false);
let tc_pool = StaticMemoryPool::new(pool_cfg.clone());
let tm_pool = StaticMemoryPool::new(pool_cfg);
let shared_tc_pool = SharedStaticMemoryPool::new(RwLock::new(tc_pool));
@@ -1062,7 +1063,7 @@ pub mod tests {
let token = self.verification_handler.add_tc(tc);
let token = self
.verification_handler
- .acceptance_success(token, Some(&[0; 7]))
+ .acceptance_success(token, &[0; 7])
.unwrap();
let tc_size = tc.write_to_bytes(&mut self.pus_buf).unwrap();
let mut tc_pool = self.tc_pool.write().unwrap();
@@ -1109,15 +1110,18 @@ pub mod tests {
}
}
- pub struct PusServiceHandlerWithVecCommon {
+ pub struct PusServiceHandlerWithVecCommon {
current_tm: Option>,
tc_sender: mpsc::Sender,
tm_receiver: mpsc::Receiver>,
- verification_handler: VerificationReporterWithSender,
+ pub verification_handler: VerificationReporter,
}
- impl PusServiceHandlerWithVecCommon {
- pub fn new() -> (Self, PusServiceHelper) {
+ impl PusServiceHandlerWithVecCommon {
+ pub fn new_with_standard_verif_reporter() -> (
+ Self,
+ PusServiceHelper,
+ ) {
let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel();
let (tm_tx, tm_rx) = mpsc::channel();
@@ -1125,6 +1129,7 @@ pub mod tests {
let verif_cfg = VerificationReporterCfg::new(TEST_APID, 1, 2, 8).unwrap();
let verification_handler =
VerificationReporterWithSender::new(&verif_cfg, Box::new(verif_sender));
+
let test_srv_tm_sender = MpscTmAsVecSender::new(0, "test-sender", tm_tx);
let test_srv_tc_receiver = MpscTcReceiver::new(0, "test-receiver", test_srv_tc_rx);
let in_store_converter = EcssTcInVecConverter::default();
@@ -1144,12 +1149,47 @@ pub mod tests {
),
)
}
+ }
+ impl PusServiceHandlerWithVecCommon {
+ pub fn new_with_test_verif_sender() -> (
+ Self,
+ PusServiceHelper,
+ ) {
+ let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel();
+ let (tm_tx, tm_rx) = mpsc::channel();
+
+ let test_srv_tm_sender = MpscTmAsVecSender::new(0, "test-sender", tm_tx);
+ let test_srv_tc_receiver = MpscTcReceiver::new(0, "test-receiver", test_srv_tc_rx);
+ let in_store_converter = EcssTcInVecConverter::default();
+ let shared_verif_map = SharedVerificationMap::default();
+ let verification_handler = TestVerificationReporter::new(shared_verif_map);
+ (
+ Self {
+ current_tm: None,
+ tc_sender: test_srv_tc_tx,
+ tm_receiver: tm_rx,
+ verification_handler: verification_handler.clone(),
+ },
+ PusServiceHelper::new(
+ Box::new(test_srv_tc_receiver),
+ Box::new(test_srv_tm_sender),
+ TEST_APID,
+ verification_handler,
+ in_store_converter,
+ ),
+ )
+ }
+ }
+
+ impl
+ PusServiceHandlerWithVecCommon
+ {
pub fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken {
let token = self.verification_handler.add_tc(tc);
let token = self
.verification_handler
- .acceptance_success(token, Some(&[0; 7]))
+ .acceptance_success(token, &[0; 7])
.unwrap();
// Send accepted TC to test service handler.
self.tc_sender
@@ -1191,4 +1231,106 @@ pub mod tests {
assert_eq!(req_id, expected_request_id);
}
}
+
+ pub const APP_DATA_TOO_SHORT: ResultU16 = ResultU16::new(1, 1);
+
+ #[derive(Default)]
+ pub struct TestConverter {
+ pub conversion_request: VecDeque>,
+ }
+
+ impl TestConverter {
+ pub fn check_service(&self, tc: &PusTcReader) -> Result<(), PusPacketHandlingError> {
+ if tc.service() != SERVICE {
+ return Err(PusPacketHandlingError::WrongService(tc.service()));
+ }
+ Ok(())
+ }
+
+ pub fn is_empty(&self) {
+ self.conversion_request.is_empty();
+ }
+
+ pub fn check_next_conversion(&mut self, tc: &PusTcCreator) {
+ assert!(!self.conversion_request.is_empty());
+ assert_eq!(
+ self.conversion_request.pop_front().unwrap(),
+ tc.to_vec().unwrap()
+ );
+ }
+ }
+
+ #[derive(Default)]
+ pub struct TestRoutingErrorHandler {
+ pub routing_errors: RefCell>,
+ }
+
+ impl PusRoutingErrorHandler for TestRoutingErrorHandler {
+ type Error = GenericRoutingError;
+
+ fn handle_error(
+ &self,
+ target_id: TargetId,
+ _token: VerificationToken,
+ _tc: &PusTcReader,
+ error: Self::Error,
+ _time_stamp: &[u8],
+ _verif_reporter: &impl VerificationReportingProvider,
+ ) {
+ self.routing_errors
+ .borrow_mut()
+ .push_back((target_id, error));
+ }
+ }
+
+ impl TestRoutingErrorHandler {
+ pub fn is_empty(&self) -> bool {
+ self.routing_errors.borrow().is_empty()
+ }
+
+ pub fn retrieve_next_error(&mut self) -> (TargetId, GenericRoutingError) {
+ if self.routing_errors.borrow().is_empty() {
+ panic!("no routing request available");
+ }
+ self.routing_errors.borrow_mut().pop_front().unwrap()
+ }
+ }
+
+ pub struct TestRouter {
+ pub routing_requests: RefCell>,
+ pub injected_routing_failure: RefCell