diff --git a/embedded-examples/embedded-client/Cargo.toml b/embedded-examples/embedded-client/Cargo.toml index face393..d7cce99 100644 --- a/embedded-examples/embedded-client/Cargo.toml +++ b/embedded-examples/embedded-client/Cargo.toml @@ -10,7 +10,7 @@ toml = "0.9" serde = { version = "1", features = ["derive"] } spacepackets = { version = "0.18" } embedded-types = { path = "../types" } -tmtc-utils = { git = "https://egit.irs.uni-stuttgart.de/rust/tmtc-utils.git", version = "0.1", rev = "d8015379a45e77400dd9c71afbf22ca415ed67d1" } +tmtc-utils = { path = "../../tmtc-utils" } postcard = { version = "1", features = ["alloc"] } anyhow = "1" cobs = "0.5" diff --git a/satrs-book/src/communication.md b/satrs-book/src/communication.md index 0cc1dcc..9ff1c6a 100644 --- a/satrs-book/src/communication.md +++ b/satrs-book/src/communication.md @@ -81,15 +81,14 @@ This is a protocol which already provides us with some useful fields: - Basic sequence counter which can be used to determine missed packets However, how does the actual payload that we want to send to or from the satellite actually look -like? While there are standards like the Packet Utilisation Standard (PUS), we recommend a payload -format which is created with the excellent [`serde`](https://serde.rs/) library. The -[TMTC modelling](./tmtc-modelling.md) chapter provides more information. +like? We recommend a payload format which is created with the excellent [`serde`](https://serde.rs/) +library. The [TMTC modelling](./tmtc-modelling.md) chapter provides more information. # Low-level protocols and the bridge to the communcation subsystem Many satellite systems usually use the lower levels of the OSI layer in addition to the application -layer covered by the PUS standard or the CCSDS space packets standard. This oftentimes requires -special hardware like dedicated FPGAs to handle forward error correction fast enough. `sat-rs` +layer. This oftentimes requires special hardware like dedicated FPGAs to handle forward error +correction fast enough. `sat-rs` might provide components to handle standard like the Unified Space Data Link Standard (USLP) in software but most of the time the handling of communication is performed through custom software and hardware. Still, connecting this custom software and hardware to `sat-rs` can mostly diff --git a/satrs-book/src/events.md b/satrs-book/src/events.md index 3f910a7..6961ad7 100644 --- a/satrs-book/src/events.md +++ b/satrs-book/src/events.md @@ -2,23 +2,3 @@ Events are an important mechanism used for remote systems to monitor unexpected or expected anomalies and events occuring on these systems. - -One common use case for events on remote systems is to offer a light-weight publish-subscribe -mechanism and IPC mechanism for software and hardware events which are also packaged as telemetry -(TM) or can trigger a system response. They can also be tied to -Fault Detection, Isolation and Recovery (FDIR) operations, which need to happen autonomously. - -The PUS Service 5 standardizes how the ground interface for events might look like, but does not -specify how other software components might react to those events. There is the PUS Service 19, -which might be used for that purpose, but the event components recommended by this framework do not -rely on the present of this service. - -The following images shows how the flow of events could look like in a system where components -can generate events, and where other system components might be interested in those events: - -![Event flow](images/events/event_man_arch.png) - -For the concrete implementation of your own event management and/or event routing system, you -can have a look at the event management documentation inside the -[API documentation](https://docs.rs/satrs/latest/satrs/event_man/index.html) where you can also -find references to all examples. diff --git a/satrs-book/src/example.md b/satrs-book/src/example.md index 4e1f590..1b2b36e 100644 --- a/satrs-book/src/example.md +++ b/satrs-book/src/example.md @@ -12,10 +12,59 @@ The example project contains components which could also be expected to be part On-Board Software. A structural diagram of the example application is given to provide a brief high-level view of the components used inside the example application: -![satrs-example component structure](images/satrs-example/satrs-example-structure.png) +```mermaid +flowchart TD + subgraph TMTC[TMTC Infrastructure] + subgraph TMTCRow1[ ] + direction LR + Udp[UDP Server] + Tcp[TCP Server] + end + subgraph TMTCRow2[ ] + direction LR + Source[TC Source] + Sink[TM Sink] + end + end -The dotted lines are used to denote optional components. In this case, the static pool components -are optional because the heap can be used as a simpler mechanism to store TMTC packets as well. + subgraph AOCS[AOCS Stack] + subgraph AOCSRow1[ ] + direction LR + Mgm0[MGM 0 Handler] + Mgm1[MGM 1 Handler] + Assy[MGM Assembly] + end + subgraph AOCSRow2[ ] + direction LR + AcsCtrl[ACS Controller] + Mgt[MGT Handler] + AcsSub[ACS Subsystem] + end + end + + subgraph EPS[EPS Stack] + Pcdu[PCDU Handler] + end + + subgraph Core[Core] + direction LR + Ctrl[Core Controller] + Evt[Event Manager] + end + + Sim[Sim Client]:::optional + + TMTC ~~~ EPS + AOCS ~~~ Core + Core ~~~ Sim + + classDef optional stroke-dasharray: 5 5; + classDef invisible fill:none,stroke:none; + class TMTCRow1,TMTCRow2,AOCSRow1,AOCSRow2 invisible; +``` + +The dotted lines are used to denote optional components. In this case, the simulation client is +optional because a dummy interface can be used instead to run the example without the simulator. Some additional explanation is provided for the various components. ### TCP/IP server components @@ -37,116 +86,35 @@ telecommands from the client. The most important components of the TMTC infrastructure include the following components: - A TC source component which demultiplexes and routes telecommands based on parameters like - packet APID or PUS service and subservice type. + packet APID and a target ID which is part of the packet payload. - A TM sink sink component which is the target of all sent telemetry and sends it to downlink handlers like the UDP and TCP server. You can read the [Communications chapter](./communication.md) for more background information on the chosen TMTC infrastructure approach. -### PUS Service Components - -A PUS service stack is provided which exposes some functionality conformant with the ECSS PUS -services. This currently includes the following services: - -- Service 1 for telecommand verification. The verification handling is handled locally: Each - component which generates verification telemetry in some shape or form receives a - [reporter](https://docs.rs/satrs/latest/satrs/pus/verification/struct.VerificationReporterWithSender.html) - object which can be used to send PUS 1 verification telemetry to the TM funnel. -- Service 3 for housekeeping telemetry handling. -- Service 5 for management and downlink of on-board events. -- Service 8 for handling on-board actions. -- Service 11 for scheduling telecommands to be released at a specific time. This component - uses the [PUS scheduler class](https://docs.rs/satrs/latest/satrs/pus/scheduler/alloc_mod/struct.PusScheduler.html) - which performs the core logic of scheduling telecommands. All telecommands released by the - scheduler are sent to the central TC source using a message. -- Service 17 for test purposes like pings. - -### Event Management Component - -An event manager based on the sat-rs -[event manager component](https://docs.rs/satrs/latest/satrs/event_man/index.html) -is provided to handle the event IPC and FDIR mechanism. The event message are converted to PUS 5 -telemetry by the -[PUS event dispatcher](https://docs.rs/satrs/latest/satrs/pus/event_man/alloc_mod/struct.PusEventDispatcher.html). - -You can read the [events](./events.md) chapter for more in-depth information about event management. - -### Sample Application Components - -These components are example mission specific. They provide an idea how mission specific modules -would look like the sat-rs context. It currently includes the following components: - -- An Attitute and Orbit Control (AOCS) example task which can also process some PUS commands. - ## Dataflow -The interaction of the various components is provided in the following diagram: +### TMTC component group -![satrs-example dataflow diagram](images/satrs-example/satrs-example-dataflow.png) - -It should be noted that an arrow coming out of a component group refers to multiple components -in that group. An explanation for important component groups will be given. - -#### TMTC component group - -This groups is the primary interface for clients to communicate with the on-board software -using a standardized TMTC protocol. The example uses the -[ECSS PUS protocol](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). +This group is the primary interface for clients to communicate with the on-board software +using the combination of CCSDS space packets and `serde` serialized payloads. In the future, this might be extended with the [CCSDS File Delivery Protocol](https://public.ccsds.org/Pubs/727x0b5.pdf). -A client can connect to the UDP or TCP server to send these PUS packets to the on-board software. -These servers then forward the telecommads to a centralized TC source component using a dedicated -message abstraction. +A client can connect to the UDP or TCP server to send telecommands to the on-board software. +These servers forward all telecommands to a centralized TC source component, which demultiplexes +them and routes each one to its target component. -This TC source component then demultiplexes the message and forwards it to the relevant components. -Right now, it forwards all PUS requests to the respective PUS service handlers using the PUS -receiver component. The individual PUS services are running in a separate thread. In the future, -additional forwarding to components like a CFDP handler might be added as well. It should be noted -that PUS11 commands might contain other PUS commands which should be scheduled in the future. -These wrapped commands are forwarded to the PUS11 handler. When the schedule releases those -commands, it forwards the released commands to the TC source again. This allows the scheduler -and the TC source to run in separate threads and keeps them cleanly separated. +All telemetry generated by the on-board software is sent to a centralized TM sink. The core +controller also forwards events to the event manager, which converts them into telemetry and +sends it to the TM sink as well. The TM sink performs a demultiplexing step to forward all +telemetry to the relevant recipients, which in the example case are the last connected UDP +client and any connected TCP client. -All telemetry generated by the on-board software is sent to a centralized TM funnel. This component -also performs a demultiplexing step to forward all telemetry to the relevant TM recipients. -In the example case, this is the last UDP client, or a connected TCP client. In the future, -forwarding to a persistent telemetry store and a simulated communication component might be -added here as well. The centralized TM funnel also takes care of some packet processing steps which -need to be applied for each ECSS PUS packet, for example CCSDS specific APID incrementation and -PUS specific message counter incrementation. +### Application Group -#### Application Group +The application group contain some components you might also find in a real satellite software. +This includes an AOCS stack with various device handlers and system level objects. -The application components generally do not receive raw PUS packets directly, even though -this is certainly possible. Instead, they receive internalized messages from the PUS service -handlers. For example, instead of receiving a PUS 8 Action Telecommand directly, an application -component will receive a special `ActionRequest` message type reduced to the basic important -information required to execute a request. These special requests are denoted by the blue arrow -in the diagram. - -It should be noted that the arrow pointing towards the event manager points in both directions. -This is because the application components might be interested in events generated by other -components as well. This mechanism is oftentimes used to implement the FDIR functionality on system -and component level. - -#### Shared components and functional interfaces - -It should be noted that sometimes, a functional interface is used instead of a message. This -is used for the generation of verification telemetry. The verification reporter is a clonable -component which generates and sends PUS1 verification telemetry directly to the TM funnel. This -introduces a loose coupling to the PUS standard but was considered the easiest solution for -a project which utilizes PUS as the main communication protocol. In the future, a generic -verification abstraction might be introduced to completely decouple the application layer from -PUS. - -The same concept is applied if the backing store of TMTC packets are shared pools. Every -component which needs to read telecommands inside that shared pool or generate new telemetry -into that shared pool will received a clonable shared handle to that pool. - -The same concept could be extended to power or thermal handling. For example, a shared power helper -component might be used to retrieve power state information and send power switch commands through -a functional interface. The actual implementation of the functional interface might still use -shared memory and/or messages, but the functional interface makes using and testing the interaction -with these components easier. +### Shared components and functional interfaces diff --git a/satrs-book/src/housekeeping.md b/satrs-book/src/housekeeping.md index 5a7d73b..9987a1a 100644 --- a/satrs-book/src/housekeeping.md +++ b/satrs-book/src/housekeeping.md @@ -1,11 +1,12 @@ # Housekeeping Data +If you have not read [the TMTC modelling chapter](./tmtc-modelling.md) yet, it is recommended to +do that first. + Remote systems like satellites and rovers oftentimes generate data autonomously and periodically. -The most common example for this is temperature or attitude data. Data like this is commonly +An example for this could be temperature or attitude data. Data like this is commonly referred to as housekeeping data, and is usually one of the most important and most resource heavy -data sources received from a satellite. Standards like the PUS Service 3 make recommendation how to -expose housekeeping data, but the applicability of the interface offered by PUS 3 has proven to be -partially difficult and clunky for modular systems. +data sources received from a satellite. First, we are going to list some assumption and requirements about Housekeeping (HK) data: @@ -18,7 +19,89 @@ First, we are going to list some assumption and requirements about Housekeeping 3. HK data often needs to be shared to other software components. For example, a thermal controller wants to read the data samples of all sensor components. -A commonly required way to model HK data in a clean way is also to group related HK data into sets, -which can then dumped via a similar interface. +## Modelling our data -TODO: Write down `sat-rs` recommendations how to expose and work with HK data. +Generally, it makes sense to model the data with Rust data structures for various reasons. For +example, the sensor data received from a 3-axis magnetometer might me modelled like this: + +```rust +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub struct MgmData { + pub x: i16, + pub y: i16, + pub z: i16, +} +``` + +You can then re-use this data structure for various purposes. Also note the `serde` implementations, +which are useful for generating the housekeeping data sent to ground. + +We can model the housekeeping requests for a handler with a single data set like this: + +```rust +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub enum HkRequest { + OneShot, + EnablePeriodic, + DisablePeriodic, + ModifyInterval(core::time::Duration) + +} +``` + +which might then be a part of a top level request type, e.g. + +```rust +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub enum Request { + Ping, + Hk(HkRequest) +} +``` + +A corresponding `Response` type might just include a HK data variant: + +```rust +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub enum Response { + Ok, + Hk(MgmData) +} +``` + +If the software object managed multiple data sets, you could model it like this: + +```rust +/// Example set ID. +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub enum SetId { + Data, + Config +} + +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub enum Request { + Ping, + Hk { + set_id: SetId, + request: HkRequest + } +} +``` + +Sometimes, you need to share the generated data as well. Furthermore, it might make sense to +decouple the HK generation from the data acquisition and only return the latest snapshot +of the data. In this case, you can put the `MgmData` inside an appropriate lock structure for your +platform/runtime to share it safely with other software components. For example, in a `std` system, +you might simply use an `Arc>` or a `Arc>` for this. + +Now, you can update that shared data structure when acquiring new data, and other software objects +or the HK generation routine can safely read from it. + +## Helper components + +You need some application logic to track whether periodic data generation is enabled, what +the current generation interval is and whether a HK set needs to be generated if the interval +period has elapsed. + +`sat-rs` provides some simple helper components for this inside the [`hk`](https://docs.rs/satrs/latest/satrs/hk/index.html) module. The module documentation contains more information. diff --git a/satrs-book/src/modes-and-health.md b/satrs-book/src/modes-and-health.md index e5b0193..4a05081 100644 --- a/satrs-book/src/modes-and-health.md +++ b/satrs-book/src/modes-and-health.md @@ -73,8 +73,6 @@ In summary, a component which has modes has to expose the following 4 capabiliti 3. Announce the mode 4. Announce the mode recursively -## Using ECSS PUS to perform mode commanding - # Health Health is an important concept for systems and components which might fail. @@ -99,5 +97,3 @@ could be power-cycled if there were multiple communication issues in the last ti example, on operator might be interested in testing a component in isolation, and the interference of the system is not desired. In that case, the `EXTERNAL CONTROL` health state might be used to prevent mode commands from the system while allowing external mode commands. - - diff --git a/satrs-example/src/config.rs b/satrs-example/src/config.rs index 16be4cf..3b90006 100644 --- a/satrs-example/src/config.rs +++ b/satrs-example/src/config.rs @@ -1,7 +1,7 @@ use arbitrary_int::u11; use lazy_static::lazy_static; use satrs::{ - res_code::ResultU16, + legacy::res_code::ResultU16, spacepackets::{PacketId, PacketType}, }; use satrs_mib::res_code::ResultU16Info; diff --git a/satrs-example/src/control.rs b/satrs-example/src/controller.rs similarity index 100% rename from satrs-example/src/control.rs rename to satrs-example/src/controller.rs diff --git a/satrs-example/src/interface/sim_client_udp.rs b/satrs-example/src/interface/sim_client_udp.rs index bec6db4..87b2d50 100644 --- a/satrs-example/src/interface/sim_client_udp.rs +++ b/satrs-example/src/interface/sim_client_udp.rs @@ -5,7 +5,7 @@ use std::{ time::Duration, }; -use satrs::pus::HandlingStatus; +use satrs::HandlingStatus; use satrs_minisim::{ SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimReply, SimRequest, udp::SIM_CTRL_PORT, diff --git a/satrs-example/src/interface/udp.rs b/satrs-example/src/interface/udp.rs index da20c98..b8c5e79 100644 --- a/satrs-example/src/interface/udp.rs +++ b/satrs-example/src/interface/udp.rs @@ -4,8 +4,8 @@ use std::net::{SocketAddr, UdpSocket}; use std::sync::{Arc, Mutex, mpsc}; use log::warn; +use satrs::HandlingStatus; use satrs::hal::std::udp_server::{ReceiveResult, UdpTcServer}; -use satrs::pus::HandlingStatus; use satrs::queue::GenericSendError; use types::ccsds::CcsdsTmPacketOwned; diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index 30ba351..8eeb968 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -21,8 +21,8 @@ use interface::{ use log::info; use logger::setup_logger; use satrs::{ + HandlingStatus, hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer}, - pus::HandlingStatus, spacepackets::time::cds::CdsTime, }; use satrs_example::{ @@ -38,7 +38,7 @@ use types::{ComponentId, DeviceMode}; use crate::{ acs::{ctrl, mgm, mgm_assembly, mgt, subsystem}, - control::Controller, + controller::Controller, eps::pcdu::SwitchSet, event_manager::EventManager, interface::udp::UdpTmHandlerWithChannel, @@ -47,7 +47,7 @@ use crate::{ mod acs; mod ccsds; -mod control; +mod controller; mod eps; mod event_manager; mod interface; diff --git a/satrs-example/src/tmtc/tc_source.rs b/satrs-example/src/tmtc/tc_source.rs index 4d3c005..bfbde64 100644 --- a/satrs-example/src/tmtc/tc_source.rs +++ b/satrs-example/src/tmtc/tc_source.rs @@ -1,5 +1,5 @@ use satrs::{ - pus::HandlingStatus, + HandlingStatus, spacepackets::{CcsdsPacketReader, ChecksumType}, tmtc::PacketAsVec, }; diff --git a/satrs/CHANGELOG.md b/satrs/CHANGELOG.md index d154323..9e41552 100644 --- a/satrs/CHANGELOG.md +++ b/satrs/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +- Added `hk` module helpers to track whether a single HK set needs regeneration: + `SingleSetHkHelperStd` (`std`), `SingleSetHkHelperEmbassy` (new `embassy-time` feature), + and `SingleSetHkHelperCountdown`, generic over the existing `Countdown` trait. + # [v0.3.0-alpha.3] 2025-11-06 - Bump `sat-rs` edition to 2024. diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index 8140fee..023ea3b 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -35,6 +35,7 @@ socket2 = { version = "0.6", features = ["all"], optional = true } arbitrary-int = "2" mio = { version = "1", features = ["os-poll", "net"], optional = true } defmt = { version = "1", optional = true } +embassy-time = { version = "0.5", optional = true } [dev-dependencies] serde = "1" @@ -72,6 +73,7 @@ alloc = [ serde = ["dep:serde", "spacepackets/serde", "satrs-shared/serde"] crossbeam = ["crossbeam-channel"] defmt = ["dep:defmt", "spacepackets/defmt"] +embassy-time = ["dep:embassy-time"] test_util = [] [package.metadata.docs.rs] diff --git a/satrs/src/hk.rs b/satrs/src/hk.rs new file mode 100644 index 0000000..e323131 --- /dev/null +++ b/satrs/src/hk.rs @@ -0,0 +1,181 @@ +//! # HK generation helpers +//! +//! Each helper contains the minimal state to support the periodic generation of housekeeping +//! packets. Call [SingleSetHkHelperStd::needs_generation](crate::hk::SingleSetHkHelperStd::needs_generation) periodically, for example once per task +//! cycle. When it returns `true`, generate the HK set and send it, the helper has already reset +//! its clock for the next period. +//! +//! Pick a helper based on what clock is available: +//! +//! - [SingleSetHkHelperStd](crate::hk::SingleSetHkHelperStd): `std::time::Instant`, behind the `std` feature. +//! - [SingleSetHkHelperEmbassy](crate::hk::SingleSetHkHelperEmbassy): `embassy_time::Instant`, behind the `embassy-time` feature. +//! - [SingleSetHkHelperCountdown](crate::hk::SingleSetHkHelperCountdown): any clock implementing [Countdown](crate::time::Countdown), for example a +//! `fugit`-based monotonic. +//! +//! If your software object has multiple HK sets, you can simply put the helpers inside a dynamic +//! list like [alloc::vec::Vec], [heapless::vec::Vec] or a hash map. +#![deny(missing_docs)] +use crate::time::Countdown; + +/// Generic single-set HK helper for any clock, backed by a [Countdown] implementation. +/// +/// Useful for clocks not covered by [SingleSetHkHelperStd] or [SingleSetHkHelperEmbassy], for +/// example a `fugit`-based monotonic. Users implement [Countdown] for their clock and hand it in. +pub struct SingleSetHkHelperCountdown { + countdown: C, + enabled: bool, +} + +impl SingleSetHkHelperCountdown { + /// Create a new, enabled helper wrapping the given countdown. + pub fn new(countdown: C) -> Self { + Self { + countdown, + enabled: true, + } + } + + /// Reference to the wrapped countdown. + pub fn countdown(&self) -> &C { + &self.countdown + } + + /// Mutable reference to the wrapped countdown. + pub fn countdown_mut(&mut self) -> &mut C { + &mut self.countdown + } + + /// Whether the helper is enabled. + pub fn enabled(&self) -> bool { + self.enabled + } + + /// Enable or disable the helper. + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + } + + /// Returns true if the HK set needs regeneration, resetting the countdown. + /// + /// Always returns false while disabled, leaving the countdown untouched. + pub fn needs_generation(&mut self) -> bool { + if !self.enabled { + return false; + } + if self.countdown.has_expired() { + self.countdown.reset(); + return true; + } + false + } +} + +/// Single-set HK helper backed by [std::time::Instant]. +#[cfg(feature = "std")] +pub struct SingleSetHkHelperStd { + interval: core::time::Duration, + last_generated: std::time::Instant, + enabled: bool, +} + +#[cfg(feature = "std")] +impl SingleSetHkHelperStd { + /// Create a new, enabled helper with the given interval, starting the clock now. + pub fn new(initial_interval: core::time::Duration) -> Self { + Self { + interval: initial_interval, + last_generated: std::time::Instant::now(), + enabled: true, + } + } + + /// Update the generation interval. + pub fn update_interval(&mut self, interval: core::time::Duration) { + self.interval = interval; + } + + /// Current generation interval. + pub fn interval(&self) -> core::time::Duration { + self.interval + } + + /// Whether the helper is enabled. + pub fn enabled(&self) -> bool { + self.enabled + } + + /// Enable or disable the helper. + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + } + + /// Returns true if the HK set needs regeneration. + /// + /// Always returns false while disabled, leaving the clock untouched. + pub fn needs_generation(&mut self) -> bool { + if !self.enabled { + return false; + } + let now = std::time::Instant::now(); + if now - self.last_generated > self.interval { + self.last_generated = now; + return true; + } + false + } +} + +/// Single-set HK helper backed by [embassy_time::Instant]. +#[cfg(feature = "embassy-time")] +pub struct SingleSetHkHelperEmbassy { + interval: embassy_time::Duration, + last_generated: embassy_time::Instant, + enabled: bool, +} + +#[cfg(feature = "embassy-time")] +impl SingleSetHkHelperEmbassy { + /// Create a new, enabled helper with the given interval, starting the clock now. + pub fn new(initial_interval: embassy_time::Duration) -> Self { + Self { + interval: initial_interval, + last_generated: embassy_time::Instant::now(), + enabled: true, + } + } + + /// Update the generation interval. + pub fn update_interval(&mut self, interval: embassy_time::Duration) { + self.interval = interval; + } + + /// Current generation interval. + pub fn interval(&self) -> embassy_time::Duration { + self.interval + } + + /// Whether the helper is enabled. + pub fn enabled(&self) -> bool { + self.enabled + } + + /// Enable or disable the helper. + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + } + + /// Returns true if the HK set needs regeneration. + /// + /// Always returns false while disabled, leaving the clock untouched. + pub fn needs_generation(&mut self) -> bool { + if !self.enabled { + return false; + } + let now = embassy_time::Instant::now(); + if now - self.last_generated > self.interval { + self.last_generated = now; + return true; + } + false + } +} diff --git a/satrs/src/action.rs b/satrs/src/legacy/action.rs similarity index 100% rename from satrs/src/action.rs rename to satrs/src/legacy/action.rs diff --git a/satrs/src/legacy/event_man.rs b/satrs/src/legacy/event_man.rs index c774d4f..7d5cce7 100644 --- a/satrs/src/legacy/event_man.rs +++ b/satrs/src/legacy/event_man.rs @@ -579,7 +579,7 @@ mod tests { use super::*; use crate::legacy::events::{EventErasedAlloc, Severity}; - use crate::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1}; + use crate::legacy::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1}; use std::sync::mpsc; const TEST_GROUP_ID_0: u14 = u14::new(0); diff --git a/satrs/src/legacy/event_man_legacy.rs b/satrs/src/legacy/event_man_legacy.rs index 9f567a9..3ee22d7 100644 --- a/satrs/src/legacy/event_man_legacy.rs +++ b/satrs/src/legacy/event_man_legacy.rs @@ -591,7 +591,7 @@ mod tests { use crate::legacy::event_man_legacy::EventManager; use crate::legacy::events_legacy::{EventU32, GenericEvent, Severity}; use crate::params::{ParamsHeapless, ParamsRaw}; - use crate::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1}; + use crate::legacy::pus::test_util::{TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1}; use std::format; use std::sync::mpsc::{self}; diff --git a/satrs/src/legacy/mod.rs b/satrs/src/legacy/mod.rs index 9ffb557..aa2284a 100644 --- a/satrs/src/legacy/mod.rs +++ b/satrs/src/legacy/mod.rs @@ -1,4 +1,6 @@ +pub mod action; pub mod event_man; pub mod events; +pub mod res_code; pub mod pus; diff --git a/satrs/src/pus/action.rs b/satrs/src/legacy/pus/action.rs similarity index 98% rename from satrs/src/pus/action.rs rename to satrs/src/legacy/pus/action.rs index eb84b94..87c8b3f 100644 --- a/satrs/src/pus/action.rs +++ b/satrs/src/legacy/pus/action.rs @@ -1,5 +1,5 @@ use crate::{ - action::{ActionId, ActionRequest}, + legacy::action::{ActionId, ActionRequest}, params::Params, request::{GenericMessage, MessageMetadata, RequestId}, }; @@ -66,7 +66,7 @@ impl GenericActionReplyPus { pub mod alloc_mod { use crate::{ ComponentId, - action::ActionRequest, + legacy::action::ActionRequest, queue::{GenericReceiveError, GenericSendError}, request::{ GenericMessage, MessageReceiverProvider, MessageSenderAndReceiver, @@ -142,7 +142,7 @@ pub mod std_mod { use crate::{ ComponentId, - pus::{ + legacy::pus::{ ActivePusRequestStd, ActiveRequest, DefaultActiveRequestMap, verification::{self, TcStateToken}, }, diff --git a/satrs/src/legacy/pus/event.rs b/satrs/src/legacy/pus/event.rs index 57a3bc1..ea8394c 100644 --- a/satrs/src/legacy/pus/event.rs +++ b/satrs/src/legacy/pus/event.rs @@ -1,4 +1,3 @@ -use crate::pus::source_buffer_large_enough; use arbitrary_int::u11; use spacepackets::ByteConversionError; use spacepackets::SpHeader; @@ -110,7 +109,7 @@ impl EventReportCreator { if let Some(aux_data) = params { src_data_len += aux_data.len(); } - source_buffer_large_enough(src_data_buf.len(), src_data_len)?; + super::source_buffer_large_enough(src_data_buf.len(), src_data_len)?; let sec_header = PusTmSecondaryHeader::new( MessageTypeId::new(5, subservice.into()), 0, @@ -137,7 +136,7 @@ impl EventReportCreator { mod alloc_mod { use super::*; use crate::ComponentId; - use crate::pus::{EcssTmSender, EcssTmtcError}; + use crate::legacy::pus::{EcssTmSender, EcssTmtcError}; use alloc::vec; use alloc::vec::Vec; use core::cell::RefCell; diff --git a/satrs/src/legacy/pus/mod.rs b/satrs/src/legacy/pus/mod.rs index 53f1126..33cebda 100644 --- a/satrs/src/legacy/pus/mod.rs +++ b/satrs/src/legacy/pus/mod.rs @@ -1 +1,1693 @@ +//! # PUS support modules +//! +//! This module contains structures to make working with the PUS C standard easier. +//! The satrs-example application contains various usage examples of these components. pub mod event; + +use self::verification::{TcStateAccepted, TcStateToken, VerificationToken}; +use crate::ComponentId; +use crate::pool::{PoolAddr, PoolError}; +use crate::queue::{GenericReceiveError, GenericSendError}; +use crate::request::{GenericMessage, MessageMetadata, RequestId}; +#[cfg(feature = "alloc")] +use crate::tmtc::PacketAsVec; +use crate::tmtc::PacketInPool; +use core::fmt::{Display, Formatter}; +use core::time::Duration; +#[cfg(feature = "alloc")] +use downcast_rs::{Downcast, impl_downcast}; +#[cfg(feature = "alloc")] +use dyn_clone::DynClone; +#[cfg(feature = "std")] +use std::error::Error; + +use spacepackets::ecss::PusError; +use spacepackets::ecss::tc::{PusTcCreator, PusTcReader}; +use spacepackets::ecss::tm::PusTmCreator; +use spacepackets::{ByteConversionError, SpHeader}; + +pub mod action; +pub mod mode; +pub mod scheduler; +#[cfg(feature = "std")] +pub mod scheduler_srv; +#[cfg(feature = "std")] +pub mod test; +pub mod verification; + +#[cfg(feature = "alloc")] +pub use alloc_mod::*; + +#[cfg(feature = "std")] +pub use std_mod::*; + +use self::verification::VerificationReportingProvider; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum PusTmVariant<'time, 'src_data> { + InStore(PoolAddr), + Direct(PusTmCreator<'time, 'src_data>), +} + +impl From for PusTmVariant<'_, '_> { + fn from(value: PoolAddr) -> Self { + Self::InStore(value) + } +} + +impl<'time, 'src_data> From> for PusTmVariant<'time, 'src_data> { + fn from(value: PusTmCreator<'time, 'src_data>) -> Self { + Self::Direct(value) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EcssTmtcError { + Store(PoolError), + ByteConversion(ByteConversionError), + Pus(PusError), + CantSendAddr(PoolAddr), + CantSendDirectTm, + Send(GenericSendError), + Receive(GenericReceiveError), +} + +impl Display for EcssTmtcError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + EcssTmtcError::Store(store) => { + write!(f, "ecss tmtc error: {store}") + } + EcssTmtcError::ByteConversion(e) => { + write!(f, "ecss tmtc error: {e}") + } + EcssTmtcError::Pus(e) => { + write!(f, "ecss tmtc error: {e}") + } + EcssTmtcError::CantSendAddr(addr) => { + write!(f, "can not send address {addr}") + } + EcssTmtcError::CantSendDirectTm => { + write!(f, "can not send TM directly") + } + EcssTmtcError::Send(e) => { + write!(f, "ecss tmtc error: {e}") + } + EcssTmtcError::Receive(e) => { + write!(f, "ecss tmtc error {e}") + } + } + } +} + +impl From for EcssTmtcError { + fn from(value: PoolError) -> Self { + Self::Store(value) + } +} + +impl From for EcssTmtcError { + fn from(value: PusError) -> Self { + Self::Pus(value) + } +} + +impl From for EcssTmtcError { + fn from(value: GenericSendError) -> Self { + Self::Send(value) + } +} + +impl From for EcssTmtcError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversion(value) + } +} + +impl From for EcssTmtcError { + fn from(value: GenericReceiveError) -> Self { + Self::Receive(value) + } +} + +#[cfg(feature = "std")] +impl Error for EcssTmtcError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + EcssTmtcError::Store(e) => Some(e), + EcssTmtcError::ByteConversion(e) => Some(e), + EcssTmtcError::Pus(e) => Some(e), + EcssTmtcError::Send(e) => Some(e), + EcssTmtcError::Receive(e) => Some(e), + _ => None, + } + } +} +pub trait ChannelWithId: Send { + /// Each sender can have an ID associated with it + fn id(&self) -> ComponentId; + fn name(&self) -> &'static str { + "unset" + } +} + +/// Generic trait for a user supplied sender object. +/// +/// This sender object is responsible for sending PUS telemetry to a TM sink. +pub trait EcssTmSender: Send { + fn send_tm(&self, sender_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError>; +} + +/// Generic trait for a user supplied sender object. +/// +/// This sender object is responsible for sending PUS telecommands to a TC recipient. Each +/// telecommand can optionally have a token which contains its verification state. +pub trait EcssTcSender { + fn send_tc(&self, tc: PusTcCreator, token: Option) -> Result<(), EcssTmtcError>; +} + +/// Dummy object which can be useful for tests. +#[derive(Default)] +pub struct EcssTmDummySender {} + +impl EcssTmSender for EcssTmDummySender { + fn send_tm(&self, _source_id: ComponentId, _tm: PusTmVariant) -> Result<(), EcssTmtcError> { + Ok(()) + } +} + +/// A PUS telecommand packet can be stored in memory and sent using different methods. Right now, +/// storage inside a pool structure like [crate::pool::StaticMemoryPool], and storage inside a +/// `Vec` are supported. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TcInMemory { + Pool(PacketInPool), + #[cfg(feature = "alloc")] + Vec(PacketAsVec), +} + +impl From for TcInMemory { + fn from(value: PacketInPool) -> Self { + Self::Pool(value) + } +} + +#[cfg(feature = "alloc")] +impl From for TcInMemory { + fn from(value: PacketAsVec) -> Self { + Self::Vec(value) + } +} + +/// Generic structure for an ECSS PUS Telecommand and its correspoding verification token. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EcssTcAndToken { + pub tc_in_memory: TcInMemory, + pub token: Option, +} + +impl EcssTcAndToken { + pub fn new(tc_in_memory: impl Into, token: impl Into) -> Self { + Self { + tc_in_memory: tc_in_memory.into(), + token: Some(token.into()), + } + } +} + +/// Generic abstraction for a telecommand being sent around after is has been accepted. +pub struct AcceptedEcssTcAndToken { + pub tc_in_memory: TcInMemory, + pub token: VerificationToken, +} + +impl From for EcssTcAndToken { + fn from(value: AcceptedEcssTcAndToken) -> Self { + EcssTcAndToken { + tc_in_memory: value.tc_in_memory, + token: Some(value.token.into()), + } + } +} + +impl TryFrom for AcceptedEcssTcAndToken { + type Error = (); + + fn try_from(value: EcssTcAndToken) -> Result { + if let Some(TcStateToken::Accepted(token)) = value.token { + return Ok(AcceptedEcssTcAndToken { + tc_in_memory: value.tc_in_memory, + token, + }); + } + Err(()) + } +} + +#[derive(Debug, Clone)] +pub enum TryRecvTmtcError { + Tmtc(EcssTmtcError), + Empty, +} + +impl From for TryRecvTmtcError { + fn from(value: EcssTmtcError) -> Self { + Self::Tmtc(value) + } +} + +impl From for TryRecvTmtcError { + fn from(value: PusError) -> Self { + Self::Tmtc(value.into()) + } +} + +impl From for TryRecvTmtcError { + fn from(value: PoolError) -> Self { + Self::Tmtc(value.into()) + } +} + +/// Generic trait for a user supplied receiver object. +pub trait EcssTcReceiver { + fn recv_tc(&self) -> Result; +} + +/// Generic trait for objects which can send ECSS PUS telecommands. +pub trait PacketSenderPusTc: Send { + type Error; + fn send_pus_tc( + &self, + sender_id: ComponentId, + header: &SpHeader, + pus_tc: &PusTcReader, + ) -> Result<(), Self::Error>; +} + +pub trait ActiveRequestStore: Sized { + fn insert(&mut self, request_id: &RequestId, request_info: V); + fn get(&self, request_id: RequestId) -> Option<&V>; + fn get_mut(&mut self, request_id: RequestId) -> Option<&mut V>; + fn remove(&mut self, request_id: RequestId) -> bool; + + /// Call a user-supplied closure for each active request. + fn for_each(&self, f: F); + + /// Call a user-supplied closure for each active request. Mutable variant. + fn for_each_mut(&mut self, f: F); +} + +pub trait ActiveRequest { + fn target_id(&self) -> ComponentId; + fn token(&self) -> TcStateToken; + fn set_token(&mut self, token: TcStateToken); + fn has_timed_out(&self) -> bool; + fn timeout(&self) -> Duration; +} + +/// This trait is an abstraction for the routing of PUS request to a dedicated +/// recipient using the generic [ComponentId]. +pub trait PusRequestRouter { + type Error; + + fn route( + &self, + requestor_info: MessageMetadata, + target_id: ComponentId, + request: Request, + ) -> Result<(), Self::Error>; +} + +pub trait PusReplyHandler { + type Error; + + /// This function handles a reply for a given PUS request and returns whether that request + /// is finished. A finished PUS request will be removed from the active request map. + fn handle_reply( + &mut self, + reply: &GenericMessage, + active_request: &ActiveRequestInfo, + tm_sender: &impl EcssTmSender, + verification_handler: &impl VerificationReportingProvider, + time_stamp: &[u8], + ) -> Result; + + fn handle_unrequested_reply( + &mut self, + reply: &GenericMessage, + tm_sender: &impl EcssTmSender, + ) -> Result<(), Self::Error>; + + /// Handle the timeout of an active request. + fn handle_request_timeout( + &mut self, + active_request: &ActiveRequestInfo, + tm_sender: &impl EcssTmSender, + verification_handler: &impl VerificationReportingProvider, + time_stamp: &[u8], + ) -> Result<(), Self::Error>; +} + +#[cfg(feature = "alloc")] +pub mod alloc_mod { + use hashbrown::HashMap; + + use super::*; + + /// Extension trait for [EcssTmSender]. + /// + /// It provides additional functionality, for example by implementing the [Downcast] trait + /// and the [DynClone] trait. + /// + /// [Downcast] is implemented to allow passing the sender as a boxed trait object and still + /// retrieve the concrete type at a later point. + /// + /// [DynClone] allows cloning the trait object as long as the boxed object implements + /// [Clone]. + #[cfg(feature = "alloc")] + pub trait EcssTmSenderExt: EcssTmSender + Downcast + DynClone { + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast(&self) -> &dyn EcssTmSender; + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast_mut(&mut self) -> &mut dyn EcssTmSender; + } + + /// Blanket implementation for all types which implement [EcssTmSender] and are clonable. + impl EcssTmSenderExt for T + where + T: EcssTmSender + Clone + 'static, + { + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast(&self) -> &dyn EcssTmSender { + self + } + // Remove this once trait upcasting coercion has been implemented. + // Tracking issue: https://github.com/rust-lang/rust/issues/65991 + fn upcast_mut(&mut self) -> &mut dyn EcssTmSender { + self + } + } + + dyn_clone::clone_trait_object!(EcssTmSenderExt); + impl_downcast!(EcssTmSenderExt); + + /// Extension trait for [EcssTcSender]. + /// + /// It provides additional functionality, for example by implementing the [Downcast] trait + /// and the [DynClone] trait. + /// + /// [Downcast] is implemented to allow passing the sender as a boxed trait object and still + /// retrieve the concrete type at a later point. + /// + /// [DynClone] allows cloning the trait object as long as the boxed object implements + /// [Clone]. + #[cfg(feature = "alloc")] + pub trait EcssTcSenderExt: EcssTcSender + Downcast + DynClone {} + + /// Blanket implementation for all types which implement [EcssTcSender] and are clonable. + impl EcssTcSenderExt for T where T: EcssTcSender + Clone + 'static {} + + dyn_clone::clone_trait_object!(EcssTcSenderExt); + impl_downcast!(EcssTcSenderExt); + + /// Extension trait for [EcssTcReceiver]. + /// + /// It provides additional functionality, for example by implementing the [Downcast] trait + /// and the [DynClone] trait. + /// + /// [Downcast] is implemented to allow passing the sender as a boxed trait object and still + /// retrieve the concrete type at a later point. + /// + /// [DynClone] allows cloning the trait object as long as the boxed object implements + /// [Clone]. + #[cfg(feature = "alloc")] + pub trait EcssTcReceiverExt: EcssTcReceiver + Downcast {} + + /// Blanket implementation for all types which implement [EcssTcReceiver] and are clonable. + impl EcssTcReceiverExt for T where T: EcssTcReceiver + 'static {} + + impl_downcast!(EcssTcReceiverExt); + + /// This trait is an abstraction for the conversion of a PUS telecommand into a generic request + /// type. + /// + /// Having a dedicated trait for this allows maximum flexiblity and tailoring of the standard. + /// The only requirement is that a valid active request information instance and a request + /// are returned by the core conversion function. The active request type needs to fulfill + /// the [ActiveRequest] trait bound. + /// + /// 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 [VerificationReportingProvider] instance is passed to the user to also allow handling + /// of the verification process as part of the PUS standard requirements. + pub trait PusTcToRequestConverter { + type Error; + fn convert( + &mut self, + token: VerificationToken, + tc: &PusTcReader, + tm_sender: &(impl EcssTmSender + ?Sized), + verif_reporter: &impl VerificationReportingProvider, + time_stamp: &[u8], + ) -> Result<(ActiveRequestInfo, Request), Self::Error>; + } + + #[derive(Clone, Debug)] + pub struct DefaultActiveRequestMap(pub HashMap); + + impl Default for DefaultActiveRequestMap { + fn default() -> Self { + Self(HashMap::new()) + } + } + + impl ActiveRequestStore for DefaultActiveRequestMap { + fn insert(&mut self, request_id: &RequestId, request: V) { + self.0.insert(*request_id, request); + } + + fn get(&self, request_id: RequestId) -> Option<&V> { + self.0.get(&request_id) + } + + fn get_mut(&mut self, request_id: RequestId) -> Option<&mut V> { + self.0.get_mut(&request_id) + } + + fn remove(&mut self, request_id: RequestId) -> bool { + self.0.remove(&request_id).is_some() + } + + fn for_each(&self, mut f: F) { + for (req_id, active_req) in &self.0 { + f(req_id, active_req); + } + } + + fn for_each_mut(&mut self, mut f: F) { + for (req_id, active_req) in &mut self.0 { + f(req_id, active_req); + } + } + } + + /* + /// Generic reply handler structure which can be used to handle replies for a specific PUS + /// service. + /// + /// This is done by keeping track of active requests using an internal map structure. An API + /// to register new active requests is exposed as well. + /// The reply handler performs boilerplate tasks like performing the verification handling and + /// timeout handling. + /// + /// This object is not useful by itself but serves as a common building block for high-level + /// PUS reply handlers. Concrete PUS handlers should constrain the [ActiveRequestProvider] and + /// the `ReplyType` generics to specific types tailored towards PUS services in addition to + /// providing an API which can process received replies and convert them into verification + /// completions or other operation like user hook calls. The framework also provides some + /// concrete PUS handlers for common PUS services like the mode, action and housekeeping + /// service. + /// + /// This object does not automatically update its internal time information used to check for + /// timeouts. The user should call the [Self::update_time] and [Self::update_time_from_now] + /// methods to do this. + pub struct PusServiceReplyHandler< + ActiveRequestMap: ActiveRequestMapProvider, + ReplyHook: ReplyHandlerHook, + ActiveRequestType: ActiveRequestProvider, + ReplyType, + > { + pub active_request_map: ActiveRequestMap, + pub tm_buf: alloc::vec::Vec, + pub current_time: UnixTimestamp, + pub user_hook: ReplyHook, + phantom: PhantomData<(ActiveRequestType, ReplyType)>, + } + + impl< + ActiveRequestMap: ActiveRequestMapProvider, + ReplyHook: ReplyHandlerHook, + ActiveRequestType: ActiveRequestProvider, + ReplyType, + > + PusServiceReplyHandler< + ActiveRequestMap, + ReplyHook, + ActiveRequestType, + ReplyType, + > + { + #[cfg(feature = "std")] + pub fn new_from_now( + active_request_map: ActiveRequestMap, + fail_data_buf_size: usize, + user_hook: ReplyHook, + ) -> Result { + let current_time = UnixTimestamp::from_now()?; + Ok(Self::new( + active_request_map, + fail_data_buf_size, + user_hook, + tm_sender, + current_time, + )) + } + + pub fn new( + active_request_map: ActiveRequestMap, + fail_data_buf_size: usize, + user_hook: ReplyHook, + tm_sender: TmSender, + init_time: UnixTimestamp, + ) -> Self { + Self { + active_request_map, + tm_buf: alloc::vec![0; fail_data_buf_size], + current_time: init_time, + user_hook, + tm_sender, + phantom: PhantomData, + } + } + + pub fn add_routed_request( + &mut self, + request_id: verification::RequestId, + active_request_type: ActiveRequestType, + ) { + self.active_request_map + .insert(&request_id.into(), active_request_type); + } + + pub fn request_active(&self, request_id: RequestId) -> bool { + self.active_request_map.get(request_id).is_some() + } + + /// 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> { + let mut timed_out_commands = alloc::vec::Vec::new(); + self.active_request_map.for_each(|request_id, active_req| { + let diff = self.current_time - active_req.start_time(); + if diff.duration_absolute > active_req.timeout() { + self.handle_timeout(active_req, time_stamp); + } + timed_out_commands.push(*request_id); + }); + for timed_out_command in timed_out_commands { + self.active_request_map.remove(timed_out_command); + } + 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: &ActiveRequestType, 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(), + &timeout_raw, + ), + ) + .unwrap(); + self.user_hook.timeout_callback(active_request); + } + + /// Update the current time used for timeout checks based on the current OS time. + #[cfg(feature = "std")] + pub fn update_time_from_now(&mut self) -> Result<(), std::time::SystemTimeError> { + self.current_time = UnixTimestamp::from_now()?; + Ok(()) + } + + /// Update the current time used for timeout checks. + pub fn update_time(&mut self, time: UnixTimestamp) { + self.current_time = time; + } + } + */ +} + +#[cfg(feature = "std")] +pub mod std_mod { + use super::verification::{TcStateAccepted, VerificationToken}; + use super::*; + use crate::ComponentId; + use crate::HandlingStatus; + use crate::pool::{ + PoolAddr, PoolError, PoolProvider, PoolProviderWithGuards, SharedStaticMemoryPool, + }; + use crate::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; + use alloc::vec::Vec; + use core::time::Duration; + use spacepackets::ByteConversionError; + use spacepackets::ecss::WritablePusPacket; + use spacepackets::ecss::tc::PusTcReader; + use spacepackets::time::StdTimestampError; + use std::string::String; + use std::sync::mpsc; + use std::sync::mpsc::TryRecvError; + use thiserror::Error; + + #[cfg(feature = "crossbeam")] + pub use cb_mod::*; + + use super::verification::{TcStateToken, VerificationReportingProvider}; + use super::{AcceptedEcssTcAndToken, ActiveRequest, TcInMemory}; + use crate::tmtc::PacketInPool; + + impl From> for EcssTmtcError { + fn from(_: mpsc::SendError) -> Self { + Self::Send(GenericSendError::RxDisconnected) + } + } + + impl EcssTmSender for mpsc::Sender { + fn send_tm(&self, source_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> { + match tm { + PusTmVariant::InStore(store_addr) => self + .send(PacketInPool { + sender_id: source_id, + store_addr, + }) + .map_err(|_| GenericSendError::RxDisconnected)?, + PusTmVariant::Direct(_) => return Err(EcssTmtcError::CantSendDirectTm), + }; + Ok(()) + } + } + + impl EcssTmSender for mpsc::SyncSender { + fn send_tm(&self, source_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> { + match tm { + PusTmVariant::InStore(store_addr) => self + .try_send(PacketInPool { + sender_id: source_id, + store_addr, + }) + .map_err(|e| EcssTmtcError::Send(e.into()))?, + PusTmVariant::Direct(_) => return Err(EcssTmtcError::CantSendDirectTm), + }; + Ok(()) + } + } + + pub type MpscTmAsVecSender = mpsc::Sender; + + impl EcssTmSender for MpscTmAsVecSender { + fn send_tm(&self, source_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> { + match tm { + PusTmVariant::InStore(addr) => return Err(EcssTmtcError::CantSendAddr(addr)), + PusTmVariant::Direct(tm) => self + .send(PacketAsVec { + sender_id: source_id, + packet: tm.to_vec()?, + }) + .map_err(|e| EcssTmtcError::Send(e.into()))?, + }; + Ok(()) + } + } + + pub type MpscTmAsVecSenderBounded = mpsc::SyncSender; + + impl EcssTmSender for MpscTmAsVecSenderBounded { + fn send_tm(&self, source_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> { + match tm { + PusTmVariant::InStore(addr) => return Err(EcssTmtcError::CantSendAddr(addr)), + PusTmVariant::Direct(tm) => self + .send(PacketAsVec { + sender_id: source_id, + packet: tm.to_vec()?, + }) + .map_err(|e| EcssTmtcError::Send(e.into()))?, + }; + Ok(()) + } + } + + pub type MpscTcReceiver = mpsc::Receiver; + + impl EcssTcReceiver for MpscTcReceiver { + fn recv_tc(&self) -> Result { + self.try_recv().map_err(|e| match e { + TryRecvError::Empty => TryRecvTmtcError::Empty, + TryRecvError::Disconnected => TryRecvTmtcError::Tmtc(EcssTmtcError::from( + GenericReceiveError::TxDisconnected(None), + )), + }) + } + } + + #[cfg(feature = "crossbeam")] + pub mod cb_mod { + use super::*; + use crossbeam_channel as cb; + + impl From> for EcssTmtcError { + fn from(_: cb::SendError) -> Self { + Self::Send(GenericSendError::RxDisconnected) + } + } + + impl From> for EcssTmtcError { + fn from(value: cb::TrySendError) -> Self { + match value { + cb::TrySendError::Full(_) => Self::Send(GenericSendError::QueueFull(None)), + cb::TrySendError::Disconnected(_) => { + Self::Send(GenericSendError::RxDisconnected) + } + } + } + } + + impl EcssTmSender for cb::Sender { + fn send_tm( + &self, + sender_id: ComponentId, + tm: PusTmVariant, + ) -> Result<(), EcssTmtcError> { + match tm { + PusTmVariant::InStore(addr) => self + .try_send(PacketInPool::new(sender_id, addr)) + .map_err(|e| EcssTmtcError::Send(e.into()))?, + PusTmVariant::Direct(_) => return Err(EcssTmtcError::CantSendDirectTm), + }; + Ok(()) + } + } + impl EcssTmSender for cb::Sender { + fn send_tm( + &self, + sender_id: ComponentId, + tm: PusTmVariant, + ) -> Result<(), EcssTmtcError> { + match tm { + PusTmVariant::InStore(addr) => return Err(EcssTmtcError::CantSendAddr(addr)), + PusTmVariant::Direct(tm) => self + .send(PacketAsVec::new(sender_id, tm.to_vec()?)) + .map_err(|e| EcssTmtcError::Send(e.into()))?, + }; + Ok(()) + } + } + + pub type CrossbeamTcReceiver = cb::Receiver; + } + + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct ActivePusRequestStd { + target_id: ComponentId, + token: TcStateToken, + start_time: std::time::Instant, + timeout: Duration, + } + + impl ActivePusRequestStd { + pub fn new( + target_id: ComponentId, + token: impl Into, + timeout: Duration, + ) -> Self { + Self { + target_id, + token: token.into(), + start_time: std::time::Instant::now(), + timeout, + } + } + } + + impl ActiveRequest for ActivePusRequestStd { + fn target_id(&self) -> ComponentId { + self.target_id + } + + fn token(&self) -> TcStateToken { + self.token + } + + fn timeout(&self) -> Duration { + self.timeout + } + fn set_token(&mut self, token: TcStateToken) { + self.token = token; + } + + fn has_timed_out(&self) -> bool { + std::time::Instant::now() - self.start_time > self.timeout + } + } + + // 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 PusTcFromMemError { + #[error("generic PUS error: {0}")] + EcssTmtc(#[from] EcssTmtcError), + #[error("invalid format of TC in memory: {0:?}")] + InvalidFormat(TcInMemory), + } + + #[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(ComponentId), + #[error("Sending action request failed: {0}")] + Send(GenericSendError), + } + + /// This error can be used for generic conversions from PUS Telecommands to request types. + /// + /// Please note that this error can also be used if no request is generated and the PUS + /// service, subservice and application data is used directly to perform some request. + #[derive(Debug, Clone, Error)] + pub enum GenericConversionError { + #[error("wrong service number {0} for packet handler")] + WrongService(u8), + #[error("invalid subservice {0}")] + InvalidSubservice(u8), + #[error("not enough application data, expected at least {expected}, found {found}")] + NotEnoughAppData { expected: usize, found: usize }, + #[error("invalid application data")] + InvalidAppData(String), + } + + /// Wrapper type which tries to encapsulate all possible errors when handling PUS packets. + #[derive(Debug, Clone, Error)] + pub enum PusPacketHandlingError { + #[error("error polling PUS TC packet: {0}")] + TcPolling(#[from] EcssTmtcError), + #[error("error generating PUS reader from memory: {0}")] + TcFromMem(#[from] PusTcFromMemError), + #[error("generic request conversion error: {0}")] + RequestConversion(#[from] GenericConversionError), + #[error("request routing error: {0}")] + RequestRouting(#[from] GenericRoutingError), + #[error("invalid verification token")] + InvalidVerificationToken, + #[error("other error {0}")] + Other(String), + } + + #[derive(Debug, Clone, Error)] + pub enum PartialPusHandlingError { + #[error("generic timestamp generation error")] + Time(#[from] StdTimestampError), + #[error("error sending telemetry: {0}")] + TmSend(EcssTmtcError), + #[error("error sending verification message")] + Verification(EcssTmtcError), + #[error("invalid verification token")] + NoVerificationToken, + } + + /// Generic result type for handlers which can process PUS packets. + #[derive(Debug, Clone)] + pub enum DirectPusPacketHandlerResult { + Handled(HandlingStatus), + SubserviceNotImplemented(u8, VerificationToken), + CustomSubservice(u8, VerificationToken), + } + + impl From for DirectPusPacketHandlerResult { + fn from(value: HandlingStatus) -> Self { + Self::Handled(value) + } + } + + /// This trait provides an abstraction for caching a raw ECSS telecommand and then + /// providing the [PusTcReader] abstraction to read the cache raw telecommand. + pub trait CacheAndReadRawEcssTc { + fn cache(&mut self, possible_packet: &TcInMemory) -> Result<(), PusTcFromMemError>; + + fn tc_slice_raw(&self) -> &[u8]; + + fn sender_id(&self) -> Option; + + fn cache_and_convert( + &mut self, + possible_packet: &TcInMemory, + ) -> Result, PusTcFromMemError> { + self.cache(possible_packet)?; + Ok(PusTcReader::new(self.tc_slice_raw()).map_err(EcssTmtcError::Pus)?) + } + + fn convert(&self) -> Result, PusTcFromMemError> { + Ok(PusTcReader::new(self.tc_slice_raw()).map_err(EcssTmtcError::Pus)?) + } + } + + /// Converter structure for PUS telecommands which are stored inside a `Vec` structure. + /// Please note that this structure is not able to convert TCs which are stored inside a + /// [SharedStaticMemoryPool]. + #[derive(Default, Clone)] + pub struct EcssTcVecCacher { + sender_id: Option, + pub pus_tc_raw: Option>, + } + + impl CacheAndReadRawEcssTc for EcssTcVecCacher { + fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> { + self.pus_tc_raw = None; + match tc_in_memory { + super::TcInMemory::Pool(_packet_in_pool) => { + return Err(PusTcFromMemError::InvalidFormat(tc_in_memory.clone())); + } + super::TcInMemory::Vec(packet_with_sender) => { + self.pus_tc_raw = Some(packet_with_sender.packet.clone()); + self.sender_id = Some(packet_with_sender.sender_id); + } + }; + Ok(()) + } + + fn sender_id(&self) -> Option { + self.sender_id + } + + fn tc_slice_raw(&self) -> &[u8] { + if self.pus_tc_raw.is_none() { + return &[]; + } + self.pus_tc_raw.as_ref().unwrap() + } + } + + /// Converter structure for PUS telecommands which are stored inside + /// [SharedStaticMemoryPool] structure. This is useful if run-time allocation for these + /// packets should be avoided. Please note that this structure is not able to convert TCs which + /// are stored as a `Vec`. + #[derive(Clone)] + pub struct EcssTcInSharedPoolCacher { + sender_id: Option, + shared_tc_pool: SharedStaticMemoryPool, + pus_buf: Vec, + } + + impl EcssTcInSharedPoolCacher { + pub fn new(shared_tc_store: SharedStaticMemoryPool, max_expected_tc_size: usize) -> Self { + Self { + sender_id: None, + shared_tc_pool: shared_tc_store, + pus_buf: alloc::vec![0; max_expected_tc_size], + } + } + + pub fn copy_tc_to_buf(&mut self, addr: PoolAddr) -> Result<(), PusTcFromMemError> { + // Keep locked section as short as possible. + let mut tc_pool = self.shared_tc_pool.write().map_err(|_| { + PusTcFromMemError::EcssTmtc(EcssTmtcError::Store(PoolError::LockError)) + })?; + let tc_size = tc_pool.len_of_data(&addr).map_err(EcssTmtcError::Store)?; + if tc_size > self.pus_buf.len() { + return Err( + EcssTmtcError::ByteConversion(ByteConversionError::ToSliceTooSmall { + found: self.pus_buf.len(), + expected: tc_size, + }) + .into(), + ); + } + let tc_guard = tc_pool.read_with_guard(addr); + // TODO: Proper error handling. + tc_guard.read(&mut self.pus_buf[0..tc_size]).unwrap(); + Ok(()) + } + } + + impl CacheAndReadRawEcssTc for EcssTcInSharedPoolCacher { + fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> { + match tc_in_memory { + super::TcInMemory::Pool(packet_in_pool) => { + self.copy_tc_to_buf(packet_in_pool.store_addr)?; + self.sender_id = Some(packet_in_pool.sender_id); + } + super::TcInMemory::Vec(_) => { + return Err(PusTcFromMemError::InvalidFormat(tc_in_memory.clone())); + } + }; + Ok(()) + } + + fn tc_slice_raw(&self) -> &[u8] { + self.pus_buf.as_ref() + } + + fn sender_id(&self) -> Option { + self.sender_id + } + } + + // TODO: alloc feature flag? + #[derive(Clone)] + pub enum EcssTcCacher { + Static(EcssTcInSharedPoolCacher), + Heap(EcssTcVecCacher), + } + + impl EcssTcCacher { + pub fn new_static(static_store_converter: EcssTcInSharedPoolCacher) -> Self { + Self::Static(static_store_converter) + } + + pub fn new_heap(heap_converter: EcssTcVecCacher) -> Self { + Self::Heap(heap_converter) + } + } + + impl CacheAndReadRawEcssTc for EcssTcCacher { + fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> { + match self { + Self::Static(converter) => converter.cache(tc_in_memory), + Self::Heap(converter) => converter.cache(tc_in_memory), + } + } + fn tc_slice_raw(&self) -> &[u8] { + match self { + Self::Static(converter) => converter.tc_slice_raw(), + Self::Heap(converter) => converter.tc_slice_raw(), + } + } + fn sender_id(&self) -> Option { + match self { + Self::Static(converter) => converter.sender_id(), + Self::Heap(converter) => converter.sender_id(), + } + } + } + + pub struct PusServiceBase< + TcReceiver: EcssTcReceiver, + TmSender: EcssTmSender, + VerificationReporter: VerificationReportingProvider, + > { + pub id: ComponentId, + pub tc_receiver: TcReceiver, + pub tm_sender: TmSender, + pub verif_reporter: VerificationReporter, + } + + /// This is a high-level PUS packet handler helper. + /// + /// It performs some of the boilerplate acitivities involved when handling PUS telecommands and + /// it can be used to implement the handling of PUS telecommands for certain PUS telecommands + /// groups (for example individual services). + /// + /// This base class can handle PUS telecommands backed by different memory storage machanisms + /// by using the [CacheAndReadRawEcssTc] abstraction. This object provides some convenience + /// methods to make the generic parts of TC handling easier. + pub struct PusServiceHelper< + TcReceiver: EcssTcReceiver, + TmSender: EcssTmSender, + TcInMemConverter: CacheAndReadRawEcssTc, + VerificationReporter: VerificationReportingProvider, + > { + pub common: PusServiceBase, + pub tc_in_mem_converter: TcInMemConverter, + } + + impl< + TcReceiver: EcssTcReceiver, + TmSender: EcssTmSender, + TcInMemConverter: CacheAndReadRawEcssTc, + VerificationReporter: VerificationReportingProvider, + > PusServiceHelper + { + pub fn new( + id: ComponentId, + tc_receiver: TcReceiver, + tm_sender: TmSender, + verification_handler: VerificationReporter, + tc_in_mem_converter: TcInMemConverter, + ) -> Self { + Self { + common: PusServiceBase { + id, + tc_receiver, + tm_sender, + verif_reporter: verification_handler, + }, + tc_in_mem_converter, + } + } + + pub fn id(&self) -> ComponentId { + self.common.id + } + + pub fn tm_sender(&self) -> &TmSender { + &self.common.tm_sender + } + + /// This function can be used to poll the internal [EcssTcReceiver] object for the next + /// telecommand packet. It will return `Ok(None)` if there are not packets available. + /// In any other case, it will perform the acceptance of the ECSS TC packet using the + /// internal [VerificationReportingProvider] object. It will then return the telecommand + /// and the according accepted token. + pub fn retrieve_and_accept_next_packet( + &mut self, + ) -> Result, PusPacketHandlingError> { + match self.common.tc_receiver.recv_tc() { + Ok(EcssTcAndToken { + tc_in_memory, + token, + }) => { + if token.is_none() { + return Err(PusPacketHandlingError::InvalidVerificationToken); + } + let token = token.unwrap(); + let accepted_token = VerificationToken::::try_from(token) + .map_err(|_| PusPacketHandlingError::InvalidVerificationToken)?; + Ok(Some(AcceptedEcssTcAndToken { + tc_in_memory, + token: accepted_token, + })) + } + Err(e) => match e { + TryRecvTmtcError::Tmtc(e) => Err(PusPacketHandlingError::TcPolling(e)), + TryRecvTmtcError::Empty => Ok(None), + }, + } + } + + pub fn verif_reporter(&self) -> &VerificationReporter { + &self.common.verif_reporter + } + pub fn verif_reporter_mut(&mut self) -> &mut VerificationReporter { + &mut self.common.verif_reporter + } + + pub fn tc_in_mem_converter(&self) -> &TcInMemConverter { + &self.tc_in_mem_converter + } + + pub fn tc_in_mem_converter_mut(&mut self) -> &mut TcInMemConverter { + &mut self.tc_in_mem_converter + } + } + + pub type PusServiceHelperDynWithMpsc = + PusServiceHelper; + pub type PusServiceHelperDynWithBoundedMpsc = + PusServiceHelper< + MpscTcReceiver, + MpscTmAsVecSenderBounded, + TcInMemConverter, + VerificationReporter, + >; + pub type PusServiceHelperStaticWithMpsc = + PusServiceHelper< + MpscTcReceiver, + PacketSenderWithSharedPool, + TcInMemConverter, + VerificationReporter, + >; + pub type PusServiceHelperStaticWithBoundedMpsc = + PusServiceHelper< + MpscTcReceiver, + PacketSenderWithSharedPool, + TcInMemConverter, + VerificationReporter, + >; +} + +pub(crate) fn source_buffer_large_enough( + cap: usize, + len: usize, +) -> Result<(), ByteConversionError> { + if len > cap { + return Err(ByteConversionError::ToSliceTooSmall { + found: cap, + expected: len, + }); + } + Ok(()) +} + +#[cfg(any(feature = "test_util", test))] +pub mod test_util { + use arbitrary_int::{u11, u21}; + use spacepackets::ecss::{tc::PusTcCreator, tm::PusTmReader}; + + use crate::request::UniqueApidTargetId; + + use super::{ + DirectPusPacketHandlerResult, PusPacketHandlingError, + verification::{self, TcStateAccepted, VerificationToken}, + }; + + pub const TEST_APID: u11 = u11::new(0x101); + pub const TEST_UNIQUE_ID_0: u21 = u21::new(0x05); + pub const TEST_UNIQUE_ID_1: u21 = u21::new(0x06); + + pub const TEST_COMPONENT_ID_0: UniqueApidTargetId = + UniqueApidTargetId::new(TEST_APID, TEST_UNIQUE_ID_0); + pub const TEST_COMPONENT_ID_1: UniqueApidTargetId = + UniqueApidTargetId::new(TEST_APID, TEST_UNIQUE_ID_1); + + pub trait PusTestHarness { + fn start_verification(&mut self, tc: &PusTcCreator) -> VerificationToken; + fn send_tc(&self, token: &VerificationToken, tc: &PusTcCreator); + 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: verification::RequestId, + ); + } + + pub trait SimplePusPacketHandler { + fn handle_one_tc(&mut self) + -> Result; + } +} + +#[cfg(test)] +pub mod tests { + use core::cell::RefCell; + use std::sync::mpsc::TryRecvError; + use std::sync::{RwLock, mpsc}; + + use alloc::collections::VecDeque; + use alloc::vec::Vec; + use arbitrary_int::{u11, u14}; + use satrs_shared::res_code::ResultU16; + use spacepackets::CcsdsPacket; + use spacepackets::ecss::tc::{PusTcCreator, PusTcReader}; + use spacepackets::ecss::tm::{GenericPusTmSecondaryHeader, PusTmCreator, PusTmReader}; + use spacepackets::ecss::{PusPacket, WritablePusPacket}; + use test_util::{TEST_APID, TEST_COMPONENT_ID_0}; + + use super::verification::{RequestId, VerificationReporter}; + use crate::ComponentId; + use crate::pool::{PoolProvider, SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig}; + use crate::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool}; + + use super::verification::test_util::TestVerificationReporter; + use super::verification::{ + TcStateAccepted, VerificationReporterConfig, VerificationReportingProvider, + VerificationToken, + }; + use super::*; + + #[derive(Debug, Eq, PartialEq, Clone)] + pub(crate) struct CommonTmInfo { + pub subservice: u8, + pub apid: u11, + pub seq_count: u14, + pub msg_counter: u16, + pub dest_id: u16, + pub timestamp: Vec, + } + + impl CommonTmInfo { + pub fn new( + subservice: u8, + apid: u11, + seq_count: u14, + msg_counter: u16, + dest_id: u16, + timestamp: &[u8], + ) -> Self { + Self { + subservice, + apid, + seq_count, + msg_counter, + dest_id, + timestamp: timestamp.to_vec(), + } + } + pub fn new_zero_seq_count( + subservice: u8, + apid: u11, + dest_id: u16, + timestamp: &[u8], + ) -> Self { + Self::new(subservice, apid, u14::new(0), 0, dest_id, timestamp) + } + + pub fn new_from_tm(tm: &PusTmCreator) -> Self { + let mut timestamp = [0; 7]; + timestamp.clone_from_slice(&tm.timestamp()[0..7]); + Self { + subservice: PusPacket::message_subtype_id(tm), + apid: tm.apid(), + seq_count: tm.seq_count(), + msg_counter: tm.msg_type_counter(), + dest_id: tm.dest_id(), + timestamp: timestamp.to_vec(), + } + } + } + + /// Common fields for a PUS service test harness. + pub struct PusServiceHandlerWithSharedStoreCommon { + pus_buf: RefCell<[u8; 2048]>, + tm_buf: [u8; 2048], + tc_pool: SharedStaticMemoryPool, + tm_pool: SharedPacketPool, + tc_sender: mpsc::SyncSender, + tm_receiver: mpsc::Receiver, + } + + pub type PusServiceHelperStatic = PusServiceHelper< + MpscTcReceiver, + PacketSenderWithSharedPool, + EcssTcInSharedPoolCacher, + VerificationReporter, + >; + + impl PusServiceHandlerWithSharedStoreCommon { + /// This function generates the structure in addition to the PUS service handler + /// [PusServiceHandler] which might be required for a specific PUS service handler. + /// + /// The PUS service handler is instantiated with a [EcssTcInStoreConverter]. + pub fn new(id: ComponentId) -> (Self, PusServiceHelperStatic) { + let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples( + 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)); + let shared_tm_pool = SharedStaticMemoryPool::new(RwLock::new(tm_pool)); + let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool); + let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::sync_channel(10); + let (tm_tx, tm_rx) = mpsc::sync_channel(10); + + let verif_cfg = VerificationReporterConfig::new(TEST_APID, 1, 2, 8); + let verification_handler = + VerificationReporter::new(TEST_COMPONENT_ID_0.id(), &verif_cfg); + let test_srv_tm_sender = + PacketSenderWithSharedPool::new(tm_tx, shared_tm_pool_wrapper.clone()); + let in_store_converter = EcssTcInSharedPoolCacher::new(shared_tc_pool.clone(), 2048); + ( + Self { + pus_buf: RefCell::new([0; 2048]), + tm_buf: [0; 2048], + tc_pool: shared_tc_pool, + tm_pool: shared_tm_pool_wrapper, + tc_sender: test_srv_tc_tx, + tm_receiver: tm_rx, + }, + PusServiceHelper::new( + id, + test_srv_tc_rx, + test_srv_tm_sender, + verification_handler, + in_store_converter, + ), + ) + } + pub fn send_tc( + &self, + sender_id: ComponentId, + token: &VerificationToken, + tc: &PusTcCreator, + ) { + let mut mut_buf = self.pus_buf.borrow_mut(); + let tc_size = tc.write_to_bytes(mut_buf.as_mut_slice()).unwrap(); + let mut tc_pool = self.tc_pool.write().unwrap(); + let addr = tc_pool.add(&mut_buf[..tc_size]).unwrap(); + drop(tc_pool); + // Send accepted TC to test service handler. + self.tc_sender + .send(EcssTcAndToken::new( + PacketInPool::new(sender_id, addr), + *token, + )) + .expect("sending tc failed"); + } + + pub fn read_next_tm(&mut self) -> PusTmReader<'_> { + let next_msg = self.tm_receiver.try_recv(); + assert!(next_msg.is_ok()); + let tm_in_pool = next_msg.unwrap(); + let tm_pool = self.tm_pool.0.read().unwrap(); + let tm_raw = tm_pool.read_as_vec(&tm_in_pool.store_addr).unwrap(); + self.tm_buf[0..tm_raw.len()].copy_from_slice(&tm_raw); + PusTmReader::new(&self.tm_buf, 7).unwrap() + } + + pub fn check_no_tm_available(&self) -> bool { + let next_msg = self.tm_receiver.try_recv(); + if let TryRecvError::Empty = next_msg.unwrap_err() { + return true; + } + false + } + + pub fn check_next_verification_tm(&self, subservice: u8, expected_request_id: RequestId) { + let next_msg = self.tm_receiver.try_recv(); + assert!(next_msg.is_ok()); + let tm_in_pool = next_msg.unwrap(); + let tm_pool = self.tm_pool.0.read().unwrap(); + let tm_raw = tm_pool.read_as_vec(&tm_in_pool.store_addr).unwrap(); + let tm = PusTmReader::new(&tm_raw, 7).unwrap(); + assert_eq!(PusPacket::service_type_id(&tm), 1); + assert_eq!(PusPacket::message_subtype_id(&tm), subservice); + assert_eq!(tm.apid(), TEST_APID); + let req_id = + RequestId::from_bytes(tm.user_data()).expect("generating request ID failed"); + assert_eq!(req_id, expected_request_id); + } + } + + pub struct PusServiceHandlerWithVecCommon { + current_tm: Option>, + tc_sender: mpsc::Sender, + tm_receiver: mpsc::Receiver, + } + pub type PusServiceHelperDynamic = + PusServiceHelper; + + impl PusServiceHandlerWithVecCommon { + pub fn new_with_standard_verif_reporter( + id: ComponentId, + ) -> (Self, PusServiceHelperDynamic) { + let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel(); + let (tm_tx, tm_rx) = mpsc::channel(); + + let verif_cfg = VerificationReporterConfig::new(TEST_APID, 1, 2, 8); + let verification_handler = + VerificationReporter::new(TEST_COMPONENT_ID_0.id(), &verif_cfg); + let in_store_converter = EcssTcVecCacher::default(); + ( + Self { + current_tm: None, + tc_sender: test_srv_tc_tx, + tm_receiver: tm_rx, + }, + PusServiceHelper::new( + id, + test_srv_tc_rx, + tm_tx, + verification_handler, + in_store_converter, + ), + ) + } + } + + impl PusServiceHandlerWithVecCommon { + pub fn new_with_test_verif_sender( + id: ComponentId, + ) -> ( + Self, + PusServiceHelper< + MpscTcReceiver, + MpscTmAsVecSender, + EcssTcVecCacher, + TestVerificationReporter, + >, + ) { + let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel(); + let (tm_tx, tm_rx) = mpsc::channel(); + + let in_store_converter = EcssTcVecCacher::default(); + let verification_handler = TestVerificationReporter::new(id); + ( + Self { + current_tm: None, + tc_sender: test_srv_tc_tx, + tm_receiver: tm_rx, + //verification_handler: verification_handler.clone(), + }, + PusServiceHelper::new( + id, + test_srv_tc_rx, + tm_tx, + verification_handler, + in_store_converter, + ), + ) + } + } + + impl PusServiceHandlerWithVecCommon { + pub fn send_tc( + &self, + sender_id: ComponentId, + token: &VerificationToken, + tc: &PusTcCreator, + ) { + // Send accepted TC to test service handler. + self.tc_sender + .send(EcssTcAndToken::new( + TcInMemory::Vec(PacketAsVec::new( + sender_id, + tc.to_vec().expect("pus tc conversion to vec failed"), + )), + *token, + )) + .expect("sending tc failed"); + } + + pub fn read_next_tm(&mut self) -> PusTmReader<'_> { + let next_msg = self.tm_receiver.try_recv(); + assert!(next_msg.is_ok()); + self.current_tm = Some(next_msg.unwrap().packet); + PusTmReader::new(self.current_tm.as_ref().unwrap(), 7).unwrap() + } + + pub fn check_no_tm_available(&self) -> bool { + let next_msg = self.tm_receiver.try_recv(); + if let TryRecvError::Empty = next_msg.unwrap_err() { + return true; + } + false + } + + pub fn check_next_verification_tm(&self, subservice: u8, expected_request_id: RequestId) { + let next_msg = self.tm_receiver.try_recv(); + assert!(next_msg.is_ok()); + let next_msg = next_msg.unwrap(); + let tm = PusTmReader::new(next_msg.packet.as_slice(), 7).unwrap(); + assert_eq!(PusPacket::service_type_id(&tm), 1); + assert_eq!(PusPacket::message_subtype_id(&tm), subservice); + assert_eq!(tm.apid(), TEST_APID); + let req_id = + RequestId::from_bytes(tm.user_data()).expect("generating request ID failed"); + 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_type_id() != SERVICE { + return Err(PusPacketHandlingError::RequestConversion( + GenericConversionError::WrongService(tc.service_type_id()), + )); + } + 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() + ); + } + } + + pub struct TestRouter { + pub routing_requests: RefCell>, + pub routing_errors: RefCell>, + pub injected_routing_failure: RefCell>, + } + + impl Default for TestRouter { + fn default() -> Self { + Self { + routing_requests: Default::default(), + routing_errors: 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 handle_error( + &self, + target_id: ComponentId, + _token: VerificationToken, + _tc: &PusTcReader, + error: GenericRoutingError, + _time_stamp: &[u8], + _verif_reporter: &impl VerificationReportingProvider, + ) { + self.routing_errors + .borrow_mut() + .push_back((target_id, error)); + } + + pub fn no_routing_errors(&self) -> bool { + self.routing_errors.borrow().is_empty() + } + + pub fn retrieve_next_routing_error(&mut self) -> (ComponentId, GenericRoutingError) { + if self.routing_errors.borrow().is_empty() { + panic!("no routing request available"); + } + self.routing_errors.borrow_mut().pop_front().unwrap() + } + + 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) -> (ComponentId, 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/mode.rs b/satrs/src/legacy/pus/mode.rs similarity index 100% rename from satrs/src/pus/mode.rs rename to satrs/src/legacy/pus/mode.rs diff --git a/satrs/src/pus/scheduler.rs b/satrs/src/legacy/pus/scheduler.rs similarity index 100% rename from satrs/src/pus/scheduler.rs rename to satrs/src/legacy/pus/scheduler.rs diff --git a/satrs/src/pus/scheduler_srv.rs b/satrs/src/legacy/pus/scheduler_srv.rs similarity index 96% rename from satrs/src/pus/scheduler_srv.rs rename to satrs/src/legacy/pus/scheduler_srv.rs index 629f72e..7ee14d9 100644 --- a/satrs/src/pus/scheduler_srv.rs +++ b/satrs/src/legacy/pus/scheduler_srv.rs @@ -1,12 +1,12 @@ +use super::PusPacketHandlingError; use super::scheduler::PusScheduler; use super::verification::{VerificationReporter, VerificationReportingProvider}; use super::{ CacheAndReadRawEcssTc, DirectPusPacketHandlerResult, EcssTcInSharedPoolCacher, EcssTcReceiver, - EcssTcVecCacher, EcssTmSender, HandlingStatus, MpscTcReceiver, PartialPusHandlingError, - PusServiceHelper, + EcssTcVecCacher, EcssTmSender, MpscTcReceiver, PartialPusHandlingError, PusServiceHelper, }; +use crate::HandlingStatus; use crate::pool::PoolProvider; -use crate::pus::PusPacketHandlingError; use crate::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use alloc::string::ToString; use spacepackets::ecss::{PusPacket, scheduling}; @@ -247,12 +247,14 @@ pub type PusService11SchedHandlerStaticWithBoundedMpsc = PusSchedS #[cfg(test)] mod tests { + use crate::legacy::pus::test_util::{PusTestHarness, TEST_APID}; + use crate::legacy::pus::verification::{VerificationReporter, VerificationReportingProvider}; use crate::pool::{StaticMemoryPool, StaticPoolConfig}; - use crate::pus::test_util::{PusTestHarness, TEST_APID}; - use crate::pus::verification::{VerificationReporter, VerificationReportingProvider}; - use crate::pus::{DirectPusPacketHandlerResult, MpscTcReceiver, PusPacketHandlingError}; - use crate::pus::{ + use crate::legacy::pus::{ + DirectPusPacketHandlerResult, MpscTcReceiver, PusPacketHandlingError, + }; + use crate::legacy::pus::{ EcssTcInSharedPoolCacher, scheduler::{self, PusScheduler, TcInfo}, tests::PusServiceHandlerWithSharedStoreCommon, @@ -377,8 +379,8 @@ mod tests { fn insert_unwrapped_and_stored_tc( &mut self, _time_stamp: spacepackets::time::UnixTime, - info: crate::pus::scheduler::TcInfo, - ) -> Result<(), crate::pus::scheduler::ScheduleError> { + info: crate::legacy::pus::scheduler::TcInfo, + ) -> Result<(), crate::legacy::pus::scheduler::ScheduleError> { self.inserted_tcs.push_back(info); Ok(()) } diff --git a/satrs/src/pus/test.rs b/satrs/src/legacy/pus/test.rs similarity index 96% rename from satrs/src/pus/test.rs rename to satrs/src/legacy/pus/test.rs index ab7c74f..ba32c2f 100644 --- a/satrs/src/pus/test.rs +++ b/satrs/src/legacy/pus/test.rs @@ -1,4 +1,4 @@ -use crate::pus::{ +use super::{ DirectPusPacketHandlerResult, PartialPusHandlingError, PusPacketHandlingError, PusTmVariant, }; use crate::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; @@ -12,8 +12,9 @@ use std::sync::mpsc; use super::verification::{VerificationReporter, VerificationReportingProvider}; use super::{ CacheAndReadRawEcssTc, EcssTcInSharedPoolCacher, EcssTcReceiver, EcssTcVecCacher, EcssTmSender, - GenericConversionError, HandlingStatus, MpscTcReceiver, PusServiceHelper, + GenericConversionError, MpscTcReceiver, PusServiceHelper, }; +use crate::HandlingStatus; /// 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. @@ -140,18 +141,19 @@ pub type PusService17TestHandlerStaticWithBoundedMpsc = PusService17TestHandler< #[cfg(test)] mod tests { use crate::ComponentId; - use crate::pus::test_util::{PusTestHarness, SimplePusPacketHandler, TEST_APID}; - use crate::pus::tests::{ + use crate::HandlingStatus; + use crate::legacy::pus::test_util::{PusTestHarness, SimplePusPacketHandler, TEST_APID}; + use crate::legacy::pus::tests::{ PusServiceHandlerWithSharedStoreCommon, PusServiceHandlerWithVecCommon, }; - use crate::pus::verification::{ + use crate::legacy::pus::verification::{ RequestId, VerificationReporter, VerificationReportingProvider, }; - use crate::pus::verification::{TcStateAccepted, VerificationToken}; - use crate::pus::{ + use crate::legacy::pus::verification::{TcStateAccepted, VerificationToken}; + use crate::legacy::pus::{ DirectPusPacketHandlerResult, EcssTcInSharedPoolCacher, EcssTcVecCacher, - GenericConversionError, HandlingStatus, MpscTcReceiver, MpscTmAsVecSender, - PartialPusHandlingError, PusPacketHandlingError, + GenericConversionError, MpscTcReceiver, MpscTmAsVecSender, PartialPusHandlingError, + PusPacketHandlingError, }; use crate::tmtc::PacketSenderWithSharedPool; use arbitrary_int::traits::Integer as _; diff --git a/satrs/src/pus/verification.rs b/satrs/src/legacy/pus/verification.rs similarity index 99% rename from satrs/src/pus/verification.rs rename to satrs/src/legacy/pus/verification.rs index a701039..6a568b1 100644 --- a/satrs/src/pus/verification.rs +++ b/satrs/src/legacy/pus/verification.rs @@ -16,7 +16,7 @@ //! use std::sync::{Arc, mpsc, RwLock}; //! use std::time::Duration; //! use satrs::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; -//! use satrs::pus::verification::{ +//! use satrs::legacy::pus::verification::{ //! VerificationReportingProvider, VerificationReporterConfig, VerificationReporter //! }; //! use satrs::tmtc::{SharedStaticMemoryPool, PacketSenderWithSharedPool}; @@ -80,8 +80,8 @@ //! The [integration test](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/fsrc-core/tests/verification_test.rs) //! for the verification module contains examples how this module could be used in a more complex //! context involving multiple threads +use super::{EcssTmSender, EcssTmtcError, source_buffer_large_enough}; use crate::params::{Params, WritableToBeBytes}; -use crate::pus::{EcssTmSender, EcssTmtcError, source_buffer_large_enough}; use arbitrary_int::{u3, u11, u14}; use core::fmt::{Debug, Display, Formatter}; use core::hash::{Hash, Hasher}; @@ -841,7 +841,7 @@ pub mod alloc_mod { use spacepackets::ecss::PusError; use super::*; - use crate::pus::PusTmVariant; + use crate::legacy::pus::PusTmVariant; use core::cell::RefCell; #[derive(Clone)] @@ -1712,16 +1712,16 @@ pub mod test_util { #[cfg(test)] pub mod tests { use crate::ComponentId; - use crate::params::Params; - use crate::pool::{SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig}; - use crate::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0}; - use crate::pus::tests::CommonTmInfo; - use crate::pus::verification::{ + use crate::legacy::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0}; + use crate::legacy::pus::tests::CommonTmInfo; + use crate::legacy::pus::verification::{ EcssTmSender, EcssTmtcError, FailParams, FailParamsWithStep, RequestId, TcStateNone, VerificationReporter, VerificationReporterConfig, VerificationToken, handle_step_failure_with_generic_params, }; - use crate::pus::{ChannelWithId, PusTmVariant}; + use crate::legacy::pus::{ChannelWithId, PusTmVariant}; + use crate::params::Params; + use crate::pool::{SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig}; use crate::request::MessageMetadata; use crate::spacepackets::seq_count::{SequenceCounter, SequenceCounterCcsdsSimple}; use crate::tmtc::{PacketSenderWithSharedPool, SharedPacketPool}; diff --git a/satrs/src/res_code.rs b/satrs/src/legacy/res_code.rs similarity index 100% rename from satrs/src/res_code.rs rename to satrs/src/legacy/res_code.rs diff --git a/satrs/src/lib.rs b/satrs/src/lib.rs index fed4d14..70f9dfc 100644 --- a/satrs/src/lib.rs +++ b/satrs/src/lib.rs @@ -1,16 +1,7 @@ -//! # sat-rs: A library to build on-board software for remote systems +//! # sat-rs: A helper library to build on-board software for remote systems //! -//! You can find more information about the sat-rs framework on the -//! [homepage](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/). -//! The [satrs-book](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/) contains -//! high-level information about this framework. -//! -//! ## Overview -//! -//! The core modules of this crate include -//! -//! - The [pus] module which provides special support for projects using -//! the [ECSS PUS C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/). +//! The [satrs-book](https://robamu.github.io/sat-rs/book/) contains +//! high-level information about this library. #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(any(feature = "alloc", test))] @@ -20,23 +11,22 @@ extern crate downcast_rs; #[cfg(any(feature = "std", test))] extern crate std; -pub mod action; pub mod ccsds; pub mod encoding; #[cfg(feature = "std")] pub mod executable; pub mod hal; pub mod health; +/// Helpers to track when housekeeping sets need to be regenerated. +pub mod hk; pub mod legacy; pub mod mode; #[cfg(feature = "std")] pub mod mode_tree; pub mod params; pub mod pool; -pub mod pus; pub mod queue; pub mod request; -pub mod res_code; #[cfg(feature = "alloc")] pub mod scheduling; #[cfg(feature = "alloc")] @@ -48,6 +38,15 @@ pub use spacepackets; use spacepackets::PacketId; +/// Generic handling status for an object which is able to continuosly handle a queue to handle +/// request or replies until the queue is empty. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum HandlingStatus { + HandledOne, + Empty, +} + /// Generic component ID type. pub type ComponentId = u32; diff --git a/satrs/src/pus/mod.rs b/satrs/src/pus/mod.rs deleted file mode 100644 index b927934..0000000 --- a/satrs/src/pus/mod.rs +++ /dev/null @@ -1,1699 +0,0 @@ -//! # PUS support modules -//! -//! 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::ComponentId; -use crate::pool::{PoolAddr, PoolError}; -use crate::pus::verification::{TcStateAccepted, TcStateToken, VerificationToken}; -use crate::queue::{GenericReceiveError, GenericSendError}; -use crate::request::{GenericMessage, MessageMetadata, RequestId}; -#[cfg(feature = "alloc")] -use crate::tmtc::PacketAsVec; -use crate::tmtc::PacketInPool; -use core::fmt::{Display, Formatter}; -use core::time::Duration; -#[cfg(feature = "alloc")] -use downcast_rs::{Downcast, impl_downcast}; -#[cfg(feature = "alloc")] -use dyn_clone::DynClone; -#[cfg(feature = "std")] -use std::error::Error; - -use spacepackets::ecss::PusError; -use spacepackets::ecss::tc::{PusTcCreator, PusTcReader}; -use spacepackets::ecss::tm::PusTmCreator; -use spacepackets::{ByteConversionError, SpHeader}; - -pub mod action; -pub mod mode; -pub mod scheduler; -#[cfg(feature = "std")] -pub mod scheduler_srv; -#[cfg(feature = "std")] -pub mod test; -pub mod verification; - -#[cfg(feature = "alloc")] -pub use alloc_mod::*; - -#[cfg(feature = "std")] -pub use std_mod::*; - -use self::verification::VerificationReportingProvider; - -/// Generic handling status for an object which is able to continuosly handle a queue to handle -/// request or replies until the queue is empty. -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum HandlingStatus { - HandledOne, - Empty, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum PusTmVariant<'time, 'src_data> { - InStore(PoolAddr), - Direct(PusTmCreator<'time, 'src_data>), -} - -impl From for PusTmVariant<'_, '_> { - fn from(value: PoolAddr) -> Self { - Self::InStore(value) - } -} - -impl<'time, 'src_data> From> for PusTmVariant<'time, 'src_data> { - fn from(value: PusTmCreator<'time, 'src_data>) -> Self { - Self::Direct(value) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum EcssTmtcError { - Store(PoolError), - ByteConversion(ByteConversionError), - Pus(PusError), - CantSendAddr(PoolAddr), - CantSendDirectTm, - Send(GenericSendError), - Receive(GenericReceiveError), -} - -impl Display for EcssTmtcError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self { - EcssTmtcError::Store(store) => { - write!(f, "ecss tmtc error: {store}") - } - EcssTmtcError::ByteConversion(e) => { - write!(f, "ecss tmtc error: {e}") - } - EcssTmtcError::Pus(e) => { - write!(f, "ecss tmtc error: {e}") - } - EcssTmtcError::CantSendAddr(addr) => { - write!(f, "can not send address {addr}") - } - EcssTmtcError::CantSendDirectTm => { - write!(f, "can not send TM directly") - } - EcssTmtcError::Send(e) => { - write!(f, "ecss tmtc error: {e}") - } - EcssTmtcError::Receive(e) => { - write!(f, "ecss tmtc error {e}") - } - } - } -} - -impl From for EcssTmtcError { - fn from(value: PoolError) -> Self { - Self::Store(value) - } -} - -impl From for EcssTmtcError { - fn from(value: PusError) -> Self { - Self::Pus(value) - } -} - -impl From for EcssTmtcError { - fn from(value: GenericSendError) -> Self { - Self::Send(value) - } -} - -impl From for EcssTmtcError { - fn from(value: ByteConversionError) -> Self { - Self::ByteConversion(value) - } -} - -impl From for EcssTmtcError { - fn from(value: GenericReceiveError) -> Self { - Self::Receive(value) - } -} - -#[cfg(feature = "std")] -impl Error for EcssTmtcError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - EcssTmtcError::Store(e) => Some(e), - EcssTmtcError::ByteConversion(e) => Some(e), - EcssTmtcError::Pus(e) => Some(e), - EcssTmtcError::Send(e) => Some(e), - EcssTmtcError::Receive(e) => Some(e), - _ => None, - } - } -} -pub trait ChannelWithId: Send { - /// Each sender can have an ID associated with it - fn id(&self) -> ComponentId; - fn name(&self) -> &'static str { - "unset" - } -} - -/// Generic trait for a user supplied sender object. -/// -/// This sender object is responsible for sending PUS telemetry to a TM sink. -pub trait EcssTmSender: Send { - fn send_tm(&self, sender_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError>; -} - -/// Generic trait for a user supplied sender object. -/// -/// This sender object is responsible for sending PUS telecommands to a TC recipient. Each -/// telecommand can optionally have a token which contains its verification state. -pub trait EcssTcSender { - fn send_tc(&self, tc: PusTcCreator, token: Option) -> Result<(), EcssTmtcError>; -} - -/// Dummy object which can be useful for tests. -#[derive(Default)] -pub struct EcssTmDummySender {} - -impl EcssTmSender for EcssTmDummySender { - fn send_tm(&self, _source_id: ComponentId, _tm: PusTmVariant) -> Result<(), EcssTmtcError> { - Ok(()) - } -} - -/// A PUS telecommand packet can be stored in memory and sent using different methods. Right now, -/// storage inside a pool structure like [crate::pool::StaticMemoryPool], and storage inside a -/// `Vec` are supported. -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TcInMemory { - Pool(PacketInPool), - #[cfg(feature = "alloc")] - Vec(PacketAsVec), -} - -impl From for TcInMemory { - fn from(value: PacketInPool) -> Self { - Self::Pool(value) - } -} - -#[cfg(feature = "alloc")] -impl From for TcInMemory { - fn from(value: PacketAsVec) -> Self { - Self::Vec(value) - } -} - -/// Generic structure for an ECSS PUS Telecommand and its correspoding verification token. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EcssTcAndToken { - pub tc_in_memory: TcInMemory, - pub token: Option, -} - -impl EcssTcAndToken { - pub fn new(tc_in_memory: impl Into, token: impl Into) -> Self { - Self { - tc_in_memory: tc_in_memory.into(), - token: Some(token.into()), - } - } -} - -/// Generic abstraction for a telecommand being sent around after is has been accepted. -pub struct AcceptedEcssTcAndToken { - pub tc_in_memory: TcInMemory, - pub token: VerificationToken, -} - -impl From for EcssTcAndToken { - fn from(value: AcceptedEcssTcAndToken) -> Self { - EcssTcAndToken { - tc_in_memory: value.tc_in_memory, - token: Some(value.token.into()), - } - } -} - -impl TryFrom for AcceptedEcssTcAndToken { - type Error = (); - - fn try_from(value: EcssTcAndToken) -> Result { - if let Some(TcStateToken::Accepted(token)) = value.token { - return Ok(AcceptedEcssTcAndToken { - tc_in_memory: value.tc_in_memory, - token, - }); - } - Err(()) - } -} - -#[derive(Debug, Clone)] -pub enum TryRecvTmtcError { - Tmtc(EcssTmtcError), - Empty, -} - -impl From for TryRecvTmtcError { - fn from(value: EcssTmtcError) -> Self { - Self::Tmtc(value) - } -} - -impl From for TryRecvTmtcError { - fn from(value: PusError) -> Self { - Self::Tmtc(value.into()) - } -} - -impl From for TryRecvTmtcError { - fn from(value: PoolError) -> Self { - Self::Tmtc(value.into()) - } -} - -/// Generic trait for a user supplied receiver object. -pub trait EcssTcReceiver { - fn recv_tc(&self) -> Result; -} - -/// Generic trait for objects which can send ECSS PUS telecommands. -pub trait PacketSenderPusTc: Send { - type Error; - fn send_pus_tc( - &self, - sender_id: ComponentId, - header: &SpHeader, - pus_tc: &PusTcReader, - ) -> Result<(), Self::Error>; -} - -pub trait ActiveRequestStore: Sized { - fn insert(&mut self, request_id: &RequestId, request_info: V); - fn get(&self, request_id: RequestId) -> Option<&V>; - fn get_mut(&mut self, request_id: RequestId) -> Option<&mut V>; - fn remove(&mut self, request_id: RequestId) -> bool; - - /// Call a user-supplied closure for each active request. - fn for_each(&self, f: F); - - /// Call a user-supplied closure for each active request. Mutable variant. - fn for_each_mut(&mut self, f: F); -} - -pub trait ActiveRequest { - fn target_id(&self) -> ComponentId; - fn token(&self) -> TcStateToken; - fn set_token(&mut self, token: TcStateToken); - fn has_timed_out(&self) -> bool; - fn timeout(&self) -> Duration; -} - -/// This trait is an abstraction for the routing of PUS request to a dedicated -/// recipient using the generic [ComponentId]. -pub trait PusRequestRouter { - type Error; - - fn route( - &self, - requestor_info: MessageMetadata, - target_id: ComponentId, - request: Request, - ) -> Result<(), Self::Error>; -} - -pub trait PusReplyHandler { - type Error; - - /// This function handles a reply for a given PUS request and returns whether that request - /// is finished. A finished PUS request will be removed from the active request map. - fn handle_reply( - &mut self, - reply: &GenericMessage, - active_request: &ActiveRequestInfo, - tm_sender: &impl EcssTmSender, - verification_handler: &impl VerificationReportingProvider, - time_stamp: &[u8], - ) -> Result; - - fn handle_unrequested_reply( - &mut self, - reply: &GenericMessage, - tm_sender: &impl EcssTmSender, - ) -> Result<(), Self::Error>; - - /// Handle the timeout of an active request. - fn handle_request_timeout( - &mut self, - active_request: &ActiveRequestInfo, - tm_sender: &impl EcssTmSender, - verification_handler: &impl VerificationReportingProvider, - time_stamp: &[u8], - ) -> Result<(), Self::Error>; -} - -#[cfg(feature = "alloc")] -pub mod alloc_mod { - use hashbrown::HashMap; - - use super::*; - - /// Extension trait for [EcssTmSender]. - /// - /// It provides additional functionality, for example by implementing the [Downcast] trait - /// and the [DynClone] trait. - /// - /// [Downcast] is implemented to allow passing the sender as a boxed trait object and still - /// retrieve the concrete type at a later point. - /// - /// [DynClone] allows cloning the trait object as long as the boxed object implements - /// [Clone]. - #[cfg(feature = "alloc")] - pub trait EcssTmSenderExt: EcssTmSender + Downcast + DynClone { - // Remove this once trait upcasting coercion has been implemented. - // Tracking issue: https://github.com/rust-lang/rust/issues/65991 - fn upcast(&self) -> &dyn EcssTmSender; - // Remove this once trait upcasting coercion has been implemented. - // Tracking issue: https://github.com/rust-lang/rust/issues/65991 - fn upcast_mut(&mut self) -> &mut dyn EcssTmSender; - } - - /// Blanket implementation for all types which implement [EcssTmSender] and are clonable. - impl EcssTmSenderExt for T - where - T: EcssTmSender + Clone + 'static, - { - // Remove this once trait upcasting coercion has been implemented. - // Tracking issue: https://github.com/rust-lang/rust/issues/65991 - fn upcast(&self) -> &dyn EcssTmSender { - self - } - // Remove this once trait upcasting coercion has been implemented. - // Tracking issue: https://github.com/rust-lang/rust/issues/65991 - fn upcast_mut(&mut self) -> &mut dyn EcssTmSender { - self - } - } - - dyn_clone::clone_trait_object!(EcssTmSenderExt); - impl_downcast!(EcssTmSenderExt); - - /// Extension trait for [EcssTcSender]. - /// - /// It provides additional functionality, for example by implementing the [Downcast] trait - /// and the [DynClone] trait. - /// - /// [Downcast] is implemented to allow passing the sender as a boxed trait object and still - /// retrieve the concrete type at a later point. - /// - /// [DynClone] allows cloning the trait object as long as the boxed object implements - /// [Clone]. - #[cfg(feature = "alloc")] - pub trait EcssTcSenderExt: EcssTcSender + Downcast + DynClone {} - - /// Blanket implementation for all types which implement [EcssTcSender] and are clonable. - impl EcssTcSenderExt for T where T: EcssTcSender + Clone + 'static {} - - dyn_clone::clone_trait_object!(EcssTcSenderExt); - impl_downcast!(EcssTcSenderExt); - - /// Extension trait for [EcssTcReceiver]. - /// - /// It provides additional functionality, for example by implementing the [Downcast] trait - /// and the [DynClone] trait. - /// - /// [Downcast] is implemented to allow passing the sender as a boxed trait object and still - /// retrieve the concrete type at a later point. - /// - /// [DynClone] allows cloning the trait object as long as the boxed object implements - /// [Clone]. - #[cfg(feature = "alloc")] - pub trait EcssTcReceiverExt: EcssTcReceiver + Downcast {} - - /// Blanket implementation for all types which implement [EcssTcReceiver] and are clonable. - impl EcssTcReceiverExt for T where T: EcssTcReceiver + 'static {} - - impl_downcast!(EcssTcReceiverExt); - - /// This trait is an abstraction for the conversion of a PUS telecommand into a generic request - /// type. - /// - /// Having a dedicated trait for this allows maximum flexiblity and tailoring of the standard. - /// The only requirement is that a valid active request information instance and a request - /// are returned by the core conversion function. The active request type needs to fulfill - /// the [ActiveRequest] trait bound. - /// - /// 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 [VerificationReportingProvider] instance is passed to the user to also allow handling - /// of the verification process as part of the PUS standard requirements. - pub trait PusTcToRequestConverter { - type Error; - fn convert( - &mut self, - token: VerificationToken, - tc: &PusTcReader, - tm_sender: &(impl EcssTmSender + ?Sized), - verif_reporter: &impl VerificationReportingProvider, - time_stamp: &[u8], - ) -> Result<(ActiveRequestInfo, Request), Self::Error>; - } - - #[derive(Clone, Debug)] - pub struct DefaultActiveRequestMap(pub HashMap); - - impl Default for DefaultActiveRequestMap { - fn default() -> Self { - Self(HashMap::new()) - } - } - - impl ActiveRequestStore for DefaultActiveRequestMap { - fn insert(&mut self, request_id: &RequestId, request: V) { - self.0.insert(*request_id, request); - } - - fn get(&self, request_id: RequestId) -> Option<&V> { - self.0.get(&request_id) - } - - fn get_mut(&mut self, request_id: RequestId) -> Option<&mut V> { - self.0.get_mut(&request_id) - } - - fn remove(&mut self, request_id: RequestId) -> bool { - self.0.remove(&request_id).is_some() - } - - fn for_each(&self, mut f: F) { - for (req_id, active_req) in &self.0 { - f(req_id, active_req); - } - } - - fn for_each_mut(&mut self, mut f: F) { - for (req_id, active_req) in &mut self.0 { - f(req_id, active_req); - } - } - } - - /* - /// Generic reply handler structure which can be used to handle replies for a specific PUS - /// service. - /// - /// This is done by keeping track of active requests using an internal map structure. An API - /// to register new active requests is exposed as well. - /// The reply handler performs boilerplate tasks like performing the verification handling and - /// timeout handling. - /// - /// This object is not useful by itself but serves as a common building block for high-level - /// PUS reply handlers. Concrete PUS handlers should constrain the [ActiveRequestProvider] and - /// the `ReplyType` generics to specific types tailored towards PUS services in addition to - /// providing an API which can process received replies and convert them into verification - /// completions or other operation like user hook calls. The framework also provides some - /// concrete PUS handlers for common PUS services like the mode, action and housekeeping - /// service. - /// - /// This object does not automatically update its internal time information used to check for - /// timeouts. The user should call the [Self::update_time] and [Self::update_time_from_now] - /// methods to do this. - pub struct PusServiceReplyHandler< - ActiveRequestMap: ActiveRequestMapProvider, - ReplyHook: ReplyHandlerHook, - ActiveRequestType: ActiveRequestProvider, - ReplyType, - > { - pub active_request_map: ActiveRequestMap, - pub tm_buf: alloc::vec::Vec, - pub current_time: UnixTimestamp, - pub user_hook: ReplyHook, - phantom: PhantomData<(ActiveRequestType, ReplyType)>, - } - - impl< - ActiveRequestMap: ActiveRequestMapProvider, - ReplyHook: ReplyHandlerHook, - ActiveRequestType: ActiveRequestProvider, - ReplyType, - > - PusServiceReplyHandler< - ActiveRequestMap, - ReplyHook, - ActiveRequestType, - ReplyType, - > - { - #[cfg(feature = "std")] - pub fn new_from_now( - active_request_map: ActiveRequestMap, - fail_data_buf_size: usize, - user_hook: ReplyHook, - ) -> Result { - let current_time = UnixTimestamp::from_now()?; - Ok(Self::new( - active_request_map, - fail_data_buf_size, - user_hook, - tm_sender, - current_time, - )) - } - - pub fn new( - active_request_map: ActiveRequestMap, - fail_data_buf_size: usize, - user_hook: ReplyHook, - tm_sender: TmSender, - init_time: UnixTimestamp, - ) -> Self { - Self { - active_request_map, - tm_buf: alloc::vec![0; fail_data_buf_size], - current_time: init_time, - user_hook, - tm_sender, - phantom: PhantomData, - } - } - - pub fn add_routed_request( - &mut self, - request_id: verification::RequestId, - active_request_type: ActiveRequestType, - ) { - self.active_request_map - .insert(&request_id.into(), active_request_type); - } - - pub fn request_active(&self, request_id: RequestId) -> bool { - self.active_request_map.get(request_id).is_some() - } - - /// 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> { - let mut timed_out_commands = alloc::vec::Vec::new(); - self.active_request_map.for_each(|request_id, active_req| { - let diff = self.current_time - active_req.start_time(); - if diff.duration_absolute > active_req.timeout() { - self.handle_timeout(active_req, time_stamp); - } - timed_out_commands.push(*request_id); - }); - for timed_out_command in timed_out_commands { - self.active_request_map.remove(timed_out_command); - } - 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: &ActiveRequestType, 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(), - &timeout_raw, - ), - ) - .unwrap(); - self.user_hook.timeout_callback(active_request); - } - - /// Update the current time used for timeout checks based on the current OS time. - #[cfg(feature = "std")] - pub fn update_time_from_now(&mut self) -> Result<(), std::time::SystemTimeError> { - self.current_time = UnixTimestamp::from_now()?; - Ok(()) - } - - /// Update the current time used for timeout checks. - pub fn update_time(&mut self, time: UnixTimestamp) { - self.current_time = time; - } - } - */ -} - -#[cfg(feature = "std")] -pub mod std_mod { - use super::*; - use crate::ComponentId; - use crate::pool::{ - PoolAddr, PoolError, PoolProvider, PoolProviderWithGuards, SharedStaticMemoryPool, - }; - use crate::pus::verification::{TcStateAccepted, VerificationToken}; - use crate::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; - use alloc::vec::Vec; - use core::time::Duration; - use spacepackets::ByteConversionError; - use spacepackets::ecss::WritablePusPacket; - use spacepackets::ecss::tc::PusTcReader; - use spacepackets::time::StdTimestampError; - use std::string::String; - use std::sync::mpsc; - use std::sync::mpsc::TryRecvError; - use thiserror::Error; - - #[cfg(feature = "crossbeam")] - pub use cb_mod::*; - - use super::verification::{TcStateToken, VerificationReportingProvider}; - use super::{AcceptedEcssTcAndToken, ActiveRequest, TcInMemory}; - use crate::tmtc::PacketInPool; - - impl From> for EcssTmtcError { - fn from(_: mpsc::SendError) -> Self { - Self::Send(GenericSendError::RxDisconnected) - } - } - - impl EcssTmSender for mpsc::Sender { - fn send_tm(&self, source_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> { - match tm { - PusTmVariant::InStore(store_addr) => self - .send(PacketInPool { - sender_id: source_id, - store_addr, - }) - .map_err(|_| GenericSendError::RxDisconnected)?, - PusTmVariant::Direct(_) => return Err(EcssTmtcError::CantSendDirectTm), - }; - Ok(()) - } - } - - impl EcssTmSender for mpsc::SyncSender { - fn send_tm(&self, source_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> { - match tm { - PusTmVariant::InStore(store_addr) => self - .try_send(PacketInPool { - sender_id: source_id, - store_addr, - }) - .map_err(|e| EcssTmtcError::Send(e.into()))?, - PusTmVariant::Direct(_) => return Err(EcssTmtcError::CantSendDirectTm), - }; - Ok(()) - } - } - - pub type MpscTmAsVecSender = mpsc::Sender; - - impl EcssTmSender for MpscTmAsVecSender { - fn send_tm(&self, source_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> { - match tm { - PusTmVariant::InStore(addr) => return Err(EcssTmtcError::CantSendAddr(addr)), - PusTmVariant::Direct(tm) => self - .send(PacketAsVec { - sender_id: source_id, - packet: tm.to_vec()?, - }) - .map_err(|e| EcssTmtcError::Send(e.into()))?, - }; - Ok(()) - } - } - - pub type MpscTmAsVecSenderBounded = mpsc::SyncSender; - - impl EcssTmSender for MpscTmAsVecSenderBounded { - fn send_tm(&self, source_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> { - match tm { - PusTmVariant::InStore(addr) => return Err(EcssTmtcError::CantSendAddr(addr)), - PusTmVariant::Direct(tm) => self - .send(PacketAsVec { - sender_id: source_id, - packet: tm.to_vec()?, - }) - .map_err(|e| EcssTmtcError::Send(e.into()))?, - }; - Ok(()) - } - } - - pub type MpscTcReceiver = mpsc::Receiver; - - impl EcssTcReceiver for MpscTcReceiver { - fn recv_tc(&self) -> Result { - self.try_recv().map_err(|e| match e { - TryRecvError::Empty => TryRecvTmtcError::Empty, - TryRecvError::Disconnected => TryRecvTmtcError::Tmtc(EcssTmtcError::from( - GenericReceiveError::TxDisconnected(None), - )), - }) - } - } - - #[cfg(feature = "crossbeam")] - pub mod cb_mod { - use super::*; - use crossbeam_channel as cb; - - impl From> for EcssTmtcError { - fn from(_: cb::SendError) -> Self { - Self::Send(GenericSendError::RxDisconnected) - } - } - - impl From> for EcssTmtcError { - fn from(value: cb::TrySendError) -> Self { - match value { - cb::TrySendError::Full(_) => Self::Send(GenericSendError::QueueFull(None)), - cb::TrySendError::Disconnected(_) => { - Self::Send(GenericSendError::RxDisconnected) - } - } - } - } - - impl EcssTmSender for cb::Sender { - fn send_tm( - &self, - sender_id: ComponentId, - tm: PusTmVariant, - ) -> Result<(), EcssTmtcError> { - match tm { - PusTmVariant::InStore(addr) => self - .try_send(PacketInPool::new(sender_id, addr)) - .map_err(|e| EcssTmtcError::Send(e.into()))?, - PusTmVariant::Direct(_) => return Err(EcssTmtcError::CantSendDirectTm), - }; - Ok(()) - } - } - impl EcssTmSender for cb::Sender { - fn send_tm( - &self, - sender_id: ComponentId, - tm: PusTmVariant, - ) -> Result<(), EcssTmtcError> { - match tm { - PusTmVariant::InStore(addr) => return Err(EcssTmtcError::CantSendAddr(addr)), - PusTmVariant::Direct(tm) => self - .send(PacketAsVec::new(sender_id, tm.to_vec()?)) - .map_err(|e| EcssTmtcError::Send(e.into()))?, - }; - Ok(()) - } - } - - pub type CrossbeamTcReceiver = cb::Receiver; - } - - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct ActivePusRequestStd { - target_id: ComponentId, - token: TcStateToken, - start_time: std::time::Instant, - timeout: Duration, - } - - impl ActivePusRequestStd { - pub fn new( - target_id: ComponentId, - token: impl Into, - timeout: Duration, - ) -> Self { - Self { - target_id, - token: token.into(), - start_time: std::time::Instant::now(), - timeout, - } - } - } - - impl ActiveRequest for ActivePusRequestStd { - fn target_id(&self) -> ComponentId { - self.target_id - } - - fn token(&self) -> TcStateToken { - self.token - } - - fn timeout(&self) -> Duration { - self.timeout - } - fn set_token(&mut self, token: TcStateToken) { - self.token = token; - } - - fn has_timed_out(&self) -> bool { - std::time::Instant::now() - self.start_time > self.timeout - } - } - - // 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 PusTcFromMemError { - #[error("generic PUS error: {0}")] - EcssTmtc(#[from] EcssTmtcError), - #[error("invalid format of TC in memory: {0:?}")] - InvalidFormat(TcInMemory), - } - - #[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(ComponentId), - #[error("Sending action request failed: {0}")] - Send(GenericSendError), - } - - /// This error can be used for generic conversions from PUS Telecommands to request types. - /// - /// Please note that this error can also be used if no request is generated and the PUS - /// service, subservice and application data is used directly to perform some request. - #[derive(Debug, Clone, Error)] - pub enum GenericConversionError { - #[error("wrong service number {0} for packet handler")] - WrongService(u8), - #[error("invalid subservice {0}")] - InvalidSubservice(u8), - #[error("not enough application data, expected at least {expected}, found {found}")] - NotEnoughAppData { expected: usize, found: usize }, - #[error("invalid application data")] - InvalidAppData(String), - } - - /// Wrapper type which tries to encapsulate all possible errors when handling PUS packets. - #[derive(Debug, Clone, Error)] - pub enum PusPacketHandlingError { - #[error("error polling PUS TC packet: {0}")] - TcPolling(#[from] EcssTmtcError), - #[error("error generating PUS reader from memory: {0}")] - TcFromMem(#[from] PusTcFromMemError), - #[error("generic request conversion error: {0}")] - RequestConversion(#[from] GenericConversionError), - #[error("request routing error: {0}")] - RequestRouting(#[from] GenericRoutingError), - #[error("invalid verification token")] - InvalidVerificationToken, - #[error("other error {0}")] - Other(String), - } - - #[derive(Debug, Clone, Error)] - pub enum PartialPusHandlingError { - #[error("generic timestamp generation error")] - Time(#[from] StdTimestampError), - #[error("error sending telemetry: {0}")] - TmSend(EcssTmtcError), - #[error("error sending verification message")] - Verification(EcssTmtcError), - #[error("invalid verification token")] - NoVerificationToken, - } - - /// Generic result type for handlers which can process PUS packets. - #[derive(Debug, Clone)] - pub enum DirectPusPacketHandlerResult { - Handled(HandlingStatus), - SubserviceNotImplemented(u8, VerificationToken), - CustomSubservice(u8, VerificationToken), - } - - impl From for DirectPusPacketHandlerResult { - fn from(value: HandlingStatus) -> Self { - Self::Handled(value) - } - } - - /// This trait provides an abstraction for caching a raw ECSS telecommand and then - /// providing the [PusTcReader] abstraction to read the cache raw telecommand. - pub trait CacheAndReadRawEcssTc { - fn cache(&mut self, possible_packet: &TcInMemory) -> Result<(), PusTcFromMemError>; - - fn tc_slice_raw(&self) -> &[u8]; - - fn sender_id(&self) -> Option; - - fn cache_and_convert( - &mut self, - possible_packet: &TcInMemory, - ) -> Result, PusTcFromMemError> { - self.cache(possible_packet)?; - Ok(PusTcReader::new(self.tc_slice_raw()).map_err(EcssTmtcError::Pus)?) - } - - fn convert(&self) -> Result, PusTcFromMemError> { - Ok(PusTcReader::new(self.tc_slice_raw()).map_err(EcssTmtcError::Pus)?) - } - } - - /// Converter structure for PUS telecommands which are stored inside a `Vec` structure. - /// Please note that this structure is not able to convert TCs which are stored inside a - /// [SharedStaticMemoryPool]. - #[derive(Default, Clone)] - pub struct EcssTcVecCacher { - sender_id: Option, - pub pus_tc_raw: Option>, - } - - impl CacheAndReadRawEcssTc for EcssTcVecCacher { - fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> { - self.pus_tc_raw = None; - match tc_in_memory { - super::TcInMemory::Pool(_packet_in_pool) => { - return Err(PusTcFromMemError::InvalidFormat(tc_in_memory.clone())); - } - super::TcInMemory::Vec(packet_with_sender) => { - self.pus_tc_raw = Some(packet_with_sender.packet.clone()); - self.sender_id = Some(packet_with_sender.sender_id); - } - }; - Ok(()) - } - - fn sender_id(&self) -> Option { - self.sender_id - } - - fn tc_slice_raw(&self) -> &[u8] { - if self.pus_tc_raw.is_none() { - return &[]; - } - self.pus_tc_raw.as_ref().unwrap() - } - } - - /// Converter structure for PUS telecommands which are stored inside - /// [SharedStaticMemoryPool] structure. This is useful if run-time allocation for these - /// packets should be avoided. Please note that this structure is not able to convert TCs which - /// are stored as a `Vec`. - #[derive(Clone)] - pub struct EcssTcInSharedPoolCacher { - sender_id: Option, - shared_tc_pool: SharedStaticMemoryPool, - pus_buf: Vec, - } - - impl EcssTcInSharedPoolCacher { - pub fn new(shared_tc_store: SharedStaticMemoryPool, max_expected_tc_size: usize) -> Self { - Self { - sender_id: None, - shared_tc_pool: shared_tc_store, - pus_buf: alloc::vec![0; max_expected_tc_size], - } - } - - pub fn copy_tc_to_buf(&mut self, addr: PoolAddr) -> Result<(), PusTcFromMemError> { - // Keep locked section as short as possible. - let mut tc_pool = self.shared_tc_pool.write().map_err(|_| { - PusTcFromMemError::EcssTmtc(EcssTmtcError::Store(PoolError::LockError)) - })?; - let tc_size = tc_pool.len_of_data(&addr).map_err(EcssTmtcError::Store)?; - if tc_size > self.pus_buf.len() { - return Err( - EcssTmtcError::ByteConversion(ByteConversionError::ToSliceTooSmall { - found: self.pus_buf.len(), - expected: tc_size, - }) - .into(), - ); - } - let tc_guard = tc_pool.read_with_guard(addr); - // TODO: Proper error handling. - tc_guard.read(&mut self.pus_buf[0..tc_size]).unwrap(); - Ok(()) - } - } - - impl CacheAndReadRawEcssTc for EcssTcInSharedPoolCacher { - fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> { - match tc_in_memory { - super::TcInMemory::Pool(packet_in_pool) => { - self.copy_tc_to_buf(packet_in_pool.store_addr)?; - self.sender_id = Some(packet_in_pool.sender_id); - } - super::TcInMemory::Vec(_) => { - return Err(PusTcFromMemError::InvalidFormat(tc_in_memory.clone())); - } - }; - Ok(()) - } - - fn tc_slice_raw(&self) -> &[u8] { - self.pus_buf.as_ref() - } - - fn sender_id(&self) -> Option { - self.sender_id - } - } - - // TODO: alloc feature flag? - #[derive(Clone)] - pub enum EcssTcCacher { - Static(EcssTcInSharedPoolCacher), - Heap(EcssTcVecCacher), - } - - impl EcssTcCacher { - pub fn new_static(static_store_converter: EcssTcInSharedPoolCacher) -> Self { - Self::Static(static_store_converter) - } - - pub fn new_heap(heap_converter: EcssTcVecCacher) -> Self { - Self::Heap(heap_converter) - } - } - - impl CacheAndReadRawEcssTc for EcssTcCacher { - fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> { - match self { - Self::Static(converter) => converter.cache(tc_in_memory), - Self::Heap(converter) => converter.cache(tc_in_memory), - } - } - fn tc_slice_raw(&self) -> &[u8] { - match self { - Self::Static(converter) => converter.tc_slice_raw(), - Self::Heap(converter) => converter.tc_slice_raw(), - } - } - fn sender_id(&self) -> Option { - match self { - Self::Static(converter) => converter.sender_id(), - Self::Heap(converter) => converter.sender_id(), - } - } - } - - pub struct PusServiceBase< - TcReceiver: EcssTcReceiver, - TmSender: EcssTmSender, - VerificationReporter: VerificationReportingProvider, - > { - pub id: ComponentId, - pub tc_receiver: TcReceiver, - pub tm_sender: TmSender, - pub verif_reporter: VerificationReporter, - } - - /// This is a high-level PUS packet handler helper. - /// - /// It performs some of the boilerplate acitivities involved when handling PUS telecommands and - /// it can be used to implement the handling of PUS telecommands for certain PUS telecommands - /// groups (for example individual services). - /// - /// This base class can handle PUS telecommands backed by different memory storage machanisms - /// by using the [CacheAndReadRawEcssTc] abstraction. This object provides some convenience - /// methods to make the generic parts of TC handling easier. - pub struct PusServiceHelper< - TcReceiver: EcssTcReceiver, - TmSender: EcssTmSender, - TcInMemConverter: CacheAndReadRawEcssTc, - VerificationReporter: VerificationReportingProvider, - > { - pub common: PusServiceBase, - pub tc_in_mem_converter: TcInMemConverter, - } - - impl< - TcReceiver: EcssTcReceiver, - TmSender: EcssTmSender, - TcInMemConverter: CacheAndReadRawEcssTc, - VerificationReporter: VerificationReportingProvider, - > PusServiceHelper - { - pub fn new( - id: ComponentId, - tc_receiver: TcReceiver, - tm_sender: TmSender, - verification_handler: VerificationReporter, - tc_in_mem_converter: TcInMemConverter, - ) -> Self { - Self { - common: PusServiceBase { - id, - tc_receiver, - tm_sender, - verif_reporter: verification_handler, - }, - tc_in_mem_converter, - } - } - - pub fn id(&self) -> ComponentId { - self.common.id - } - - pub fn tm_sender(&self) -> &TmSender { - &self.common.tm_sender - } - - /// This function can be used to poll the internal [EcssTcReceiver] object for the next - /// telecommand packet. It will return `Ok(None)` if there are not packets available. - /// In any other case, it will perform the acceptance of the ECSS TC packet using the - /// internal [VerificationReportingProvider] object. It will then return the telecommand - /// and the according accepted token. - pub fn retrieve_and_accept_next_packet( - &mut self, - ) -> Result, PusPacketHandlingError> { - match self.common.tc_receiver.recv_tc() { - Ok(EcssTcAndToken { - tc_in_memory, - token, - }) => { - if token.is_none() { - return Err(PusPacketHandlingError::InvalidVerificationToken); - } - let token = token.unwrap(); - let accepted_token = VerificationToken::::try_from(token) - .map_err(|_| PusPacketHandlingError::InvalidVerificationToken)?; - Ok(Some(AcceptedEcssTcAndToken { - tc_in_memory, - token: accepted_token, - })) - } - Err(e) => match e { - TryRecvTmtcError::Tmtc(e) => Err(PusPacketHandlingError::TcPolling(e)), - TryRecvTmtcError::Empty => Ok(None), - }, - } - } - - pub fn verif_reporter(&self) -> &VerificationReporter { - &self.common.verif_reporter - } - pub fn verif_reporter_mut(&mut self) -> &mut VerificationReporter { - &mut self.common.verif_reporter - } - - pub fn tc_in_mem_converter(&self) -> &TcInMemConverter { - &self.tc_in_mem_converter - } - - pub fn tc_in_mem_converter_mut(&mut self) -> &mut TcInMemConverter { - &mut self.tc_in_mem_converter - } - } - - pub type PusServiceHelperDynWithMpsc = - PusServiceHelper; - pub type PusServiceHelperDynWithBoundedMpsc = - PusServiceHelper< - MpscTcReceiver, - MpscTmAsVecSenderBounded, - TcInMemConverter, - VerificationReporter, - >; - pub type PusServiceHelperStaticWithMpsc = - PusServiceHelper< - MpscTcReceiver, - PacketSenderWithSharedPool, - TcInMemConverter, - VerificationReporter, - >; - pub type PusServiceHelperStaticWithBoundedMpsc = - PusServiceHelper< - MpscTcReceiver, - PacketSenderWithSharedPool, - TcInMemConverter, - VerificationReporter, - >; -} - -pub(crate) fn source_buffer_large_enough( - cap: usize, - len: usize, -) -> Result<(), ByteConversionError> { - if len > cap { - return Err(ByteConversionError::ToSliceTooSmall { - found: cap, - expected: len, - }); - } - Ok(()) -} - -#[cfg(any(feature = "test_util", test))] -pub mod test_util { - use arbitrary_int::{u11, u21}; - use spacepackets::ecss::{tc::PusTcCreator, tm::PusTmReader}; - - use crate::request::UniqueApidTargetId; - - use super::{ - DirectPusPacketHandlerResult, PusPacketHandlingError, - verification::{self, TcStateAccepted, VerificationToken}, - }; - - pub const TEST_APID: u11 = u11::new(0x101); - pub const TEST_UNIQUE_ID_0: u21 = u21::new(0x05); - pub const TEST_UNIQUE_ID_1: u21 = u21::new(0x06); - - pub const TEST_COMPONENT_ID_0: UniqueApidTargetId = - UniqueApidTargetId::new(TEST_APID, TEST_UNIQUE_ID_0); - pub const TEST_COMPONENT_ID_1: UniqueApidTargetId = - UniqueApidTargetId::new(TEST_APID, TEST_UNIQUE_ID_1); - - pub trait PusTestHarness { - fn start_verification(&mut self, tc: &PusTcCreator) -> VerificationToken; - fn send_tc(&self, token: &VerificationToken, tc: &PusTcCreator); - 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: verification::RequestId, - ); - } - - pub trait SimplePusPacketHandler { - fn handle_one_tc(&mut self) - -> Result; - } -} - -#[cfg(test)] -pub mod tests { - use core::cell::RefCell; - use std::sync::mpsc::TryRecvError; - use std::sync::{RwLock, mpsc}; - - use alloc::collections::VecDeque; - use alloc::vec::Vec; - use arbitrary_int::{u11, u14}; - use satrs_shared::res_code::ResultU16; - use spacepackets::CcsdsPacket; - use spacepackets::ecss::tc::{PusTcCreator, PusTcReader}; - use spacepackets::ecss::tm::{GenericPusTmSecondaryHeader, PusTmCreator, PusTmReader}; - use spacepackets::ecss::{PusPacket, WritablePusPacket}; - use test_util::{TEST_APID, TEST_COMPONENT_ID_0}; - - use crate::ComponentId; - use crate::pool::{PoolProvider, SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig}; - use crate::pus::verification::{RequestId, VerificationReporter}; - use crate::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool}; - - use super::verification::test_util::TestVerificationReporter; - use super::verification::{ - TcStateAccepted, VerificationReporterConfig, VerificationReportingProvider, - VerificationToken, - }; - use super::*; - - #[derive(Debug, Eq, PartialEq, Clone)] - pub(crate) struct CommonTmInfo { - pub subservice: u8, - pub apid: u11, - pub seq_count: u14, - pub msg_counter: u16, - pub dest_id: u16, - pub timestamp: Vec, - } - - impl CommonTmInfo { - pub fn new( - subservice: u8, - apid: u11, - seq_count: u14, - msg_counter: u16, - dest_id: u16, - timestamp: &[u8], - ) -> Self { - Self { - subservice, - apid, - seq_count, - msg_counter, - dest_id, - timestamp: timestamp.to_vec(), - } - } - pub fn new_zero_seq_count( - subservice: u8, - apid: u11, - dest_id: u16, - timestamp: &[u8], - ) -> Self { - Self::new(subservice, apid, u14::new(0), 0, dest_id, timestamp) - } - - pub fn new_from_tm(tm: &PusTmCreator) -> Self { - let mut timestamp = [0; 7]; - timestamp.clone_from_slice(&tm.timestamp()[0..7]); - Self { - subservice: PusPacket::message_subtype_id(tm), - apid: tm.apid(), - seq_count: tm.seq_count(), - msg_counter: tm.msg_type_counter(), - dest_id: tm.dest_id(), - timestamp: timestamp.to_vec(), - } - } - } - - /// Common fields for a PUS service test harness. - pub struct PusServiceHandlerWithSharedStoreCommon { - pus_buf: RefCell<[u8; 2048]>, - tm_buf: [u8; 2048], - tc_pool: SharedStaticMemoryPool, - tm_pool: SharedPacketPool, - tc_sender: mpsc::SyncSender, - tm_receiver: mpsc::Receiver, - } - - pub type PusServiceHelperStatic = PusServiceHelper< - MpscTcReceiver, - PacketSenderWithSharedPool, - EcssTcInSharedPoolCacher, - VerificationReporter, - >; - - impl PusServiceHandlerWithSharedStoreCommon { - /// This function generates the structure in addition to the PUS service handler - /// [PusServiceHandler] which might be required for a specific PUS service handler. - /// - /// The PUS service handler is instantiated with a [EcssTcInStoreConverter]. - pub fn new(id: ComponentId) -> (Self, PusServiceHelperStatic) { - let pool_cfg = StaticPoolConfig::new_from_subpool_cfg_tuples( - 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)); - let shared_tm_pool = SharedStaticMemoryPool::new(RwLock::new(tm_pool)); - let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool); - let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::sync_channel(10); - let (tm_tx, tm_rx) = mpsc::sync_channel(10); - - let verif_cfg = VerificationReporterConfig::new(TEST_APID, 1, 2, 8); - let verification_handler = - VerificationReporter::new(TEST_COMPONENT_ID_0.id(), &verif_cfg); - let test_srv_tm_sender = - PacketSenderWithSharedPool::new(tm_tx, shared_tm_pool_wrapper.clone()); - let in_store_converter = EcssTcInSharedPoolCacher::new(shared_tc_pool.clone(), 2048); - ( - Self { - pus_buf: RefCell::new([0; 2048]), - tm_buf: [0; 2048], - tc_pool: shared_tc_pool, - tm_pool: shared_tm_pool_wrapper, - tc_sender: test_srv_tc_tx, - tm_receiver: tm_rx, - }, - PusServiceHelper::new( - id, - test_srv_tc_rx, - test_srv_tm_sender, - verification_handler, - in_store_converter, - ), - ) - } - pub fn send_tc( - &self, - sender_id: ComponentId, - token: &VerificationToken, - tc: &PusTcCreator, - ) { - let mut mut_buf = self.pus_buf.borrow_mut(); - let tc_size = tc.write_to_bytes(mut_buf.as_mut_slice()).unwrap(); - let mut tc_pool = self.tc_pool.write().unwrap(); - let addr = tc_pool.add(&mut_buf[..tc_size]).unwrap(); - drop(tc_pool); - // Send accepted TC to test service handler. - self.tc_sender - .send(EcssTcAndToken::new( - PacketInPool::new(sender_id, addr), - *token, - )) - .expect("sending tc failed"); - } - - pub fn read_next_tm(&mut self) -> PusTmReader<'_> { - let next_msg = self.tm_receiver.try_recv(); - assert!(next_msg.is_ok()); - let tm_in_pool = next_msg.unwrap(); - let tm_pool = self.tm_pool.0.read().unwrap(); - let tm_raw = tm_pool.read_as_vec(&tm_in_pool.store_addr).unwrap(); - self.tm_buf[0..tm_raw.len()].copy_from_slice(&tm_raw); - PusTmReader::new(&self.tm_buf, 7).unwrap() - } - - pub fn check_no_tm_available(&self) -> bool { - let next_msg = self.tm_receiver.try_recv(); - if let TryRecvError::Empty = next_msg.unwrap_err() { - return true; - } - false - } - - pub fn check_next_verification_tm(&self, subservice: u8, expected_request_id: RequestId) { - let next_msg = self.tm_receiver.try_recv(); - assert!(next_msg.is_ok()); - let tm_in_pool = next_msg.unwrap(); - let tm_pool = self.tm_pool.0.read().unwrap(); - let tm_raw = tm_pool.read_as_vec(&tm_in_pool.store_addr).unwrap(); - let tm = PusTmReader::new(&tm_raw, 7).unwrap(); - assert_eq!(PusPacket::service_type_id(&tm), 1); - assert_eq!(PusPacket::message_subtype_id(&tm), subservice); - assert_eq!(tm.apid(), TEST_APID); - let req_id = - RequestId::from_bytes(tm.user_data()).expect("generating request ID failed"); - assert_eq!(req_id, expected_request_id); - } - } - - pub struct PusServiceHandlerWithVecCommon { - current_tm: Option>, - tc_sender: mpsc::Sender, - tm_receiver: mpsc::Receiver, - } - pub type PusServiceHelperDynamic = - PusServiceHelper; - - impl PusServiceHandlerWithVecCommon { - pub fn new_with_standard_verif_reporter( - id: ComponentId, - ) -> (Self, PusServiceHelperDynamic) { - let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel(); - let (tm_tx, tm_rx) = mpsc::channel(); - - let verif_cfg = VerificationReporterConfig::new(TEST_APID, 1, 2, 8); - let verification_handler = - VerificationReporter::new(TEST_COMPONENT_ID_0.id(), &verif_cfg); - let in_store_converter = EcssTcVecCacher::default(); - ( - Self { - current_tm: None, - tc_sender: test_srv_tc_tx, - tm_receiver: tm_rx, - }, - PusServiceHelper::new( - id, - test_srv_tc_rx, - tm_tx, - verification_handler, - in_store_converter, - ), - ) - } - } - - impl PusServiceHandlerWithVecCommon { - pub fn new_with_test_verif_sender( - id: ComponentId, - ) -> ( - Self, - PusServiceHelper< - MpscTcReceiver, - MpscTmAsVecSender, - EcssTcVecCacher, - TestVerificationReporter, - >, - ) { - let (test_srv_tc_tx, test_srv_tc_rx) = mpsc::channel(); - let (tm_tx, tm_rx) = mpsc::channel(); - - let in_store_converter = EcssTcVecCacher::default(); - let verification_handler = TestVerificationReporter::new(id); - ( - Self { - current_tm: None, - tc_sender: test_srv_tc_tx, - tm_receiver: tm_rx, - //verification_handler: verification_handler.clone(), - }, - PusServiceHelper::new( - id, - test_srv_tc_rx, - tm_tx, - verification_handler, - in_store_converter, - ), - ) - } - } - - impl PusServiceHandlerWithVecCommon { - pub fn send_tc( - &self, - sender_id: ComponentId, - token: &VerificationToken, - tc: &PusTcCreator, - ) { - // Send accepted TC to test service handler. - self.tc_sender - .send(EcssTcAndToken::new( - TcInMemory::Vec(PacketAsVec::new( - sender_id, - tc.to_vec().expect("pus tc conversion to vec failed"), - )), - *token, - )) - .expect("sending tc failed"); - } - - pub fn read_next_tm(&mut self) -> PusTmReader<'_> { - let next_msg = self.tm_receiver.try_recv(); - assert!(next_msg.is_ok()); - self.current_tm = Some(next_msg.unwrap().packet); - PusTmReader::new(self.current_tm.as_ref().unwrap(), 7).unwrap() - } - - pub fn check_no_tm_available(&self) -> bool { - let next_msg = self.tm_receiver.try_recv(); - if let TryRecvError::Empty = next_msg.unwrap_err() { - return true; - } - false - } - - pub fn check_next_verification_tm(&self, subservice: u8, expected_request_id: RequestId) { - let next_msg = self.tm_receiver.try_recv(); - assert!(next_msg.is_ok()); - let next_msg = next_msg.unwrap(); - let tm = PusTmReader::new(next_msg.packet.as_slice(), 7).unwrap(); - assert_eq!(PusPacket::service_type_id(&tm), 1); - assert_eq!(PusPacket::message_subtype_id(&tm), subservice); - assert_eq!(tm.apid(), TEST_APID); - let req_id = - RequestId::from_bytes(tm.user_data()).expect("generating request ID failed"); - 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_type_id() != SERVICE { - return Err(PusPacketHandlingError::RequestConversion( - GenericConversionError::WrongService(tc.service_type_id()), - )); - } - 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() - ); - } - } - - pub struct TestRouter { - pub routing_requests: RefCell>, - pub routing_errors: RefCell>, - pub injected_routing_failure: RefCell>, - } - - impl Default for TestRouter { - fn default() -> Self { - Self { - routing_requests: Default::default(), - routing_errors: 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 handle_error( - &self, - target_id: ComponentId, - _token: VerificationToken, - _tc: &PusTcReader, - error: GenericRoutingError, - _time_stamp: &[u8], - _verif_reporter: &impl VerificationReportingProvider, - ) { - self.routing_errors - .borrow_mut() - .push_back((target_id, error)); - } - - pub fn no_routing_errors(&self) -> bool { - self.routing_errors.borrow().is_empty() - } - - pub fn retrieve_next_routing_error(&mut self) -> (ComponentId, GenericRoutingError) { - if self.routing_errors.borrow().is_empty() { - panic!("no routing request available"); - } - self.routing_errors.borrow_mut().pop_front().unwrap() - } - - 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) -> (ComponentId, 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/tmtc/mod.rs b/satrs/src/tmtc/mod.rs index ab2e9f3..5737494 100644 --- a/satrs/src/tmtc/mod.rs +++ b/satrs/src/tmtc/mod.rs @@ -229,8 +229,8 @@ pub mod std_mod { use spacepackets::ecss::WritablePusPacket; use thiserror::Error; + use crate::legacy::pus::{EcssTmSender, EcssTmtcError, PacketSenderPusTc}; use crate::pool::PoolProvider; - use crate::pus::{EcssTmSender, EcssTmtcError, PacketSenderPusTc}; use super::*; @@ -460,16 +460,16 @@ pub mod std_mod { fn send_tm( &self, sender_id: crate::ComponentId, - tm: crate::pus::PusTmVariant, - ) -> Result<(), crate::pus::EcssTmtcError> { + tm: crate::legacy::pus::PusTmVariant, + ) -> Result<(), crate::legacy::pus::EcssTmtcError> { let send_addr = |store_addr: PoolAddr| { self.sender .send_packet(sender_id, store_addr) .map_err(EcssTmtcError::Send) }; match tm { - crate::pus::PusTmVariant::InStore(store_addr) => send_addr(store_addr), - crate::pus::PusTmVariant::Direct(tm_creator) => { + crate::legacy::pus::PusTmVariant::InStore(store_addr) => send_addr(store_addr), + crate::legacy::pus::PusTmVariant::Direct(tm_creator) => { let mut pool = self.shared_pool.borrow_mut(); let store_addr = pool.add_pus_tm_from_creator(&tm_creator)?; send_addr(store_addr) diff --git a/satrs/tests/pus_verification.rs b/satrs/tests/pus_verification.rs index ca1383a..3e63fdb 100644 --- a/satrs/tests/pus_verification.rs +++ b/satrs/tests/pus_verification.rs @@ -3,12 +3,12 @@ pub mod crossbeam_test { use arbitrary_int::traits::Integer as _; use arbitrary_int::u14; use hashbrown::HashMap; - use satrs::pool::{PoolProvider, PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; - use satrs::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0}; - use satrs::pus::verification::{ + use satrs::legacy::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0}; + use satrs::legacy::pus::verification::{ FailParams, RequestId, VerificationReporter, VerificationReporterConfig, VerificationReportingProvider, }; + use satrs::pool::{PoolProvider, PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig}; use satrs::tmtc::{PacketSenderWithSharedPool, SharedStaticMemoryPool}; use spacepackets::SpHeader; use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader};