diff --git a/satrs/src/params.rs b/satrs/src/params.rs index 5365d28..35781c1 100644 --- a/satrs/src/params.rs +++ b/satrs/src/params.rs @@ -609,6 +609,12 @@ impl From for Params { } } +impl From for Params { + fn from(x: ParamsRaw) -> Self { + Self::Heapless(ParamsHeapless::Raw(x)) + } +} + #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl From> for Params { diff --git a/satrs/src/pus/action.rs b/satrs/src/pus/action.rs index d9ae9de..5db8275 100644 --- a/satrs/src/pus/action.rs +++ b/satrs/src/pus/action.rs @@ -256,16 +256,35 @@ pub mod std_mod { impl PusService8ReplyHandler { + #[cfg(feature = "std")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] + pub fn new_with_init_time_now( + verification_reporter: VerificationReporter, + fail_data_buf_size: usize, + user_hook: UserHook, + ) -> Result { + let mut reply_handler = Self { + active_requests: HashMap::new(), + verification_reporter, + fail_data_buf: alloc::vec![0; fail_data_buf_size], + current_time: UnixTimestamp::default(), + user_hook, + }; + reply_handler.update_time_from_now()?; + Ok(reply_handler) + } + pub fn new( verification_reporter: VerificationReporter, fail_data_buf_size: usize, user_hook: UserHook, + init_time: UnixTimestamp, ) -> Self { Self { active_requests: HashMap::new(), verification_reporter, fail_data_buf: alloc::vec![0; fail_data_buf_size], - current_time: UnixTimestamp::from_now().unwrap(), + current_time: init_time, user_hook, } } @@ -294,6 +313,9 @@ pub mod std_mod { Ok(()) } + /// Check for timeouts across all active requests. + /// + /// It will call [Self::handle_timeout] for all active requests which have timed out. pub fn check_for_timeouts(&mut self, time_stamp: &[u8]) -> Result<(), EcssTmtcError> { for active_req in self.active_requests.values() { let diff = self.current_time - active_req.start_time; @@ -304,11 +326,22 @@ pub mod std_mod { Ok(()) } + /// Handle the timeout for a given active request. + /// + /// This implementation will report a verification completion failure with a user-provided + /// error code. It supplies the configured request timeout in milliseconds as a [u64] + /// serialized in big-endian format as the failure data. pub fn handle_timeout(&self, active_request: &ActiveActionRequest, time_stamp: &[u8]) { + let timeout = active_request.timeout.as_millis() as u64; + let timeout_raw = timeout.to_be_bytes(); self.verification_reporter .completion_failure( active_request.token, - FailParams::new(time_stamp, &self.user_hook.timeout_error_code(), &[]), + FailParams::new( + time_stamp, + &self.user_hook.timeout_error_code(), + &timeout_raw, + ), ) .unwrap(); self.user_hook.timeout_callback(active_request); @@ -327,11 +360,15 @@ pub mod std_mod { let active_req = active_req.unwrap(); match action_reply_with_ids.reply { ActionReplyPus::CompletionFailed { error_code, params } => { - params.write_to_be_bytes(&mut self.fail_data_buf)?; + let fail_data_len = params.write_to_be_bytes(&mut self.fail_data_buf)?; self.verification_reporter .completion_failure( active_req.token, - FailParams::new(time_stamp, &error_code, &self.fail_data_buf), + FailParams::new( + time_stamp, + &error_code, + &self.fail_data_buf[..fail_data_len], + ), ) .map_err(|e| e.0)?; self.active_requests @@ -342,7 +379,7 @@ pub mod std_mod { step, params, } => { - params.write_to_be_bytes(&mut self.fail_data_buf)?; + let fail_data_len = params.write_to_be_bytes(&mut self.fail_data_buf)?; self.verification_reporter .step_failure( active_req.token, @@ -350,7 +387,7 @@ pub mod std_mod { time_stamp, &EcssEnumU16::new(step), &error_code, - &self.fail_data_buf, + &self.fail_data_buf[..fail_data_len], ), ) .map_err(|e| e.0)?; @@ -380,6 +417,7 @@ pub mod std_mod { #[cfg(test)] mod tests { use core::{cell::RefCell, time::Duration}; + use std::time::SystemTimeError; use alloc::collections::VecDeque; use delegate::delegate; @@ -395,6 +433,7 @@ mod tests { use crate::{ action::ActionRequestVariant, + params::{self, ParamsRaw, WritableToBeBytes}, pus::{ tests::{ PusServiceHandlerWithVecCommon, PusTestHarness, SimplePusPacketHandler, @@ -402,7 +441,7 @@ mod tests { }, verification::{ self, - tests::{SharedVerificationMap, TestVerificationReporter}, + tests::{SharedVerificationMap, TestVerificationReporter, VerificationStatus}, FailParams, TcStateNone, TcStateStarted, VerificationReportingProvider, }, EcssTcInVecConverter, EcssTmtcError, GenericRoutingError, MpscTcReceiver, @@ -542,6 +581,8 @@ mod tests { } const TIMEOUT_ERROR_CODE: ResultU16 = ResultU16::new(1, 2); + const COMPLETION_ERROR_CODE: ResultU16 = ResultU16::new(2, 0); + const COMPLETION_ERROR_CODE_STEP: ResultU16 = ResultU16::new(2, 1); #[derive(Default)] pub struct TestReplyHandlerHook { @@ -573,8 +614,12 @@ mod tests { let reply_handler_hook = TestReplyHandlerHook::default(); let shared_verif_map = SharedVerificationMap::default(); let test_verif_reporter = TestVerificationReporter::new(shared_verif_map.clone()); - let reply_handler = - PusService8ReplyHandler::new(test_verif_reporter.clone(), 128, reply_handler_hook); + let reply_handler = PusService8ReplyHandler::new_with_init_time_now( + test_verif_reporter.clone(), + 128, + reply_handler_hook, + ) + .expect("creating reply handler failed"); Self { verif_reporter: test_verif_reporter, handler: reply_handler, @@ -605,17 +650,55 @@ mod tests { token } - pub fn assert_request_completion(&self, step: Option, request_id: RequestId) { + pub fn assert_request_completion_success(&self, step: Option, request_id: RequestId) { let verif_info = self .verif_reporter .verification_info(&verification::RequestId::from(request_id)) .expect("no verification info found"); + self.assert_request_completion_common(&verif_info, step, true) + } + + pub fn assert_request_completion_failure( + &self, + step: Option, + request_id: RequestId, + fail_enum: ResultU16, + fail_data: &[u8], + ) { + let verif_info = self + .verif_reporter + .verification_info(&verification::RequestId::from(request_id)) + .expect("no verification info found"); + self.assert_request_completion_common(&verif_info, step, false); + assert_eq!(verif_info.fail_enum.unwrap(), fail_enum.raw() as u64); + assert_eq!(verif_info.failure_data.unwrap(), fail_data); + } + + pub fn assert_request_completion_common( + &self, + verif_info: &VerificationStatus, + step: Option, + completion_success: bool, + ) { if let Some(step) = step { assert!(verif_info.step_status.is_some()); assert!(verif_info.step_status.unwrap()); assert_eq!(step, verif_info.step); } - assert!(verif_info.completed.expect("request is not completed")); + assert_eq!( + verif_info.completed.expect("request is not completed"), + completion_success + ); + } + + pub fn assert_request_step_failure(&self, step: u16, request_id: RequestId) { + let verif_info = self + .verif_reporter + .verification_info(&verification::RequestId::from(request_id)) + .expect("no verification info found"); + assert!(verif_info.step_status.is_some()); + assert!(!verif_info.step_status.unwrap()); + assert_eq!(step, verif_info.step); } delegate! { @@ -633,6 +716,10 @@ mod tests { token: VerificationToken, timeout: Duration, ); + + pub fn update_time_from_now(&mut self) -> Result<(), SystemTimeError>; + + pub fn check_for_timeouts(&mut self, time_stamp: &[u8]) -> Result<(), EcssTmtcError>; } to self.verif_reporter { fn add_tc_with_req_id(&mut self, req_id: verification::RequestId) -> VerificationToken; @@ -703,7 +790,7 @@ mod tests { } #[test] - fn test_reply_handler() { + fn test_reply_handler_completion_success() { let mut reply_testbench = Pus8ReplyTestbench::new(); let request_id = 0x02; let action_id = 0x03; @@ -722,7 +809,7 @@ mod tests { reply_testbench .handle_action_reply(action_reply, &[]) .expect("reply handling failure"); - reply_testbench.assert_request_completion(None, request_id); + reply_testbench.assert_request_completion_success(None, request_id); } #[test] @@ -753,6 +840,92 @@ mod tests { reply_testbench .handle_action_reply(action_reply, &[]) .expect("reply handling failure"); - reply_testbench.assert_request_completion(Some(1), request_id); + reply_testbench.assert_request_completion_success(Some(1), request_id); + } + + #[test] + fn test_reply_handler_completion_failure() { + let mut reply_testbench = Pus8ReplyTestbench::new(); + let request_id = 0x02; + let action_id = 0x03; + let token = reply_testbench.init_handling_for_request(request_id, action_id); + reply_testbench.add_routed_request( + request_id.into(), + action_id, + token, + Duration::from_millis(1), + ); + let params_raw = ParamsRaw::U32(params::U32(5)); + let action_reply = ActionReplyPusWithIds { + request_id, + action_id, + reply: ActionReplyPus::CompletionFailed { + error_code: COMPLETION_ERROR_CODE, + params: params_raw.into(), + }, + }; + reply_testbench + .handle_action_reply(action_reply, &[]) + .expect("reply handling failure"); + reply_testbench.assert_request_completion_failure( + None, + request_id, + COMPLETION_ERROR_CODE, + ¶ms_raw.to_vec().unwrap(), + ); + } + + #[test] + fn test_reply_handler_step_failure() { + let mut reply_testbench = Pus8ReplyTestbench::new(); + let request_id = 0x02; + let action_id = 0x03; + let token = reply_testbench.init_handling_for_request(request_id, action_id); + reply_testbench.add_routed_request( + request_id.into(), + action_id, + token, + Duration::from_millis(1), + ); + let action_reply = ActionReplyPusWithIds { + request_id, + action_id, + reply: ActionReplyPus::StepFailed { + error_code: COMPLETION_ERROR_CODE_STEP, + step: 2, + params: ParamsRaw::U32(crate::params::U32(5)).into(), + }, + }; + reply_testbench + .handle_action_reply(action_reply, &[]) + .expect("reply handling failure"); + reply_testbench.assert_request_step_failure(2, request_id); + } + + #[test] + fn test_reply_handler_timeout_handling() { + let mut reply_testbench = Pus8ReplyTestbench::new(); + let request_id = 0x02; + let action_id = 0x03; + let token = reply_testbench.init_handling_for_request(request_id, action_id); + reply_testbench.add_routed_request( + request_id.into(), + action_id, + token, + Duration::from_millis(1), + ); + let timeout_param = Duration::from_millis(1).as_millis() as u64; + let timeout_param_raw = timeout_param.to_be_bytes(); + std::thread::sleep(Duration::from_millis(2)); + reply_testbench + .update_time_from_now() + .expect("time update failure"); + reply_testbench.check_for_timeouts(&[]).unwrap(); + reply_testbench.assert_request_completion_failure( + None, + request_id, + TIMEOUT_ERROR_CODE, + &timeout_param_raw, + ); } } diff --git a/satrs/src/pus/verification.rs b/satrs/src/pus/verification.rs index 02e7978..154e67a 100644 --- a/satrs/src/pus/verification.rs +++ b/satrs/src/pus/verification.rs @@ -298,9 +298,9 @@ impl VerificationToken { /// Composite helper struct to pass failure parameters to the [VerificationReporter] pub struct FailParams<'stamp, 'fargs> { - time_stamp: &'stamp [u8], - failure_code: &'fargs dyn EcssEnumeration, - failure_data: &'fargs [u8], + pub time_stamp: &'stamp [u8], + pub failure_code: &'fargs dyn EcssEnumeration, + pub failure_data: &'fargs [u8], } impl<'stamp, 'fargs> FailParams<'stamp, 'fargs> { @@ -326,8 +326,8 @@ impl<'stamp, 'fargs> FailParams<'stamp, 'fargs> { /// Composite helper struct to pass step failure parameters to the [VerificationReporter] pub struct FailParamsWithStep<'stamp, 'fargs> { - bp: FailParams<'stamp, 'fargs>, - step: &'fargs dyn EcssEnumeration, + pub params: FailParams<'stamp, 'fargs>, + pub step: &'fargs dyn EcssEnumeration, } impl<'stamp, 'fargs> FailParamsWithStep<'stamp, 'fargs> { @@ -338,7 +338,7 @@ impl<'stamp, 'fargs> FailParamsWithStep<'stamp, 'fargs> { failure_data: &'fargs [u8], ) -> Self { Self { - bp: FailParams::new(time_stamp, failure_code, failure_data), + params: FailParams::new(time_stamp, failure_code, failure_data), step, } } @@ -783,7 +783,7 @@ impl VerificationReporterCore { msg_count, &token.req_id, Some(params.step), - ¶ms.bp, + ¶ms.params, ) .map_err(|e| VerificationErrorWithToken(e, token))?, token, @@ -1601,12 +1601,15 @@ pub mod tests { fn step_failure( &self, token: VerificationToken, - _params: FailParamsWithStep, + 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); + entry.step = params.step.value().try_into().unwrap(); + entry.failure_data = Some(params.params.failure_data.to_vec()); + entry.fail_enum = Some(params.params.failure_code.value()); } None => panic!("unexpected start success for request ID {}", token.req_id()), };