From ba03150178d9e0181242e50583dd9ae3f4d83abc Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 20 Feb 2024 14:33:21 +0100 Subject: [PATCH 1/2] Added high-level abstraction for some PUS services Introduced high-level abstractions for targetable requests in general. - PUS Service 3 (HK) abstraction for targetable HK requests - PUS Service 8 (Action) abstraction for targetable action requests --- README.md | 3 + coverage.py | 4 +- satrs-example/Cargo.toml | 8 +- satrs-example/src/acs.rs | 15 +- satrs-example/src/config.rs | 16 +- satrs-example/src/events.rs | 7 +- satrs-example/src/lib.rs | 58 ---- satrs-example/src/main.rs | 19 +- satrs-example/src/pus/action.rs | 242 ++++++-------- satrs-example/src/pus/event.rs | 2 +- satrs-example/src/pus/hk.rs | 319 +++++++++--------- satrs-example/src/pus/mod.rs | 71 +++- satrs-example/src/pus/scheduler.rs | 3 +- satrs-example/src/pus/test.rs | 17 +- satrs-example/src/queue.rs | 45 +++ satrs-example/src/requests.rs | 68 +++- satrs-mib/CHANGELOG.md | 4 + satrs-mib/Cargo.toml | 6 +- satrs-mib/codegen/Cargo.toml | 6 +- satrs-mib/codegen/src/lib.rs | 10 +- satrs-shared/CHANGELOG.md | 4 + satrs-shared/Cargo.toml | 4 +- satrs-shared/src/res_code.rs | 4 + satrs/CHANGELOG.md | 9 + satrs/Cargo.toml | 4 +- satrs/src/action.rs | 42 +++ satrs/src/cfdp/dest.rs | 2 +- satrs/src/cfdp/mod.rs | 2 +- satrs/src/events.rs | 10 + satrs/src/hk.rs | 28 +- satrs/src/lib.rs | 14 +- satrs/src/mode.rs | 7 +- satrs/src/objects.rs | 3 +- satrs/src/pus/action.rs | 385 ++++++++++++++++++++++ satrs/src/pus/event_srv.rs | 40 ++- satrs/src/pus/hk.rs | 393 ++++++++++++++++++++++ satrs/src/pus/mod.rs | 300 ++++++++++++----- satrs/src/pus/scheduler_srv.rs | 51 +-- satrs/src/pus/test.rs | 39 ++- satrs/src/pus/verification.rs | 509 +++++++++++++++++++++-------- satrs/src/queue.rs | 49 +++ satrs/src/request.rs | 109 ++++++ satrs/src/tmtc/mod.rs | 2 - satrs/tests/pus_verification.rs | 19 +- 44 files changed, 2221 insertions(+), 731 deletions(-) create mode 100644 satrs-example/src/queue.rs create mode 100644 satrs/src/action.rs create mode 100644 satrs/src/pus/action.rs create mode 100644 satrs/src/queue.rs 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..94338f0 100644 --- a/satrs/CHANGELOG.md +++ b/satrs/CHANGELOG.md @@ -8,6 +8,15 @@ 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. + # [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>, + } + + impl Default for TestRouter { + fn default() -> Self { + Self { + routing_requests: Default::default(), + injected_routing_failure: Default::default(), + } + } + } + + impl TestRouter { + pub fn check_for_injected_error(&self) -> Result<(), GenericRoutingError> { + if self.injected_routing_failure.borrow().is_some() { + return Err(self.injected_routing_failure.borrow_mut().take().unwrap()); + } + Ok(()) + } + + pub fn inject_routing_error(&mut self, error: GenericRoutingError) { + *self.injected_routing_failure.borrow_mut() = Some(error); + } + + pub fn is_empty(&self) -> bool { + self.routing_requests.borrow().is_empty() + } + + pub fn retrieve_next_request(&mut self) -> (TargetId, REQUEST) { + if self.routing_requests.borrow().is_empty() { + panic!("no routing request available"); + } + self.routing_requests.borrow_mut().pop_front().unwrap() + } + } } diff --git a/satrs/src/pus/scheduler_srv.rs b/satrs/src/pus/scheduler_srv.rs index 9913338..72e8a7b 100644 --- a/satrs/src/pus/scheduler_srv.rs +++ b/satrs/src/pus/scheduler_srv.rs @@ -1,4 +1,5 @@ use super::scheduler::PusSchedulerProvider; +use super::verification::VerificationReportingProvider; use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper}; use crate::pool::PoolProvider; use crate::pus::{PusPacketHandlerResult, PusPacketHandlingError}; @@ -16,16 +17,23 @@ use spacepackets::time::cds::TimeProvider; /// telecommands when applicable. pub struct PusService11SchedHandler< TcInMemConverter: EcssTcInMemConverter, + VerificationReporter: VerificationReportingProvider, PusScheduler: PusSchedulerProvider, > { - pub service_helper: PusServiceHelper, + pub service_helper: PusServiceHelper, scheduler: PusScheduler, } -impl - PusService11SchedHandler +impl< + TcInMemConverter: EcssTcInMemConverter, + VerificationReporter: VerificationReportingProvider, + Scheduler: PusSchedulerProvider, + > PusService11SchedHandler { - pub fn new(service_helper: PusServiceHelper, scheduler: Scheduler) -> Self { + pub fn new( + service_helper: PusServiceHelper, + scheduler: Scheduler, + ) -> Self { Self { service_helper, scheduler, @@ -62,15 +70,16 @@ impl )); } let mut partial_error = None; - let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error); + let time_stamp = PusServiceBase::::get_current_cds_short_timestamp( + &mut partial_error, + ); match standard_subservice.unwrap() { scheduling::Subservice::TcEnableScheduling => { let start_token = self .service_helper .common .verification_handler - .get_mut() - .start_success(ecss_tc_and_token.token, Some(&time_stamp)) + .start_success(ecss_tc_and_token.token, &time_stamp) .expect("Error sending start success"); self.scheduler.enable(); @@ -78,8 +87,7 @@ impl self.service_helper .common .verification_handler - .get_mut() - .completion_success(start_token, Some(&time_stamp)) + .completion_success(start_token, &time_stamp) .expect("Error sending completion success"); } else { return Err(PusPacketHandlingError::Other( @@ -92,8 +100,7 @@ impl .service_helper .common .verification_handler - .get_mut() - .start_success(ecss_tc_and_token.token, Some(&time_stamp)) + .start_success(ecss_tc_and_token.token, &time_stamp) .expect("Error sending start success"); self.scheduler.disable(); @@ -101,8 +108,7 @@ impl self.service_helper .common .verification_handler - .get_mut() - .completion_success(start_token, Some(&time_stamp)) + .completion_success(start_token, &time_stamp) .expect("Error sending completion success"); } else { return Err(PusPacketHandlingError::Other( @@ -115,8 +121,7 @@ impl .service_helper .common .verification_handler - .get_mut() - .start_success(ecss_tc_and_token.token, Some(&time_stamp)) + .start_success(ecss_tc_and_token.token, &time_stamp) .expect("Error sending start success"); self.scheduler @@ -126,8 +131,7 @@ impl self.service_helper .common .verification_handler - .get_mut() - .completion_success(start_token, Some(&time_stamp)) + .completion_success(start_token, &time_stamp) .expect("Error sending completion success"); } scheduling::Subservice::TcInsertActivity => { @@ -135,8 +139,7 @@ impl .service_helper .common .verification_handler - .get_mut() - .start_success(ecss_tc_and_token.token, Some(&time_stamp)) + .start_success(ecss_tc_and_token.token, &time_stamp) .expect("error sending start success"); // let mut pool = self.sched_tc_pool.write().expect("locking pool failed"); @@ -147,8 +150,7 @@ impl self.service_helper .common .verification_handler - .get_mut() - .completion_success(start_token, Some(&time_stamp)) + .completion_success(start_token, &time_stamp) .expect("sending completion success failed"); } _ => { @@ -172,6 +174,7 @@ impl mod tests { use crate::pool::{StaticMemoryPool, StaticPoolConfig}; use crate::pus::tests::TEST_APID; + use crate::pus::verification::VerificationReporterWithSender; use crate::pus::{ scheduler::{self, PusSchedulerProvider, TcInfo}, tests::{PusServiceHandlerWithSharedStoreCommon, PusTestHarness}, @@ -194,7 +197,11 @@ mod tests { struct Pus11HandlerWithStoreTester { common: PusServiceHandlerWithSharedStoreCommon, - handler: PusService11SchedHandler, + handler: PusService11SchedHandler< + EcssTcInSharedStoreConverter, + VerificationReporterWithSender, + TestScheduler, + >, sched_tc_pool: StaticMemoryPool, } diff --git a/satrs/src/pus/test.rs b/satrs/src/pus/test.rs index bdaed69..bfea48b 100644 --- a/satrs/src/pus/test.rs +++ b/satrs/src/pus/test.rs @@ -5,16 +5,24 @@ use spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader}; use spacepackets::ecss::PusPacket; use spacepackets::SpHeader; +use super::verification::VerificationReportingProvider; use super::{EcssTcInMemConverter, PusServiceBase, PusServiceHelper}; /// This is a helper class for [std] environments to handle generic PUS 17 (test service) packets. /// This handler only processes ping requests and generates a ping reply for them accordingly. -pub struct PusService17TestHandler { - pub service_helper: PusServiceHelper, +pub struct PusService17TestHandler< + TcInMemConverter: EcssTcInMemConverter, + VerificationReporter: VerificationReportingProvider, +> { + pub service_helper: PusServiceHelper, } -impl PusService17TestHandler { - pub fn new(service_helper: PusServiceHelper) -> Self { +impl< + TcInMemConverter: EcssTcInMemConverter, + VerificationReporter: VerificationReportingProvider, + > PusService17TestHandler +{ + pub fn new(service_helper: PusServiceHelper) -> Self { Self { service_helper } } @@ -33,13 +41,15 @@ impl PusService17TestHandler::get_current_cds_short_timestamp( + &mut partial_error, + ); let result = self .service_helper .common .verification_handler - .get_mut() - .start_success(ecss_tc_and_token.token, Some(&time_stamp)) + .start_success(ecss_tc_and_token.token, &time_stamp) .map_err(|_| PartialPusHandlingError::Verification); let start_token = if let Ok(result) = result { Some(result) @@ -67,8 +77,7 @@ impl PusService17TestHandler, + handler: + PusService17TestHandler, } impl Pus17HandlerWithStoreTester { @@ -148,13 +158,14 @@ mod tests { } struct Pus17HandlerWithVecTester { - common: PusServiceHandlerWithVecCommon, - handler: PusService17TestHandler, + common: PusServiceHandlerWithVecCommon, + handler: PusService17TestHandler, } impl Pus17HandlerWithVecTester { pub fn new() -> Self { - let (common, srv_handler) = PusServiceHandlerWithVecCommon::new(); + let (common, srv_handler) = + PusServiceHandlerWithVecCommon::new_with_standard_verif_reporter(); Self { common, handler: PusService17TestHandler::new(srv_handler), diff --git a/satrs/src/pus/verification.rs b/satrs/src/pus/verification.rs index 0cf6c24..369b46e 100644 --- a/satrs/src/pus/verification.rs +++ b/satrs/src/pus/verification.rs @@ -16,7 +16,9 @@ //! use std::sync::{Arc, mpsc, RwLock}; //! use std::time::Duration; //! use satrs::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; -//! use satrs::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender}; +//! use satrs::pus::verification::{ +//! VerificationReportingProvider, VerificationReporterCfg, VerificationReporterWithSender +//! }; //! use satrs::seq_count::SeqCountProviderSimple; //! use satrs::pus::MpscTmInSharedPoolSender; //! use satrs::tmtc::tm_helper::SharedTmPool; @@ -43,9 +45,9 @@ //! let init_token = reporter.add_tc(&pus_tc_0); //! //! // Complete success sequence for a telecommand -//! let accepted_token = reporter.acceptance_success(init_token, Some(&EMPTY_STAMP)).unwrap(); -//! let started_token = reporter.start_success(accepted_token, Some(&EMPTY_STAMP)).unwrap(); -//! reporter.completion_success(started_token, Some(&EMPTY_STAMP)).unwrap(); +//! let accepted_token = reporter.acceptance_success(init_token, &EMPTY_STAMP).unwrap(); +//! let started_token = reporter.start_success(accepted_token, &EMPTY_STAMP).unwrap(); +//! reporter.completion_success(started_token, &EMPTY_STAMP).unwrap(); //! //! // Verify it arrives correctly on receiver end //! let mut tm_buf: [u8; 1024] = [0; 1024]; @@ -279,16 +281,16 @@ impl VerificationToken { /// Composite helper struct to pass failure parameters to the [VerificationReporter] pub struct FailParams<'stamp, 'fargs> { - time_stamp: Option<&'stamp [u8]>, + time_stamp: &'stamp [u8], failure_code: &'fargs dyn EcssEnumeration, - failure_data: Option<&'fargs [u8]>, + failure_data: &'fargs [u8], } impl<'stamp, 'fargs> FailParams<'stamp, 'fargs> { pub fn new( - time_stamp: Option<&'stamp [u8]>, + time_stamp: &'stamp [u8], failure_code: &'fargs impl EcssEnumeration, - failure_data: Option<&'fargs [u8]>, + failure_data: &'fargs [u8], ) -> Self { Self { time_stamp, @@ -296,6 +298,13 @@ impl<'stamp, 'fargs> FailParams<'stamp, 'fargs> { failure_data, } } + + pub fn new_no_fail_data( + time_stamp: &'stamp [u8], + failure_code: &'fargs impl EcssEnumeration, + ) -> Self { + Self::new(time_stamp, failure_code, &[]) + } } /// Composite helper struct to pass step failure parameters to the [VerificationReporter] @@ -306,10 +315,10 @@ pub struct FailParamsWithStep<'stamp, 'fargs> { impl<'stamp, 'fargs> FailParamsWithStep<'stamp, 'fargs> { pub fn new( - time_stamp: Option<&'stamp [u8]>, + time_stamp: &'stamp [u8], step: &'fargs impl EcssEnumeration, failure_code: &'fargs impl EcssEnumeration, - failure_data: Option<&'fargs [u8]>, + failure_data: &'fargs [u8], ) -> Self { Self { bp: FailParams::new(time_stamp, failure_code, failure_data), @@ -399,6 +408,66 @@ impl<'src_data, TcState: WasAtLeastAccepted + Copy> pub fn send_success_step_or_completion_success(self) {} } +pub trait VerificationReportingProvider { + fn add_tc( + &mut self, + pus_tc: &(impl CcsdsPacket + IsPusTelecommand), + ) -> VerificationToken { + self.add_tc_with_req_id(RequestId::new(pus_tc)) + } + + fn add_tc_with_req_id(&mut self, req_id: RequestId) -> VerificationToken; + + fn acceptance_success( + &self, + token: VerificationToken, + time_stamp: &[u8], + ) -> Result, VerificationOrSendErrorWithToken>; + + fn acceptance_failure( + &self, + token: VerificationToken, + params: FailParams, + ) -> Result<(), VerificationOrSendErrorWithToken>; + + fn start_success( + &self, + token: VerificationToken, + time_stamp: &[u8], + ) -> Result, VerificationOrSendErrorWithToken>; + + fn start_failure( + &self, + token: VerificationToken, + params: FailParams, + ) -> Result<(), VerificationOrSendErrorWithToken>; + + fn step_success( + &self, + token: &VerificationToken, + time_stamp: &[u8], + step: impl EcssEnumeration, + ) -> Result<(), EcssTmtcError>; + + fn step_failure( + &self, + token: VerificationToken, + params: FailParamsWithStep, + ) -> Result<(), VerificationOrSendErrorWithToken>; + + fn completion_success( + &self, + token: VerificationToken, + time_stamp: &[u8], + ) -> Result<(), VerificationOrSendErrorWithToken>; + + fn completion_failure( + &self, + token: VerificationToken, + params: FailParams, + ) -> Result<(), VerificationOrSendErrorWithToken>; +} + /// Primary verification handler. It provides an API to send PUS 1 verification telemetry packets /// and verify the various steps of telecommand handling as specified in the PUS standard. /// @@ -456,7 +525,7 @@ impl VerificationReporterCore { token: VerificationToken, seq_count: u16, msg_count: u16, - time_stamp: Option<&'src_data [u8]>, + time_stamp: &'src_data [u8], ) -> Result< VerificationSendable<'src_data, State, VerifSuccess>, VerificationErrorWithToken, @@ -513,7 +582,7 @@ impl VerificationReporterCore { token: VerificationToken, seq_count: u16, msg_count: u16, - time_stamp: Option<&'src_data [u8]>, + time_stamp: &'src_data [u8], ) -> Result< VerificationSendable<'src_data, TcStateNone, VerifSuccess>, VerificationErrorWithToken, @@ -584,7 +653,7 @@ impl VerificationReporterCore { token: VerificationToken, seq_count: u16, msg_count: u16, - time_stamp: Option<&'src_data [u8]>, + time_stamp: &'src_data [u8], ) -> Result< VerificationSendable<'src_data, TcStateAccepted, VerifSuccess>, VerificationErrorWithToken, @@ -658,7 +727,7 @@ impl VerificationReporterCore { token: &VerificationToken, seq_count: u16, msg_count: u16, - time_stamp: Option<&'src_data [u8]>, + time_stamp: &'src_data [u8], step: impl EcssEnumeration, ) -> Result, EcssTmtcError> { Ok(VerificationSendable::new_no_token( @@ -714,7 +783,7 @@ impl VerificationReporterCore { token: VerificationToken, seq_counter: u16, msg_counter: u16, - time_stamp: Option<&'src_data [u8]>, + time_stamp: &'src_data [u8], ) -> Result< VerificationSendable<'src_data, TcState, VerifSuccess>, VerificationErrorWithToken, @@ -788,7 +857,7 @@ impl VerificationReporterCore { seq_count: u16, msg_counter: u16, req_id: &RequestId, - time_stamp: Option<&'src_data [u8]>, + time_stamp: &'src_data [u8], step: Option<&(impl EcssEnumeration + ?Sized)>, ) -> Result, EcssTmtcError> { let mut source_data_len = size_of::(); @@ -832,9 +901,7 @@ impl VerificationReporterCore { if let Some(step) = step { source_data_len += step.size(); } - if let Some(failure_data) = params.failure_data { - source_data_len += failure_data.len(); - } + source_data_len += params.failure_data.len(); source_buffer_large_enough(src_data_buf.len(), source_data_len)?; req_id.to_bytes(&mut src_data_buf[0..RequestId::SIZE_AS_BYTES]); idx += RequestId::SIZE_AS_BYTES; @@ -849,9 +916,7 @@ impl VerificationReporterCore { .write_to_be_bytes(&mut src_data_buf[idx..idx + params.failure_code.size()]) .map_err(PusError::ByteConversion)?; idx += params.failure_code.size(); - if let Some(failure_data) = params.failure_data { - src_data_buf[idx..idx + failure_data.len()].copy_from_slice(failure_data); - } + src_data_buf[idx..idx + params.failure_data.len()].copy_from_slice(params.failure_data); let mut sp_header = SpHeader::tm_unseg(self.apid(), seq_count, 0).unwrap(); Ok(self.create_pus_verif_tm_base( src_data_buf, @@ -869,11 +934,11 @@ impl VerificationReporterCore { subservice: u8, msg_counter: u16, sp_header: &mut SpHeader, - time_stamp: Option<&'src_data [u8]>, + time_stamp: &'src_data [u8], source_data_len: usize, ) -> PusTmCreator<'src_data> { let tm_sec_header = - PusTmSecondaryHeader::new(1, subservice, msg_counter, self.dest_id, time_stamp); + PusTmSecondaryHeader::new(1, subservice, msg_counter, self.dest_id, Some(time_stamp)); PusTmCreator::new( sp_header, tm_sec_header, @@ -970,7 +1035,7 @@ mod alloc_mod { &self, token: VerificationToken, sender: &(impl EcssTmSenderCore + ?Sized), - time_stamp: Option<&[u8]>, + time_stamp: &[u8], ) -> Result, VerificationOrSendErrorWithToken> { let seq_count = self @@ -1025,7 +1090,7 @@ mod alloc_mod { &self, token: VerificationToken, sender: &(impl EcssTmSenderCore + ?Sized), - time_stamp: Option<&[u8]>, + time_stamp: &[u8], ) -> Result< VerificationToken, VerificationOrSendErrorWithToken, @@ -1085,7 +1150,7 @@ mod alloc_mod { &self, token: &VerificationToken, sender: &(impl EcssTmSenderCore + ?Sized), - time_stamp: Option<&[u8]>, + time_stamp: &[u8], step: impl EcssEnumeration, ) -> Result<(), EcssTmtcError> { let seq_count = self @@ -1148,7 +1213,7 @@ mod alloc_mod { &self, token: VerificationToken, sender: &(impl EcssTmSenderCore + ?Sized), - time_stamp: Option<&[u8]>, + time_stamp: &[u8], ) -> Result<(), VerificationOrSendErrorWithToken> { let seq_count = self .seq_count_provider @@ -1226,24 +1291,34 @@ mod alloc_mod { to self.reporter { pub fn set_apid(&mut self, apid: u16) -> bool; pub fn apid(&self) -> u16; - pub fn add_tc(&mut self, pus_tc: &(impl CcsdsPacket + IsPusTelecommand)) -> VerificationToken; - pub fn add_tc_with_req_id(&mut self, req_id: RequestId) -> VerificationToken; pub fn dest_id(&self) -> u16; pub fn set_dest_id(&mut self, dest_id: u16); } } + } - pub fn acceptance_success( + impl VerificationReportingProvider for VerificationReporterWithSender { + delegate! { + to self.reporter { + fn add_tc( + &mut self, + pus_tc: &(impl CcsdsPacket + IsPusTelecommand), + ) -> VerificationToken; + fn add_tc_with_req_id(&mut self, req_id: RequestId) -> VerificationToken; + } + } + + fn acceptance_success( &self, token: VerificationToken, - time_stamp: Option<&[u8]>, + time_stamp: &[u8], ) -> Result, VerificationOrSendErrorWithToken> { self.reporter .acceptance_success(token, self.sender.as_ref(), time_stamp) } - pub fn acceptance_failure( + fn acceptance_failure( &self, token: VerificationToken, params: FailParams, @@ -1252,10 +1327,10 @@ mod alloc_mod { .acceptance_failure(token, self.sender.as_ref(), params) } - pub fn start_success( + fn start_success( &self, token: VerificationToken, - time_stamp: Option<&[u8]>, + time_stamp: &[u8], ) -> Result< VerificationToken, VerificationOrSendErrorWithToken, @@ -1264,7 +1339,7 @@ mod alloc_mod { .start_success(token, self.sender.as_ref(), time_stamp) } - pub fn start_failure( + fn start_failure( &self, token: VerificationToken, params: FailParams, @@ -1273,17 +1348,17 @@ mod alloc_mod { .start_failure(token, self.sender.as_ref(), params) } - pub fn step_success( + fn step_success( &self, token: &VerificationToken, - time_stamp: Option<&[u8]>, + time_stamp: &[u8], step: impl EcssEnumeration, ) -> Result<(), EcssTmtcError> { self.reporter .step_success(token, self.sender.as_ref(), time_stamp, step) } - pub fn step_failure( + fn step_failure( &self, token: VerificationToken, params: FailParamsWithStep, @@ -1292,16 +1367,16 @@ mod alloc_mod { .step_failure(token, self.sender.as_ref(), params) } - pub fn completion_success( + fn completion_success( &self, token: VerificationToken, - time_stamp: Option<&[u8]>, + time_stamp: &[u8], ) -> Result<(), VerificationOrSendErrorWithToken> { self.reporter .completion_success(token, self.sender.as_ref(), time_stamp) } - pub fn completion_failure( + fn completion_failure( &self, token: VerificationToken, params: FailParams, @@ -1322,7 +1397,7 @@ mod std_mod { } #[cfg(test)] -mod tests { +pub mod tests { use crate::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; use crate::pus::tests::CommonTmInfo; use crate::pus::verification::{ @@ -1335,6 +1410,8 @@ mod tests { use crate::ChannelId; use alloc::boxed::Box; use alloc::format; + use alloc::sync::Arc; + use hashbrown::HashMap; use spacepackets::ecss::tc::{PusTcCreator, PusTcSecondaryHeader}; use spacepackets::ecss::tm::PusTmReader; use spacepackets::ecss::{EcssEnumU16, EcssEnumU32, EcssEnumU8, PusError, PusPacket}; @@ -1342,15 +1419,208 @@ mod tests { use spacepackets::{ByteConversionError, CcsdsPacket, SpHeader}; use std::cell::RefCell; use std::collections::VecDeque; - use std::sync::mpsc; + use std::sync::{mpsc, Mutex}; use std::time::Duration; use std::vec; use std::vec::Vec; + use super::VerificationReportingProvider; + fn is_send(_: &T) {} #[allow(dead_code)] fn is_sync(_: &T) {} + pub struct VerificationStatus { + pub accepted: Option, + pub started: Option, + pub step: u64, + pub step_status: Option, + pub completed: Option, + pub failure_data: Option>, + pub fail_enum: Option, + } + + pub type SharedVerificationMap = Arc>>>; + + #[derive(Clone)] + pub struct TestVerificationReporter { + pub verification_map: SharedVerificationMap, + } + + impl TestVerificationReporter { + pub fn new(verification_map: SharedVerificationMap) -> Self { + Self { verification_map } + } + } + + impl VerificationReportingProvider for TestVerificationReporter { + fn add_tc_with_req_id(&mut self, req_id: RequestId) -> VerificationToken { + let verif_map = self.verification_map.lock().unwrap(); + verif_map.borrow_mut().insert( + req_id, + VerificationStatus { + accepted: None, + started: None, + step: 0, + step_status: None, + completed: None, + failure_data: None, + fail_enum: None, + }, + ); + VerificationToken { + state: core::marker::PhantomData, + req_id, + } + } + + fn acceptance_success( + &self, + token: VerificationToken, + _time_stamp: &[u8], + ) -> Result< + VerificationToken, + super::VerificationOrSendErrorWithToken, + > { + let verif_map = self.verification_map.lock().unwrap(); + match verif_map.borrow_mut().get_mut(&token.req_id) { + Some(entry) => entry.accepted = Some(true), + None => panic!( + "unexpected acceptance success for request ID {}", + token.req_id() + ), + }; + Ok(VerificationToken { + state: core::marker::PhantomData, + req_id: token.req_id, + }) + } + + fn acceptance_failure( + &self, + token: VerificationToken, + params: FailParams, + ) -> Result<(), super::VerificationOrSendErrorWithToken> { + let verif_map = self.verification_map.lock().unwrap(); + match verif_map.borrow_mut().get_mut(&token.req_id) { + Some(entry) => { + entry.accepted = Some(false); + entry.failure_data = Some(params.failure_data.to_vec()); + entry.fail_enum = Some(params.failure_code.value()); + } + None => panic!( + "unexpected acceptance failure for request ID {}", + token.req_id() + ), + }; + Ok(()) + } + + fn start_success( + &self, + token: VerificationToken, + _time_stamp: &[u8], + ) -> Result< + VerificationToken, + super::VerificationOrSendErrorWithToken, + > { + let verif_map = self.verification_map.lock().unwrap(); + match verif_map.borrow_mut().get_mut(&token.req_id) { + Some(entry) => entry.started = Some(true), + None => panic!("unexpected start success for request ID {}", token.req_id()), + }; + Ok(VerificationToken { + state: core::marker::PhantomData, + req_id: token.req_id, + }) + } + + fn start_failure( + &self, + token: VerificationToken, + params: FailParams, + ) -> Result<(), super::VerificationOrSendErrorWithToken> { + let verif_map = self.verification_map.lock().unwrap(); + match verif_map.borrow_mut().get_mut(&token.req_id) { + Some(entry) => { + entry.started = Some(false); + entry.failure_data = Some(params.failure_data.to_vec()); + entry.fail_enum = Some(params.failure_code.value()); + } + None => panic!("unexpected start failure for request ID {}", token.req_id()), + }; + Ok(()) + } + + fn step_success( + &self, + token: &VerificationToken, + _time_stamp: &[u8], + step: impl spacepackets::ecss::EcssEnumeration, + ) -> Result<(), EcssTmtcError> { + let verif_map = self.verification_map.lock().unwrap(); + match verif_map.borrow_mut().get_mut(&token.req_id) { + Some(entry) => { + entry.step = step.value(); + entry.step_status = Some(true); + } + None => panic!("unexpected start success for request ID {}", token.req_id()), + }; + Ok(()) + } + + fn step_failure( + &self, + token: VerificationToken, + _params: FailParamsWithStep, + ) -> Result<(), super::VerificationOrSendErrorWithToken> { + let verif_map = self.verification_map.lock().unwrap(); + match verif_map.borrow_mut().get_mut(&token.req_id) { + Some(entry) => { + entry.step_status = Some(false); + } + None => panic!("unexpected start success for request ID {}", token.req_id()), + }; + Ok(()) + } + + fn completion_success( + &self, + token: VerificationToken, + _time_stamp: &[u8], + ) -> Result<(), super::VerificationOrSendErrorWithToken> { + let verif_map = self.verification_map.lock().unwrap(); + match verif_map.borrow_mut().get_mut(&token.req_id) { + Some(entry) => entry.completed = Some(true), + None => panic!( + "unexpected acceptance success for request ID {}", + token.req_id() + ), + }; + Ok(()) + } + + fn completion_failure( + &self, + token: VerificationToken, + params: FailParams, + ) -> Result<(), super::VerificationOrSendErrorWithToken> { + let verif_map = self.verification_map.lock().unwrap(); + match verif_map.borrow_mut().get_mut(&token.req_id) { + Some(entry) => { + entry.completed = Some(false); + entry.failure_data = Some(params.failure_data.to_vec()); + entry.fail_enum = Some(params.failure_code.value()); + } + None => panic!( + "unexpected acceptance success for request ID {}", + token.req_id() + ), + }; + Ok(()) + } + } + const TEST_APID: u16 = 0x02; const EMPTY_STAMP: [u8; 7] = [0; 7]; @@ -1504,7 +1774,7 @@ mod tests { fn test_basic_acceptance_success() { let (b, tok) = base_init(false); let mut sender = TestSender::default(); - b.vr.acceptance_success(tok, &sender, Some(&EMPTY_STAMP)) + b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP) .expect("Sending acceptance success failed"); acceptance_check(&mut sender, &tok.req_id); } @@ -1513,7 +1783,7 @@ mod tests { fn test_basic_acceptance_success_with_helper() { let (mut b, tok) = base_with_helper_init(); b.helper - .acceptance_success(tok, Some(&EMPTY_STAMP)) + .acceptance_success(tok, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); acceptance_check(sender, &tok.req_id); @@ -1544,7 +1814,7 @@ mod tests { let stamp_buf = [1, 2, 3, 4, 5, 6, 7]; let mut sender = TestSender::default(); let fail_code = EcssEnumU16::new(2); - let fail_params = FailParams::new(Some(stamp_buf.as_slice()), &fail_code, None); + let fail_params = FailParams::new_no_fail_data(stamp_buf.as_slice(), &fail_code); b.vr.acceptance_failure(tok, &sender, fail_params) .expect("Sending acceptance success failed"); acceptance_fail_check(&mut sender, tok.req_id, stamp_buf); @@ -1556,7 +1826,7 @@ mod tests { b.rep().reporter.dest_id = 5; let stamp_buf = [1, 2, 3, 4, 5, 6, 7]; let fail_code = EcssEnumU16::new(2); - let fail_params = FailParams::new(Some(stamp_buf.as_slice()), &fail_code, None); + let fail_params = FailParams::new_no_fail_data(stamp_buf.as_slice(), &fail_code); b.helper .acceptance_failure(tok, fail_params) .expect("Sending acceptance success failed"); @@ -1573,11 +1843,7 @@ mod tests { let fail_data: [u8; 16] = [0; 16]; // 4 req ID + 1 byte step + 2 byte error code + 8 byte fail data assert_eq!(b.rep().allowed_source_data_len(), 15); - let fail_params = FailParams::new( - Some(stamp_buf.as_slice()), - &fail_code, - Some(fail_data.as_slice()), - ); + let fail_params = FailParams::new(stamp_buf.as_slice(), &fail_code, fail_data.as_slice()); let res = b.helper.acceptance_failure(tok, fail_params); assert!(res.is_err()); let err_with_token = res.unwrap_err(); @@ -1609,11 +1875,7 @@ mod tests { let fail_data = EcssEnumU32::new(12); let mut fail_data_raw = [0; 4]; fail_data.write_to_be_bytes(&mut fail_data_raw).unwrap(); - let fail_params = FailParams::new( - Some(&EMPTY_STAMP), - &fail_code, - Some(fail_data_raw.as_slice()), - ); + let fail_params = FailParams::new(&EMPTY_STAMP, &fail_code, fail_data_raw.as_slice()); b.vr.acceptance_failure(tok, &sender, fail_params) .expect("Sending acceptance success failed"); let cmp_info = TmInfo { @@ -1673,16 +1935,12 @@ mod tests { let fail_data: i32 = -12; let mut fail_data_raw = [0; 4]; fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice()); - let fail_params = FailParams::new( - Some(&EMPTY_STAMP), - &fail_code, - Some(fail_data_raw.as_slice()), - ); + let fail_params = FailParams::new(&EMPTY_STAMP, &fail_code, fail_data_raw.as_slice()); let accepted_token = - b.vr.acceptance_success(tok, &sender, Some(&EMPTY_STAMP)) + b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP) .expect("Sending acceptance success failed"); - b.vr.start_failure(accepted_token, &mut sender, fail_params) + b.vr.start_failure(accepted_token, &sender, fail_params) .expect("Start failure failure"); start_fail_check(&mut sender, tok.req_id, fail_data_raw); } @@ -1694,21 +1952,15 @@ mod tests { let fail_data: i32 = -12; let mut fail_data_raw = [0; 4]; fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice()); - let fail_params = FailParams::new( - Some(&EMPTY_STAMP), - &fail_code, - Some(fail_data_raw.as_slice()), - ); + let fail_params = FailParams::new(&EMPTY_STAMP, &fail_code, fail_data_raw.as_slice()); let accepted_token = b .helper - .acceptance_success(tok, Some(&EMPTY_STAMP)) + .acceptance_success(tok, &EMPTY_STAMP) .expect("Sending acceptance success failed"); - let empty = b - .helper + b.helper .start_failure(accepted_token, fail_params) .expect("Start failure failure"); - assert_eq!(empty, ()); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); start_fail_check(sender, tok.req_id, fail_data_raw); } @@ -1775,27 +2027,17 @@ mod tests { let mut sender = TestSender::default(); let accepted_token = b .rep() - .acceptance_success(tok, &sender, Some(&EMPTY_STAMP)) + .acceptance_success(tok, &sender, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = b .rep() - .start_success(accepted_token, &sender, Some(&[0, 1, 0, 1, 0, 1, 0])) + .start_success(accepted_token, &sender, &[0, 1, 0, 1, 0, 1, 0]) .expect("Sending start success failed"); b.rep() - .step_success( - &started_token, - &sender, - Some(&EMPTY_STAMP), - EcssEnumU8::new(0), - ) + .step_success(&started_token, &sender, &EMPTY_STAMP, EcssEnumU8::new(0)) .expect("Sending step 0 success failed"); - b.vr.step_success( - &started_token, - &sender, - Some(&EMPTY_STAMP), - EcssEnumU8::new(1), - ) - .expect("Sending step 1 success failed"); + b.vr.step_success(&started_token, &sender, &EMPTY_STAMP, EcssEnumU8::new(1)) + .expect("Sending step 1 success failed"); assert_eq!(sender.service_queue.borrow().len(), 4); step_success_check(&mut sender, tok.req_id); } @@ -1805,17 +2047,17 @@ mod tests { let (mut b, tok) = base_with_helper_init(); let accepted_token = b .helper - .acceptance_success(tok, Some(&EMPTY_STAMP)) + .acceptance_success(tok, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = b .helper - .start_success(accepted_token, Some(&[0, 1, 0, 1, 0, 1, 0])) + .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0]) .expect("Sending start success failed"); b.helper - .step_success(&started_token, Some(&EMPTY_STAMP), EcssEnumU8::new(0)) + .step_success(&started_token, &EMPTY_STAMP, EcssEnumU8::new(0)) .expect("Sending step 0 success failed"); b.helper - .step_success(&started_token, Some(&EMPTY_STAMP), EcssEnumU8::new(1)) + .step_success(&started_token, &EMPTY_STAMP, EcssEnumU8::new(1)) .expect("Sending step 1 success failed"); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); assert_eq!(sender.service_queue.borrow().len(), 4); @@ -1900,31 +2142,22 @@ mod tests { fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice()); let fail_step = EcssEnumU8::new(1); let fail_params = FailParamsWithStep::new( - Some(&EMPTY_STAMP), + &EMPTY_STAMP, &fail_step, &fail_code, - Some(fail_data_raw.as_slice()), + fail_data_raw.as_slice(), ); let accepted_token = - b.vr.acceptance_success(tok, &mut sender, Some(&EMPTY_STAMP)) + b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = - b.vr.start_success(accepted_token, &mut sender, Some(&[0, 1, 0, 1, 0, 1, 0])) + b.vr.start_success(accepted_token, &sender, &[0, 1, 0, 1, 0, 1, 0]) .expect("Sending start success failed"); - let mut empty = - b.vr.step_success( - &started_token, - &mut sender, - Some(&EMPTY_STAMP), - EcssEnumU8::new(0), - ) + b.vr.step_success(&started_token, &sender, &EMPTY_STAMP, EcssEnumU8::new(0)) .expect("Sending completion success failed"); - assert_eq!(empty, ()); - empty = - b.vr.step_failure(started_token, &mut sender, fail_params) - .expect("Step failure failed"); - assert_eq!(empty, ()); + b.vr.step_failure(started_token, &sender, fail_params) + .expect("Step failure failed"); check_step_failure(&mut sender, req_id, fail_data_raw); } @@ -1938,30 +2171,26 @@ mod tests { fail_data_raw.copy_from_slice(fail_data.to_be_bytes().as_slice()); let fail_step = EcssEnumU8::new(1); let fail_params = FailParamsWithStep::new( - Some(&EMPTY_STAMP), + &EMPTY_STAMP, &fail_step, &fail_code, - Some(fail_data_raw.as_slice()), + fail_data_raw.as_slice(), ); let accepted_token = b .helper - .acceptance_success(tok, Some(&EMPTY_STAMP)) + .acceptance_success(tok, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = b .helper - .start_success(accepted_token, Some(&[0, 1, 0, 1, 0, 1, 0])) + .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0]) .expect("Sending start success failed"); - let mut empty = b - .helper - .step_success(&started_token, Some(&EMPTY_STAMP), EcssEnumU8::new(0)) + b.helper + .step_success(&started_token, &EMPTY_STAMP, EcssEnumU8::new(0)) .expect("Sending completion success failed"); - assert_eq!(empty, ()); - empty = b - .helper + b.helper .step_failure(started_token, fail_params) .expect("Step failure failed"); - assert_eq!(empty, ()); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); check_step_failure(sender, req_id, fail_data_raw); } @@ -2018,18 +2247,16 @@ mod tests { let mut sender = TestSender::default(); let req_id = tok.req_id; let fail_code = EcssEnumU32::new(0x1020); - let fail_params = FailParams::new(Some(&EMPTY_STAMP), &fail_code, None); + let fail_params = FailParams::new_no_fail_data(&EMPTY_STAMP, &fail_code); let accepted_token = - b.vr.acceptance_success(tok, &mut sender, Some(&EMPTY_STAMP)) + b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = - b.vr.start_success(accepted_token, &mut sender, Some(&[0, 1, 0, 1, 0, 1, 0])) + b.vr.start_success(accepted_token, &sender, &[0, 1, 0, 1, 0, 1, 0]) .expect("Sending start success failed"); - let empty = - b.vr.completion_failure(started_token, &mut sender, fail_params) - .expect("Completion failure"); - assert_eq!(empty, ()); + b.vr.completion_failure(started_token, &sender, fail_params) + .expect("Completion failure"); completion_fail_check(&mut sender, req_id); } @@ -2038,21 +2265,19 @@ mod tests { let (mut b, tok) = base_with_helper_init(); let req_id = tok.req_id; let fail_code = EcssEnumU32::new(0x1020); - let fail_params = FailParams::new(Some(&EMPTY_STAMP), &fail_code, None); + let fail_params = FailParams::new_no_fail_data(&EMPTY_STAMP, &fail_code); let accepted_token = b .helper - .acceptance_success(tok, Some(&EMPTY_STAMP)) + .acceptance_success(tok, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = b .helper - .start_success(accepted_token, Some(&[0, 1, 0, 1, 0, 1, 0])) + .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0]) .expect("Sending start success failed"); - let empty = b - .helper + b.helper .completion_failure(started_token, fail_params) .expect("Completion failure"); - assert_eq!(empty, ()); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); completion_fail_check(sender, req_id); } @@ -2106,12 +2331,12 @@ mod tests { let (b, tok) = base_init(false); let mut sender = TestSender::default(); let accepted_token = - b.vr.acceptance_success(tok, &mut sender, Some(&EMPTY_STAMP)) + b.vr.acceptance_success(tok, &sender, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = - b.vr.start_success(accepted_token, &mut sender, Some(&[0, 1, 0, 1, 0, 1, 0])) + b.vr.start_success(accepted_token, &sender, &[0, 1, 0, 1, 0, 1, 0]) .expect("Sending start success failed"); - b.vr.completion_success(started_token, &mut sender, Some(&EMPTY_STAMP)) + b.vr.completion_success(started_token, &sender, &EMPTY_STAMP) .expect("Sending completion success failed"); completion_success_check(&mut sender, tok.req_id); } @@ -2121,14 +2346,14 @@ mod tests { let (mut b, tok) = base_with_helper_init(); let accepted_token = b .helper - .acceptance_success(tok, Some(&EMPTY_STAMP)) + .acceptance_success(tok, &EMPTY_STAMP) .expect("Sending acceptance success failed"); let started_token = b .helper - .start_success(accepted_token, Some(&[0, 1, 0, 1, 0, 1, 0])) + .start_success(accepted_token, &[0, 1, 0, 1, 0, 1, 0]) .expect("Sending start success failed"); b.helper - .completion_success(started_token, Some(&EMPTY_STAMP)) + .completion_success(started_token, &EMPTY_STAMP) .expect("Sending completion success failed"); let sender: &mut TestSender = b.helper.sender.downcast_mut().unwrap(); completion_success_check(sender, tok.req_id); @@ -2154,13 +2379,13 @@ mod tests { // Complete success sequence for a telecommand let accepted_token = reporter - .acceptance_success(init_token, Some(&EMPTY_STAMP)) + .acceptance_success(init_token, &EMPTY_STAMP) .unwrap(); let started_token = reporter - .start_success(accepted_token, Some(&EMPTY_STAMP)) + .start_success(accepted_token, &EMPTY_STAMP) .unwrap(); reporter - .completion_success(started_token, Some(&EMPTY_STAMP)) + .completion_success(started_token, &EMPTY_STAMP) .unwrap(); // Verify it arrives correctly on receiver end diff --git a/satrs/src/queue.rs b/satrs/src/queue.rs new file mode 100644 index 0000000..25ff6c6 --- /dev/null +++ b/satrs/src/queue.rs @@ -0,0 +1,49 @@ +use core::fmt::{Display, Formatter}; +#[cfg(feature = "std")] +use std::error::Error; + +/// 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 {} diff --git a/satrs/src/request.rs b/satrs/src/request.rs index 8b13789..24ca497 100644 --- a/satrs/src/request.rs +++ b/satrs/src/request.rs @@ -1 +1,110 @@ +use core::fmt; +#[cfg(feature = "std")] +use std::error::Error; +use spacepackets::{ + ecss::{tc::IsPusTelecommand, PusPacket}, + ByteConversionError, CcsdsPacket, +}; + +use crate::TargetId; + +pub type Apid = u16; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TargetIdCreationError { + ByteConversion(ByteConversionError), + NotEnoughAppData(usize), +} + +impl From for TargetIdCreationError { + fn from(e: ByteConversionError) -> Self { + Self::ByteConversion(e) + } +} + +impl fmt::Display for TargetIdCreationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::ByteConversion(e) => write!(f, "target ID creation: {}", e), + Self::NotEnoughAppData(len) => { + write!(f, "not enough app data to generate target ID: {}", len) + } + } + } +} + +#[cfg(feature = "std")] +impl Error for TargetIdCreationError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + if let Self::ByteConversion(e) = self { + return Some(e); + } + None + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct TargetAndApidId { + pub apid: Apid, + pub target: u32, +} + +impl TargetAndApidId { + pub fn new(apid: Apid, target: u32) -> Self { + Self { apid, target } + } + + pub fn apid(&self) -> Apid { + self.apid + } + + pub fn target(&self) -> u32 { + self.target + } + + pub fn raw(&self) -> TargetId { + ((self.apid as u64) << 32) | (self.target as u64) + } + + pub fn target_id(&self) -> TargetId { + self.raw() + } + + pub fn from_pus_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()), + }) + } +} + +impl From for TargetAndApidId { + fn from(raw: u64) -> Self { + Self { + apid: (raw >> 32) as u16, + target: raw as u32, + } + } +} + +impl From for u64 { + fn from(target_and_apid_id: TargetAndApidId) -> Self { + target_and_apid_id.raw() + } +} + +impl fmt::Display for TargetAndApidId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}, {}", self.apid, self.target) + } +} diff --git a/satrs/src/tmtc/mod.rs b/satrs/src/tmtc/mod.rs index 0f548a2..b930359 100644 --- a/satrs/src/tmtc/mod.rs +++ b/satrs/src/tmtc/mod.rs @@ -20,8 +20,6 @@ pub use ccsds_distrib::{CcsdsDistributor, CcsdsError, CcsdsPacketHandler}; #[cfg(feature = "alloc")] pub use pus_distrib::{PusDistributor, PusServiceProvider}; -pub type TargetId = u32; - /// Generic trait for object which can receive any telecommands in form of a raw bytestream, with /// no assumptions about the received protocol. /// diff --git a/satrs/tests/pus_verification.rs b/satrs/tests/pus_verification.rs index bc52d6f..dd72394 100644 --- a/satrs/tests/pus_verification.rs +++ b/satrs/tests/pus_verification.rs @@ -1,9 +1,10 @@ -//#[cfg(feature = "crossbeam")] +#[cfg(feature = "crossbeam")] pub mod crossbeam_test { use hashbrown::HashMap; use satrs::pool::{PoolProvider, PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; use satrs::pus::verification::{ FailParams, RequestId, VerificationReporterCfg, VerificationReporterWithSender, + VerificationReportingProvider, }; use satrs::pus::CrossbeamTmInStoreSender; use satrs::tmtc::tm_helper::SharedTmPool; @@ -89,24 +90,24 @@ pub mod crossbeam_test { let token = reporter_with_sender_0.add_tc_with_req_id(req_id_0); let accepted_token = reporter_with_sender_0 - .acceptance_success(token, Some(&FIXED_STAMP)) + .acceptance_success(token, &FIXED_STAMP) .expect("Acceptance success failed"); // Do some start handling here let started_token = reporter_with_sender_0 - .start_success(accepted_token, Some(&FIXED_STAMP)) + .start_success(accepted_token, &FIXED_STAMP) .expect("Start success failed"); // Do some step handling here reporter_with_sender_0 - .step_success(&started_token, Some(&FIXED_STAMP), EcssEnumU8::new(0)) + .step_success(&started_token, &FIXED_STAMP, EcssEnumU8::new(0)) .expect("Start success failed"); // Finish up reporter_with_sender_0 - .step_success(&started_token, Some(&FIXED_STAMP), EcssEnumU8::new(1)) + .step_success(&started_token, &FIXED_STAMP, EcssEnumU8::new(1)) .expect("Start success failed"); reporter_with_sender_0 - .completion_success(started_token, Some(&FIXED_STAMP)) + .completion_success(started_token, &FIXED_STAMP) .expect("Completion success failed"); }); @@ -124,13 +125,13 @@ pub mod crossbeam_test { let (tc, _) = PusTcReader::new(&tc_buf[0..tc_len]).unwrap(); let token = reporter_with_sender_1.add_tc(&tc); let accepted_token = reporter_with_sender_1 - .acceptance_success(token, Some(&FIXED_STAMP)) + .acceptance_success(token, &FIXED_STAMP) .expect("Acceptance success failed"); let started_token = reporter_with_sender_1 - .start_success(accepted_token, Some(&FIXED_STAMP)) + .start_success(accepted_token, &FIXED_STAMP) .expect("Start success failed"); let fail_code = EcssEnumU16::new(2); - let params = FailParams::new(Some(&FIXED_STAMP), &fail_code, None); + let params = FailParams::new_no_fail_data(&FIXED_STAMP, &fail_code); reporter_with_sender_1 .completion_failure(started_token, params) .expect("Completion success failed"); -- 2.43.0 From ca2c8aa359397234043128b1a75e4a6a94b5e8ed Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 20 Feb 2024 14:36:34 +0100 Subject: [PATCH 2/2] update changelog --- satrs/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/satrs/CHANGELOG.md b/satrs/CHANGELOG.md index 94338f0..156e328 100644 --- a/satrs/CHANGELOG.md +++ b/satrs/CHANGELOG.md @@ -17,6 +17,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - 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. -- 2.43.0