87 Commits

Author SHA1 Message Date
282f799203 Merge pull request 'prep v0.2.0' (#184) from prep_v0.2.0 into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #184
2024-05-02 14:57:14 +02:00
46dbb4309b new clippy check
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build started...
2024-05-02 14:44:22 +02:00
42d1257e83 prepare next release v0.2.0
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build started...
2024-05-02 14:39:30 +02:00
583f6ce4d2 Merge pull request 'small robustness fix' (#183) from robustness-fix into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #183
2024-05-02 13:41:55 +02:00
408803fe86 small robustness fix
Some checks are pending
Rust/sat-rs/pipeline/head Build queued...
2024-05-02 13:41:27 +02:00
9ffe4d0ae0 Merge pull request 'smaller improvements' (#182) from smaller-improvements into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #182
2024-05-02 12:28:39 +02:00
e37061dcf0 smaller improvements 2024-05-02 12:28:09 +02:00
3a2ac11407 Merge pull request 'bounded the PUS stack hot loop' (#181) from pus-hot-loop-bounding into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #181
2024-05-02 12:02:02 +02:00
23327a7786 bounded the PUS stack hot loop
Some checks are pending
Rust/sat-rs/pipeline/head Build queued...
2024-05-02 12:01:24 +02:00
89d5a1022f Merge pull request 'optimize PUS stack code' (#180) from optimize-pus-stack-code into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #180
2024-05-02 11:59:26 +02:00
a00c843698 optimize PUS stack code
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-05-02 11:58:46 +02:00
c586fd7fef Merge pull request 'try unifying some direct PUS handler code' (#179) from unify-some-example-code into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #179
2024-05-02 11:29:11 +02:00
7e78e70a17 try unifying some direct PUS handler code
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-05-02 11:14:05 +02:00
424dfc439c Merge pull request 'simplified PUS stack' (#178) from simplify-pus-stack into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #178
2024-05-02 10:01:16 +02:00
45eb2f1343 cargo fmt
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-05-01 21:16:26 +02:00
736eb74e66 simplified PUS stack
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-05-01 21:13:08 +02:00
29f71c2a57 Merge pull request 'Reworked generic parameter handling for PUS service 1 and 5' (#175) from rework-generic-params-for-pus into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #175
2024-04-30 15:42:05 +02:00
f0d08b65a4 Merge branch 'main' into rework-generic-params-for-pus
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-04-30 13:35:08 +02:00
c7a74a844c Merge pull request 'renamed thread name' (#176) from small-tweak into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #176
2024-04-30 13:31:46 +02:00
9c60427f89 Reworked generic parameter handling for PUS service 1 and 5
Some checks are pending
Rust/sat-rs/pipeline/head Build queued...
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-04-30 13:29:55 +02:00
958ab9bab6 renamed thread name
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-25 11:11:31 +02:00
312849bddb Merge pull request 'More improvements for Event API' (#173) from improve-event-api into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #173
2024-04-24 19:34:33 +02:00
b0159a3ba7 prep next release candidate
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-04-24 19:18:45 +02:00
c477739f6d more improvements for API, tests for example event module
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-24 18:50:08 +02:00
b7ce039406 add optional defmt support for events
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-24 18:36:00 +02:00
4736d40997 Merge pull request 'simplified event management' (#172) from simplify-event-management into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #172
2024-04-24 15:58:00 +02:00
5ec5124ea3 Updated events modules and docs
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-04-24 14:30:45 +02:00
bfaddd0ebb Merge pull request 'prep next release' (#171) from pre-v0.2.0-rc.4 into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #171
2024-04-23 16:32:03 +02:00
423a068736 prep next release
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-23 14:55:19 +02:00
8022af1bf2 Merge pull request 'update Python client for example' (#170) from update-example-pyclient into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #170
2024-04-23 14:52:04 +02:00
acd2260dfd update Python client for example
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-23 14:22:50 +02:00
e5ee698dc4 Merge pull request 'TCP server improvements' (#169) from tcp-ip-improvements into main
Some checks failed
Rust/sat-rs/pipeline/head There was a failure building this commit
Reviewed-on: #169
2024-04-23 13:21:41 +02:00
e8907c74d4 changelog
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-23 11:23:00 +02:00
536051e05b improvements and fixes
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-22 20:29:14 +02:00
701db659e9 Merge pull request 'formatting' (#168) from fmt into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #168
2024-04-22 15:47:58 +02:00
4b8e54b91b formatting
Some checks are pending
Rust/sat-rs/pipeline/head This commit looks good
Rust/sat-rs/pipeline/pr-main Build started...
2024-04-22 10:42:49 +02:00
870d60cfd6 Merge pull request 'bugfix and improvements for CCSDS SP decoder' (#167) from ccsds-decoder-bugfix into main
Some checks failed
Rust/sat-rs/pipeline/head There was a failure building this commit
Reviewed-on: #167
2024-04-22 10:23:12 +02:00
9e62e4292c bugfix and improvements for CCSDS SP decoder
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build started...
2024-04-20 11:19:46 +02:00
b2e77fbc09 Merge pull request 'requires another hotfix' (#166) from and-another-docs-rs-hotfix into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #166
2024-04-17 20:42:09 +02:00
5371928496 docs_rs build argument hotfix
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build queued...
2024-04-17 20:41:30 +02:00
31cddbd99b Merge pull request 'bump msrv version' (#165) from bump-msrv into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #165
2024-04-17 18:56:21 +02:00
7c00e13e70 bump msrv version
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-17 18:10:32 +02:00
aa72063454 Merge pull request 'prepare next release candidate' (#164) from prep-v0.2.0-rc.2 into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #164
2024-04-17 18:03:28 +02:00
7b37b76695 prepare next release candidate
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-17 17:19:38 +02:00
ea5d95c12d Merge pull request 'why is this an issue for docs-rs?' (#163) from fix-for-docs-build-docs-rs into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #163
2024-04-17 17:09:36 +02:00
c62adbb300 Merge branch 'main' into fix-for-docs-build-docs-rs
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build started...
2024-04-17 16:41:45 +02:00
9242b8a607 Merge pull request 'prepare MIB release' (#162) from prepare-mib-release into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #162
2024-04-17 16:37:34 +02:00
4a27d2605d why is this an issue for docs-rs?
Some checks are pending
Rust/sat-rs/pipeline/head Build queued...
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-04-17 16:34:56 +02:00
8195245481 prepare MIB release
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
Rust/sat-rs/pipeline/pr-main Build queued...
2024-04-17 16:17:30 +02:00
f6f7519625 Merge pull request 'small cleanup' (#161) from small-cargo-toml-cleaning into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #161
2024-04-17 16:03:03 +02:00
0f0fbc1a18 small cleanup
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-17 15:17:46 +02:00
6e55e2ac95 Merge pull request 'Prepare next releases' (#160) from prep-next-satrs-releases into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #160
2024-04-17 14:58:01 +02:00
2f96bfe992 changelog sat-rs
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-17 10:03:17 +02:00
52aafb3aab prep next releases
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-17 10:01:46 +02:00
6ce9cb5ead Merge pull request 'use released satrs-shared' (#159) from use-released-satrs-shared into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #159
2024-04-16 21:31:21 +02:00
273f79d1e6 use release satrs-shared
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-16 21:07:51 +02:00
622221835e Merge pull request 'allow sat-rs shared spacepackets range' (#158) from satrs-shared-spacepackets-range into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #158
2024-04-16 20:54:59 +02:00
e396ad2e7a small fix
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-16 19:52:32 +02:00
772927d50b allow spacepackets range
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-16 19:50:46 +02:00
be9a45e55f Merge pull request 'changelog satrs-shared v0.1.3' (#157) from changelog-satrs-shared into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #157
2024-04-16 19:48:12 +02:00
eee8a69550 changelog satrs-shared v0.1.3
Some checks are pending
Rust/sat-rs/pipeline/head Build queued...
2024-04-16 19:47:36 +02:00
f7a6d3ce47 Merge pull request 'bump spacepackets to v0.11.0' (#156) from bump-spacepackets into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #156
2024-04-16 19:46:17 +02:00
df97a3a93e small adjustment
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build started...
2024-04-16 19:39:07 +02:00
42750e08c0 bump spacepackets to v0.11.0
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-16 19:26:46 +02:00
786671bbd7 Merge pull request 're-worked TMTC modules' (#155) from rework-tmtc-modules into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #155
2024-04-16 11:10:52 +02:00
63f37f0917 Re-worked TMTC modules
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-04-16 11:04:22 +02:00
8cfe3b81e7 Merge pull request 'bugfix for targeted services' (#154) from bugfix-targeted-services into main
Some checks failed
Rust/sat-rs/pipeline/head There was a failure building this commit
Reviewed-on: #154
2024-04-13 15:10:14 +02:00
de50bec562 bugfix for targeted services
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-04-10 17:18:53 +02:00
39ab9fa27b Merge pull request 'closure param name tweak' (#153) from small-example-tweak into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #153
2024-04-10 17:17:13 +02:00
1dbc81a8f5 closure param name tweak
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-10 15:51:08 +02:00
1ad74ee1d5 Merge pull request 'this makes a bit more sense' (#152) from naming-improvement-pus-actions into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #152
2024-04-10 15:37:39 +02:00
f96fe6bdc0 this makes a bit more sense
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
2024-04-10 15:19:08 +02:00
d43a8eb571 Merge pull request 'improve example structure' (#151) from improve-example-structure into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #151
2024-04-10 13:19:41 +02:00
0bbada90ef improve example structure
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
2024-04-10 12:58:51 +02:00
3375780e00 Merge pull request 'Refactor and improve TCP servers' (#150) from refactor-tcp-server into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #150
2024-04-10 12:29:23 +02:00
de028ed827 bugfix in example
Some checks are pending
Rust/sat-rs/pipeline/pr-main Build started...
2024-04-10 11:54:05 +02:00
d27ac5dfc9 refactored TCP server
Some checks are pending
Rust/sat-rs/pipeline/head Build queued...
2024-04-10 11:28:16 +02:00
c67b7cb93a this is non-trivial
Some checks failed
Rust/sat-rs/pipeline/head There was a failure building this commit
2024-04-09 19:40:55 +02:00
f71ba3e8d8 Merge pull request 'introduce stop signal handling for TCP' (#149) from tcp-server-stop-signal into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #149
2024-04-09 18:11:29 +02:00
3cc9dd3c48 introduce stop signal handling
Some checks are pending
Rust/sat-rs/pipeline/head Build started...
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-04-09 17:21:43 +02:00
0fec994028 Merge pull request 'Update STM32F3 example' (#148) from update-stm32f3-example-tmtc-handling into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #148
2024-04-04 18:33:00 +02:00
226a134aff Update STM32F3 example
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
1. New command to change blinky frequency.
2. Bump used sat-rs version.
2024-04-04 18:21:30 +02:00
aac59ec7c1 Merge pull request 'Major refactoring and update of PUS module' (#146) from pus-modules-update into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #146
2024-04-04 15:27:29 +02:00
ce7eb8650f changelog
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-04-04 15:20:07 +02:00
df2733a176 Major refactoring and update of PUS module 2024-04-04 15:18:53 +02:00
344fe6a4c0 Merge pull request 'Simplify low level PUS API' (#145) from simplify-low-level-pus-api into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good
Reviewed-on: #145
2024-03-29 23:41:14 +01:00
a5941751d7 Simplify low-level PUS API for verification and events
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good
2024-03-29 16:22:40 +01:00
108 changed files with 12877 additions and 8486 deletions

64
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: ci
on: [push, pull_request]
jobs:
check:
name: Check build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo check --release
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install nextest
uses: taiki-e/install-action@nextest
- run: cargo nextest run --all-features
- run: cargo test --doc
cross-check:
name: Check Cross-Compilation
runs-on: ubuntu-latest
strategy:
matrix:
target:
- armv7-unknown-linux-gnueabihf
- thumbv7em-none-eabihf
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf"
- run: cargo check -p satrs --release --target=${{matrix.target}} --no-default-features
fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo fmt --all -- --check
docs:
name: Check Documentation Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]'
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo clippy -- -D warnings

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
target/
output.log
/Cargo.lock
output.log

View File

@ -1,4 +1,4 @@
<p align="center"> <img src="misc/satrs-logo.png" width="40%"> </p>
<p align="center"> <img src="misc/satrs-logo-v2.png" width="40%"> </p>
[![sat-rs website](https://img.shields.io/badge/sat--rs-website-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/)
[![sat-rs book](https://img.shields.io/badge/sat--rs-book-darkgreen?style=flat)](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/)
@ -40,7 +40,7 @@ This project currently contains following crates:
* [`satrs-mib`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-mib):
Components to build a mission information base from the on-board software directly.
* [`satrs-example-stm32f3-disco`](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example-stm32f3-disco):
Example of a simple example on-board software using sat-rs components on a bare-metal system
Example of a simple example using low-level sat-rs components on a bare-metal system
with constrained resources.
Each project has its own `CHANGELOG.md`.

View File

@ -33,6 +33,7 @@ pipeline {
stage('Test') {
steps {
sh 'cargo nextest r --all-features'
sh 'cargo test --doc'
}
}
stage('Check with all features') {

View File

@ -47,7 +47,7 @@ def main():
parser.add_argument(
"-p",
"--package",
choices=["satrs", "satrs-minisim"],
choices=["satrs", "satrs-minisim", "satrs-example"],
default="satrs",
help="Choose project to generate coverage for",
)

BIN
misc/satrs-logo-v2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -17,7 +17,7 @@ it is still centered around small packets. `sat-rs` provides support for these E
standards and also attempts to fill the gap to the internet protocol by providing the following
components.
1. [UDP TMTC Server](https://docs.rs/satrs/latest/satrs/hal/host/udp_server/index.html).
1. [UDP TMTC Server](https://docs.rs/satrs/latest/satrs/hal/std/udp_server/index.html).
UDP is already packet based which makes it an excellent fit for exchanging space packets.
2. [TCP TMTC Server Components](https://docs.rs/satrs/latest/satrs/hal/std/tcp_server/index.html).
TCP is a stream based protocol, so the library provides building blocks to parse telemetry
@ -39,8 +39,12 @@ task might be to store all arriving telemetry persistently. This is especially i
space systems which do not have permanent contact like low-earth-orbit (LEO) satellites.
The most important task of a TC source is to deliver the telecommands to the correct recipients.
For modern component oriented software using message passing, this usually includes staged
demultiplexing components to determine where a command needs to be sent.
For component oriented software using message passing, this usually includes staged demultiplexing
components to determine where a command needs to be sent.
Using a generic concept of a TC source and a TM sink as part of the software design simplifies
the flexibility of the TMTC infrastructure: Newly added TM generators and TC receiver only have to
forward their generated or received packets to those handler objects.
# Low-level protocols and the bridge to the communcation subsystem

View File

@ -1,14 +1,17 @@
# Events
Events can be an extremely important mechanism used for remote systems to monitor unexpected
or expected anomalies and events occuring on these systems. They are oftentimes tied to
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.
Events can also be used as a convenient Inter-Process Communication (IPC) mechansism, which is
also observable for the Ground segment. 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 really need this service.
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:

View File

@ -1,11 +1,11 @@
# Modes
Modes are an extremely useful concept for complex system in general. They also allow simplified
system reasoning for both system operators and OBSW developers. They model the behaviour of a
component and also provide observability of a system. A few examples of how to model
different components of a space system with modes will be given.
Modes are an extremely useful concept to model complex systems. They allow simplified
system reasoning for both system operators and OBSW developers. They also provide a way to alter
the behaviour of a component and also provide observability of a system. A few examples of how to
model the mode of different components within a space system with modes will be given.
## Modelling a pyhsical devices with modes
## Pyhsical device component with modes
The following simple mode scheme with the following three mode
@ -13,7 +13,8 @@ The following simple mode scheme with the following three mode
- `ON`
- `NORMAL`
can be applied to a large number of simpler devices of a remote system, for example sensors.
can be applied to a large number of simpler device controllers of a remote system, for example
sensors.
1. `OFF` means that a device is physically switched off, and the corresponding software component
does not poll the device regularly.
@ -31,7 +32,7 @@ for the majority of devices:
2. `NORMAL` or `ON` to `OFF`: Any important shutdown configuration or handling must be performed
before powering off the device.
## Modelling a controller with modes
## Controller components with modes
Controller components are not modelling physical devices, but a mode scheme is still the best
way to model most of these components.

View File

@ -22,9 +22,9 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]]
name = "bare-metal"
@ -88,19 +88,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.35"
version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
dependencies = [
"num-traits",
]
[[package]]
name = "cobs"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
[[package]]
name = "cobs"
version = "0.2.3"
@ -189,7 +183,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"syn 2.0.53",
"syn 2.0.58",
]
[[package]]
@ -200,7 +194,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 2.0.53",
"syn 2.0.58",
]
[[package]]
@ -233,7 +227,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.53",
"syn 2.0.58",
]
[[package]]
@ -265,7 +259,7 @@ checksum = "984bc6eca246389726ac2826acc2488ca0fe5fcd6b8d9b48797021951d76a125"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.53",
"syn 2.0.58",
]
[[package]]
@ -331,7 +325,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.53",
"syn 2.0.58",
]
[[package]]
@ -559,7 +553,7 @@ checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.53",
"syn 2.0.58",
]
[[package]]
@ -580,9 +574,9 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pin-project-lite"
version = "0.2.13"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pin-utils"
@ -634,9 +628,9 @@ dependencies = [
[[package]]
name = "rtcc"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4fbd0d5bed2b76e27a7ef872568b34072c1af94c277cd52c17a89d54673b3fe"
checksum = "95973c3a0274adc4f3c5b70d2b5b85618d6de9559a6737d3293ecae9a2fc0839"
dependencies = [
"chrono",
]
@ -680,7 +674,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.53",
"syn 2.0.58",
]
[[package]]
@ -729,10 +723,8 @@ dependencies = [
[[package]]
name = "satrs"
version = "0.2.0-rc.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cb19cba46a45047ff0879ebfbf9d6ae1c5b2e0e38b2e08760b10a441d4dae6"
dependencies = [
"cobs 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cobs",
"crc",
"delegate",
"num-traits",
@ -747,7 +739,7 @@ dependencies = [
name = "satrs-example-stm32f3-disco"
version = "0.1.0"
dependencies = [
"cobs 0.2.3 (git+https://github.com/robamu/cobs.rs.git?branch=all_features)",
"cobs",
"cortex-m",
"cortex-m-rt",
"cortex-m-semihosting",
@ -767,9 +759,7 @@ dependencies = [
[[package]]
name = "satrs-shared"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75a402ba556a7f5eef707035b45e64a3259b09674311e98697f3dd0508a1bf51"
version = "0.1.3"
dependencies = [
"spacepackets",
]
@ -809,12 +799,12 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "spacepackets"
version = "0.10.0"
version = "0.11.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28246ae2451af240c3e3ff3c51363c7b6ad565ca6aa9bad23b8c725687c485e1"
checksum = "c2cfd5f9a4c7f10714d21f9bc61f2d176cb7ae092cdd687e7ade2d4e6f7d7125"
dependencies = [
"chrono",
"crc",
"defmt",
"delegate",
"num-traits",
"num_enum",
@ -909,9 +899,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.53"
version = "2.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
dependencies = [
"proc-macro2",
"quote",
@ -935,7 +925,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.53",
"syn 2.0.58",
]
[[package]]
@ -1001,5 +991,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.53",
"syn 2.0.58",
]

View File

@ -46,8 +46,9 @@ branch = "complete-dma-update-hal"
# path = "../stm32f3-discovery"
[dependencies.satrs]
version = "0.2.0-rc.0"
path = "../satrs"
default-features = false
features = ["defmt"]
[dev-dependencies]
defmt-test = "0.3"

View File

@ -103,3 +103,12 @@ After that, you can for example send a ping to the MCU using the following comma
```sh
./main.py -p /ping
```
You can configure the blinky frequency using
```sh
./main.py -p /change_blink_freq
```
All these commands will package a PUS telecommand which will be sent to the MCU using the COBS
format as the packet framing format.

View File

@ -94,6 +94,7 @@ class SatRsConfigHook(HookBase):
def create_cmd_definition_tree() -> CmdTreeNode:
root_node = CmdTreeNode.root_node()
root_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
root_node.add_child(CmdTreeNode("change_blink_freq", "Change blink frequency"))
return root_node
@ -215,6 +216,25 @@ class TcHandler(TcHandlerBase):
if cmd_path == "/ping":
q.add_log_cmd("Sending PUS ping telecommand")
q.add_pus_tc(PusTelecommand(service=17, subservice=1))
if cmd_path == "/change_blink_freq":
self.create_change_blink_freq_command(q)
def create_change_blink_freq_command(self, q: DefaultPusQueueHelper):
q.add_log_cmd("Changing blink frequency")
while True:
blink_freq = int(
input(
"Please specify new blink frequency in ms. Valid Range [2..10000]: "
)
)
if blink_freq < 2 or blink_freq > 10000:
print(
"Invalid blink frequency. Please specify a value between 2 and 10000."
)
continue
break
app_data = struct.pack("!I", blink_freq)
q.add_pus_tc(PusTelecommand(service=8, subservice=1, app_data=app_data))
def main():

View File

@ -1,5 +1,13 @@
#![no_std]
#![no_main]
use satrs::pus::verification::{
FailParams, TcStateAccepted, VerificationReportCreator, VerificationToken,
};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use satrs::spacepackets::ecss::EcssEnumU16;
use satrs::spacepackets::CcsdsPacket;
use satrs::spacepackets::{ByteConversionError, SpHeader};
// global logger + panicking-behavior + memory layout
use satrs_example_stm32f3_disco as _;
@ -7,21 +15,17 @@ use rtic::app;
use heapless::{mpmc::Q8, Vec};
#[allow(unused_imports)]
use rtic_monotonics::systick::fugit::TimerInstantU32;
use rtic_monotonics::systick::fugit::{MillisDurationU32, TimerInstantU32};
use rtic_monotonics::systick::ExtU32;
use satrs::seq_count::SequenceCountProviderCore;
use satrs::{
pool::StoreError,
pus::{EcssChannel, EcssTmSenderCore, EcssTmtcError, PusTmWrapper},
spacepackets::{ecss::PusPacket, ecss::WritablePusPacket},
};
use satrs::spacepackets::{ecss::PusPacket, ecss::WritablePusPacket};
use stm32f3xx_hal::dma::dma1;
use stm32f3xx_hal::gpio::{PushPull, AF7, PA2, PA3};
use stm32f3xx_hal::pac::USART2;
use stm32f3xx_hal::serial::{Rx, RxEvent, Serial, SerialDmaRx, SerialDmaTx, Tx, TxEvent};
const UART_BAUD: u32 = 115200;
const BLINK_FREQ_MS: u32 = 1000;
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
const TX_HANDLER_FREQ_MS: u32 = 20;
const MIN_DELAY_BETWEEN_TX_PACKETS_MS: u32 = 5;
const MAX_TC_LEN: usize = 128;
@ -54,7 +58,6 @@ type TcPacket = Vec<u8, MAX_TC_LEN>;
static TM_REQUESTS: Q8<TmPacket> = Q8::new();
use core::cell::RefCell;
use core::sync::atomic::{AtomicU16, Ordering};
pub struct SeqCountProviderAtomicRef {
@ -93,56 +96,45 @@ pub struct TxIdle {
dma_channel: dma1::C7,
}
pub struct TmSender {
vec: Option<RefCell<Vec<u8, MAX_TM_LEN>>>,
#[derive(Debug, defmt::Format)]
pub enum TmSendError {
ByteConversion(ByteConversionError),
Queue,
}
impl TmSender {
pub fn new(tm_packet: TmPacket) -> Self {
Self {
vec: Some(RefCell::new(tm_packet)),
}
impl From<ByteConversionError> for TmSendError {
fn from(value: ByteConversionError) -> Self {
Self::ByteConversion(value)
}
}
impl EcssChannel for TmSender {
fn id(&self) -> satrs::ChannelId {
0
fn send_tm(tm_creator: PusTmCreator) -> Result<(), TmSendError> {
if tm_creator.len_written() > MAX_TM_LEN {
return Err(ByteConversionError::ToSliceTooSmall {
expected: tm_creator.len_written(),
found: MAX_TM_LEN,
}
.into());
}
let mut tm_vec = TmPacket::new();
tm_vec
.resize(tm_creator.len_written(), 0)
.expect("vec resize failed");
tm_creator.write_to_bytes(tm_vec.as_mut_slice())?;
defmt::info!(
"Sending TM[{},{}] with size {}",
tm_creator.service(),
tm_creator.subservice(),
tm_creator.len_written()
);
TM_REQUESTS
.enqueue(tm_vec)
.map_err(|_| TmSendError::Queue)?;
Ok(())
}
impl EcssTmSenderCore for TmSender {
fn send_tm(&self, tm: PusTmWrapper) -> Result<(), EcssTmtcError> {
let vec = self.vec.as_ref();
if vec.is_none() {
panic!("send_tm should only be called once");
}
let vec_ref = vec.unwrap();
let mut vec = vec_ref.borrow_mut();
match tm {
PusTmWrapper::InStore(addr) => return Err(EcssTmtcError::CantSendAddr(addr)),
PusTmWrapper::Direct(tm) => {
if tm.len_written() > MAX_TM_LEN {
return Err(EcssTmtcError::Store(StoreError::DataTooLarge(
tm.len_written(),
)));
}
vec.resize(tm.len_written(), 0).expect("vec resize failed");
tm.write_to_bytes(vec.as_mut_slice())?;
defmt::info!(
"Sending TM[{},{}] with size {}",
tm.service(),
tm.subservice(),
tm.len_written()
);
drop(vec);
TM_REQUESTS
.enqueue(vec_ref.take())
.map_err(|_| EcssTmtcError::Store(StoreError::StoreFull(0)))?;
}
}
Ok(())
}
fn handle_tm_send_error(error: TmSendError) {
defmt::warn!("sending tm failed with error {}", error);
}
pub enum UartTxState {
@ -157,18 +149,106 @@ pub struct UartTxShared {
state: UartTxState,
}
pub struct RequestWithToken {
token: VerificationToken<TcStateAccepted>,
request: Request,
}
#[derive(Debug, defmt::Format)]
pub enum Request {
Ping,
ChangeBlinkFrequency(u32),
}
#[derive(Debug, defmt::Format)]
pub enum RequestError {
InvalidApid = 1,
InvalidService = 2,
InvalidSubservice = 3,
NotEnoughAppData = 4,
}
pub fn convert_pus_tc_to_request(
tc: &PusTcReader,
verif_reporter: &mut VerificationReportCreator,
src_data_buf: &mut [u8],
timestamp: &[u8],
) -> Result<RequestWithToken, RequestError> {
defmt::info!(
"Found PUS TC [{},{}] with length {}",
tc.service(),
tc.subservice(),
tc.len_packed()
);
let token = verif_reporter.add_tc(tc);
if tc.apid() != PUS_APID {
defmt::warn!("Received tc with unknown APID {}", tc.apid());
let result = send_tm(
verif_reporter
.acceptance_failure(
src_data_buf,
token,
SEQ_COUNT_PROVIDER.get_and_increment(),
0,
FailParams::new(timestamp, &EcssEnumU16::new(0), &[]),
)
.unwrap(),
);
if let Err(e) = result {
handle_tm_send_error(e);
}
return Err(RequestError::InvalidApid);
}
let (tm_creator, accepted_token) = verif_reporter
.acceptance_success(
src_data_buf,
token,
SEQ_COUNT_PROVIDER.get_and_increment(),
0,
timestamp,
)
.unwrap();
if let Err(e) = send_tm(tm_creator) {
handle_tm_send_error(e);
}
if tc.service() == 17 && tc.subservice() == 1 {
if tc.subservice() == 1 {
return Ok(RequestWithToken {
request: Request::Ping,
token: accepted_token,
});
} else {
return Err(RequestError::InvalidSubservice);
}
} else if tc.service() == 8 {
if tc.subservice() == 1 {
if tc.user_data().len() < 4 {
return Err(RequestError::NotEnoughAppData);
}
let new_freq_ms = u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap());
return Ok(RequestWithToken {
request: Request::ChangeBlinkFrequency(new_freq_ms),
token: accepted_token,
});
} else {
return Err(RequestError::InvalidSubservice);
}
} else {
return Err(RequestError::InvalidService);
}
}
#[app(device = stm32f3xx_hal::pac, peripherals = true)]
mod app {
use super::*;
use core::slice::Iter;
use rtic_monotonics::systick::Systick;
use rtic_monotonics::Monotonic;
use satrs::pus::verification::FailParams;
use satrs::pus::verification::VerificationReporterCore;
use satrs::spacepackets::{
ecss::tc::PusTcReader, ecss::tm::PusTmCreator, ecss::tm::PusTmSecondaryHeader,
ecss::EcssEnumU16, time::cds::P_FIELD_BASE, CcsdsPacket, SpHeader,
};
use satrs::pus::verification::{TcStateStarted, VerificationReportCreator};
use satrs::spacepackets::{ecss::tc::PusTcReader, time::cds::P_FIELD_BASE};
#[allow(unused_imports)]
use stm32f3_discovery::leds::Direction;
use stm32f3_discovery::leds::Leds;
@ -181,15 +261,16 @@ mod app {
#[shared]
struct Shared {
blink_freq: MillisDurationU32,
tx_shared: UartTxShared,
rx_transfer: Option<RxDmaTransferType>,
}
#[local]
struct Local {
verif_reporter: VerificationReportCreator,
leds: Leds,
last_dir: Direction,
verif_reporter: VerificationReporterCore,
curr_dir: Iter<'static, Direction>,
}
@ -215,8 +296,6 @@ mod app {
defmt::info!("Starting sat-rs demo application for the STM32F3-Discovery");
let mut gpioe = cx.device.GPIOE.split(&mut rcc.ahb);
let verif_reporter = VerificationReporterCore::new(PUS_APID).unwrap();
let leds = Leds::new(
gpioe.pe8,
gpioe.pe9,
@ -265,8 +344,12 @@ mod app {
defmt::info!("Spawning tasks");
blink::spawn().unwrap();
serial_tx_handler::spawn().unwrap();
let verif_reporter = VerificationReportCreator::new(PUS_APID).unwrap();
(
Shared {
blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
tx_shared: UartTxShared {
last_completed: None,
state: UartTxState::Idle(Some(TxIdle {
@ -277,17 +360,16 @@ mod app {
rx_transfer: Some(rx_transfer),
},
Local {
//timer: mono_timer,
verif_reporter,
leds,
last_dir: Direction::North,
curr_dir: Direction::iter(),
verif_reporter,
},
)
}
#[task(local = [leds, curr_dir, last_dir])]
async fn blink(cx: blink::Context) {
#[task(local = [leds, curr_dir, last_dir], shared=[blink_freq])]
async fn blink(mut cx: blink::Context) {
let blink::LocalResources {
leds,
curr_dir,
@ -311,7 +393,8 @@ mod app {
toggle_leds(curr_dir.next().unwrap());
}
}
Systick::delay(BLINK_FREQ_MS.millis()).await;
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
Systick::delay(current_blink_freq).await;
}
}
@ -386,18 +469,18 @@ mod app {
#[task(
local = [
stamp_buf: [u8; 7] = [0; 7],
verif_reporter,
decode_buf: [u8; MAX_TC_LEN] = [0; MAX_TC_LEN],
src_data_buf: [u8; MAX_TM_LEN] = [0; MAX_TM_LEN],
verif_reporter
timestamp: [u8; 7] = [0; 7],
],
shared = [blink_freq]
)]
async fn serial_rx_handler(
cx: serial_rx_handler::Context,
mut cx: serial_rx_handler::Context,
received_packet: Vec<u8, MAX_TC_LEN>,
) {
defmt::info!("running rx handler");
cx.local.stamp_buf[0] = P_FIELD_BASE;
cx.local.timestamp[0] = P_FIELD_BASE;
defmt::info!("Received packet with {} bytes", received_packet.len());
let decode_buf = cx.local.decode_buf;
let packet = received_packet.as_slice();
@ -417,18 +500,49 @@ mod app {
Ok(len) => {
defmt::info!("Decoded packet length: {}", len);
let pus_tc = PusTcReader::new(decode_buf);
let verif_reporter = cx.local.verif_reporter;
match pus_tc {
Ok((tc, tc_len)) => handle_tc(
tc,
tc_len,
verif_reporter,
cx.local.src_data_buf,
cx.local.stamp_buf,
),
Err(_e) => {
// TODO: Print error after API rework.
defmt::warn!("Error unpacking PUS TC");
Ok((tc, _tc_len)) => {
match convert_pus_tc_to_request(
&tc,
cx.local.verif_reporter,
cx.local.src_data_buf,
cx.local.timestamp,
) {
Ok(request_with_token) => {
let started_token = handle_start_verification(
request_with_token.token,
cx.local.verif_reporter,
cx.local.src_data_buf,
cx.local.timestamp,
);
match request_with_token.request {
Request::Ping => {
handle_ping_request(cx.local.timestamp);
}
Request::ChangeBlinkFrequency(new_freq_ms) => {
defmt::info!("Received blink frequency change request with new frequncy {}", new_freq_ms);
cx.shared.blink_freq.lock(|blink_freq| {
*blink_freq =
MillisDurationU32::from_ticks(new_freq_ms);
});
}
}
handle_completion_verification(
started_token,
cx.local.verif_reporter,
cx.local.src_data_buf,
cx.local.timestamp,
);
}
Err(e) => {
// TODO: Error handling: Send verification failure based on request error.
defmt::warn!("request error {}", e);
}
}
}
Err(e) => {
defmt::warn!("Error unpacking PUS TC: {}", e);
}
}
}
@ -438,104 +552,64 @@ mod app {
}
}
fn handle_tc(
tc: PusTcReader,
tc_len: usize,
verif_reporter: &mut VerificationReporterCore,
src_data_buf: &mut [u8; MAX_TM_LEN],
stamp_buf: &[u8; 7],
) {
defmt::info!(
"Found PUS TC [{},{}] with length {}",
tc.service(),
tc.subservice(),
tc_len
);
let token = verif_reporter.add_tc(&tc);
if tc.apid() != PUS_APID {
defmt::warn!("Received tc with unknown APID {}", tc.apid());
let sendable = verif_reporter
.acceptance_failure(
src_data_buf,
token,
SEQ_COUNT_PROVIDER.get(),
0,
FailParams::new(stamp_buf, &EcssEnumU16::new(0), &[]),
)
.unwrap();
let sender = TmSender::new(TmPacket::new());
if let Err(_e) = verif_reporter.send_acceptance_failure(sendable, &sender) {
defmt::warn!("Sending acceptance failure failed");
};
fn handle_ping_request(timestamp: &[u8]) {
defmt::info!("Received PUS ping telecommand, sending ping reply TM[17,2]");
let sp_header =
SpHeader::new_for_unseg_tc(PUS_APID, SEQ_COUNT_PROVIDER.get_and_increment(), 0);
let sec_header = PusTmSecondaryHeader::new_simple(17, 2, timestamp);
let ping_reply = PusTmCreator::new(sp_header, sec_header, &[], true);
let mut tm_packet = TmPacket::new();
tm_packet
.resize(ping_reply.len_written(), 0)
.expect("vec resize failed");
ping_reply.write_to_bytes(&mut tm_packet).unwrap();
if TM_REQUESTS.enqueue(tm_packet).is_err() {
defmt::warn!("TC queue full");
return;
}
let sendable = verif_reporter
.acceptance_success(src_data_buf, token, SEQ_COUNT_PROVIDER.get(), 0, stamp_buf)
}
fn handle_start_verification(
accepted_token: VerificationToken<TcStateAccepted>,
verif_reporter: &mut VerificationReportCreator,
src_data_buf: &mut [u8],
timestamp: &[u8],
) -> VerificationToken<TcStateStarted> {
let (tm_creator, started_token) = verif_reporter
.start_success(
src_data_buf,
accepted_token,
SEQ_COUNT_PROVIDER.get(),
0,
&timestamp,
)
.unwrap();
let result = send_tm(tm_creator);
if let Err(e) = result {
handle_tm_send_error(e);
}
started_token
}
let sender = TmSender::new(TmPacket::new());
let accepted_token = match verif_reporter.send_acceptance_success(sendable, &sender) {
Ok(token) => token,
Err(_e) => {
// TODO: Print error as soon as EcssTmtcError has Format attr.. or rework API.
defmt::warn!("Sending acceptance success failed");
return;
}
};
if tc.service() == 17 {
if tc.subservice() == 1 {
let sendable = verif_reporter
.start_success(
src_data_buf,
accepted_token,
SEQ_COUNT_PROVIDER.get(),
0,
stamp_buf,
)
.unwrap();
// let mem_block = poolmod::TM::alloc().unwrap().init([0u8; MAX_TM_LEN]);
let sender = TmSender::new(TmPacket::new());
let started_token = match verif_reporter.send_start_success(sendable, &sender) {
Ok(token) => token,
Err(_e) => {
// TODO: Print error as soon as EcssTmtcError has Format attr.. or rework API.
defmt::warn!("Sending acceptance success failed");
return;
}
};
defmt::info!("Received PUS ping telecommand, sending ping reply TM[17,2]");
let mut sp_header =
SpHeader::tc_unseg(PUS_APID, SEQ_COUNT_PROVIDER.get(), 0).unwrap();
let sec_header = PusTmSecondaryHeader::new_simple(17, 2, stamp_buf);
let ping_reply = PusTmCreator::new(&mut sp_header, sec_header, &[], true);
let mut tm_packet = TmPacket::new();
tm_packet
.resize(ping_reply.len_written(), 0)
.expect("vec resize failed");
ping_reply.write_to_bytes(&mut tm_packet).unwrap();
if TM_REQUESTS.enqueue(tm_packet).is_err() {
defmt::warn!("TC queue full");
return;
}
SEQ_COUNT_PROVIDER.increment();
let sendable = verif_reporter
.completion_success(
src_data_buf,
started_token,
SEQ_COUNT_PROVIDER.get(),
0,
stamp_buf,
)
.unwrap();
let sender = TmSender::new(TmPacket::new());
if let Err(_e) = verif_reporter.send_step_or_completion_success(sendable, &sender) {
defmt::warn!("Sending completion success failed");
}
} else {
// TODO: Invalid subservice
}
fn handle_completion_verification(
started_token: VerificationToken<TcStateStarted>,
verif_reporter: &mut VerificationReportCreator,
src_data_buf: &mut [u8],
timestamp: &[u8],
) {
let result = send_tm(
verif_reporter
.completion_success(
src_data_buf,
started_token,
SEQ_COUNT_PROVIDER.get(),
0,
timestamp,
)
.unwrap(),
);
if let Err(e) = result {
handle_tm_send_error(e);
}
}

View File

@ -17,16 +17,23 @@ zerocopy = "0.6"
csv = "1"
num_enum = "0.7"
thiserror = "1"
lazy_static = "1"
strum = { version = "0.26", features = ["derive"] }
derive-new = "0.5"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[dependencies.satrs]
# version = "0.2.0-rc.0"
path = "../satrs"
features = ["test_util"]
[dependencies.satrs-mib]
version = "0.1.1"
# path = "../satrs-mib"
path = "../satrs-mib"
[features]
dyn_tmtc = []
default = ["dyn_tmtc"]
[dev-dependencies]
env_logger = "0.11"

View File

@ -4,11 +4,13 @@ import dataclasses
import enum
import struct
from spacepackets.ecss.tc import PacketId, PacketType
EXAMPLE_PUS_APID = 0x02
EXAMPLE_PUS_PACKET_ID_TM = PacketId(PacketType.TM, True, EXAMPLE_PUS_APID)
TM_PACKET_IDS = [EXAMPLE_PUS_PACKET_ID_TM]
class Apid(enum.IntEnum):
SCHED = 1
GENERIC_PUS = 2
ACS = 3
CFDP = 4
TMTC = 5
class EventSeverity(enum.IntEnum):
@ -36,8 +38,8 @@ class EventU32:
)
class RequestTargetId(enum.IntEnum):
ACS = 1
class AcsId(enum.IntEnum):
MGM_0 = 0
class AcsHkIds(enum.IntEnum):

View File

@ -3,10 +3,11 @@
import logging
import sys
import time
from typing import Optional
from typing import Any, Optional
from prompt_toolkit.history import History
from prompt_toolkit.history import FileHistory
from spacepackets.ccsds import PacketId, PacketType
import tmtccmd
from spacepackets.ecss import PusTelemetry, PusVerificator
from spacepackets.ecss.pus_17_test import Service17Tm
@ -16,7 +17,7 @@ from spacepackets.ccsds.time import CdsShortTimestamp
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
from tmtccmd.core.base import BackendRequest
from tmtccmd.pus import VerificationWrapper
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
from tmtccmd.tmtc import CcsdsTmHandler, GenericApidHandlerBase
from tmtccmd.com import ComInterface
from tmtccmd.config import (
CmdTreeNode,
@ -46,7 +47,7 @@ from tmtccmd.util.obj_id import ObjectIdDictT
import pus_tc
from common import EXAMPLE_PUS_APID, TM_PACKET_IDS, EventU32
from common import Apid, EventU32
_LOGGER = logging.getLogger()
@ -62,10 +63,13 @@ class SatRsConfigHook(HookBase):
)
assert self.cfg_path is not None
packet_id_list = []
for apid in Apid:
packet_id_list.append(PacketId(PacketType.TM, True, apid))
cfg = create_com_interface_cfg_default(
com_if_key=com_if_key,
json_cfg_path=self.cfg_path,
space_packet_ids=TM_PACKET_IDS,
space_packet_ids=packet_id_list,
)
assert cfg is not None
return create_com_interface_default(cfg)
@ -85,21 +89,23 @@ class SatRsConfigHook(HookBase):
return get_core_object_ids()
class PusHandler(SpecificApidHandlerBase):
class PusHandler(GenericApidHandlerBase):
def __init__(
self,
file_logger: logging.Logger,
verif_wrapper: VerificationWrapper,
raw_logger: RawTmtcTimedLogWrapper,
):
super().__init__(EXAMPLE_PUS_APID, None)
super().__init__(None)
self.file_logger = file_logger
self.raw_logger = raw_logger
self.verif_wrapper = verif_wrapper
def handle_tm(self, packet: bytes, _user_args: any):
def handle_tm(self, apid: int, packet: bytes, _user_args: Any):
try:
pus_tm = PusTelemetry.unpack(packet, time_reader=CdsShortTimestamp.empty())
pus_tm = PusTelemetry.unpack(
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
)
except ValueError as e:
_LOGGER.warning("Could not generate PUS TM object from raw data")
_LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
@ -107,7 +113,7 @@ class PusHandler(SpecificApidHandlerBase):
service = pus_tm.service
if service == 1:
tm_packet = Service1Tm.unpack(
data=packet, params=UnpackParams(CdsShortTimestamp.empty(), 1, 2)
data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
)
res = self.verif_wrapper.add_tm(tm_packet)
if res is None:
@ -124,7 +130,9 @@ class PusHandler(SpecificApidHandlerBase):
elif service == 3:
_LOGGER.info("No handling for HK packets implemented")
_LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
pus_tm = PusTelemetry.unpack(packet, time_reader=CdsShortTimestamp.empty())
pus_tm = PusTelemetry.unpack(
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
)
if pus_tm.subservice == 25:
if len(pus_tm.source_data) < 8:
raise ValueError("No addressable ID in HK packet")
@ -132,16 +140,18 @@ class PusHandler(SpecificApidHandlerBase):
_LOGGER.info(json_str)
elif service == 5:
tm_packet = PusTelemetry.unpack(
packet, time_reader=CdsShortTimestamp.empty()
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
)
src_data = tm_packet.source_data
event_u32 = EventU32.unpack(src_data)
_LOGGER.info(f"Received event packet. Event: {event_u32}")
_LOGGER.info(
f"Received event packet. Source APID: {Apid(tm_packet.apid)!r}, Event: {event_u32}"
)
if event_u32.group_id == 0 and event_u32.unique_id == 0:
_LOGGER.info("Received test event")
elif service == 17:
tm_packet = Service17Tm.unpack(
packet, time_reader=CdsShortTimestamp.empty()
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
)
if tm_packet.subservice == 2:
self.file_logger.info("Received Ping Reply TM[17,2]")
@ -158,7 +168,7 @@ class PusHandler(SpecificApidHandlerBase):
f"The service {service} is not implemented in Telemetry Factory"
)
tm_packet = PusTelemetry.unpack(
packet, time_reader=CdsShortTimestamp.empty()
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
)
self.raw_logger.log_tm(pus_tm)
@ -177,7 +187,7 @@ class TcHandler(TcHandlerBase):
tc_sched_timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE,
seq_cnt_provider=seq_count_provider,
pus_verificator=self.verif_wrapper.pus_verificator,
default_pus_apid=EXAMPLE_PUS_APID,
default_pus_apid=None,
)
def send_cb(self, send_params: SendCbParams):
@ -193,15 +203,15 @@ class TcHandler(TcHandlerBase):
_LOGGER.info(log_entry.log_str)
def queue_finished_cb(self, info: ProcedureWrapper):
if info.proc_type == TcProcedureType.DEFAULT:
def_proc = info.to_def_procedure()
if info.proc_type == TcProcedureType.TREE_COMMANDING:
def_proc = info.to_tree_commanding_procedure()
_LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
q = self.queue_helper
q.queue_wrapper = wrapper.queue_wrapper
if info.proc_type == TcProcedureType.DEFAULT:
def_proc = info.to_def_procedure()
if info.proc_type == TcProcedureType.TREE_COMMANDING:
def_proc = info.to_tree_commanding_procedure()
assert def_proc.cmd_path is not None
pus_tc.pack_pus_telecommands(q, def_proc.cmd_path)
@ -221,7 +231,6 @@ def main():
post_args_wrapper.set_params_without_prompts(proc_wrapper)
else:
post_args_wrapper.set_params_with_prompts(proc_wrapper)
params.apid = EXAMPLE_PUS_APID
setup_args = SetupWrapper(
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
)
@ -233,8 +242,9 @@ def main():
verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
# Create primary TM handler and add it to the CCSDS Packet Handler
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
ccsds_handler = CcsdsTmHandler(generic_handler=None)
ccsds_handler.add_apid_handler(tm_handler)
ccsds_handler = CcsdsTmHandler(generic_handler=tm_handler)
# TODO: We could add the CFDP handler for the CFDP APID at a later stage.
# ccsds_handler.add_apid_handler(tm_handler)
# Create TC handler
seq_count_provider = PusFileSeqCountProvider()
@ -252,6 +262,7 @@ def main():
while True:
state = tmtc_backend.periodic_op(None)
if state.request == BackendRequest.TERMINATION_NO_ERROR:
tmtc_backend.close_com_if()
sys.exit(0)
elif state.request == BackendRequest.DELAY_IDLE:
_LOGGER.info("TMTC Client in IDLE mode")
@ -266,6 +277,7 @@ def main():
elif state.request == BackendRequest.CALL_NEXT:
pass
except KeyboardInterrupt:
tmtc_backend.close_com_if()
sys.exit(0)

View File

@ -0,0 +1,143 @@
import datetime
import struct
import logging
from spacepackets.ccsds import CdsShortTimestamp
from spacepackets.ecss import PusTelecommand
from tmtccmd.config import CmdTreeNode
from tmtccmd.pus.tc.s200_fsfw_mode import Mode
from tmtccmd.tmtc import DefaultPusQueueHelper
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
from tmtccmd.pus.s200_fsfw_mode import Subservice as ModeSubservice
from common import AcsId, Apid
_LOGGER = logging.getLogger(__name__)
def create_set_mode_cmd(
apid: int, unique_id: int, mode: int, submode: int
) -> PusTelecommand:
app_data = bytearray()
app_data.extend(struct.pack("!I", unique_id))
app_data.extend(struct.pack("!I", mode))
app_data.extend(struct.pack("!H", submode))
return PusTelecommand(
service=200,
subservice=ModeSubservice.TC_MODE_COMMAND,
apid=apid,
app_data=app_data,
)
def create_cmd_definition_tree() -> CmdTreeNode:
root_node = CmdTreeNode.root_node()
hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True)
hk_node.add_child(CmdTreeNode("one_shot_hk", "Request One Shot HK set"))
hk_node.add_child(
CmdTreeNode("enable", "Enable periodic housekeeping data generation")
)
hk_node.add_child(
CmdTreeNode("disable", "Disable periodic housekeeping data generation")
)
mode_node = CmdTreeNode("mode", "Mode Node", hide_children_for_print=True)
set_mode_node = CmdTreeNode(
"set_mode", "Set Node", hide_children_which_are_leaves=True
)
set_mode_node.add_child(CmdTreeNode("off", "Set OFF Mode"))
set_mode_node.add_child(CmdTreeNode("on", "Set ON Mode"))
set_mode_node.add_child(CmdTreeNode("normal", "Set NORMAL Mode"))
mode_node.add_child(set_mode_node)
mode_node.add_child(CmdTreeNode("read_mode", "Read Mode"))
test_node = CmdTreeNode("test", "Test Node")
test_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
test_node.add_child(CmdTreeNode("trigger_event", "Send PUS test to trigger event"))
root_node.add_child(test_node)
scheduler_node = CmdTreeNode("scheduler", "Scheduler Node")
scheduler_node.add_child(
CmdTreeNode(
"schedule_ping_10_secs_ahead", "Schedule Ping to execute in 10 seconds"
)
)
root_node.add_child(scheduler_node)
acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
mgm_node = CmdTreeNode("mgms", "MGM devices node")
mgm_node.add_child(mode_node)
mgm_node.add_child(hk_node)
acs_node.add_child(mgm_node)
root_node.add_child(acs_node)
return root_node
def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
# It should always be at least the root path "/", so we split of the empty portion left of it.
cmd_path_list = cmd_path.split("/")[1:]
if len(cmd_path_list) == 0:
_LOGGER.warning("empty command path")
return
if cmd_path_list[0] == "test":
assert len(cmd_path_list) >= 2
if cmd_path_list[1] == "ping":
q.add_log_cmd("Sending PUS ping telecommand")
return q.add_pus_tc(
PusTelecommand(apid=Apid.GENERIC_PUS, service=17, subservice=1)
)
elif cmd_path_list[1] == "trigger_event":
q.add_log_cmd("Triggering test event")
return q.add_pus_tc(
PusTelecommand(apid=Apid.GENERIC_PUS, service=17, subservice=128)
)
if cmd_path_list[0] == "scheduler":
assert len(cmd_path_list) >= 2
if cmd_path_list[1] == "schedule_ping_10_secs_ahead":
q.add_log_cmd("Sending PUS scheduled TC telecommand")
crt_time = CdsShortTimestamp.from_now()
time_stamp = crt_time + datetime.timedelta(seconds=10)
time_stamp = time_stamp.pack()
return q.add_pus_tc(
create_time_tagged_cmd(
time_stamp,
PusTelecommand(service=17, subservice=1),
apid=Apid.SCHED,
)
)
if cmd_path_list[0] == "acs":
assert len(cmd_path_list) >= 2
if cmd_path_list[1] == "mgms":
assert len(cmd_path_list) >= 3
if cmd_path_list[2] == "hk":
if cmd_path_list[3] == "one_shot_hk":
q.add_log_cmd("Sending HK one shot request")
# TODO: Fix
# q.add_pus_tc(
# create_request_one_hk_command(
# make_addressable_id(Apid.ACS, AcsId.MGM_SET)
# )
# )
if cmd_path_list[2] == "mode":
if cmd_path_list[3] == "set_mode":
handle_set_mode_cmd(
q, "MGM 0", cmd_path_list[4], Apid.ACS, AcsId.MGM_0
)
def handle_set_mode_cmd(
q: DefaultPusQueueHelper, target_str: str, mode_str: str, apid: int, unique_id: int
):
if mode_str == "off":
q.add_log_cmd(f"Sending Mode OFF to {target_str}")
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.OFF, 0))
elif mode_str == "on":
q.add_log_cmd(f"Sending Mode ON to {target_str}")
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.ON, 0))
elif mode_str == "normal":
q.add_log_cmd(f"Sending Mode NORMAL to {target_str}")
q.add_pus_tc(create_set_mode_cmd(apid, unique_id, Mode.NORMAL, 0))

View File

@ -1,2 +1,2 @@
tmtccmd == 8.0.0rc1
tmtccmd == 8.0.0rc2
# -e git+https://github.com/robamu-org/tmtccmd@97e5e51101a08b21472b3ddecc2063359f7e307a#egg=tmtccmd

View File

@ -1,85 +0,0 @@
import datetime
import logging
from spacepackets.ccsds import CdsShortTimestamp
from spacepackets.ecss import PusTelecommand
from tmtccmd.config import CmdTreeNode
from tmtccmd.tmtc import DefaultPusQueueHelper
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
from tmtccmd.pus.tc.s3_fsfw_hk import create_request_one_hk_command
from common import (
EXAMPLE_PUS_APID,
make_addressable_id,
RequestTargetId,
AcsHkIds,
)
_LOGGER = logging.getLogger(__name__)
def create_cmd_definition_tree() -> CmdTreeNode:
root_node = CmdTreeNode.root_node()
test_node = CmdTreeNode("test", "Test Node")
test_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
test_node.add_child(CmdTreeNode("trigger_event", "Send PUS test to trigger event"))
root_node.add_child(test_node)
scheduler_node = CmdTreeNode("scheduler", "Scheduler Node")
scheduler_node.add_child(
CmdTreeNode(
"schedule_ping_10_secs_ahead", "Schedule Ping to execute in 10 seconds"
)
)
root_node.add_child(scheduler_node)
acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
mgm_node = CmdTreeNode("mgms", "MGM devices node")
mgm_node.add_child(CmdTreeNode("one_shot_hk", "Request one shot HK"))
acs_node.add_child(mgm_node)
root_node.add_child(acs_node)
return root_node
def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
# It should always be at least the root path "/", so we split of the empty portion left of it.
cmd_path_list = cmd_path.split("/")[1:]
if len(cmd_path_list) == 0:
_LOGGER.warning("empty command path")
return
if cmd_path_list[0] == "test":
assert len(cmd_path_list) >= 2
if cmd_path_list[1] == "ping":
q.add_log_cmd("Sending PUS ping telecommand")
return q.add_pus_tc(PusTelecommand(service=17, subservice=1))
elif cmd_path_list[1] == "trigger_event":
q.add_log_cmd("Triggering test event")
return q.add_pus_tc(PusTelecommand(service=17, subservice=128))
if cmd_path_list[0] == "scheduler":
assert len(cmd_path_list) >= 2
if cmd_path_list[1] == "schedule_ping_10_secs_ahead":
q.add_log_cmd("Sending PUS scheduled TC telecommand")
crt_time = CdsShortTimestamp.from_now()
time_stamp = crt_time + datetime.timedelta(seconds=10)
time_stamp = time_stamp.pack()
return q.add_pus_tc(
create_time_tagged_cmd(
time_stamp,
PusTelecommand(service=17, subservice=1),
apid=EXAMPLE_PUS_APID,
)
)
if cmd_path_list[0] == "acs":
assert len(cmd_path_list) >= 2
if cmd_path_list[1] == "mgm":
assert len(cmd_path_list) >= 3
if cmd_path_list[2] == "one_shot_hk":
q.add_log_cmd("Sending HK one shot request")
q.add_pus_tc(
create_request_one_hk_command(
make_addressable_id(RequestTargetId.ACS, AcsHkIds.MGM_SET)
)
)

View File

@ -1,118 +0,0 @@
use std::sync::mpsc::{self, TryRecvError};
use log::{info, warn};
use satrs::pus::verification::VerificationReportingProvider;
use satrs::pus::{EcssTmSender, PusTmWrapper};
use satrs::request::TargetAndApidId;
use satrs::spacepackets::ecss::hk::Subservice as HkSubservice;
use satrs::{
hk::HkRequest,
spacepackets::{
ecss::tm::{PusTmCreator, PusTmSecondaryHeader},
time::cds::{DaysLen16Bits, TimeProvider},
SequenceFlags, SpHeader,
},
};
use satrs_example::config::{RequestTargetId, PUS_APID};
use crate::{
hk::{AcsHkIds, HkUniqueId},
requests::{Request, RequestWithToken},
update_time,
};
pub struct AcsTask<VerificationReporter: VerificationReportingProvider> {
timestamp: [u8; 7],
time_provider: TimeProvider<DaysLen16Bits>,
verif_reporter: VerificationReporter,
tm_sender: Box<dyn EcssTmSender>,
request_rx: mpsc::Receiver<RequestWithToken>,
}
impl<VerificationReporter: VerificationReportingProvider> AcsTask<VerificationReporter> {
pub fn new(
tm_sender: impl EcssTmSender,
request_rx: mpsc::Receiver<RequestWithToken>,
verif_reporter: VerificationReporter,
) -> Self {
Self {
timestamp: [0; 7],
time_provider: TimeProvider::new_with_u16_days(0, 0),
verif_reporter,
tm_sender: Box::new(tm_sender),
request_rx,
}
}
fn handle_hk_request(&mut self, target_id: u32, unique_id: u32) {
assert_eq!(target_id, RequestTargetId::AcsSubsystem as u32);
if unique_id == AcsHkIds::TestMgmSet as u32 {
let mut sp_header = SpHeader::tm(PUS_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sec_header = PusTmSecondaryHeader::new_simple(
3,
HkSubservice::TmHkPacket as u8,
&self.timestamp,
);
let mut buf: [u8; 8] = [0; 8];
let hk_id = HkUniqueId::new(target_id, unique_id);
hk_id.write_to_be_bytes(&mut buf).unwrap();
let pus_tm = PusTmCreator::new(&mut sp_header, sec_header, &buf, true);
self.tm_sender
.send_tm(PusTmWrapper::Direct(pus_tm))
.expect("Sending HK TM failed");
}
// TODO: Verification failure for invalid unique IDs.
}
pub fn try_reading_one_request(&mut self) -> bool {
match self.request_rx.try_recv() {
Ok(request) => {
info!(
"ACS thread: Received HK request {:?}",
request.targeted_request
);
let target_and_apid_id = TargetAndApidId::from(request.targeted_request.target_id);
match request.targeted_request.request {
Request::Hk(hk_req) => match hk_req {
HkRequest::OneShot(unique_id) => {
self.handle_hk_request(target_and_apid_id.target(), unique_id)
}
HkRequest::Enable(_) => {}
HkRequest::Disable(_) => {}
HkRequest::ModifyCollectionInterval(_, _) => {}
},
Request::Mode(_mode_req) => {
warn!("mode request handling not implemented yet")
}
Request::Action(_action_req) => {
warn!("action request handling not implemented yet")
}
}
let started_token = self
.verif_reporter
.start_success(request.token, &self.timestamp)
.expect("Sending start success failed");
self.verif_reporter
.completion_success(started_token, &self.timestamp)
.expect("Sending completion success failed");
true
}
Err(e) => match e {
TryRecvError::Empty => false,
TryRecvError::Disconnected => {
warn!("ACS thread: Message Queue TX disconnected!");
false
}
},
}
}
pub fn periodic_operation(&mut self) {
update_time(&mut self.time_provider, &mut self.timestamp);
loop {
if !self.try_reading_one_request() {
break;
}
}
}
}

View File

@ -0,0 +1,282 @@
use derive_new::new;
use satrs::hk::{HkRequest, HkRequestVariant};
use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
use satrs::spacepackets::ecss::hk;
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use satrs::spacepackets::SpHeader;
use satrs_example::{DeviceMode, TimeStampHelper};
use std::sync::mpsc::{self};
use std::sync::{Arc, Mutex};
use satrs::mode::{
ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequest, ModeRequestHandler,
};
use satrs::pus::{EcssTmSender, PusTmVariant};
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
use satrs_example::config::components::PUS_MODE_SERVICE;
use crate::pus::hk::{HkReply, HkReplyVariant};
use crate::requests::CompositeRequest;
use serde::{Deserialize, Serialize};
const GAUSS_TO_MICROTESLA_FACTOR: f32 = 100.0;
// This is the selected resoltion for the STM LIS3MDL device for the 4 Gauss sensitivity setting.
const FIELD_LSB_PER_GAUSS_4_SENS: f32 = 1.0 / 6842.0;
pub trait SpiInterface {
type Error;
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
}
#[derive(Default)]
pub struct SpiDummyInterface {
pub dummy_val_0: i16,
pub dummy_val_1: i16,
pub dummy_val_2: i16,
}
impl SpiInterface for SpiDummyInterface {
type Error = ();
fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
rx[0..2].copy_from_slice(&self.dummy_val_0.to_be_bytes());
rx[2..4].copy_from_slice(&self.dummy_val_1.to_be_bytes());
rx[4..6].copy_from_slice(&self.dummy_val_2.to_be_bytes());
Ok(())
}
}
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
pub struct MgmData {
pub valid: bool,
pub x: f32,
pub y: f32,
pub z: f32,
}
pub struct MpscModeLeafInterface {
pub request_rx: mpsc::Receiver<GenericMessage<ModeRequest>>,
pub reply_tx_to_pus: mpsc::Sender<GenericMessage<ModeReply>>,
pub reply_tx_to_parent: mpsc::Sender<GenericMessage<ModeReply>>,
}
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
#[derive(new)]
#[allow(clippy::too_many_arguments)]
pub struct MgmHandlerLis3Mdl<ComInterface: SpiInterface, TmSender: EcssTmSender> {
id: UniqueApidTargetId,
dev_str: &'static str,
mode_interface: MpscModeLeafInterface,
composite_request_receiver: mpsc::Receiver<GenericMessage<CompositeRequest>>,
hk_reply_sender: mpsc::Sender<GenericMessage<HkReply>>,
tm_sender: TmSender,
com_interface: ComInterface,
shared_mgm_set: Arc<Mutex<MgmData>>,
#[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
mode_and_submode: ModeAndSubmode,
#[new(default)]
tx_buf: [u8; 12],
#[new(default)]
rx_buf: [u8; 12],
#[new(default)]
tm_buf: [u8; 16],
#[new(default)]
stamp_helper: TimeStampHelper,
}
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComInterface, TmSender> {
pub fn periodic_operation(&mut self) {
self.stamp_helper.update_from_now();
// Handle requests.
self.handle_composite_requests();
self.handle_mode_requests();
if self.mode() == DeviceMode::Normal as u32 {
log::trace!("polling LIS3MDL sensor {}", self.dev_str);
// Communicate with the device.
let result = self.com_interface.transfer(&self.tx_buf, &mut self.rx_buf);
assert!(result.is_ok());
// Actual data begins on the second byte, similarly to how a lot of SPI devices behave.
let x_raw = i16::from_be_bytes(self.rx_buf[1..3].try_into().unwrap());
let y_raw = i16::from_be_bytes(self.rx_buf[3..5].try_into().unwrap());
let z_raw = i16::from_be_bytes(self.rx_buf[5..7].try_into().unwrap());
// Simple scaling to retrieve the float value, assuming a sensor resolution of
let mut mgm_guard = self.shared_mgm_set.lock().unwrap();
mgm_guard.x = x_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
mgm_guard.y = y_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
mgm_guard.z = z_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
drop(mgm_guard);
}
}
pub fn handle_composite_requests(&mut self) {
loop {
match self.composite_request_receiver.try_recv() {
Ok(ref msg) => match &msg.message {
CompositeRequest::Hk(hk_request) => {
self.handle_hk_request(&msg.requestor_info, hk_request)
}
// TODO: This object does not have actions (yet).. Still send back completion failure
// reply.
CompositeRequest::Action(_action_req) => {}
},
Err(e) => {
if e != mpsc::TryRecvError::Empty {
log::warn!(
"{}: failed to receive composite request: {:?}",
self.dev_str,
e
);
} else {
break;
}
}
}
}
}
pub fn handle_hk_request(&mut self, requestor_info: &MessageMetadata, hk_request: &HkRequest) {
match hk_request.variant {
HkRequestVariant::OneShot => {
self.hk_reply_sender
.send(GenericMessage::new(
*requestor_info,
HkReply::new(hk_request.unique_id, HkReplyVariant::Ack),
))
.expect("failed to send HK reply");
let sec_header = PusTmSecondaryHeader::new(
3,
hk::Subservice::TmHkPacket as u8,
0,
0,
self.stamp_helper.stamp(),
);
let mgm_snapshot = *self.shared_mgm_set.lock().unwrap();
// Use binary serialization here. We want the data to be tightly packed.
self.tm_buf[0] = mgm_snapshot.valid as u8;
self.tm_buf[1..5].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
self.tm_buf[5..9].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
self.tm_buf[9..13].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
let hk_tm = PusTmCreator::new(
SpHeader::new_from_apid(self.id.apid),
sec_header,
&self.tm_buf[0..12],
true,
);
self.tm_sender
.send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
.expect("failed to send HK TM");
}
HkRequestVariant::EnablePeriodic => todo!(),
HkRequestVariant::DisablePeriodic => todo!(),
HkRequestVariant::ModifyCollectionInterval(_) => todo!(),
}
}
pub fn handle_mode_requests(&mut self) {
loop {
// TODO: Only allow one set mode request per cycle?
match self.mode_interface.request_rx.try_recv() {
Ok(msg) => {
let result = self.handle_mode_request(msg);
// TODO: Trigger event?
if result.is_err() {
log::warn!(
"{}: mode request failed with error {:?}",
self.dev_str,
result.err().unwrap()
);
}
}
Err(e) => {
if e != mpsc::TryRecvError::Empty {
log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e);
} else {
break;
}
}
}
}
}
}
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeProvider
for MgmHandlerLis3Mdl<ComInterface, TmSender>
{
fn mode_and_submode(&self) -> ModeAndSubmode {
self.mode_and_submode
}
}
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> ModeRequestHandler
for MgmHandlerLis3Mdl<ComInterface, TmSender>
{
type Error = ModeError;
fn start_transition(
&mut self,
requestor: MessageMetadata,
mode_and_submode: ModeAndSubmode,
) -> Result<(), satrs::mode::ModeError> {
log::info!(
"{}: transitioning to mode {:?}",
self.dev_str,
mode_and_submode
);
self.mode_and_submode = mode_and_submode;
self.handle_mode_reached(Some(requestor))?;
Ok(())
}
fn announce_mode(&self, _requestor_info: Option<MessageMetadata>, _recursive: bool) {
log::info!(
"{} announcing mode: {:?}",
self.dev_str,
self.mode_and_submode
);
}
fn handle_mode_reached(
&mut self,
requestor: Option<MessageMetadata>,
) -> Result<(), Self::Error> {
self.announce_mode(requestor, false);
if let Some(requestor) = requestor {
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
log::warn!(
"can not send back mode reply to sender {}",
requestor.sender_id()
);
} else {
self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode()))?;
}
}
Ok(())
}
fn send_mode_reply(
&self,
requestor: MessageMetadata,
reply: ModeReply,
) -> Result<(), Self::Error> {
if requestor.sender_id() != PUS_MODE_SERVICE.id() {
log::warn!(
"can not send back mode reply to sender {}",
requestor.sender_id()
);
}
self.mode_interface
.reply_tx_to_pus
.send(GenericMessage::new(requestor, reply))
.map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?;
Ok(())
}
fn handle_mode_info(
&mut self,
_requestor_info: MessageMetadata,
_info: ModeAndSubmode,
) -> Result<(), Self::Error> {
Ok(())
}
}

View File

@ -0,0 +1 @@
pub mod mgm;

View File

@ -12,8 +12,7 @@ use std::time::Duration;
fn main() {
let mut buf = [0; 32];
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let mut sph = SpHeader::tc_unseg(0x02, 0, 0).unwrap();
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let pus_tc = PusTcCreator::new_simple(SpHeader::new_from_apid(0x02), 17, 1, &[], true);
let client = UdpSocket::bind("127.0.0.1:7302").expect("Connecting to UDP server failed");
let tc_req_id = RequestId::new(&pus_tc);
println!("Packing and sending PUS ping command TC[17,1] with request ID {tc_req_id}");

View File

@ -1,44 +0,0 @@
use satrs::pus::ReceivesEcssPusTc;
use satrs::spacepackets::{CcsdsPacket, SpHeader};
use satrs::tmtc::{CcsdsPacketHandler, ReceivesCcsdsTc};
use satrs_example::config::PUS_APID;
#[derive(Clone)]
pub struct CcsdsReceiver<
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone,
E,
> {
pub tc_source: TcSource,
}
impl<
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone + 'static,
E: 'static,
> CcsdsPacketHandler for CcsdsReceiver<TcSource, E>
{
type Error = E;
fn valid_apids(&self) -> &'static [u16] {
&[PUS_APID]
}
fn handle_known_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
if sp_header.apid() == PUS_APID {
return self.tc_source.pass_ccsds(sp_header, tc_raw);
}
Ok(())
}
fn handle_unknown_apid(
&mut self,
sp_header: &SpHeader,
_tc_raw: &[u8],
) -> Result<(), Self::Error> {
println!("Unknown APID 0x{:x?} detected", sp_header.apid());
Ok(())
}
}

View File

@ -1,7 +1,12 @@
use satrs::res_code::ResultU16;
use lazy_static::lazy_static;
use satrs::{
res_code::ResultU16,
spacepackets::{PacketId, PacketType},
};
use satrs_mib::res_code::ResultU16Info;
use satrs_mib::resultcode;
use std::net::Ipv4Addr;
use std::{collections::HashSet, net::Ipv4Addr};
use strum::IntoEnumIterator;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use satrs::{
@ -9,8 +14,6 @@ use satrs::{
pool::{StaticMemoryPool, StaticPoolConfig},
};
pub const PUS_APID: u16 = 0x02;
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum CustomPusServiceId {
@ -29,13 +32,30 @@ pub const AOCS_APID: u16 = 1;
pub enum GroupId {
Tmtc = 0,
Hk = 1,
Mode = 2,
}
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
pub const SERVER_PORT: u16 = 7301;
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> =
EventU32TypedSev::<SeverityInfo>::const_new(0, 0);
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<SeverityInfo>::new(0, 0);
lazy_static! {
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
let mut set = HashSet::new();
for id in components::Apid::iter() {
set.insert(PacketId::new(PacketType::Tc, true, id as u16));
}
set
};
pub static ref APID_VALIDATOR: HashSet<u16> = {
let mut set = HashSet::new();
for id in components::Apid::iter() {
set.insert(id as u16);
}
set
};
}
pub mod tmtc_err {
@ -53,6 +73,8 @@ pub mod tmtc_err {
pub const UNKNOWN_TARGET_ID: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 4);
#[resultcode]
pub const ROUTING_ERROR: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 5);
#[resultcode(info = "Request timeout for targeted PUS request. P1: Request ID. P2: Target ID")]
pub const REQUEST_TIMEOUT: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 6);
#[resultcode(
info = "Not enough data inside the TC application data field. Optionally includes: \
@ -92,27 +114,70 @@ pub mod hk_err {
];
}
#[allow(clippy::enum_variant_names)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum TmSenderId {
PusVerification = 0,
PusTest = 1,
PusEvent = 2,
PusHk = 3,
PusAction = 4,
PusSched = 5,
AllEvents = 6,
AcsSubsystem = 7,
pub mod mode_err {
use super::*;
#[resultcode]
pub const WRONG_MODE: ResultU16 = ResultU16::new(GroupId::Mode as u8, 0);
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum TcReceiverId {
PusTest = 1,
PusEvent = 2,
PusHk = 3,
PusAction = 4,
PusSched = 5,
pub mod components {
use satrs::request::UniqueApidTargetId;
use strum::EnumIter;
#[derive(Copy, Clone, PartialEq, Eq, EnumIter)]
pub enum Apid {
Sched = 1,
GenericPus = 2,
Acs = 3,
Cfdp = 4,
Tmtc = 5,
}
// Component IDs for components with the PUS APID.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum PusId {
PusEventManagement = 0,
PusRouting = 1,
PusTest = 2,
PusAction = 3,
PusMode = 4,
PusHk = 5,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum AcsId {
Mgm0 = 0,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum TmtcId {
UdpServer = 0,
TcpServer = 1,
}
pub const PUS_ACTION_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusAction as u32);
pub const PUS_EVENT_MANAGEMENT: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, 0);
pub const PUS_ROUTING_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusRouting as u32);
pub const PUS_TEST_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusTest as u32);
pub const PUS_MODE_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusMode as u32);
pub const PUS_HK_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusHk as u32);
pub const PUS_SCHED_SERVICE: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Sched as u16, 0);
pub const MGM_HANDLER_0: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Mgm0 as u32);
pub const UDP_SERVER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::UdpServer as u32);
pub const TCP_SERVER: UniqueApidTargetId =
UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::TcpServer as u32);
}
pub mod pool {
use super::*;
pub fn create_static_pools() -> (StaticMemoryPool, StaticMemoryPool) {

View File

@ -1,66 +1,90 @@
use std::sync::mpsc::{self};
use crate::pus::create_verification_reporter;
use satrs::event_man::{EventMessageU32, EventRoutingError};
use satrs::pus::event::EventTmHookProvider;
use satrs::pus::verification::VerificationReporter;
use satrs::pus::EcssTmSender;
use satrs::request::UniqueApidTargetId;
use satrs::{
event_man::{
EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded,
MpscEventReceiver,
},
events::EventU32,
params::Params,
event_man::{EventManagerWithBoundedMpsc, EventSendProvider, EventU32SenderMpscBounded},
pus::{
event_man::{
DefaultPusEventU32Dispatcher, EventReporter, EventRequest, EventRequestWithToken,
DefaultPusEventU32TmCreator, EventReporter, EventRequest, EventRequestWithToken,
},
verification::{TcStateStarted, VerificationReportingProvider, VerificationToken},
EcssTmSender,
},
spacepackets::time::cds::{self, TimeProvider},
spacepackets::time::cds::CdsTime,
};
use satrs_example::config::PUS_APID;
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
use crate::update_time;
pub struct PusEventHandler<VerificationReporter: VerificationReportingProvider> {
// This helper sets the APID of the event sender for the PUS telemetry.
#[derive(Default)]
pub struct EventApidSetter {
pub next_apid: u16,
}
impl EventTmHookProvider for EventApidSetter {
fn modify_tm(&self, tm: &mut satrs::spacepackets::ecss::tm::PusTmCreator) {
tm.set_apid(self.next_apid);
}
}
/// The PUS event handler subscribes for all events and converts them into ECSS PUS 5 event
/// packets. It also handles the verification completion of PUS event service requests.
pub struct PusEventHandler<TmSender: EcssTmSender> {
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
pus_event_dispatcher: DefaultPusEventU32Dispatcher<()>,
pus_event_man_rx: mpsc::Receiver<(EventU32, Option<Params>)>,
tm_sender: Box<dyn EcssTmSender>,
time_provider: TimeProvider,
pus_event_tm_creator: DefaultPusEventU32TmCreator<EventApidSetter>,
pus_event_man_rx: mpsc::Receiver<EventMessageU32>,
tm_sender: TmSender,
time_provider: CdsTime,
timestamp: [u8; 7],
small_data_buf: [u8; 64],
verif_handler: VerificationReporter,
}
/*
*/
impl<VerificationReporter: VerificationReportingProvider> PusEventHandler<VerificationReporter> {
impl<TmSender: EcssTmSender> PusEventHandler<TmSender> {
pub fn new(
tm_sender: TmSender,
verif_handler: VerificationReporter,
event_manager: &mut EventManagerWithBoundedMpsc,
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
tm_sender: impl EcssTmSender,
) -> Self {
let event_queue_cap = 30;
let (pus_event_man_tx, pus_event_man_rx) = mpsc::sync_channel(event_queue_cap);
// All events sent to the manager are routed to the PUS event manager, which generates PUS event
// telemetry for each event.
let event_reporter = EventReporter::new(PUS_APID, 128).unwrap();
let event_reporter = EventReporter::new_with_hook(
PUS_EVENT_MANAGEMENT.raw(),
0,
0,
128,
EventApidSetter::default(),
)
.unwrap();
let pus_event_dispatcher =
DefaultPusEventU32Dispatcher::new_with_default_backend(event_reporter);
let pus_event_man_send_provider =
EventU32SenderMpscBounded::new(1, pus_event_man_tx, event_queue_cap);
DefaultPusEventU32TmCreator::new_with_default_backend(event_reporter);
let pus_event_man_send_provider = EventU32SenderMpscBounded::new(
PUS_EVENT_MANAGEMENT.raw(),
pus_event_man_tx,
event_queue_cap,
);
event_manager.subscribe_all(pus_event_man_send_provider.channel_id());
event_manager.subscribe_all(pus_event_man_send_provider.target_id());
event_manager.add_sender(pus_event_man_send_provider);
Self {
event_request_rx,
pus_event_dispatcher,
pus_event_tm_creator: pus_event_dispatcher,
pus_event_man_rx,
time_provider: cds::TimeProvider::new_with_u16_days(0, 0),
time_provider: CdsTime::new_with_u16_days(0, 0),
timestamp: [0; 7],
small_data_buf: [0; 64],
verif_handler,
tm_sender: Box::new(tm_sender),
tm_sender,
}
}
@ -71,115 +95,203 @@ impl<VerificationReporter: VerificationReportingProvider> PusEventHandler<Verifi
.try_into()
.expect("expected start verification token");
self.verif_handler
.completion_success(started_token, timestamp)
.completion_success(&self.tm_sender, started_token, timestamp)
.expect("Sending completion success failed");
};
// handle event requests
if let Ok(event_req) = self.event_request_rx.try_recv() {
match event_req.request {
EventRequest::Enable(event) => {
self.pus_event_dispatcher
.enable_tm_for_event(&event)
.expect("Enabling TM failed");
update_time(&mut self.time_provider, &mut self.timestamp);
report_completion(event_req, &self.timestamp);
}
EventRequest::Disable(event) => {
self.pus_event_dispatcher
.disable_tm_for_event(&event)
.expect("Disabling TM failed");
update_time(&mut self.time_provider, &mut self.timestamp);
report_completion(event_req, &self.timestamp);
}
loop {
// handle event requests
match self.event_request_rx.try_recv() {
Ok(event_req) => match event_req.request {
EventRequest::Enable(event) => {
self.pus_event_tm_creator
.enable_tm_for_event(&event)
.expect("Enabling TM failed");
update_time(&mut self.time_provider, &mut self.timestamp);
report_completion(event_req, &self.timestamp);
}
EventRequest::Disable(event) => {
self.pus_event_tm_creator
.disable_tm_for_event(&event)
.expect("Disabling TM failed");
update_time(&mut self.time_provider, &mut self.timestamp);
report_completion(event_req, &self.timestamp);
}
},
Err(e) => match e {
mpsc::TryRecvError::Empty => break,
mpsc::TryRecvError::Disconnected => {
log::warn!("all event request senders have disconnected");
break;
}
},
}
}
}
pub fn generate_pus_event_tm(&mut self) {
// Perform the generation of PUS event packets
if let Ok((event, _param)) = self.pus_event_man_rx.try_recv() {
update_time(&mut self.time_provider, &mut self.timestamp);
self.pus_event_dispatcher
.generate_pus_event_tm_generic(
self.tm_sender.upcast_mut(),
&self.timestamp,
event,
None,
)
.expect("Sending TM as event failed");
loop {
// Perform the generation of PUS event packets
match self.pus_event_man_rx.try_recv() {
Ok(event_msg) => {
// We use the TM modification hook to set the sender APID for each event.
self.pus_event_tm_creator.reporter.tm_hook.next_apid =
UniqueApidTargetId::from(event_msg.sender_id()).apid;
update_time(&mut self.time_provider, &mut self.timestamp);
let generation_result = self
.pus_event_tm_creator
.generate_pus_event_tm_generic_with_generic_params(
&self.tm_sender,
&self.timestamp,
event_msg.event(),
&mut self.small_data_buf,
event_msg.params(),
)
.expect("Sending TM as event failed");
if !generation_result.params_were_propagated {
log::warn!(
"Event TM parameters were not propagated: {:?}",
event_msg.params()
);
}
}
Err(e) => match e {
mpsc::TryRecvError::Empty => break,
mpsc::TryRecvError::Disconnected => {
log::warn!("All event senders have disconnected");
break;
}
},
}
}
}
}
pub struct EventManagerWrapper {
pub struct EventHandler<TmSender: EcssTmSender> {
pub pus_event_handler: PusEventHandler<TmSender>,
event_manager: EventManagerWithBoundedMpsc,
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
}
impl EventManagerWrapper {
pub fn new() -> Self {
// The sender handle is the primary sender handle for all components which want to create events.
// The event manager will receive the RX handle to receive all the events.
let (event_sender, event_man_rx) = mpsc::channel();
let event_recv = MpscEventReceiver::<EventU32>::new(event_man_rx);
Self {
event_manager: EventManagerWithBoundedMpsc::new(event_recv),
event_sender,
}
}
pub fn clone_event_sender(&self) -> mpsc::Sender<(EventU32, Option<Params>)> {
self.event_sender.clone()
}
pub fn event_manager(&mut self) -> &mut EventManagerWithBoundedMpsc {
&mut self.event_manager
}
pub fn try_event_routing(&mut self) {
// Perform the event routing.
self.event_manager
.try_event_handling()
.expect("event handling failed");
}
}
pub struct EventHandler<VerificationReporter: VerificationReportingProvider> {
pub event_man_wrapper: EventManagerWrapper,
pub pus_event_handler: PusEventHandler<VerificationReporter>,
}
impl<VerificationReporter: VerificationReportingProvider> EventHandler<VerificationReporter> {
impl<TmSender: EcssTmSender> EventHandler<TmSender> {
pub fn new(
tm_sender: impl EcssTmSender,
verif_handler: VerificationReporter,
tm_sender: TmSender,
event_rx: mpsc::Receiver<EventMessageU32>,
event_request_rx: mpsc::Receiver<EventRequestWithToken>,
) -> Self {
let mut event_man_wrapper = EventManagerWrapper::new();
let mut event_manager = EventManagerWithBoundedMpsc::new(event_rx);
let pus_event_handler = PusEventHandler::new(
verif_handler,
event_man_wrapper.event_manager(),
event_request_rx,
tm_sender,
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
&mut event_manager,
event_request_rx,
);
Self {
event_man_wrapper,
pus_event_handler,
}
}
pub fn clone_event_sender(&self) -> mpsc::Sender<(EventU32, Option<Params>)> {
self.event_man_wrapper.clone_event_sender()
Self {
pus_event_handler,
event_manager,
}
}
#[allow(dead_code)]
pub fn event_manager(&mut self) -> &mut EventManagerWithBoundedMpsc {
self.event_man_wrapper.event_manager()
&mut self.event_manager
}
pub fn periodic_operation(&mut self) {
self.pus_event_handler.handle_event_requests();
self.event_man_wrapper.try_event_routing();
self.try_event_routing();
self.pus_event_handler.generate_pus_event_tm();
}
pub fn try_event_routing(&mut self) {
let error_handler = |event_msg: &EventMessageU32, error: EventRoutingError| {
self.routing_error_handler(event_msg, error)
};
// Perform the event routing.
self.event_manager.try_event_handling(error_handler);
}
pub fn routing_error_handler(&self, event_msg: &EventMessageU32, error: EventRoutingError) {
log::warn!("event routing error for event {event_msg:?}: {error:?}");
}
}
#[cfg(test)]
mod tests {
use satrs::{
events::EventU32,
pus::verification::VerificationReporterCfg,
spacepackets::{
ecss::{tm::PusTmReader, PusPacket},
CcsdsPacket,
},
tmtc::PacketAsVec,
};
use super::*;
const TEST_CREATOR_ID: UniqueApidTargetId = UniqueApidTargetId::new(1, 2);
const TEST_EVENT: EventU32 = EventU32::new(satrs::events::Severity::Info, 1, 1);
pub struct EventManagementTestbench {
pub event_tx: mpsc::SyncSender<EventMessageU32>,
pub event_manager: EventManagerWithBoundedMpsc,
pub tm_receiver: mpsc::Receiver<PacketAsVec>,
pub pus_event_handler: PusEventHandler<mpsc::Sender<PacketAsVec>>,
}
impl EventManagementTestbench {
pub fn new() -> Self {
let (event_tx, event_rx) = mpsc::sync_channel(10);
let (_event_req_tx, event_req_rx) = mpsc::sync_channel(10);
let (tm_sender, tm_receiver) = mpsc::channel();
let verif_reporter_cfg = VerificationReporterCfg::new(0x05, 2, 2, 128).unwrap();
let verif_reporter =
VerificationReporter::new(PUS_EVENT_MANAGEMENT.id(), &verif_reporter_cfg);
let mut event_manager = EventManagerWithBoundedMpsc::new(event_rx);
let pus_event_handler = PusEventHandler::<mpsc::Sender<PacketAsVec>>::new(
tm_sender,
verif_reporter,
&mut event_manager,
event_req_rx,
);
Self {
event_tx,
tm_receiver,
event_manager,
pus_event_handler,
}
}
}
#[test]
fn test_basic_event_generation() {
let mut testbench = EventManagementTestbench::new();
testbench
.event_tx
.send(EventMessageU32::new(
TEST_CREATOR_ID.id(),
EventU32::new(satrs::events::Severity::Info, 1, 1),
))
.expect("failed to send event");
testbench.pus_event_handler.handle_event_requests();
testbench.event_manager.try_event_handling(|_, _| {});
testbench.pus_event_handler.generate_pus_event_tm();
let tm_packet = testbench
.tm_receiver
.try_recv()
.expect("failed to receive TM packet");
assert_eq!(tm_packet.sender_id, PUS_EVENT_MANAGEMENT.id());
let tm_reader = PusTmReader::new(&tm_packet.packet, 7)
.expect("failed to create TM reader")
.0;
assert_eq!(tm_reader.apid(), TEST_CREATOR_ID.apid);
assert_eq!(tm_reader.user_data().len(), 4);
let event_read_back = EventU32::from_be_bytes(tm_reader.user_data().try_into().unwrap());
assert_eq!(event_read_back, TEST_EVENT);
}
#[test]
fn test_basic_event_disabled() {
// TODO: Add test.
}
}

View File

@ -1,27 +1,25 @@
use derive_new::new;
use satrs::hk::UniqueId;
use satrs::request::UniqueApidTargetId;
use satrs::spacepackets::ByteConversionError;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AcsHkIds {
TestMgmSet = 1,
}
#[derive(Debug, new, Copy, Clone)]
pub struct HkUniqueId {
target_id: u32,
set_id: u32,
target_id: UniqueApidTargetId,
set_id: UniqueId,
}
impl HkUniqueId {
#[allow(dead_code)]
pub fn target_id(&self) -> u32 {
pub fn target_id(&self) -> UniqueApidTargetId {
self.target_id
}
#[allow(dead_code)]
pub fn set_id(&self) -> u32 {
pub fn set_id(&self) -> UniqueId {
self.set_id
}
#[allow(dead_code)]
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
if buf.len() < 8 {
return Err(ByteConversionError::ToSliceTooSmall {
@ -29,7 +27,7 @@ impl HkUniqueId {
expected: 8,
});
}
buf[0..4].copy_from_slice(&self.target_id.to_be_bytes());
buf[0..4].copy_from_slice(&self.target_id.unique_id.to_be_bytes());
buf[4..8].copy_from_slice(&self.set_id.to_be_bytes());
Ok(8)

View File

@ -0,0 +1,3 @@
//! This module contains all component related to the direct interface of the example.
pub mod tcp;
pub mod udp;

View File

@ -0,0 +1,154 @@
use std::time::Duration;
use std::{
collections::{HashSet, VecDeque},
fmt::Debug,
marker::PhantomData,
sync::{Arc, Mutex},
};
use log::{info, warn};
use satrs::{
encoding::ccsds::{SpValidity, SpacePacketValidator},
hal::std::tcp_server::{HandledConnectionHandler, ServerConfig, TcpSpacepacketsServer},
spacepackets::{CcsdsPacket, PacketId},
tmtc::{PacketSenderRaw, PacketSource},
};
#[derive(Default)]
pub struct ConnectionFinishedHandler {}
pub struct SimplePacketValidator {
pub valid_ids: HashSet<PacketId>,
}
impl SpacePacketValidator for SimplePacketValidator {
fn validate(
&self,
sp_header: &satrs::spacepackets::SpHeader,
_raw_buf: &[u8],
) -> satrs::encoding::ccsds::SpValidity {
if self.valid_ids.contains(&sp_header.packet_id()) {
return SpValidity::Valid;
}
log::warn!("ignoring space packet with header {:?}", sp_header);
// We could perform a CRC check.. but lets keep this simple and assume that TCP ensures
// data integrity.
SpValidity::Skip
}
}
impl HandledConnectionHandler for ConnectionFinishedHandler {
fn handled_connection(&mut self, info: satrs::hal::std::tcp_server::HandledConnectionInfo) {
info!(
"Served {} TMs and {} TCs for client {:?}",
info.num_sent_tms, info.num_received_tcs, info.addr
);
}
}
#[derive(Default, Clone)]
pub struct SyncTcpTmSource {
tm_queue: Arc<Mutex<VecDeque<Vec<u8>>>>,
max_packets_stored: usize,
pub silent_packet_overwrite: bool,
}
impl SyncTcpTmSource {
pub fn new(max_packets_stored: usize) -> Self {
Self {
tm_queue: Arc::default(),
max_packets_stored,
silent_packet_overwrite: true,
}
}
pub fn add_tm(&mut self, tm: &[u8]) {
let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failec");
if tm_queue.len() > self.max_packets_stored {
if !self.silent_packet_overwrite {
warn!("TPC TM source is full, deleting oldest packet");
}
tm_queue.pop_front();
}
tm_queue.push_back(tm.to_vec());
}
}
impl PacketSource for SyncTcpTmSource {
type Error = ();
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failed");
if !tm_queue.is_empty() {
let next_vec = tm_queue.front().unwrap();
if buffer.len() < next_vec.len() {
panic!(
"provided buffer too small, must be at least {} bytes",
next_vec.len()
);
}
let next_vec = tm_queue.pop_front().unwrap();
buffer[0..next_vec.len()].copy_from_slice(&next_vec);
if next_vec.len() > 9 {
let service = next_vec[7];
let subservice = next_vec[8];
info!("Sending PUS TM[{service},{subservice}]")
} else {
info!("Sending PUS TM");
}
return Ok(next_vec.len());
}
Ok(0)
}
}
pub type TcpServer<ReceivesTc, SendError> = TcpSpacepacketsServer<
SyncTcpTmSource,
ReceivesTc,
SimplePacketValidator,
ConnectionFinishedHandler,
(),
SendError,
>;
pub struct TcpTask<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>(
pub TcpServer<TcSender, SendError>,
PhantomData<SendError>,
);
impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
TcpTask<TcSender, SendError>
{
pub fn new(
cfg: ServerConfig,
tm_source: SyncTcpTmSource,
tc_sender: TcSender,
valid_ids: HashSet<PacketId>,
) -> Result<Self, std::io::Error> {
Ok(Self(
TcpSpacepacketsServer::new(
cfg,
tm_source,
tc_sender,
SimplePacketValidator { valid_ids },
ConnectionFinishedHandler::default(),
None,
)?,
PhantomData,
))
}
pub fn periodic_operation(&mut self) {
loop {
let result = self
.0
.handle_all_connections(Some(Duration::from_millis(400)));
match result {
Ok(_conn_result) => (),
Err(e) => {
warn!("TCP server error: {e:?}");
}
}
}
}
}

View File

@ -1,13 +1,13 @@
use std::{
net::{SocketAddr, UdpSocket},
sync::mpsc::Receiver,
};
use core::fmt::Debug;
use std::net::{SocketAddr, UdpSocket};
use std::sync::mpsc;
use log::{info, warn};
use satrs::pus::HandlingStatus;
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderRaw};
use satrs::{
hal::std::udp_server::{ReceiveResult, UdpTcServer},
pool::{PoolProviderWithGuards, SharedStaticMemoryPool, StoreAddr},
tmtc::CcsdsError,
pool::{PoolProviderWithGuards, SharedStaticMemoryPool},
};
pub trait UdpTmHandler {
@ -15,20 +15,20 @@ pub trait UdpTmHandler {
}
pub struct StaticUdpTmHandler {
pub tm_rx: Receiver<StoreAddr>,
pub tm_rx: mpsc::Receiver<PacketInPool>,
pub tm_store: SharedStaticMemoryPool,
}
impl UdpTmHandler for StaticUdpTmHandler {
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, &recv_addr: &SocketAddr) {
while let Ok(addr) = self.tm_rx.try_recv() {
while let Ok(pus_tm_in_pool) = self.tm_rx.try_recv() {
let store_lock = self.tm_store.write();
if store_lock.is_err() {
warn!("Locking TM store failed");
continue;
}
let mut store_lock = store_lock.unwrap();
let pg = store_lock.read_with_guard(addr);
let pg = store_lock.read_with_guard(pus_tm_in_pool.store_addr);
let read_res = pg.read_as_vec();
if read_res.is_err() {
warn!("Error reading TM pool data");
@ -44,20 +44,20 @@ impl UdpTmHandler for StaticUdpTmHandler {
}
pub struct DynamicUdpTmHandler {
pub tm_rx: Receiver<Vec<u8>>,
pub tm_rx: mpsc::Receiver<PacketAsVec>,
}
impl UdpTmHandler for DynamicUdpTmHandler {
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr) {
while let Ok(tm) = self.tm_rx.try_recv() {
if tm.len() > 9 {
let service = tm[7];
let subservice = tm[8];
if tm.packet.len() > 9 {
let service = tm.packet[7];
let subservice = tm.packet[8];
info!("Sending PUS TM[{service},{subservice}]")
} else {
info!("Sending PUS TM");
}
let result = socket.send_to(&tm, recv_addr);
let result = socket.send_to(&tm.packet, recv_addr);
if let Err(e) = result {
warn!("Sending TM with UDP socket failed: {e}")
}
@ -65,49 +65,57 @@ impl UdpTmHandler for DynamicUdpTmHandler {
}
}
pub struct UdpTmtcServer<TmHandler: UdpTmHandler, SendError> {
pub udp_tc_server: UdpTcServer<CcsdsError<SendError>>,
pub struct UdpTmtcServer<
TcSender: PacketSenderRaw<Error = SendError>,
TmHandler: UdpTmHandler,
SendError,
> {
pub udp_tc_server: UdpTcServer<TcSender, SendError>,
pub tm_handler: TmHandler,
}
impl<TmHandler: UdpTmHandler, SendError: core::fmt::Debug + 'static>
UdpTmtcServer<TmHandler, SendError>
impl<
TcSender: PacketSenderRaw<Error = SendError>,
TmHandler: UdpTmHandler,
SendError: Debug + 'static,
> UdpTmtcServer<TcSender, TmHandler, SendError>
{
pub fn periodic_operation(&mut self) {
while self.poll_tc_server() {}
loop {
if self.poll_tc_server() == HandlingStatus::Empty {
break;
}
}
if let Some(recv_addr) = self.udp_tc_server.last_sender() {
self.tm_handler
.send_tm_to_udp_client(&self.udp_tc_server.socket, &recv_addr);
}
}
fn poll_tc_server(&mut self) -> bool {
fn poll_tc_server(&mut self) -> HandlingStatus {
match self.udp_tc_server.try_recv_tc() {
Ok(_) => true,
Err(e) => match e {
ReceiveResult::ReceiverError(e) => match e {
CcsdsError::ByteConversionError(e) => {
warn!("packet error: {e:?}");
true
Ok(_) => HandlingStatus::HandledOne,
Err(e) => {
match e {
ReceiveResult::NothingReceived => (),
ReceiveResult::Io(e) => {
warn!("IO error {e}");
}
CcsdsError::CustomError(e) => {
warn!("mpsc custom error {e:?}");
true
ReceiveResult::Send(send_error) => {
warn!("send error {send_error:?}");
}
},
ReceiveResult::IoError(e) => {
warn!("IO error {e}");
false
}
ReceiveResult::NothingReceived => false,
},
HandlingStatus::Empty
}
}
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use std::{
cell::RefCell,
collections::VecDeque,
net::IpAddr,
sync::{Arc, Mutex},
@ -118,21 +126,26 @@ mod tests {
ecss::{tc::PusTcCreator, WritablePusPacket},
SpHeader,
},
tmtc::ReceivesTcCore,
tmtc::PacketSenderRaw,
ComponentId,
};
use satrs_example::config::{OBSW_SERVER_ADDR, PUS_APID};
use satrs_example::config::{components, OBSW_SERVER_ADDR};
use super::*;
#[derive(Default, Debug, Clone)]
pub struct TestReceiver {
tc_vec: Arc<Mutex<VecDeque<Vec<u8>>>>,
const UDP_SERVER_ID: ComponentId = 0x05;
#[derive(Default, Debug)]
pub struct TestSender {
tc_vec: RefCell<VecDeque<PacketAsVec>>,
}
impl ReceivesTcCore for TestReceiver {
type Error = CcsdsError<()>;
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
self.tc_vec.lock().unwrap().push_back(tc_raw.to_vec());
impl PacketSenderRaw for TestSender {
type Error = ();
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
let mut mut_queue = self.tc_vec.borrow_mut();
mut_queue.push_back(PacketAsVec::new(sender_id, tc_raw.to_vec()));
Ok(())
}
}
@ -151,9 +164,10 @@ mod tests {
#[test]
fn test_basic() {
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
let test_receiver = TestReceiver::default();
let tc_queue = test_receiver.tc_vec.clone();
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(test_receiver)).unwrap();
let test_receiver = TestSender::default();
// let tc_queue = test_receiver.tc_vec.clone();
let udp_tc_server =
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
let tm_handler = TestTmHandler::default();
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
let mut udp_dyn_server = UdpTmtcServer {
@ -161,16 +175,18 @@ mod tests {
tm_handler,
};
udp_dyn_server.periodic_operation();
assert!(tc_queue.lock().unwrap().is_empty());
let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
assert!(queue.is_empty());
assert!(tm_handler_calls.lock().unwrap().is_empty());
}
#[test]
fn test_transactions() {
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
let test_receiver = TestReceiver::default();
let tc_queue = test_receiver.tc_vec.clone();
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(test_receiver)).unwrap();
let sock_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
let test_receiver = TestSender::default();
// let tc_queue = test_receiver.tc_vec.clone();
let udp_tc_server =
UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap();
let server_addr = udp_tc_server.socket.local_addr().unwrap();
let tm_handler = TestTmHandler::default();
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
@ -178,20 +194,21 @@ mod tests {
udp_tc_server,
tm_handler,
};
let mut sph = SpHeader::tc_unseg(PUS_APID, 0, 0).unwrap();
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true)
let sph = SpHeader::new_for_unseg_tc(components::Apid::GenericPus as u16, 0, 0);
let ping_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true)
.to_vec()
.unwrap();
let client = UdpSocket::bind("127.0.0.1:0").expect("Connecting to UDP server failed");
let client_addr = client.local_addr().unwrap();
client.connect(server_addr).unwrap();
client.send(&ping_tc).unwrap();
println!("{}", server_addr);
client.send_to(&ping_tc, server_addr).unwrap();
udp_dyn_server.periodic_operation();
{
let mut tc_queue = tc_queue.lock().unwrap();
assert!(!tc_queue.is_empty());
let received_tc = tc_queue.pop_front().unwrap();
assert_eq!(received_tc, ping_tc);
let mut queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow_mut();
assert!(!queue.is_empty());
let packet_with_sender = queue.pop_front().unwrap();
assert_eq!(packet_with_sender.packet, ping_tc);
assert_eq!(packet_with_sender.sender_id, UDP_SERVER_ID);
}
{
@ -202,7 +219,9 @@ mod tests {
assert_eq!(received_addr, client_addr);
}
udp_dyn_server.periodic_operation();
assert!(tc_queue.lock().unwrap().is_empty());
let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow();
assert!(queue.is_empty());
drop(queue);
// Still tries to send to the same client.
{
let mut tm_handler_calls = tm_handler_calls.lock().unwrap();

View File

@ -1 +1,39 @@
use satrs::spacepackets::time::{cds::CdsTime, TimeWriter};
pub mod config;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum DeviceMode {
Off = 0,
On = 1,
Normal = 2,
}
pub struct TimeStampHelper {
stamper: CdsTime,
time_stamp: [u8; 7],
}
impl TimeStampHelper {
pub fn stamp(&self) -> &[u8] {
&self.time_stamp
}
pub fn update_from_now(&mut self) {
self.stamper
.update_from_now()
.expect("Updating timestamp failed");
self.stamper
.write_to_bytes(&mut self.time_stamp)
.expect("Writing timestamp failed");
}
}
impl Default for TimeStampHelper {
fn default() -> Self {
Self {
stamper: CdsTime::now_with_u16_days().expect("creating time stamper failed"),
time_stamp: Default::default(),
}
}
}

View File

@ -1,229 +1,223 @@
mod acs;
mod ccsds;
mod events;
mod hk;
mod interface;
mod logger;
mod pus;
mod requests;
mod tcp;
mod tm_funnel;
mod tmtc;
mod udp;
use crate::events::EventHandler;
use crate::interface::udp::DynamicUdpTmHandler;
use crate::pus::stack::PusStack;
use crate::tm_funnel::{TmFunnelDynamic, TmFunnelStatic};
use crate::tmtc::tc_source::{TcSourceTaskDynamic, TcSourceTaskStatic};
use crate::tmtc::tm_sink::{TmSinkDynamic, TmSinkStatic};
use log::info;
use pus::test::create_test_service_dynamic;
use satrs::hal::std::tcp_server::ServerConfig;
use satrs::hal::std::udp_server::UdpTcServer;
use satrs::request::TargetAndApidId;
use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::request::GenericMessage;
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
use satrs_example::config::tasks::{
FREQ_MS_AOCS, FREQ_MS_EVENT_HANDLING, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC,
};
use satrs_example::config::{RequestTargetId, TmSenderId, OBSW_SERVER_ADDR, PUS_APID, SERVER_PORT};
use tmtc::PusTcSourceProviderDynamic;
use udp::DynamicUdpTmHandler;
use satrs_example::config::{OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT};
use crate::acs::AcsTask;
use crate::ccsds::CcsdsReceiver;
use crate::acs::mgm::{MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface};
use crate::interface::tcp::{SyncTcpTmSource, TcpTask};
use crate::interface::udp::{StaticUdpTmHandler, UdpTmtcServer};
use crate::logger::setup_logger;
use crate::pus::action::{create_action_service_dynamic, create_action_service_static};
use crate::pus::event::{create_event_service_dynamic, create_event_service_static};
use crate::pus::hk::{create_hk_service_dynamic, create_hk_service_static};
use crate::pus::mode::{create_mode_service_dynamic, create_mode_service_static};
use crate::pus::scheduler::{create_scheduler_service_dynamic, create_scheduler_service_static};
use crate::pus::test::create_test_service_static;
use crate::pus::{PusReceiver, PusTcMpscRouter};
use crate::requests::{GenericRequestRouter, RequestWithToken};
use crate::tcp::{SyncTcpTmSource, TcpTask};
use crate::tmtc::{
PusTcSourceProviderSharedPool, SharedTcPool, TcSourceTaskDynamic, TcSourceTaskStatic,
};
use crate::udp::{StaticUdpTmHandler, UdpTmtcServer};
use crate::pus::{PusTcDistributor, PusTcMpscRouter};
use crate::requests::{CompositeRequest, GenericRequestRouter};
use satrs::mode::ModeRequest;
use satrs::pus::event_man::EventRequestWithToken;
use satrs::pus::verification::{VerificationReporterCfg, VerificationReporterWithSender};
use satrs::pus::{EcssTmSender, TmAsVecSenderWithId, TmInSharedPoolSenderWithId};
use satrs::spacepackets::{time::cds::TimeProvider, time::TimeWriter};
use satrs::tmtc::CcsdsDistributor;
use satrs::ChannelId;
use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter};
use satrs_example::config::components::{MGM_HANDLER_0, TCP_SERVER, UDP_SERVER};
use std::net::{IpAddr, SocketAddr};
use std::sync::mpsc::{self, channel};
use std::sync::mpsc;
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
fn create_verification_reporter<Sender: EcssTmSender + Clone>(
verif_sender: Sender,
) -> VerificationReporterWithSender<Sender> {
let verif_cfg = VerificationReporterCfg::new(PUS_APID, 1, 2, 8).unwrap();
// Every software component which needs to generate verification telemetry, gets a cloned
// verification reporter.
VerificationReporterWithSender::new(&verif_cfg, verif_sender)
}
#[allow(dead_code)]
fn static_tmtc_pool_main() {
let (tm_pool, tc_pool) = create_static_pools();
let shared_tm_pool = SharedTmPool::new(tm_pool);
let shared_tc_pool = SharedTcPool {
pool: Arc::new(RwLock::new(tc_pool)),
};
let shared_tm_pool = Arc::new(RwLock::new(tm_pool));
let shared_tc_pool = Arc::new(RwLock::new(tc_pool));
let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool);
let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool);
let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50);
let (tm_funnel_tx, tm_funnel_rx) = mpsc::sync_channel(50);
let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50);
let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50);
// Every software component which needs to generate verification telemetry, receives a cloned
// verification reporter.
let verif_reporter = create_verification_reporter(TmInSharedPoolSenderWithId::new(
TmSenderId::PusVerification as ChannelId,
"verif_sender",
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
));
let tm_sink_tx_sender =
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone());
let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
mpsc::channel::<GenericMessage<CompositeRequest>>();
let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::channel::<GenericMessage<ModeRequest>>();
let acs_target_id = TargetAndApidId::new(PUS_APID, RequestTargetId::AcsSubsystem as u32);
let (acs_thread_tx, acs_thread_rx) = channel::<RequestWithToken>();
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let mut request_map = GenericRequestRouter::default();
request_map.0.insert(acs_target_id.into(), acs_thread_tx);
request_map
.composite_router_map
.insert(MGM_HANDLER_0.id(), mgm_handler_composite_tx);
request_map
.mode_router_map
.insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx);
// This helper structure is used by all telecommand providers which need to send telecommands
// to the TC source.
let tc_source = PusTcSourceProviderSharedPool {
shared_pool: shared_tc_pool.clone(),
tc_source: tc_source_tx,
};
let tc_source = PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone());
// Create event handling components
// These sender handles are used to send event requests, for example to enable or disable
// certain events.
let (event_tx, event_rx) = mpsc::sync_channel(100);
let (event_request_tx, event_request_rx) = mpsc::channel::<EventRequestWithToken>();
// The event task is the core handler to perform the event routing and TM handling as specified
// in the sat-rs documentation.
let mut event_handler = EventHandler::new(
TmInSharedPoolSenderWithId::new(
TmSenderId::AllEvents as ChannelId,
"ALL_EVENTS_TX",
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
),
verif_reporter.clone(),
event_request_rx,
);
let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx);
let (pus_test_tx, pus_test_rx) = mpsc::channel();
let (pus_event_tx, pus_event_rx) = mpsc::channel();
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
let (pus_action_tx, pus_action_rx) = mpsc::channel();
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
let (pus_test_tx, pus_test_rx) = channel();
let (pus_event_tx, pus_event_rx) = channel();
let (pus_sched_tx, pus_sched_rx) = channel();
let (pus_hk_tx, pus_hk_rx) = channel();
let (pus_action_tx, pus_action_rx) = channel();
let pus_router = PusTcMpscRouter {
test_service_receiver: pus_test_tx,
event_service_receiver: pus_event_tx,
sched_service_receiver: pus_sched_tx,
hk_service_receiver: pus_hk_tx,
action_service_receiver: pus_action_tx,
test_tc_sender: pus_test_tx,
event_tc_sender: pus_event_tx,
sched_tc_sender: pus_sched_tx,
hk_tc_sender: pus_hk_tx,
action_tc_sender: pus_action_tx,
mode_tc_sender: pus_mode_tx,
};
let pus_test_service = create_test_service_static(
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
shared_tc_pool.pool.clone(),
event_handler.clone_event_sender(),
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
event_tx.clone(),
pus_test_rx,
);
let pus_scheduler_service = create_scheduler_service_static(
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
tm_sink_tx_sender.clone(),
tc_source.clone(),
pus_sched_rx,
create_sched_tc_pool(),
);
let pus_event_service = create_event_service_static(
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
shared_tc_pool.pool.clone(),
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
pus_event_rx,
event_request_tx,
);
let pus_action_service = create_action_service_static(
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
shared_tc_pool.pool.clone(),
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
pus_action_rx,
request_map.clone(),
pus_action_reply_rx,
);
let pus_hk_service = create_hk_service_static(
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
verif_reporter.clone(),
shared_tc_pool.pool.clone(),
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
pus_hk_rx,
request_map.clone(),
pus_hk_reply_rx,
);
let pus_mode_service = create_mode_service_static(
tm_sink_tx_sender.clone(),
shared_tc_pool.clone(),
pus_mode_rx,
request_map,
pus_mode_reply_rx,
);
let mut pus_stack = PusStack::new(
pus_test_service,
pus_hk_service,
pus_event_service,
pus_action_service,
pus_scheduler_service,
pus_test_service,
pus_mode_service,
);
let ccsds_receiver = CcsdsReceiver { tc_source };
let mut tmtc_task = TcSourceTaskStatic::new(
shared_tc_pool.clone(),
shared_tc_pool_wrapper.clone(),
tc_source_rx,
PusReceiver::new(verif_reporter.clone(), pus_router),
PusTcDistributor::new(tm_sink_tx_sender, pus_router),
);
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let udp_ccsds_distributor = CcsdsDistributor::new(ccsds_receiver.clone());
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(udp_ccsds_distributor))
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source.clone())
.expect("creating UDP TMTC server failed");
let mut udp_tmtc_server = UdpTmtcServer {
udp_tc_server,
tm_handler: StaticUdpTmHandler {
tm_rx: tm_server_rx,
tm_store: shared_tm_pool.clone_backing_pool(),
tm_store: shared_tm_pool.clone(),
},
};
let tcp_ccsds_distributor = CcsdsDistributor::new(ccsds_receiver);
let tcp_server_cfg = ServerConfig::new(sock_addr, Duration::from_millis(400), 4096, 8192);
let tcp_server_cfg = ServerConfig::new(
TCP_SERVER.id(),
sock_addr,
Duration::from_millis(400),
4096,
8192,
);
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
let mut tcp_server = TcpTask::new(
tcp_server_cfg,
sync_tm_tcp_source.clone(),
tcp_ccsds_distributor,
tc_source.clone(),
PACKET_ID_VALIDATOR.clone(),
)
.expect("tcp server creation failed");
let mut acs_task = AcsTask::new(
TmInSharedPoolSenderWithId::new(
TmSenderId::AcsSubsystem as ChannelId,
"ACS_TASK_SENDER",
shared_tm_pool.clone(),
tm_funnel_tx.clone(),
),
acs_thread_rx,
verif_reporter,
let mut tm_sink = TmSinkStatic::new(
shared_tm_pool_wrapper,
sync_tm_tcp_source,
tm_sink_rx,
tm_server_tx,
);
let mut tm_funnel = TmFunnelStatic::new(
shared_tm_pool,
sync_tm_tcp_source,
tm_funnel_rx,
tm_server_tx,
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::channel();
let dummy_spi_interface = SpiDummyInterface::default();
let shared_mgm_set = Arc::default();
let mode_leaf_interface = MpscModeLeafInterface {
request_rx: mgm_handler_mode_rx,
reply_tx_to_pus: pus_mode_reply_tx,
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
};
let mut mgm_handler = MgmHandlerLis3Mdl::new(
MGM_HANDLER_0,
"MGM_0",
mode_leaf_interface,
mgm_handler_composite_rx,
pus_hk_reply_tx,
tm_sink_tx,
dummy_spi_interface,
shared_mgm_set,
);
info!("Starting TMTC and UDP task");
let jh_udp_tmtc = thread::Builder::new()
.name("TMTC and UDP".to_string())
.name("SATRS tmtc-udp".to_string())
.spawn(move || {
info!("Running UDP server on port {SERVER_PORT}");
loop {
@ -236,7 +230,7 @@ fn static_tmtc_pool_main() {
info!("Starting TCP task");
let jh_tcp = thread::Builder::new()
.name("TCP".to_string())
.name("sat-rs tcp".to_string())
.spawn(move || {
info!("Running TCP server on port {SERVER_PORT}");
loop {
@ -247,15 +241,15 @@ fn static_tmtc_pool_main() {
info!("Starting TM funnel task");
let jh_tm_funnel = thread::Builder::new()
.name("TM Funnel".to_string())
.name("tm sink".to_string())
.spawn(move || loop {
tm_funnel.operation();
tm_sink.operation();
})
.unwrap();
info!("Starting event handling task");
let jh_event_handling = thread::Builder::new()
.name("Event".to_string())
.name("sat-rs events".to_string())
.spawn(move || loop {
event_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
@ -264,16 +258,16 @@ fn static_tmtc_pool_main() {
info!("Starting AOCS thread");
let jh_aocs = thread::Builder::new()
.name("AOCS".to_string())
.name("sat-rs aocs".to_string())
.spawn(move || loop {
acs_task.periodic_operation();
mgm_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
})
.unwrap();
info!("Starting PUS handler thread");
let jh_pus_handler = thread::Builder::new()
.name("PUS".to_string())
.name("sat-rs pus".to_string())
.spawn(move || loop {
pus_stack.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
@ -300,104 +294,98 @@ fn static_tmtc_pool_main() {
#[allow(dead_code)]
fn dyn_tmtc_pool_main() {
let (tc_source_tx, tc_source_rx) = channel();
let (tm_funnel_tx, tm_funnel_rx) = channel();
let (tm_server_tx, tm_server_rx) = channel();
// Every software component which needs to generate verification telemetry, gets a cloned
// verification reporter.
let verif_reporter = create_verification_reporter(TmAsVecSenderWithId::new(
TmSenderId::PusVerification as ChannelId,
"verif_sender",
tm_funnel_tx.clone(),
));
let (tc_source_tx, tc_source_rx) = mpsc::channel();
let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
let (tm_server_tx, tm_server_rx) = mpsc::channel();
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
mpsc::channel::<GenericMessage<CompositeRequest>>();
let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::channel::<GenericMessage<ModeRequest>>();
let acs_target_id = TargetAndApidId::new(PUS_APID, RequestTargetId::AcsSubsystem as u32);
let (acs_thread_tx, acs_thread_rx) = channel::<RequestWithToken>();
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let mut request_map = GenericRequestRouter::default();
request_map.0.insert(acs_target_id.into(), acs_thread_tx);
let tc_source = PusTcSourceProviderDynamic(tc_source_tx);
request_map
.composite_router_map
.insert(MGM_HANDLER_0.raw(), mgm_handler_composite_tx);
request_map
.mode_router_map
.insert(MGM_HANDLER_0.raw(), mgm_handler_mode_tx);
// Create event handling components
// These sender handles are used to send event requests, for example to enable or disable
// certain events.
let (event_tx, event_rx) = mpsc::sync_channel(100);
let (event_request_tx, event_request_rx) = mpsc::channel::<EventRequestWithToken>();
// The event task is the core handler to perform the event routing and TM handling as specified
// in the sat-rs documentation.
let mut event_handler = EventHandler::new(
TmAsVecSenderWithId::new(
TmSenderId::AllEvents as ChannelId,
"ALL_EVENTS_TX",
tm_funnel_tx.clone(),
),
verif_reporter.clone(),
event_request_rx,
);
let mut event_handler = EventHandler::new(tm_funnel_tx.clone(), event_rx, event_request_rx);
let (pus_test_tx, pus_test_rx) = mpsc::channel();
let (pus_event_tx, pus_event_rx) = mpsc::channel();
let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
let (pus_action_tx, pus_action_rx) = mpsc::channel();
let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
let (pus_test_tx, pus_test_rx) = channel();
let (pus_event_tx, pus_event_rx) = channel();
let (pus_sched_tx, pus_sched_rx) = channel();
let (pus_hk_tx, pus_hk_rx) = channel();
let (pus_action_tx, pus_action_rx) = channel();
let pus_router = PusTcMpscRouter {
test_service_receiver: pus_test_tx,
event_service_receiver: pus_event_tx,
sched_service_receiver: pus_sched_tx,
hk_service_receiver: pus_hk_tx,
action_service_receiver: pus_action_tx,
test_tc_sender: pus_test_tx,
event_tc_sender: pus_event_tx,
sched_tc_sender: pus_sched_tx,
hk_tc_sender: pus_hk_tx,
action_tc_sender: pus_action_tx,
mode_tc_sender: pus_mode_tx,
};
let pus_test_service = create_test_service_dynamic(
tm_funnel_tx.clone(),
verif_reporter.clone(),
event_handler.clone_event_sender(),
pus_test_rx,
);
let pus_test_service =
create_test_service_dynamic(tm_funnel_tx.clone(), event_tx.clone(), pus_test_rx);
let pus_scheduler_service = create_scheduler_service_dynamic(
tm_funnel_tx.clone(),
verif_reporter.clone(),
tc_source.0.clone(),
tc_source_tx.clone(),
pus_sched_rx,
create_sched_tc_pool(),
);
let pus_event_service = create_event_service_dynamic(
tm_funnel_tx.clone(),
verif_reporter.clone(),
pus_event_rx,
event_request_tx,
);
let pus_event_service =
create_event_service_dynamic(tm_funnel_tx.clone(), pus_event_rx, event_request_tx);
let pus_action_service = create_action_service_dynamic(
tm_funnel_tx.clone(),
verif_reporter.clone(),
pus_action_rx,
request_map.clone(),
pus_action_reply_rx,
);
let pus_hk_service = create_hk_service_dynamic(
tm_funnel_tx.clone(),
verif_reporter.clone(),
pus_hk_rx,
request_map.clone(),
pus_hk_reply_rx,
);
let pus_mode_service = create_mode_service_dynamic(
tm_funnel_tx.clone(),
pus_mode_rx,
request_map,
pus_mode_reply_rx,
);
let mut pus_stack = PusStack::new(
pus_test_service,
pus_hk_service,
pus_event_service,
pus_action_service,
pus_scheduler_service,
pus_test_service,
pus_mode_service,
);
let ccsds_receiver = CcsdsReceiver { tc_source };
let mut tmtc_task = TcSourceTaskDynamic::new(
tc_source_rx,
PusReceiver::new(verif_reporter.clone(), pus_router),
PusTcDistributor::new(tm_funnel_tx.clone(), pus_router),
);
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let udp_ccsds_distributor = CcsdsDistributor::new(ccsds_receiver.clone());
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(udp_ccsds_distributor))
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source_tx.clone())
.expect("creating UDP TMTC server failed");
let mut udp_tmtc_server = UdpTmtcServer {
udp_tc_server,
@ -406,30 +394,47 @@ fn dyn_tmtc_pool_main() {
},
};
let tcp_ccsds_distributor = CcsdsDistributor::new(ccsds_receiver);
let tcp_server_cfg = ServerConfig::new(sock_addr, Duration::from_millis(400), 4096, 8192);
let tcp_server_cfg = ServerConfig::new(
TCP_SERVER.id(),
sock_addr,
Duration::from_millis(400),
4096,
8192,
);
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
let mut tcp_server = TcpTask::new(
tcp_server_cfg,
sync_tm_tcp_source.clone(),
tcp_ccsds_distributor,
tc_source_tx.clone(),
PACKET_ID_VALIDATOR.clone(),
)
.expect("tcp server creation failed");
let mut acs_task = AcsTask::new(
TmAsVecSenderWithId::new(
TmSenderId::AcsSubsystem as ChannelId,
"ACS_TASK_SENDER",
tm_funnel_tx.clone(),
),
acs_thread_rx,
verif_reporter,
let mut tm_funnel = TmSinkDynamic::new(sync_tm_tcp_source, tm_funnel_rx, tm_server_tx);
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::channel();
let dummy_spi_interface = SpiDummyInterface::default();
let shared_mgm_set = Arc::default();
let mode_leaf_interface = MpscModeLeafInterface {
request_rx: mgm_handler_mode_rx,
reply_tx_to_pus: pus_mode_reply_tx,
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
};
let mut mgm_handler = MgmHandlerLis3Mdl::new(
MGM_HANDLER_0,
"MGM_0",
mode_leaf_interface,
mgm_handler_composite_rx,
pus_hk_reply_tx,
tm_funnel_tx,
dummy_spi_interface,
shared_mgm_set,
);
let mut tm_funnel = TmFunnelDynamic::new(sync_tm_tcp_source, tm_funnel_rx, tm_server_tx);
info!("Starting TMTC and UDP task");
let jh_udp_tmtc = thread::Builder::new()
.name("TMTC and UDP".to_string())
.name("sat-rs tmtc-udp".to_string())
.spawn(move || {
info!("Running UDP server on port {SERVER_PORT}");
loop {
@ -442,7 +447,7 @@ fn dyn_tmtc_pool_main() {
info!("Starting TCP task");
let jh_tcp = thread::Builder::new()
.name("TCP".to_string())
.name("sat-rs tcp".to_string())
.spawn(move || {
info!("Running TCP server on port {SERVER_PORT}");
loop {
@ -453,7 +458,7 @@ fn dyn_tmtc_pool_main() {
info!("Starting TM funnel task");
let jh_tm_funnel = thread::Builder::new()
.name("TM Funnel".to_string())
.name("sat-rs tm-sink".to_string())
.spawn(move || loop {
tm_funnel.operation();
})
@ -461,7 +466,7 @@ fn dyn_tmtc_pool_main() {
info!("Starting event handling task");
let jh_event_handling = thread::Builder::new()
.name("Event".to_string())
.name("sat-rs events".to_string())
.spawn(move || loop {
event_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
@ -470,16 +475,16 @@ fn dyn_tmtc_pool_main() {
info!("Starting AOCS thread");
let jh_aocs = thread::Builder::new()
.name("AOCS".to_string())
.name("sat-rs aocs".to_string())
.spawn(move || loop {
acs_task.periodic_operation();
mgm_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_AOCS));
})
.unwrap();
info!("Starting PUS handler thread");
let jh_pus_handler = thread::Builder::new()
.name("PUS".to_string())
.name("sat-rs pus".to_string())
.spawn(move || loop {
pus_stack.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
@ -513,7 +518,7 @@ fn main() {
dyn_tmtc_pool_main();
}
pub fn update_time(time_provider: &mut TimeProvider, timestamp: &mut [u8]) {
pub fn update_time(time_provider: &mut CdsTime, timestamp: &mut [u8]) {
time_provider
.update_from_now()
.expect("Could not get current time");

View File

@ -1,200 +1,747 @@
use log::{error, warn};
use satrs::action::ActionRequest;
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
use satrs::pus::action::{PusActionToRequestConverter, PusService8ActionHandler};
use satrs::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
use log::warn;
use satrs::action::{ActionRequest, ActionRequestVariant};
use satrs::pool::SharedStaticMemoryPool;
use satrs::pus::action::{
ActionReplyPus, ActionReplyVariant, ActivePusActionRequestStd, DefaultActiveActionRequestMap,
};
use satrs::pus::verification::{
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
handle_completion_failure_with_generic_params, handle_step_failure_with_generic_params,
FailParamHelper, FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
VerificationReportingProvider, VerificationToken,
};
use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiverCore, EcssTmSenderCore, MpscTcReceiver, PusPacketHandlerResult,
PusPacketHandlingError, PusServiceHelper, TmAsVecSenderWithId, TmAsVecSenderWithMpsc,
TmInSharedPoolSenderWithBoundedMpsc, TmInSharedPoolSenderWithId,
ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver,
MpscTmAsVecSender, PusPacketHandlingError, PusReplyHandler, PusServiceHelper,
PusTcToRequestConverter,
};
use satrs::request::TargetAndApidId;
use satrs::request::{GenericMessage, UniqueApidTargetId};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusPacket;
use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::{ChannelId, TargetId};
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
use std::sync::mpsc::{self};
use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket, PusServiceId};
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs_example::config::components::PUS_ACTION_SERVICE;
use satrs_example::config::tmtc_err;
use std::sync::mpsc;
use std::time::Duration;
use crate::requests::GenericRequestRouter;
use super::GenericRoutingErrorHandler;
use super::{
create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus,
PusTargetedRequestService, TargetedPusService,
};
pub struct ActionReplyHandler {
fail_data_buf: [u8; 128],
}
impl Default for ActionReplyHandler {
fn default() -> Self {
Self {
fail_data_buf: [0; 128],
}
}
}
impl PusReplyHandler<ActivePusActionRequestStd, ActionReplyPus> for ActionReplyHandler {
type Error = EcssTmtcError;
fn handle_unrequested_reply(
&mut self,
reply: &GenericMessage<ActionReplyPus>,
_tm_sender: &impl EcssTmSender,
) -> Result<(), Self::Error> {
warn!("received unexpected reply for service 8: {reply:?}");
Ok(())
}
fn handle_reply(
&mut self,
reply: &GenericMessage<ActionReplyPus>,
active_request: &ActivePusActionRequestStd,
tm_sender: &(impl EcssTmSender + ?Sized),
verification_handler: &impl VerificationReportingProvider,
timestamp: &[u8],
) -> Result<bool, Self::Error> {
let verif_token: VerificationToken<TcStateStarted> = active_request
.token()
.try_into()
.expect("invalid token state");
let remove_entry = match &reply.message.variant {
ActionReplyVariant::CompletionFailed { error_code, params } => {
let error_propagated = handle_completion_failure_with_generic_params(
tm_sender,
verif_token,
verification_handler,
FailParamHelper {
error_code,
params: params.as_ref(),
timestamp,
small_data_buf: &mut self.fail_data_buf,
},
)?;
if !error_propagated {
log::warn!(
"error params for completion failure were not propated: {:?}",
params.as_ref()
);
}
true
}
ActionReplyVariant::StepFailed {
error_code,
step,
params,
} => {
let error_propagated = handle_step_failure_with_generic_params(
tm_sender,
verif_token,
verification_handler,
FailParamHelper {
error_code,
params: params.as_ref(),
timestamp,
small_data_buf: &mut self.fail_data_buf,
},
&EcssEnumU16::new(*step),
)?;
if !error_propagated {
log::warn!(
"error params for completion failure were not propated: {:?}",
params.as_ref()
);
}
true
}
ActionReplyVariant::Completed => {
verification_handler.completion_success(tm_sender, verif_token, timestamp)?;
true
}
ActionReplyVariant::StepSuccess { step } => {
verification_handler.step_success(
tm_sender,
&verif_token,
timestamp,
EcssEnumU16::new(*step),
)?;
false
}
_ => false,
};
Ok(remove_entry)
}
fn handle_request_timeout(
&mut self,
active_request: &ActivePusActionRequestStd,
tm_sender: &impl EcssTmSender,
verification_handler: &impl VerificationReportingProvider,
time_stamp: &[u8],
) -> Result<(), Self::Error> {
generic_pus_request_timeout_handler(
tm_sender,
active_request,
verification_handler,
time_stamp,
"action",
)
}
}
#[derive(Default)]
pub struct ExampleActionRequestConverter {}
pub struct ActionRequestConverter {}
impl PusActionToRequestConverter for ExampleActionRequestConverter {
type Error = PusPacketHandlingError;
impl PusTcToRequestConverter<ActivePusActionRequestStd, ActionRequest> for ActionRequestConverter {
type Error = GenericConversionError;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
tm_sender: &(impl EcssTmSender + ?Sized),
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, ActionRequest), Self::Error> {
time_stamp: &[u8],
) -> Result<(ActivePusActionRequestStd, ActionRequest), Self::Error> {
let subservice = tc.subservice();
let user_data = tc.user_data();
if user_data.len() < 8 {
verif_reporter
.start_failure(
tm_sender,
token,
FailParams::new_no_fail_data(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA),
)
.expect("Sending start failure failed");
return Err(PusPacketHandlingError::NotEnoughAppData {
return Err(GenericConversionError::NotEnoughAppData {
expected: 8,
found: user_data.len(),
});
}
let target_id = TargetAndApidId::from_pus_tc(tc).unwrap();
let target_id_and_apid = UniqueApidTargetId::from_pus_tc(tc).unwrap();
let action_id = u32::from_be_bytes(user_data[4..8].try_into().unwrap());
if subservice == 128 {
let req_variant = if user_data.len() == 8 {
ActionRequestVariant::NoData
} else {
ActionRequestVariant::VecData(user_data[8..].to_vec())
};
Ok((
target_id.raw(),
ActionRequest::UnsignedIdAndVecData {
ActivePusActionRequestStd::new(
action_id,
data: user_data[8..].to_vec(),
},
target_id_and_apid.into(),
token.into(),
Duration::from_secs(30),
),
ActionRequest::new(action_id, req_variant),
))
} else {
verif_reporter
.start_failure(
tm_sender,
token,
FailParams::new_no_fail_data(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE),
)
.expect("Sending start failure failed");
Err(PusPacketHandlingError::InvalidSubservice(subservice))
Err(GenericConversionError::InvalidSubservice(subservice))
}
}
}
pub fn create_action_service_static(
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
action_router: GenericRequestRouter,
) -> Pus8Wrapper<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
> {
let action_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusAction as ChannelId,
"PUS_8_TM_SENDER",
shared_tm_store.clone(),
tm_funnel_tx.clone(),
);
let action_srv_receiver = MpscTcReceiver::new(
TcReceiverId::PusAction as ChannelId,
"PUS_8_TC_RECV",
pus_action_rx,
);
let pus_8_handler = PusService8ActionHandler::new(
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
) -> ActionServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let action_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
action_srv_receiver,
action_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
PUS_ACTION_SERVICE.id(),
pus_action_rx,
tm_sender,
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
),
ExampleActionRequestConverter::default(),
ActionRequestConverter::default(),
// TODO: Implementation which does not use run-time allocation? Maybe something like
// a bounded wrapper which pre-allocates using [HashMap::with_capacity]..
DefaultActiveActionRequestMap::default(),
ActionReplyHandler::default(),
action_router,
GenericRoutingErrorHandler::<8>::default(),
reply_receiver,
);
Pus8Wrapper { pus_8_handler }
ActionServiceWrapper {
service: action_request_handler,
}
}
pub fn create_action_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithVecMpscSender,
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
action_router: GenericRequestRouter,
) -> Pus8Wrapper<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
EcssTcInVecConverter,
VerificationReporterWithVecMpscSender,
> {
let action_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusAction as ChannelId,
"PUS_8_TM_SENDER",
tm_funnel_tx.clone(),
);
let action_srv_receiver = MpscTcReceiver::new(
TcReceiverId::PusAction as ChannelId,
"PUS_8_TC_RECV",
pus_action_rx,
);
let pus_8_handler = PusService8ActionHandler::new(
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
) -> ActionServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let action_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
action_srv_receiver,
action_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
PUS_ACTION_SERVICE.id(),
pus_action_rx,
tm_funnel_tx,
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
EcssTcInVecConverter::default(),
),
ExampleActionRequestConverter::default(),
ActionRequestConverter::default(),
DefaultActiveActionRequestMap::default(),
ActionReplyHandler::default(),
action_router,
GenericRoutingErrorHandler::<8>::default(),
reply_receiver,
);
Pus8Wrapper { pus_8_handler }
ActionServiceWrapper {
service: action_request_handler,
}
}
pub struct Pus8Wrapper<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub(crate) pus_8_handler: PusService8ActionHandler<
TcReceiver,
pub struct ActionServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub(crate) service: PusTargetedRequestService<
MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
ExampleActionRequestConverter,
GenericRequestRouter,
GenericRoutingErrorHandler<8>,
ActionRequestConverter,
ActionReplyHandler,
DefaultActiveActionRequestMap,
ActivePusActionRequestStd,
ActionRequest,
ActionReplyPus,
>,
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> Pus8Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
for ActionServiceWrapper<TmSender, TcInMemConverter>
{
pub fn handle_next_packet(&mut self) -> bool {
match self.pus_8_handler.handle_one_tc() {
Ok(result) => match result {
PusPacketHandlerResult::RequestHandled => {}
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
warn!("PUS 8 partial packet handling success: {e:?}")
}
PusPacketHandlerResult::CustomSubservice(invalid, _) => {
warn!("PUS 8 invalid subservice {invalid}");
}
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
warn!("PUS 8 subservice {subservice} not implemented");
}
PusPacketHandlerResult::Empty => {
return true;
}
},
Err(error) => {
error!("PUS packet handling error: {error:?}")
}
const SERVICE_ID: u8 = PusServiceId::Action as u8;
const SERVICE_STR: &'static str = "action";
delegate::delegate! {
to self.service {
fn poll_and_handle_next_tc(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, PusPacketHandlingError>;
fn poll_and_handle_next_reply(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, EcssTmtcError>;
fn check_for_request_timeouts(&mut self);
}
false
}
}
#[cfg(test)]
mod tests {
use satrs::pus::test_util::{
TEST_APID, TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
};
use satrs::pus::verification;
use satrs::pus::verification::test_util::TestVerificationReporter;
use satrs::request::MessageMetadata;
use satrs::ComponentId;
use satrs::{
res_code::ResultU16,
spacepackets::{
ecss::{
tc::{PusTcCreator, PusTcSecondaryHeader},
tm::PusTmReader,
WritablePusPacket,
},
SpHeader,
},
};
use crate::{
pus::tests::{PusConverterTestbench, ReplyHandlerTestbench, TargetedPusRequestTestbench},
requests::CompositeRequest,
};
use super::*;
impl
TargetedPusRequestTestbench<
ActionRequestConverter,
ActionReplyHandler,
DefaultActiveActionRequestMap,
ActivePusActionRequestStd,
ActionRequest,
ActionReplyPus,
>
{
pub fn new_for_action(owner_id: ComponentId, target_id: ComponentId) -> Self {
let _ = env_logger::builder().is_test(true).try_init();
let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
let (pus_action_tx, pus_action_rx) = mpsc::channel();
let (action_reply_tx, action_reply_rx) = mpsc::channel();
let (action_req_tx, action_req_rx) = mpsc::channel();
let verif_reporter = TestVerificationReporter::new(owner_id);
let mut generic_req_router = GenericRequestRouter::default();
generic_req_router
.composite_router_map
.insert(target_id, action_req_tx);
Self {
service: PusTargetedRequestService::new(
PusServiceHelper::new(
owner_id,
pus_action_rx,
tm_funnel_tx.clone(),
verif_reporter,
EcssTcInVecConverter::default(),
),
ActionRequestConverter::default(),
DefaultActiveActionRequestMap::default(),
ActionReplyHandler::default(),
generic_req_router,
action_reply_rx,
),
request_id: None,
pus_packet_tx: pus_action_tx,
tm_funnel_rx,
reply_tx: action_reply_tx,
request_rx: action_req_rx,
}
}
pub fn verify_packet_started(&self) {
self.service
.service_helper
.common
.verif_reporter
.check_next_is_started_success(
self.service.service_helper.id(),
self.request_id.expect("request ID not set").into(),
);
}
pub fn verify_packet_completed(&self) {
self.service
.service_helper
.common
.verif_reporter
.check_next_is_completion_success(
self.service.service_helper.id(),
self.request_id.expect("request ID not set").into(),
);
}
pub fn verify_tm_empty(&self) {
let packet = self.tm_funnel_rx.try_recv();
if let Err(mpsc::TryRecvError::Empty) = packet {
} else {
let tm = packet.unwrap();
let unexpected_tm = PusTmReader::new(&tm.packet, 7).unwrap().0;
panic!("unexpected TM packet {unexpected_tm:?}");
}
}
pub fn verify_next_tc_is_handled_properly(&mut self, time_stamp: &[u8]) {
let result = self.service.poll_and_handle_next_tc(time_stamp);
if let Err(e) = result {
panic!("unexpected error {:?}", e);
}
let result = result.unwrap();
match result {
HandlingStatus::HandledOne => (),
_ => panic!("unexpected result {result:?}"),
}
}
pub fn verify_all_tcs_handled(&mut self, time_stamp: &[u8]) {
let result = self.service.poll_and_handle_next_tc(time_stamp);
if let Err(e) = result {
panic!("unexpected error {:?}", e);
}
let result = result.unwrap();
match result {
HandlingStatus::Empty => (),
_ => panic!("unexpected result {result:?}"),
}
}
pub fn verify_next_reply_is_handled_properly(&mut self, time_stamp: &[u8]) {
let result = self.service.poll_and_handle_next_reply(time_stamp);
assert!(result.is_ok());
assert_eq!(result.unwrap(), HandlingStatus::HandledOne);
}
pub fn verify_all_replies_handled(&mut self, time_stamp: &[u8]) {
let result = self.service.poll_and_handle_next_reply(time_stamp);
assert!(result.is_ok());
assert_eq!(result.unwrap(), HandlingStatus::Empty);
}
pub fn add_tc(&mut self, tc: &PusTcCreator) {
self.request_id = Some(verification::RequestId::new(tc).into());
let token = self.service.service_helper.verif_reporter_mut().add_tc(tc);
let accepted_token = self
.service
.service_helper
.verif_reporter()
.acceptance_success(self.service.service_helper.tm_sender(), token, &[0; 7])
.expect("TC acceptance failed");
self.service
.service_helper
.verif_reporter()
.check_next_was_added(accepted_token.request_id());
let id = self.service.service_helper.id();
self.service
.service_helper
.verif_reporter()
.check_next_is_acceptance_success(id, accepted_token.request_id());
self.pus_packet_tx
.send(EcssTcAndToken::new(
PacketAsVec::new(self.service.service_helper.id(), tc.to_vec().unwrap()),
accepted_token,
))
.unwrap();
}
}
#[test]
fn basic_request() {
let mut testbench = TargetedPusRequestTestbench::new_for_action(
TEST_COMPONENT_ID_0.id(),
TEST_COMPONENT_ID_1.id(),
);
// Create a basic action request and verify forwarding.
let sp_header = SpHeader::new_from_apid(TEST_APID);
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
let action_id = 5_u32;
let mut app_data: [u8; 8] = [0; 8];
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_1.to_be_bytes());
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
let pus8_packet = PusTcCreator::new(sp_header, sec_header, &app_data, true);
testbench.add_tc(&pus8_packet);
let time_stamp: [u8; 7] = [0; 7];
testbench.verify_next_tc_is_handled_properly(&time_stamp);
testbench.verify_all_tcs_handled(&time_stamp);
testbench.verify_packet_started();
let possible_req = testbench.request_rx.try_recv();
assert!(possible_req.is_ok());
let req = possible_req.unwrap();
if let CompositeRequest::Action(action_req) = req.message {
assert_eq!(action_req.action_id, action_id);
assert_eq!(action_req.variant, ActionRequestVariant::NoData);
let action_reply = ActionReplyPus::new(action_id, ActionReplyVariant::Completed);
testbench
.reply_tx
.send(GenericMessage::new(req.requestor_info, action_reply))
.unwrap();
} else {
panic!("unexpected request type");
}
testbench.verify_next_reply_is_handled_properly(&time_stamp);
testbench.verify_all_replies_handled(&time_stamp);
testbench.verify_packet_completed();
testbench.verify_tm_empty();
}
#[test]
fn basic_request_routing_error() {
let mut testbench = TargetedPusRequestTestbench::new_for_action(
TEST_COMPONENT_ID_0.id(),
TEST_COMPONENT_ID_1.id(),
);
// Create a basic action request and verify forwarding.
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
let action_id = 5_u32;
let mut app_data: [u8; 8] = [0; 8];
// Invalid ID, routing should fail.
app_data[0..4].copy_from_slice(&0_u32.to_be_bytes());
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
let pus8_packet = PusTcCreator::new(
SpHeader::new_from_apid(TEST_APID),
sec_header,
&app_data,
true,
);
testbench.add_tc(&pus8_packet);
let time_stamp: [u8; 7] = [0; 7];
let result = testbench.service.poll_and_handle_next_tc(&time_stamp);
assert!(result.is_err());
// Verify the correct result and completion failure.
}
#[test]
fn converter_action_req_no_data() {
let mut testbench = PusConverterTestbench::new(
TEST_COMPONENT_ID_0.raw(),
ActionRequestConverter::default(),
);
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
let action_id = 5_u32;
let mut app_data: [u8; 8] = [0; 8];
// Invalid ID, routing should fail.
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
let pus8_packet = PusTcCreator::new(
SpHeader::new_from_apid(TEST_APID),
sec_header,
&app_data,
true,
);
let token = testbench.add_tc(&pus8_packet);
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
assert!(result.is_ok());
let (active_req, request) = result.unwrap();
if let ActionRequestVariant::NoData = request.variant {
assert_eq!(request.action_id, action_id);
assert_eq!(active_req.action_id, action_id);
assert_eq!(
active_req.target_id(),
UniqueApidTargetId::new(TEST_APID, TEST_UNIQUE_ID_0).raw()
);
assert_eq!(
active_req.token().request_id(),
testbench.request_id().unwrap()
);
} else {
panic!("unexpected action request variant");
}
}
#[test]
fn converter_action_req_with_data() {
let mut testbench =
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ActionRequestConverter::default());
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
let action_id = 5_u32;
let mut app_data: [u8; 16] = [0; 16];
// Invalid ID, routing should fail.
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
for i in 0..8 {
app_data[i + 8] = i as u8;
}
let pus8_packet = PusTcCreator::new(
SpHeader::new_from_apid(TEST_APID),
sec_header,
&app_data,
true,
);
let token = testbench.add_tc(&pus8_packet);
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
assert!(result.is_ok());
let (active_req, request) = result.unwrap();
if let ActionRequestVariant::VecData(vec) = request.variant {
assert_eq!(request.action_id, action_id);
assert_eq!(active_req.action_id, action_id);
assert_eq!(vec, app_data[8..].to_vec());
} else {
panic!("unexpected action request variant");
}
}
#[test]
fn reply_handling_completion_success() {
let mut testbench =
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
let action_id = 5_u32;
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
let active_action_req =
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
let reply = ActionReplyPus::new(action_id, ActionReplyVariant::Completed);
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
assert!(result.is_ok());
assert!(result.unwrap());
testbench.verif_reporter.assert_full_completion_success(
TEST_COMPONENT_ID_0.id(),
req_id,
None,
);
}
#[test]
fn reply_handling_completion_failure() {
let mut testbench =
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
let action_id = 5_u32;
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
let active_action_req =
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
let error_code = ResultU16::new(2, 3);
let reply = ActionReplyPus::new(
action_id,
ActionReplyVariant::CompletionFailed {
error_code,
params: None,
},
);
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
assert!(result.is_ok());
assert!(result.unwrap());
testbench.verif_reporter.assert_completion_failure(
TEST_COMPONENT_ID_0.into(),
req_id,
None,
error_code.raw() as u64,
);
}
#[test]
fn reply_handling_step_success() {
let mut testbench =
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
let action_id = 5_u32;
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
let active_action_req =
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
let reply = ActionReplyPus::new(action_id, ActionReplyVariant::StepSuccess { step: 1 });
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
assert!(result.is_ok());
// Entry should not be removed, completion not done yet.
assert!(!result.unwrap());
testbench.verif_reporter.check_next_was_added(req_id);
testbench
.verif_reporter
.check_next_is_acceptance_success(TEST_COMPONENT_ID_0.raw(), req_id);
testbench
.verif_reporter
.check_next_is_started_success(TEST_COMPONENT_ID_0.raw(), req_id);
testbench
.verif_reporter
.check_next_is_step_success(TEST_COMPONENT_ID_0.raw(), req_id, 1);
}
#[test]
fn reply_handling_step_failure() {
let mut testbench =
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
let action_id = 5_u32;
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
let active_action_req =
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
let error_code = ResultU16::new(2, 3);
let reply = ActionReplyPus::new(
action_id,
ActionReplyVariant::StepFailed {
error_code,
step: 1,
params: None,
},
);
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
assert!(result.is_ok());
assert!(result.unwrap());
testbench.verif_reporter.check_next_was_added(req_id);
testbench
.verif_reporter
.check_next_is_acceptance_success(TEST_COMPONENT_ID_0.id(), req_id);
testbench
.verif_reporter
.check_next_is_started_success(TEST_COMPONENT_ID_0.id(), req_id);
testbench.verif_reporter.check_next_is_step_failure(
TEST_COMPONENT_ID_0.id(),
req_id,
error_code.raw().into(),
);
}
#[test]
fn reply_handling_unrequested_reply() {
let mut testbench =
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
let action_reply = ActionReplyPus::new(5_u32, ActionReplyVariant::Completed);
let unrequested_reply =
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), action_reply);
// Right now this function does not do a lot. We simply check that it does not panic or do
// weird stuff.
let result = testbench.handle_unrequested_reply(&unrequested_reply);
assert!(result.is_ok());
}
#[test]
fn reply_handling_reply_timeout() {
let mut testbench =
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
let action_id = 5_u32;
let (req_id, active_request) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
let result = testbench.handle_request_timeout(
&ActivePusActionRequestStd::new_from_common_req(action_id, active_request),
&[],
);
assert!(result.is_ok());
testbench.verif_reporter.assert_completion_failure(
TEST_COMPONENT_ID_0.raw(),
req_id,
None,
tmtc_err::REQUEST_TIMEOUT.raw() as u64,
);
}
}

View File

@ -1,132 +1,115 @@
use std::sync::mpsc;
use log::{error, warn};
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
use crate::pus::create_verification_reporter;
use satrs::pool::SharedStaticMemoryPool;
use satrs::pus::event_man::EventRequestWithToken;
use satrs::pus::event_srv::PusService5EventHandler;
use satrs::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::verification::VerificationReportingProvider;
use satrs::pus::event_srv::PusEventServiceHandler;
use satrs::pus::verification::VerificationReporter;
use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiverCore, EcssTmSenderCore, MpscTcReceiver, PusPacketHandlerResult, PusServiceHelper,
TmAsVecSenderWithId, TmAsVecSenderWithMpsc, TmInSharedPoolSenderWithBoundedMpsc,
TmInSharedPoolSenderWithId,
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper,
};
use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::ChannelId;
use satrs_example::config::{TcReceiverId, TmSenderId, PUS_APID};
use satrs::spacepackets::ecss::PusServiceId;
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs_example::config::components::PUS_EVENT_MANAGEMENT;
use super::{DirectPusService, HandlingStatus};
pub fn create_event_service_static(
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
event_request_tx: mpsc::Sender<EventRequestWithToken>,
) -> Pus5Wrapper<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
> {
let event_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusEvent as ChannelId,
"PUS_5_TM_SENDER",
shared_tm_store.clone(),
tm_funnel_tx.clone(),
);
let event_srv_receiver = MpscTcReceiver::new(
TcReceiverId::PusEvent as ChannelId,
"PUS_5_TC_RECV",
pus_event_rx,
);
let pus_5_handler = PusService5EventHandler::new(
) -> EventServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let pus_5_handler = PusEventServiceHandler::new(
PusServiceHelper::new(
event_srv_receiver,
event_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
PUS_EVENT_MANAGEMENT.id(),
pus_event_rx,
tm_sender,
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048),
),
event_request_tx,
);
Pus5Wrapper { pus_5_handler }
EventServiceWrapper {
handler: pus_5_handler,
}
}
pub fn create_event_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithVecMpscSender,
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_event_rx: mpsc::Receiver<EcssTcAndToken>,
event_request_tx: mpsc::Sender<EventRequestWithToken>,
) -> Pus5Wrapper<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
EcssTcInVecConverter,
VerificationReporterWithVecMpscSender,
> {
let event_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusEvent as ChannelId,
"PUS_5_TM_SENDER",
tm_funnel_tx,
);
let event_srv_receiver = MpscTcReceiver::new(
TcReceiverId::PusEvent as ChannelId,
"PUS_5_TC_RECV",
pus_event_rx,
);
let pus_5_handler = PusService5EventHandler::new(
) -> EventServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let pus_5_handler = PusEventServiceHandler::new(
PusServiceHelper::new(
event_srv_receiver,
event_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
PUS_EVENT_MANAGEMENT.id(),
pus_event_rx,
tm_funnel_tx,
create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid),
EcssTcInVecConverter::default(),
),
event_request_tx,
);
Pus5Wrapper { pus_5_handler }
}
pub struct Pus5Wrapper<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub pus_5_handler:
PusService5EventHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> Pus5Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
{
pub fn handle_next_packet(&mut self) -> bool {
match self.pus_5_handler.handle_one_tc() {
Ok(result) => match result {
PusPacketHandlerResult::RequestHandled => {}
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
warn!("PUS 5 partial packet handling success: {e:?}")
}
PusPacketHandlerResult::CustomSubservice(invalid, _) => {
warn!("PUS 5 invalid subservice {invalid}");
}
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
warn!("PUS 5 subservice {subservice} not implemented");
}
PusPacketHandlerResult::Empty => {
return true;
}
},
Err(error) => {
error!("PUS packet handling error: {error:?}")
}
}
false
EventServiceWrapper {
handler: pus_5_handler,
}
}
pub struct EventServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub handler:
PusEventServiceHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
for EventServiceWrapper<TmSender, TcInMemConverter>
{
const SERVICE_ID: u8 = PusServiceId::Event as u8;
const SERVICE_STR: &'static str = "events";
fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus {
let error_handler = |partial_error: &PartialPusHandlingError| {
log::warn!(
"PUS {}({}) partial error: {:?}",
Self::SERVICE_ID,
Self::SERVICE_STR,
partial_error
);
};
let result = self
.handler
.poll_and_handle_next_tc(error_handler, time_stamp);
if let Err(e) = result {
log::warn!(
"PUS {}({}) error: {:?}",
Self::SERVICE_ID,
Self::SERVICE_STR,
e
);
// To avoid permanent loops on continuous errors.
return HandlingStatus::Empty;
}
match result.unwrap() {
DirectPusPacketHandlerResult::Handled(handling_status) => return handling_status,
DirectPusPacketHandlerResult::CustomSubservice(subservice, _) => {
log::warn!(
"PUS {}({}) subservice {} not implemented",
Self::SERVICE_ID,
Self::SERVICE_STR,
subservice
);
}
DirectPusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
log::warn!(
"PUS {}({}) subservice {} not implemented",
Self::SERVICE_ID,
Self::SERVICE_STR,
subservice
);
}
}
HandlingStatus::HandledOne
}
}

View File

@ -1,50 +1,126 @@
use log::{error, warn};
use satrs::hk::{CollectionIntervalFactor, HkRequest};
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
use satrs::pus::hk::{PusHkToRequestConverter, PusService3HkHandler};
use satrs::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use derive_new::new;
use satrs::hk::{CollectionIntervalFactor, HkRequest, HkRequestVariant, UniqueId};
use satrs::pool::SharedStaticMemoryPool;
use satrs::pus::verification::{
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
VerificationReportingProvider, VerificationToken,
};
use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiverCore, EcssTmSenderCore, MpscTcReceiver, PusPacketHandlerResult,
PusPacketHandlingError, PusServiceHelper, TmAsVecSenderWithId, TmAsVecSenderWithMpsc,
TmInSharedPoolSenderWithBoundedMpsc, TmInSharedPoolSenderWithId,
ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap, EcssTcAndToken,
EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender,
EcssTmtcError, GenericConversionError, MpscTcReceiver, MpscTmAsVecSender,
PusPacketHandlingError, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter,
};
use satrs::request::TargetAndApidId;
use satrs::request::{GenericMessage, UniqueApidTargetId};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::{hk, PusPacket};
use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::{ChannelId, TargetId};
use satrs_example::config::{hk_err, tmtc_err, TcReceiverId, TmSenderId, PUS_APID};
use std::sync::mpsc::{self};
use satrs::spacepackets::ecss::{hk, PusPacket, PusServiceId};
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs_example::config::components::PUS_HK_SERVICE;
use satrs_example::config::{hk_err, tmtc_err};
use std::sync::mpsc;
use std::time::Duration;
use crate::pus::{create_verification_reporter, generic_pus_request_timeout_handler};
use crate::requests::GenericRequestRouter;
use super::GenericRoutingErrorHandler;
use super::{HandlingStatus, PusTargetedRequestService, TargetedPusService};
#[derive(Clone, PartialEq, Debug, new)]
pub struct HkReply {
pub unique_id: UniqueId,
pub variant: HkReplyVariant,
}
#[derive(Clone, PartialEq, Debug)]
pub enum HkReplyVariant {
Ack,
}
#[derive(Default)]
pub struct ExampleHkRequestConverter {}
pub struct HkReplyHandler {}
impl PusHkToRequestConverter for ExampleHkRequestConverter {
type Error = PusPacketHandlingError;
impl PusReplyHandler<ActivePusRequestStd, HkReply> for HkReplyHandler {
type Error = EcssTmtcError;
fn handle_unrequested_reply(
&mut self,
reply: &GenericMessage<HkReply>,
_tm_sender: &impl EcssTmSender,
) -> Result<(), Self::Error> {
log::warn!("received unexpected reply for service 3: {reply:?}");
Ok(())
}
fn handle_reply(
&mut self,
reply: &GenericMessage<HkReply>,
active_request: &ActivePusRequestStd,
tm_sender: &impl EcssTmSender,
verification_handler: &impl VerificationReportingProvider,
time_stamp: &[u8],
) -> Result<bool, Self::Error> {
let started_token: VerificationToken<TcStateStarted> = active_request
.token()
.try_into()
.expect("invalid token state");
match reply.message.variant {
HkReplyVariant::Ack => {
verification_handler
.completion_success(tm_sender, started_token, time_stamp)
.expect("sending completion success verification failed");
}
};
Ok(true)
}
fn handle_request_timeout(
&mut self,
active_request: &ActivePusRequestStd,
tm_sender: &impl EcssTmSender,
verification_handler: &impl VerificationReportingProvider,
time_stamp: &[u8],
) -> Result<(), Self::Error> {
generic_pus_request_timeout_handler(
tm_sender,
active_request,
verification_handler,
time_stamp,
"HK",
)?;
Ok(())
}
}
pub struct HkRequestConverter {
timeout: Duration,
}
impl Default for HkRequestConverter {
fn default() -> Self {
Self {
timeout: Duration::from_secs(60),
}
}
}
impl PusTcToRequestConverter<ActivePusRequestStd, HkRequest> for HkRequestConverter {
type Error = GenericConversionError;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
tm_sender: &(impl EcssTmSender + ?Sized),
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, HkRequest), Self::Error> {
time_stamp: &[u8],
) -> Result<(ActivePusRequestStd, HkRequest), Self::Error> {
let user_data = tc.user_data();
if user_data.is_empty() {
let user_data_len = user_data.len() as u32;
let user_data_len_raw = user_data_len.to_be_bytes();
verif_reporter
.start_failure(
tm_sender,
token,
FailParams::new(
time_stamp,
@ -53,7 +129,7 @@ impl PusHkToRequestConverter for ExampleHkRequestConverter {
),
)
.expect("Sending start failure TM failed");
return Err(PusPacketHandlingError::NotEnoughAppData {
return Err(GenericConversionError::NotEnoughAppData {
expected: 4,
found: 0,
});
@ -67,197 +143,402 @@ impl PusHkToRequestConverter for ExampleHkRequestConverter {
let user_data_len = user_data.len() as u32;
let user_data_len_raw = user_data_len.to_be_bytes();
verif_reporter
.start_failure(token, FailParams::new(time_stamp, err, &user_data_len_raw))
.start_failure(
tm_sender,
token,
FailParams::new(time_stamp, err, &user_data_len_raw),
)
.expect("Sending start failure TM failed");
return Err(PusPacketHandlingError::NotEnoughAppData {
return Err(GenericConversionError::NotEnoughAppData {
expected: 8,
found: 4,
});
}
let subservice = tc.subservice();
let target_id = TargetAndApidId::from_pus_tc(tc).expect("invalid tc format");
let target_id_and_apid = UniqueApidTargetId::from_pus_tc(tc).expect("invalid tc format");
let unique_id = u32::from_be_bytes(tc.user_data()[4..8].try_into().unwrap());
let standard_subservice = hk::Subservice::try_from(subservice);
if standard_subservice.is_err() {
verif_reporter
.start_failure(
tm_sender,
token,
FailParams::new(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE, &[subservice]),
)
.expect("Sending start failure TM failed");
return Err(PusPacketHandlingError::InvalidSubservice(subservice));
return Err(GenericConversionError::InvalidSubservice(subservice));
}
Ok((
target_id.into(),
match standard_subservice.unwrap() {
hk::Subservice::TcEnableHkGeneration | hk::Subservice::TcEnableDiagGeneration => {
HkRequest::Enable(unique_id)
}
hk::Subservice::TcDisableHkGeneration | hk::Subservice::TcDisableDiagGeneration => {
HkRequest::Disable(unique_id)
}
hk::Subservice::TcReportHkReportStructures => todo!(),
hk::Subservice::TmHkPacket => todo!(),
hk::Subservice::TcGenerateOneShotHk | hk::Subservice::TcGenerateOneShotDiag => {
HkRequest::OneShot(unique_id)
}
hk::Subservice::TcModifyDiagCollectionInterval
| hk::Subservice::TcModifyHkCollectionInterval => {
if user_data.len() < 12 {
verif_reporter
.start_failure(
token,
FailParams::new_no_fail_data(
time_stamp,
&tmtc_err::NOT_ENOUGH_APP_DATA,
),
)
.expect("Sending start failure TM failed");
return Err(PusPacketHandlingError::NotEnoughAppData {
expected: 12,
found: user_data.len(),
});
}
HkRequest::ModifyCollectionInterval(
unique_id,
CollectionIntervalFactor::from_be_bytes(
user_data[8..12].try_into().unwrap(),
),
)
}
_ => {
let request = match standard_subservice.unwrap() {
hk::Subservice::TcEnableHkGeneration | hk::Subservice::TcEnableDiagGeneration => {
HkRequest::new(unique_id, HkRequestVariant::EnablePeriodic)
}
hk::Subservice::TcDisableHkGeneration | hk::Subservice::TcDisableDiagGeneration => {
HkRequest::new(unique_id, HkRequestVariant::DisablePeriodic)
}
hk::Subservice::TcReportHkReportStructures => todo!(),
hk::Subservice::TmHkPacket => todo!(),
hk::Subservice::TcGenerateOneShotHk | hk::Subservice::TcGenerateOneShotDiag => {
HkRequest::new(unique_id, HkRequestVariant::OneShot)
}
hk::Subservice::TcModifyDiagCollectionInterval
| hk::Subservice::TcModifyHkCollectionInterval => {
if user_data.len() < 12 {
verif_reporter
.start_failure(
tm_sender,
token,
FailParams::new(
FailParams::new_no_fail_data(
time_stamp,
&tmtc_err::PUS_SUBSERVICE_NOT_IMPLEMENTED,
&[subservice],
&tmtc_err::NOT_ENOUGH_APP_DATA,
),
)
.expect("Sending start failure TM failed");
return Err(PusPacketHandlingError::InvalidSubservice(subservice));
return Err(GenericConversionError::NotEnoughAppData {
expected: 12,
found: user_data.len(),
});
}
},
HkRequest::new(
unique_id,
HkRequestVariant::ModifyCollectionInterval(
CollectionIntervalFactor::from_be_bytes(
user_data[8..12].try_into().unwrap(),
),
),
)
}
_ => {
verif_reporter
.start_failure(
tm_sender,
token,
FailParams::new(
time_stamp,
&tmtc_err::PUS_SUBSERVICE_NOT_IMPLEMENTED,
&[subservice],
),
)
.expect("Sending start failure TM failed");
return Err(GenericConversionError::InvalidSubservice(subservice));
}
};
Ok((
ActivePusRequestStd::new(target_id_and_apid.into(), token, self.timeout),
request,
))
}
}
pub fn create_hk_service_static(
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
request_router: GenericRequestRouter,
) -> Pus3Wrapper<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
> {
let hk_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusHk as ChannelId,
"PUS_3_TM_SENDER",
shared_tm_store.clone(),
tm_funnel_tx.clone(),
);
let hk_srv_receiver =
MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx);
let pus_3_handler = PusService3HkHandler::new(
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
) -> HkServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let pus_3_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
hk_srv_receiver,
hk_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
PUS_HK_SERVICE.id(),
pus_hk_rx,
tm_sender,
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
),
ExampleHkRequestConverter::default(),
HkRequestConverter::default(),
DefaultActiveRequestMap::default(),
HkReplyHandler::default(),
request_router,
GenericRoutingErrorHandler::default(),
reply_receiver,
);
Pus3Wrapper { pus_3_handler }
HkServiceWrapper {
service: pus_3_handler,
}
}
pub fn create_hk_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithVecMpscSender,
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_hk_rx: mpsc::Receiver<EcssTcAndToken>,
request_router: GenericRequestRouter,
) -> Pus3Wrapper<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
EcssTcInVecConverter,
VerificationReporterWithVecMpscSender,
> {
let hk_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusHk as ChannelId,
"PUS_3_TM_SENDER",
tm_funnel_tx.clone(),
);
let hk_srv_receiver =
MpscTcReceiver::new(TcReceiverId::PusHk as ChannelId, "PUS_8_TC_RECV", pus_hk_rx);
let pus_3_handler = PusService3HkHandler::new(
reply_receiver: mpsc::Receiver<GenericMessage<HkReply>>,
) -> HkServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let pus_3_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
hk_srv_receiver,
hk_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
PUS_HK_SERVICE.id(),
pus_hk_rx,
tm_funnel_tx,
create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid),
EcssTcInVecConverter::default(),
),
ExampleHkRequestConverter::default(),
HkRequestConverter::default(),
DefaultActiveRequestMap::default(),
HkReplyHandler::default(),
request_router,
GenericRoutingErrorHandler::default(),
reply_receiver,
);
Pus3Wrapper { pus_3_handler }
HkServiceWrapper {
service: pus_3_handler,
}
}
pub struct Pus3Wrapper<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub(crate) pus_3_handler: PusService3HkHandler<
TcReceiver,
pub struct HkServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub(crate) service: PusTargetedRequestService<
MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
ExampleHkRequestConverter,
GenericRequestRouter,
GenericRoutingErrorHandler<3>,
HkRequestConverter,
HkReplyHandler,
DefaultActiveRequestMap<ActivePusRequestStd>,
ActivePusRequestStd,
HkRequest,
HkReply,
>,
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> Pus3Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
for HkServiceWrapper<TmSender, TcInMemConverter>
{
pub fn handle_next_packet(&mut self) -> bool {
match self.pus_3_handler.handle_one_tc() {
Ok(result) => match result {
PusPacketHandlerResult::RequestHandled => {}
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
warn!("PUS 3 partial packet handling success: {e:?}")
}
PusPacketHandlerResult::CustomSubservice(invalid, _) => {
warn!("PUS 3 invalid subservice {invalid}");
}
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
warn!("PUS 3 subservice {subservice} not implemented");
}
PusPacketHandlerResult::Empty => {
return true;
}
},
Err(error) => {
error!("PUS packet handling error: {error:?}")
}
const SERVICE_ID: u8 = PusServiceId::Housekeeping as u8;
const SERVICE_STR: &'static str = "housekeeping";
delegate::delegate! {
to self.service {
fn poll_and_handle_next_tc(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, PusPacketHandlingError>;
fn poll_and_handle_next_reply(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, EcssTmtcError>;
fn check_for_request_timeouts(&mut self);
}
false
}
}
#[cfg(test)]
mod tests {
use satrs::pus::test_util::{
TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
};
use satrs::request::MessageMetadata;
use satrs::{
hk::HkRequestVariant,
pus::test_util::TEST_APID,
request::GenericMessage,
spacepackets::{
ecss::{hk::Subservice, tc::PusTcCreator},
SpHeader,
},
};
use satrs_example::config::tmtc_err;
use crate::pus::{
hk::HkReplyVariant,
tests::{PusConverterTestbench, ReplyHandlerTestbench},
};
use super::{HkReply, HkReplyHandler, HkRequestConverter};
#[test]
fn hk_converter_one_shot_req() {
let mut hk_bench =
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let target_id = TEST_UNIQUE_ID_0;
let unique_id = 5_u32;
let mut app_data: [u8; 8] = [0; 8];
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
let hk_req = PusTcCreator::new_simple(
sp_header,
3,
Subservice::TcGenerateOneShotHk as u8,
&app_data,
true,
);
let accepted_token = hk_bench.add_tc(&hk_req);
let (_active_req, req) = hk_bench
.convert(accepted_token, &[], TEST_APID, TEST_UNIQUE_ID_0)
.expect("conversion failed");
assert_eq!(req.unique_id, unique_id);
if let HkRequestVariant::OneShot = req.variant {
} else {
panic!("unexpected HK request")
}
}
#[test]
fn hk_converter_enable_periodic_generation() {
let mut hk_bench =
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let target_id = TEST_UNIQUE_ID_0;
let unique_id = 5_u32;
let mut app_data: [u8; 8] = [0; 8];
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
let mut generic_check = |tc: &PusTcCreator| {
let accepted_token = hk_bench.add_tc(tc);
let (_active_req, req) = hk_bench
.convert(accepted_token, &[], TEST_APID, TEST_UNIQUE_ID_0)
.expect("conversion failed");
assert_eq!(req.unique_id, unique_id);
if let HkRequestVariant::EnablePeriodic = req.variant {
} else {
panic!("unexpected HK request")
}
};
let tc0 = PusTcCreator::new_simple(
sp_header,
3,
Subservice::TcEnableHkGeneration as u8,
&app_data,
true,
);
generic_check(&tc0);
let tc1 = PusTcCreator::new_simple(
sp_header,
3,
Subservice::TcEnableDiagGeneration as u8,
&app_data,
true,
);
generic_check(&tc1);
}
#[test]
fn hk_conversion_disable_periodic_generation() {
let mut hk_bench =
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let target_id = TEST_UNIQUE_ID_0;
let unique_id = 5_u32;
let mut app_data: [u8; 8] = [0; 8];
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
let mut generic_check = |tc: &PusTcCreator| {
let accepted_token = hk_bench.add_tc(tc);
let (_active_req, req) = hk_bench
.convert(accepted_token, &[], TEST_APID, TEST_UNIQUE_ID_0)
.expect("conversion failed");
assert_eq!(req.unique_id, unique_id);
if let HkRequestVariant::DisablePeriodic = req.variant {
} else {
panic!("unexpected HK request")
}
};
let tc0 = PusTcCreator::new_simple(
sp_header,
3,
Subservice::TcDisableHkGeneration as u8,
&app_data,
true,
);
generic_check(&tc0);
let tc1 = PusTcCreator::new_simple(
sp_header,
3,
Subservice::TcDisableDiagGeneration as u8,
&app_data,
true,
);
generic_check(&tc1);
}
#[test]
fn hk_conversion_modify_interval() {
let mut hk_bench =
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), HkRequestConverter::default());
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let target_id = TEST_UNIQUE_ID_0;
let unique_id = 5_u32;
let mut app_data: [u8; 12] = [0; 12];
let collection_interval_factor = 5_u32;
app_data[0..4].copy_from_slice(&target_id.to_be_bytes());
app_data[4..8].copy_from_slice(&unique_id.to_be_bytes());
app_data[8..12].copy_from_slice(&collection_interval_factor.to_be_bytes());
let mut generic_check = |tc: &PusTcCreator| {
let accepted_token = hk_bench.add_tc(tc);
let (_active_req, req) = hk_bench
.convert(accepted_token, &[], TEST_APID, TEST_UNIQUE_ID_0)
.expect("conversion failed");
assert_eq!(req.unique_id, unique_id);
if let HkRequestVariant::ModifyCollectionInterval(interval_factor) = req.variant {
assert_eq!(interval_factor, collection_interval_factor);
} else {
panic!("unexpected HK request")
}
};
let tc0 = PusTcCreator::new_simple(
sp_header,
3,
Subservice::TcModifyHkCollectionInterval as u8,
&app_data,
true,
);
generic_check(&tc0);
let tc1 = PusTcCreator::new_simple(
sp_header,
3,
Subservice::TcModifyDiagCollectionInterval as u8,
&app_data,
true,
);
generic_check(&tc1);
}
#[test]
fn hk_reply_handler() {
let mut reply_testbench =
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), HkReplyHandler::default());
let sender_id = 2_u64;
let apid_target_id = 3_u32;
let unique_id = 5_u32;
let (req_id, active_req) = reply_testbench.add_tc(TEST_APID, apid_target_id, &[]);
let reply = GenericMessage::new(
MessageMetadata::new(req_id.into(), sender_id),
HkReply::new(unique_id, HkReplyVariant::Ack),
);
let result = reply_testbench.handle_reply(&reply, &active_req, &[]);
assert!(result.is_ok());
assert!(result.unwrap());
reply_testbench
.verif_reporter
.assert_full_completion_success(TEST_COMPONENT_ID_0.raw(), req_id, None);
}
#[test]
fn reply_handling_unrequested_reply() {
let mut testbench =
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_1.id(), HkReplyHandler::default());
let action_reply = HkReply::new(5_u32, HkReplyVariant::Ack);
let unrequested_reply =
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), action_reply);
// Right now this function does not do a lot. We simply check that it does not panic or do
// weird stuff.
let result = testbench.handle_unrequested_reply(&unrequested_reply);
assert!(result.is_ok());
}
#[test]
fn reply_handling_reply_timeout() {
let mut testbench =
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_1.id(), HkReplyHandler::default());
let (req_id, active_request) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_1, &[]);
let result = testbench.handle_request_timeout(&active_request, &[]);
assert!(result.is_ok());
testbench.verif_reporter.assert_completion_failure(
TEST_COMPONENT_ID_1.raw(),
req_id,
None,
tmtc_err::REQUEST_TIMEOUT.raw() as u64,
);
}
}

View File

@ -1,120 +1,149 @@
use crate::tmtc::MpscStoreAndSendError;
use crate::requests::GenericRequestRouter;
use log::warn;
use satrs::pus::verification::{FailParams, VerificationReportingProvider};
use satrs::pus::{
EcssTcAndToken, GenericRoutingError, PusPacketHandlerResult, PusRoutingErrorHandler, TcInMemory,
use satrs::pool::PoolAddr;
use satrs::pus::verification::{
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReporter,
VerificationReporterCfg, VerificationReportingProvider, VerificationToken,
};
use satrs::pus::{
ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter,
EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError,
HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, PusServiceHelper,
PusTcToRequestConverter, TcInMemory,
};
use satrs::queue::{GenericReceiveError, GenericSendError};
use satrs::request::{Apid, GenericMessage, MessageMetadata};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusServiceId;
use satrs::spacepackets::time::cds::TimeProvider;
use satrs::spacepackets::time::TimeWriter;
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
use satrs::tmtc::{PacketAsVec, PacketInPool};
use satrs::ComponentId;
use satrs_example::config::components::PUS_ROUTING_SERVICE;
use satrs_example::config::{tmtc_err, CustomPusServiceId};
use std::sync::mpsc::Sender;
use satrs_example::TimeStampHelper;
use std::fmt::Debug;
use std::sync::mpsc::{self, Sender};
pub mod action;
pub mod event;
pub mod hk;
pub mod mode;
pub mod scheduler;
pub mod stack;
pub mod test;
pub struct PusTcMpscRouter {
pub test_service_receiver: Sender<EcssTcAndToken>,
pub event_service_receiver: Sender<EcssTcAndToken>,
pub sched_service_receiver: Sender<EcssTcAndToken>,
pub hk_service_receiver: Sender<EcssTcAndToken>,
pub action_service_receiver: Sender<EcssTcAndToken>,
pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> VerificationReporter {
let verif_cfg = VerificationReporterCfg::new(apid, 1, 2, 8).unwrap();
// Every software component which needs to generate verification telemetry, gets a cloned
// verification reporter.
VerificationReporter::new(owner_id, &verif_cfg)
}
pub struct PusReceiver<VerificationReporter: VerificationReportingProvider> {
/// Simple router structure which forwards PUS telecommands to dedicated handlers.
pub struct PusTcMpscRouter {
pub test_tc_sender: Sender<EcssTcAndToken>,
pub event_tc_sender: Sender<EcssTcAndToken>,
pub sched_tc_sender: Sender<EcssTcAndToken>,
pub hk_tc_sender: Sender<EcssTcAndToken>,
pub action_tc_sender: Sender<EcssTcAndToken>,
pub mode_tc_sender: Sender<EcssTcAndToken>,
}
pub struct PusTcDistributor<TmSender: EcssTmSender> {
pub id: ComponentId,
pub tm_sender: TmSender,
pub verif_reporter: VerificationReporter,
pub pus_router: PusTcMpscRouter,
stamp_helper: TimeStampHelper,
}
struct TimeStampHelper {
stamper: TimeProvider,
time_stamp: [u8; 7],
}
impl TimeStampHelper {
pub fn new() -> Self {
impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self {
Self {
stamper: TimeProvider::new_with_u16_days(0, 0),
time_stamp: [0; 7],
}
}
pub fn stamp(&self) -> &[u8] {
&self.time_stamp
}
pub fn update_from_now(&mut self) {
self.stamper
.update_from_now()
.expect("Updating timestamp failed");
self.stamper
.write_to_bytes(&mut self.time_stamp)
.expect("Writing timestamp failed");
}
}
impl<VerificationReporter: VerificationReportingProvider> PusReceiver<VerificationReporter> {
pub fn new(verif_reporter: VerificationReporter, pus_router: PusTcMpscRouter) -> Self {
Self {
verif_reporter,
id: PUS_ROUTING_SERVICE.raw(),
tm_sender,
verif_reporter: create_verification_reporter(
PUS_ROUTING_SERVICE.id(),
PUS_ROUTING_SERVICE.apid,
),
pus_router,
stamp_helper: TimeStampHelper::new(),
stamp_helper: TimeStampHelper::default(),
}
}
}
impl<VerificationReporter: VerificationReportingProvider> PusReceiver<VerificationReporter> {
pub fn handle_tc_packet(
pub fn handle_tc_packet_vec(
&mut self,
tc_in_memory: TcInMemory,
service: u8,
pus_tc: &PusTcReader,
) -> Result<PusPacketHandlerResult, MpscStoreAndSendError> {
let init_token = self.verif_reporter.add_tc(pus_tc);
packet_as_vec: PacketAsVec,
) -> Result<HandlingStatus, GenericSendError> {
self.handle_tc_generic(packet_as_vec.sender_id, None, &packet_as_vec.packet)
}
pub fn handle_tc_packet_in_store(
&mut self,
packet_in_pool: PacketInPool,
pus_tc_copy: &[u8],
) -> Result<HandlingStatus, GenericSendError> {
self.handle_tc_generic(
packet_in_pool.sender_id,
Some(packet_in_pool.store_addr),
pus_tc_copy,
)
}
pub fn handle_tc_generic(
&mut self,
sender_id: ComponentId,
addr_opt: Option<PoolAddr>,
raw_tc: &[u8],
) -> Result<HandlingStatus, GenericSendError> {
let pus_tc_result = PusTcReader::new(raw_tc);
if pus_tc_result.is_err() {
log::warn!(
"error creating PUS TC from raw data received from {}: {}",
sender_id,
pus_tc_result.unwrap_err()
);
log::warn!("raw data: {:x?}", raw_tc);
// TODO: Shouldn't this be an error?
return Ok(HandlingStatus::HandledOne);
}
let pus_tc = pus_tc_result.unwrap().0;
let init_token = self.verif_reporter.add_tc(&pus_tc);
self.stamp_helper.update_from_now();
let accepted_token = self
.verif_reporter
.acceptance_success(init_token, self.stamp_helper.stamp())
.acceptance_success(&self.tm_sender, init_token, self.stamp_helper.stamp())
.expect("Acceptance success failure");
let service = PusServiceId::try_from(service);
let service = PusServiceId::try_from(pus_tc.service());
let tc_in_memory: TcInMemory = if let Some(store_addr) = addr_opt {
PacketInPool::new(sender_id, store_addr).into()
} else {
PacketAsVec::new(sender_id, Vec::from(raw_tc)).into()
};
match service {
Ok(standard_service) => match standard_service {
PusServiceId::Test => {
self.pus_router.test_service_receiver.send(EcssTcAndToken {
tc_in_memory,
token: Some(accepted_token.into()),
})?
}
PusServiceId::Test => self.pus_router.test_tc_sender.send(EcssTcAndToken {
tc_in_memory,
token: Some(accepted_token.into()),
})?,
PusServiceId::Housekeeping => {
self.pus_router.hk_service_receiver.send(EcssTcAndToken {
self.pus_router.hk_tc_sender.send(EcssTcAndToken {
tc_in_memory,
token: Some(accepted_token.into()),
})?
}
PusServiceId::Event => {
self.pus_router
.event_service_receiver
.send(EcssTcAndToken {
tc_in_memory,
token: Some(accepted_token.into()),
})?
}
PusServiceId::Event => self.pus_router.event_tc_sender.send(EcssTcAndToken {
tc_in_memory,
token: Some(accepted_token.into()),
})?,
PusServiceId::Scheduling => {
self.pus_router
.sched_service_receiver
.send(EcssTcAndToken {
tc_in_memory,
token: Some(accepted_token.into()),
})?
self.pus_router.sched_tc_sender.send(EcssTcAndToken {
tc_in_memory,
token: Some(accepted_token.into()),
})?
}
_ => {
let result = self.verif_reporter.start_failure(
&self.tm_sender,
accepted_token,
FailParams::new(
self.stamp_helper.stamp(),
@ -130,15 +159,20 @@ impl<VerificationReporter: VerificationReportingProvider> PusReceiver<Verificati
Err(e) => {
if let Ok(custom_service) = CustomPusServiceId::try_from(e.number) {
match custom_service {
CustomPusServiceId::Mode => {
// TODO: Fix mode service.
//self.handle_mode_service(pus_tc, accepted_token)
}
CustomPusServiceId::Mode => self
.pus_router
.mode_tc_sender
.send(EcssTcAndToken {
tc_in_memory,
token: Some(accepted_token.into()),
})
.map_err(|_| GenericSendError::RxDisconnected)?,
CustomPusServiceId::Health => {}
}
} else {
self.verif_reporter
.start_failure(
&self.tm_sender,
accepted_token,
FailParams::new(
self.stamp_helper.stamp(),
@ -150,59 +184,600 @@ impl<VerificationReporter: VerificationReportingProvider> PusReceiver<Verificati
}
}
}
Ok(PusPacketHandlerResult::RequestHandled)
Ok(HandlingStatus::HandledOne)
}
}
#[derive(Default)]
pub struct GenericRoutingErrorHandler<const SERVICE_ID: u8> {}
pub trait TargetedPusService {
const SERVICE_ID: u8;
const SERVICE_STR: &'static str;
impl<const SERVICE_ID: u8> PusRoutingErrorHandler for GenericRoutingErrorHandler<SERVICE_ID> {
type Error = satrs::pus::GenericRoutingError;
fn poll_and_handle_next_tc_default_handler(&mut self, time_stamp: &[u8]) -> HandlingStatus {
let result = self.poll_and_handle_next_tc(time_stamp);
if let Err(e) = result {
log::error!(
"PUS service {}({})packet handling error: {:?}",
Self::SERVICE_ID,
Self::SERVICE_STR,
e
);
// To avoid permanent loops on error cases.
return HandlingStatus::Empty;
}
result.unwrap()
}
fn handle_error(
&self,
target_id: satrs::TargetId,
token: satrs::pus::verification::VerificationToken<
satrs::pus::verification::TcStateAccepted,
>,
_tc: &PusTcReader,
error: Self::Error,
fn poll_and_handle_next_reply_default_handler(&mut self, time_stamp: &[u8]) -> HandlingStatus {
// This only fails if all senders disconnected. Treat it like an empty queue.
self.poll_and_handle_next_reply(time_stamp)
.unwrap_or_else(|e| {
warn!(
"PUS servce {}({}): Handling reply failed with error {:?}",
Self::SERVICE_ID,
Self::SERVICE_STR,
e
);
HandlingStatus::Empty
})
}
fn poll_and_handle_next_tc(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, PusPacketHandlingError>;
fn poll_and_handle_next_reply(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, EcssTmtcError>;
fn check_for_request_timeouts(&mut self);
}
/// Generic trait for services which handle packets directly. Kept minimal right now because
/// of the difficulty to allow flexible user code for these services..
pub trait DirectPusService {
const SERVICE_ID: u8;
const SERVICE_STR: &'static str;
fn poll_and_handle_next_tc(&mut self, timestamp: &[u8]) -> HandlingStatus;
}
/// This is a generic handler class for all PUS services where a PUS telecommand is converted
/// to a targeted request.
///
/// The generic steps for this process are the following
///
/// 1. Poll for TC packets
/// 2. Convert the raw packets to a [PusTcReader].
/// 3. Convert the PUS TC to a typed request using the [PusTcToRequestConverter].
/// 4. Route the requests using the [GenericRequestRouter].
/// 5. Add the request to the active request map using the [ActiveRequestMapProvider] abstraction.
/// 6. Check for replies which complete the forwarded request. The handler takes care of
/// the verification process.
/// 7. Check for timeouts of active requests. Generally, the timeout on the service level should
/// be highest expected timeout for the given target.
///
/// The handler exposes the following API:
///
/// 1. [Self::poll_and_handle_next_tc] which tries to poll and handle one TC packet, covering
/// steps 1-5.
/// 2. [Self::poll_and_check_next_reply] which tries to poll and handle one reply, covering step 6.
/// 3. [Self::check_for_request_timeouts] which checks for request timeouts, covering step 7.
pub struct PusTargetedRequestService<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
ActiveRequestInfo: ActiveRequestProvider,
RequestType,
ReplyType,
> {
pub service_helper:
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
pub request_router: GenericRequestRouter,
pub request_converter: RequestConverter,
pub active_request_map: ActiveRequestMap,
pub reply_handler: ReplyHandler,
pub reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
phantom: std::marker::PhantomData<(RequestType, ActiveRequestInfo, ReplyType)>,
}
impl<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
ActiveRequestInfo: ActiveRequestProvider,
RequestType,
ReplyType,
>
PusTargetedRequestService<
TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
RequestConverter,
ReplyHandler,
ActiveRequestMap,
ActiveRequestInfo,
RequestType,
ReplyType,
>
where
GenericRequestRouter: PusRequestRouter<RequestType, Error = GenericRoutingError>,
{
pub fn new(
service_helper: PusServiceHelper<
TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
>,
request_converter: RequestConverter,
active_request_map: ActiveRequestMap,
reply_hook: ReplyHandler,
request_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ReplyType>>,
) -> Self {
Self {
service_helper,
request_converter,
active_request_map,
reply_handler: reply_hook,
request_router,
reply_receiver,
phantom: std::marker::PhantomData,
}
}
pub fn poll_and_handle_next_tc(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(HandlingStatus::Empty);
}
let ecss_tc_and_token = possible_packet.unwrap();
self.service_helper
.tc_in_mem_converter_mut()
.cache(&ecss_tc_and_token.tc_in_memory)?;
let tc = self.service_helper.tc_in_mem_converter().convert()?;
let (mut request_info, request) = match self.request_converter.convert(
ecss_tc_and_token.token,
&tc,
self.service_helper.tm_sender(),
&self.service_helper.common.verif_reporter,
time_stamp,
) {
Ok((info, req)) => (info, req),
Err(e) => {
self.handle_conversion_to_request_error(&e, ecss_tc_and_token.token, time_stamp);
return Err(e.into());
}
};
let accepted_token: VerificationToken<TcStateAccepted> = request_info
.token()
.try_into()
.expect("token not in expected accepted state");
let verif_request_id = verification::RequestId::new(&tc).raw();
match self.request_router.route(
MessageMetadata::new(verif_request_id, self.service_helper.id()),
request_info.target_id(),
request,
) {
Ok(()) => {
let started_token = self
.service_helper
.verif_reporter()
.start_success(
&self.service_helper.common.tm_sender,
accepted_token,
time_stamp,
)
.expect("Start success failure");
request_info.set_token(started_token.into());
self.active_request_map
.insert(&verif_request_id, request_info);
}
Err(e) => {
self.request_router.handle_error_generic(
&request_info,
&tc,
e.clone(),
self.service_helper.tm_sender(),
self.service_helper.verif_reporter(),
time_stamp,
);
return Err(e.into());
}
}
Ok(HandlingStatus::HandledOne)
}
fn handle_conversion_to_request_error(
&mut self,
error: &GenericConversionError,
token: VerificationToken<TcStateAccepted>,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
) {
warn!("Routing request for service {SERVICE_ID} failed: {error:?}");
match error {
GenericRoutingError::UnknownTargetId(id) => {
let mut fail_data: [u8; 8] = [0; 8];
fail_data.copy_from_slice(&id.to_be_bytes());
verif_reporter
.start_failure(
GenericConversionError::WrongService(service) => {
let service_slice: [u8; 1] = [*service];
self.service_helper
.verif_reporter()
.completion_failure(
self.service_helper.tm_sender(),
token,
FailParams::new(time_stamp, &tmtc_err::UNKNOWN_TARGET_ID, &fail_data),
FailParams::new(time_stamp, &tmtc_err::INVALID_PUS_SERVICE, &service_slice),
)
.expect("Sending start failure failed");
.expect("Sending completion failure failed");
}
GenericRoutingError::SendError(_) => {
let mut fail_data: [u8; 8] = [0; 8];
fail_data.copy_from_slice(&target_id.to_be_bytes());
verif_reporter
.start_failure(
GenericConversionError::InvalidSubservice(subservice) => {
let subservice_slice: [u8; 1] = [*subservice];
self.service_helper
.verif_reporter()
.completion_failure(
self.service_helper.tm_sender(),
token,
FailParams::new(time_stamp, &tmtc_err::ROUTING_ERROR, &fail_data),
FailParams::new(
time_stamp,
&tmtc_err::INVALID_PUS_SUBSERVICE,
&subservice_slice,
),
)
.expect("Sending start failure failed");
.expect("Sending completion failure failed");
}
GenericRoutingError::NotEnoughAppData { expected, found } => {
let mut context_info = (found as u32).to_be_bytes().to_vec();
context_info.extend_from_slice(&(expected as u32).to_be_bytes());
verif_reporter
.start_failure(
GenericConversionError::NotEnoughAppData { expected, found } => {
let mut context_info = (*found as u32).to_be_bytes().to_vec();
context_info.extend_from_slice(&(*expected as u32).to_be_bytes());
self.service_helper
.verif_reporter()
.completion_failure(
self.service_helper.tm_sender(),
token,
FailParams::new(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA, &context_info),
)
.expect("Sending start failure failed");
.expect("Sending completion failure failed");
}
// Do nothing.. this is service-level and can not be handled generically here.
GenericConversionError::InvalidAppData(_) => (),
}
}
pub fn poll_and_handle_next_reply(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, EcssTmtcError> {
match self.reply_receiver.try_recv() {
Ok(reply) => {
self.handle_reply(&reply, time_stamp)?;
Ok(HandlingStatus::HandledOne)
}
Err(e) => match e {
mpsc::TryRecvError::Empty => Ok(HandlingStatus::Empty),
mpsc::TryRecvError::Disconnected => Err(EcssTmtcError::Receive(
GenericReceiveError::TxDisconnected(None),
)),
},
}
}
pub fn handle_reply(
&mut self,
reply: &GenericMessage<ReplyType>,
time_stamp: &[u8],
) -> Result<(), EcssTmtcError> {
let active_req_opt = self.active_request_map.get(reply.request_id());
if active_req_opt.is_none() {
self.reply_handler
.handle_unrequested_reply(reply, &self.service_helper.common.tm_sender)?;
return Ok(());
}
let active_request = active_req_opt.unwrap();
let result = self.reply_handler.handle_reply(
reply,
active_request,
&self.service_helper.common.tm_sender,
&self.service_helper.common.verif_reporter,
time_stamp,
);
if result.is_err() || (result.is_ok() && *result.as_ref().unwrap()) {
self.active_request_map.remove(reply.request_id());
}
result.map(|_| ())
}
pub fn check_for_request_timeouts(&mut self) {
let mut requests_to_delete = Vec::new();
self.active_request_map
.for_each(|request_id, request_info| {
if request_info.has_timed_out() {
requests_to_delete.push(*request_id);
}
});
if !requests_to_delete.is_empty() {
for request_id in requests_to_delete {
self.active_request_map.remove(request_id);
}
}
}
}
/// Generic timeout handling: Handle the verification failure with a dedicated return code
/// and also log the error.
pub fn generic_pus_request_timeout_handler(
sender: &(impl EcssTmSender + ?Sized),
active_request: &(impl ActiveRequestProvider + Debug),
verification_handler: &impl VerificationReportingProvider,
time_stamp: &[u8],
service_str: &'static str,
) -> Result<(), EcssTmtcError> {
log::warn!("timeout for active request {active_request:?} on {service_str} service");
let started_token: VerificationToken<TcStateStarted> = active_request
.token()
.try_into()
.expect("token not in expected started state");
verification_handler.completion_failure(
sender,
started_token,
FailParams::new(time_stamp, &tmtc_err::REQUEST_TIMEOUT, &[]),
)?;
Ok(())
}
#[cfg(test)]
pub(crate) mod tests {
use std::time::Duration;
use satrs::pus::test_util::TEST_COMPONENT_ID_0;
use satrs::pus::{MpscTmAsVecSender, PusTmVariant};
use satrs::request::RequestId;
use satrs::{
pus::{
verification::test_util::TestVerificationReporter, ActivePusRequestStd,
ActiveRequestMapProvider, EcssTcInVecConverter, MpscTcReceiver,
},
request::UniqueApidTargetId,
spacepackets::{
ecss::{
tc::{PusTcCreator, PusTcSecondaryHeader},
WritablePusPacket,
},
SpHeader,
},
};
use crate::requests::CompositeRequest;
use super::*;
// Testbench dedicated to the testing of [PusReplyHandler]s
pub struct ReplyHandlerTestbench<
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
ActiveRequestInfo: ActiveRequestProvider,
Reply,
> {
pub id: ComponentId,
pub verif_reporter: TestVerificationReporter,
pub reply_handler: ReplyHandler,
pub tm_receiver: mpsc::Receiver<PacketAsVec>,
pub default_timeout: Duration,
tm_sender: MpscTmAsVecSender,
phantom: std::marker::PhantomData<(ActiveRequestInfo, Reply)>,
}
impl<
ReplyHandler: PusReplyHandler<ActiveRequestInfo, Reply, Error = EcssTmtcError>,
ActiveRequestInfo: ActiveRequestProvider,
Reply,
> ReplyHandlerTestbench<ReplyHandler, ActiveRequestInfo, Reply>
{
pub fn new(owner_id: ComponentId, reply_handler: ReplyHandler) -> Self {
let test_verif_reporter = TestVerificationReporter::new(owner_id);
let (tm_sender, tm_receiver) = mpsc::channel();
Self {
id: TEST_COMPONENT_ID_0.raw(),
verif_reporter: test_verif_reporter,
reply_handler,
default_timeout: Duration::from_secs(30),
tm_sender,
tm_receiver,
phantom: std::marker::PhantomData,
}
}
pub fn add_tc(
&mut self,
apid: u16,
apid_target: u32,
time_stamp: &[u8],
) -> (verification::RequestId, ActivePusRequestStd) {
let sp_header = SpHeader::new_from_apid(apid);
let sec_header_dummy = PusTcSecondaryHeader::new_simple(0, 0);
let init = self.verif_reporter.add_tc(&PusTcCreator::new(
sp_header,
sec_header_dummy,
&[],
true,
));
let accepted = self
.verif_reporter
.acceptance_success(&self.tm_sender, init, time_stamp)
.expect("acceptance failed");
let started = self
.verif_reporter
.start_success(&self.tm_sender, accepted, time_stamp)
.expect("start failed");
(
started.request_id(),
ActivePusRequestStd::new(
UniqueApidTargetId::new(apid, apid_target).raw(),
started,
self.default_timeout,
),
)
}
pub fn handle_reply(
&mut self,
reply: &GenericMessage<Reply>,
active_request: &ActiveRequestInfo,
time_stamp: &[u8],
) -> Result<bool, ReplyHandler::Error> {
self.reply_handler.handle_reply(
reply,
active_request,
&self.tm_sender,
&self.verif_reporter,
time_stamp,
)
}
pub fn handle_unrequested_reply(
&mut self,
reply: &GenericMessage<Reply>,
) -> Result<(), ReplyHandler::Error> {
self.reply_handler
.handle_unrequested_reply(reply, &self.tm_sender)
}
pub fn handle_request_timeout(
&mut self,
active_request_info: &ActiveRequestInfo,
time_stamp: &[u8],
) -> Result<(), ReplyHandler::Error> {
self.reply_handler.handle_request_timeout(
active_request_info,
&self.tm_sender,
&self.verif_reporter,
time_stamp,
)
}
}
#[derive(Default)]
pub struct DummySender {}
/// Dummy sender component which does nothing on the [Self::send_tm] call.
///
/// Useful for unit tests.
impl EcssTmSender for DummySender {
fn send_tm(&self, _source_id: ComponentId, _tm: PusTmVariant) -> Result<(), EcssTmtcError> {
Ok(())
}
}
// Testbench dedicated to the testing of [PusTcToRequestConverter]s
pub struct PusConverterTestbench<
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
ActiveRequestInfo: ActiveRequestProvider,
Request,
> {
pub id: ComponentId,
pub verif_reporter: TestVerificationReporter,
pub converter: Converter,
dummy_sender: DummySender,
current_request_id: Option<verification::RequestId>,
current_packet: Option<Vec<u8>>,
phantom: std::marker::PhantomData<(ActiveRequestInfo, Request)>,
}
impl<
Converter: PusTcToRequestConverter<ActiveRequestInfo, Request, Error = GenericConversionError>,
ActiveRequestInfo: ActiveRequestProvider,
Request,
> PusConverterTestbench<Converter, ActiveRequestInfo, Request>
{
pub fn new(owner_id: ComponentId, converter: Converter) -> Self {
let test_verif_reporter = TestVerificationReporter::new(owner_id);
Self {
id: owner_id,
verif_reporter: test_verif_reporter,
converter,
dummy_sender: DummySender::default(),
current_request_id: None,
current_packet: None,
phantom: std::marker::PhantomData,
}
}
pub fn add_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
let token = self.verif_reporter.add_tc(tc);
self.current_request_id = Some(verification::RequestId::new(tc));
self.current_packet = Some(tc.to_vec().unwrap());
self.verif_reporter
.acceptance_success(&self.dummy_sender, token, &[])
.expect("acceptance failed")
}
pub fn request_id(&self) -> Option<verification::RequestId> {
self.current_request_id
}
pub fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
time_stamp: &[u8],
expected_apid: u16,
expected_apid_target: u32,
) -> Result<(ActiveRequestInfo, Request), Converter::Error> {
if self.current_packet.is_none() {
return Err(GenericConversionError::InvalidAppData(
"call add_tc first".to_string(),
));
}
let current_packet = self.current_packet.take().unwrap();
let tc_reader = PusTcReader::new(&current_packet).unwrap();
let (active_info, request) = self.converter.convert(
token,
&tc_reader.0,
&self.dummy_sender,
&self.verif_reporter,
time_stamp,
)?;
assert_eq!(
active_info.token().request_id(),
self.request_id().expect("no request id is set")
);
assert_eq!(
active_info.target_id(),
UniqueApidTargetId::new(expected_apid, expected_apid_target).raw()
);
Ok((active_info, request))
}
}
pub struct TargetedPusRequestTestbench<
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
ActiveRequestMap: ActiveRequestMapProvider<ActiveRequestInfo>,
ActiveRequestInfo: ActiveRequestProvider,
RequestType,
ReplyType,
> {
pub service: PusTargetedRequestService<
MpscTcReceiver,
MpscTmAsVecSender,
EcssTcInVecConverter,
TestVerificationReporter,
RequestConverter,
ReplyHandler,
ActiveRequestMap,
ActiveRequestInfo,
RequestType,
ReplyType,
>,
pub request_id: Option<RequestId>,
pub tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
pub pus_packet_tx: mpsc::Sender<EcssTcAndToken>,
pub reply_tx: mpsc::Sender<GenericMessage<ReplyType>>,
pub request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
}
}

View File

@ -0,0 +1,416 @@
use derive_new::new;
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use std::sync::mpsc;
use std::time::Duration;
use crate::requests::GenericRequestRouter;
use satrs::pool::SharedStaticMemoryPool;
use satrs::pus::verification::VerificationReporter;
use satrs::pus::{
DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlingError,
PusServiceHelper,
};
use satrs::request::GenericMessage;
use satrs::{
mode::{ModeAndSubmode, ModeReply, ModeRequest},
pus::{
mode::Subservice,
verification::{
self, FailParams, TcStateAccepted, TcStateStarted, VerificationReportingProvider,
VerificationToken,
},
ActivePusRequestStd, ActiveRequestProvider, EcssTmSender, EcssTmtcError,
GenericConversionError, PusReplyHandler, PusTcToRequestConverter, PusTmVariant,
},
request::UniqueApidTargetId,
spacepackets::{
ecss::{
tc::PusTcReader,
tm::{PusTmCreator, PusTmSecondaryHeader},
PusPacket,
},
SpHeader,
},
ComponentId,
};
use satrs_example::config::components::PUS_MODE_SERVICE;
use satrs_example::config::{mode_err, tmtc_err, CustomPusServiceId};
use super::{
create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus,
PusTargetedRequestService, TargetedPusService,
};
#[derive(new)]
pub struct ModeReplyHandler {
owner_id: ComponentId,
}
impl PusReplyHandler<ActivePusRequestStd, ModeReply> for ModeReplyHandler {
type Error = EcssTmtcError;
fn handle_unrequested_reply(
&mut self,
reply: &GenericMessage<ModeReply>,
_tm_sender: &impl EcssTmSender,
) -> Result<(), Self::Error> {
log::warn!("received unexpected reply for mode service 5: {reply:?}");
Ok(())
}
fn handle_reply(
&mut self,
reply: &GenericMessage<ModeReply>,
active_request: &ActivePusRequestStd,
tm_sender: &impl EcssTmSender,
verification_handler: &impl VerificationReportingProvider,
time_stamp: &[u8],
) -> Result<bool, Self::Error> {
let started_token: VerificationToken<TcStateStarted> = active_request
.token()
.try_into()
.expect("invalid token state");
match reply.message {
ModeReply::ModeReply(mode_reply) => {
let mut source_data: [u8; 12] = [0; 12];
mode_reply
.write_to_be_bytes(&mut source_data)
.expect("writing mode reply failed");
let req_id = verification::RequestId::from(reply.request_id());
let sp_header = SpHeader::new_for_unseg_tm(req_id.packet_id().apid(), 0, 0);
let sec_header =
PusTmSecondaryHeader::new(200, Subservice::TmModeReply as u8, 0, 0, time_stamp);
let pus_tm = PusTmCreator::new(sp_header, sec_header, &source_data, true);
tm_sender.send_tm(self.owner_id, PusTmVariant::Direct(pus_tm))?;
verification_handler.completion_success(tm_sender, started_token, time_stamp)?;
}
ModeReply::CantReachMode(error_code) => {
verification_handler.completion_failure(
tm_sender,
started_token,
FailParams::new(time_stamp, &error_code, &[]),
)?;
}
ModeReply::WrongMode { expected, reached } => {
let mut error_info: [u8; 24] = [0; 24];
let mut written_len = expected
.write_to_be_bytes(&mut error_info[0..ModeAndSubmode::RAW_LEN])
.expect("writing expected mode failed");
written_len += reached
.write_to_be_bytes(&mut error_info[ModeAndSubmode::RAW_LEN..])
.expect("writing reached mode failed");
verification_handler.completion_failure(
tm_sender,
started_token,
FailParams::new(
time_stamp,
&mode_err::WRONG_MODE,
&error_info[..written_len],
),
)?;
}
};
Ok(true)
}
fn handle_request_timeout(
&mut self,
active_request: &ActivePusRequestStd,
tm_sender: &impl EcssTmSender,
verification_handler: &impl VerificationReportingProvider,
time_stamp: &[u8],
) -> Result<(), Self::Error> {
generic_pus_request_timeout_handler(
tm_sender,
active_request,
verification_handler,
time_stamp,
"HK",
)?;
Ok(())
}
}
#[derive(Default)]
pub struct ModeRequestConverter {}
impl PusTcToRequestConverter<ActivePusRequestStd, ModeRequest> for ModeRequestConverter {
type Error = GenericConversionError;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
tm_sender: &(impl EcssTmSender + ?Sized),
verif_reporter: &impl VerificationReportingProvider,
time_stamp: &[u8],
) -> Result<(ActivePusRequestStd, ModeRequest), Self::Error> {
let subservice = tc.subservice();
let user_data = tc.user_data();
let not_enough_app_data = |expected: usize| {
verif_reporter
.start_failure(
tm_sender,
token,
FailParams::new_no_fail_data(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA),
)
.expect("Sending start failure failed");
Err(GenericConversionError::NotEnoughAppData {
expected,
found: user_data.len(),
})
};
if user_data.len() < core::mem::size_of::<u32>() {
return not_enough_app_data(4);
}
let target_id_and_apid = UniqueApidTargetId::from_pus_tc(tc).unwrap();
let active_request =
ActivePusRequestStd::new(target_id_and_apid.into(), token, Duration::from_secs(30));
let subservice_typed = Subservice::try_from(subservice);
let invalid_subservice = || {
// Invalid subservice
verif_reporter
.start_failure(
tm_sender,
token,
FailParams::new_no_fail_data(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE),
)
.expect("Sending start failure failed");
Err(GenericConversionError::InvalidSubservice(subservice))
};
if subservice_typed.is_err() {
return invalid_subservice();
}
let subservice_typed = subservice_typed.unwrap();
match subservice_typed {
Subservice::TcSetMode => {
if user_data.len() < core::mem::size_of::<u32>() + ModeAndSubmode::RAW_LEN {
return not_enough_app_data(4 + ModeAndSubmode::RAW_LEN);
}
let mode_and_submode = ModeAndSubmode::from_be_bytes(&tc.user_data()[4..])
.expect("mode and submode extraction failed");
Ok((active_request, ModeRequest::SetMode(mode_and_submode)))
}
Subservice::TcReadMode => Ok((active_request, ModeRequest::ReadMode)),
Subservice::TcAnnounceMode => Ok((active_request, ModeRequest::AnnounceMode)),
Subservice::TcAnnounceModeRecursive => {
Ok((active_request, ModeRequest::AnnounceModeRecursive))
}
_ => invalid_subservice(),
}
}
}
pub fn create_mode_service_static(
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
mode_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
) -> ModeServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let mode_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_MODE_SERVICE.id(),
pus_action_rx,
tm_sender,
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
),
ModeRequestConverter::default(),
DefaultActiveRequestMap::default(),
ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
mode_router,
reply_receiver,
);
ModeServiceWrapper {
service: mode_request_handler,
}
}
pub fn create_mode_service_dynamic(
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
mode_router: GenericRequestRouter,
reply_receiver: mpsc::Receiver<GenericMessage<ModeReply>>,
) -> ModeServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let mode_request_handler = PusTargetedRequestService::new(
PusServiceHelper::new(
PUS_MODE_SERVICE.id(),
pus_action_rx,
tm_funnel_tx,
create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid),
EcssTcInVecConverter::default(),
),
ModeRequestConverter::default(),
DefaultActiveRequestMap::default(),
ModeReplyHandler::new(PUS_MODE_SERVICE.id()),
mode_router,
reply_receiver,
);
ModeServiceWrapper {
service: mode_request_handler,
}
}
pub struct ModeServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
pub(crate) service: PusTargetedRequestService<
MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
ModeRequestConverter,
ModeReplyHandler,
DefaultActiveRequestMap<ActivePusRequestStd>,
ActivePusRequestStd,
ModeRequest,
ModeReply,
>,
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> TargetedPusService
for ModeServiceWrapper<TmSender, TcInMemConverter>
{
const SERVICE_ID: u8 = CustomPusServiceId::Mode as u8;
const SERVICE_STR: &'static str = "mode";
delegate::delegate! {
to self.service {
fn poll_and_handle_next_tc(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, PusPacketHandlingError>;
fn poll_and_handle_next_reply(
&mut self,
time_stamp: &[u8],
) -> Result<HandlingStatus, EcssTmtcError>;
fn check_for_request_timeouts(&mut self);
}
}
}
#[cfg(test)]
mod tests {
use satrs::pus::test_util::{TEST_APID, TEST_COMPONENT_ID_0, TEST_UNIQUE_ID_0};
use satrs::request::MessageMetadata;
use satrs::{
mode::{ModeAndSubmode, ModeReply, ModeRequest},
pus::mode::Subservice,
request::GenericMessage,
spacepackets::{
ecss::tc::{PusTcCreator, PusTcSecondaryHeader},
SpHeader,
},
};
use satrs_example::config::tmtc_err;
use crate::pus::{
mode::ModeReplyHandler,
tests::{PusConverterTestbench, ReplyHandlerTestbench},
};
use super::ModeRequestConverter;
#[test]
fn mode_converter_read_mode_request() {
let mut testbench =
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcReadMode as u8);
let mut app_data: [u8; 4] = [0; 4];
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
let token = testbench.add_tc(&tc);
let (_active_req, req) = testbench
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
.expect("conversion has failed");
assert_eq!(req, ModeRequest::ReadMode);
}
#[test]
fn mode_converter_set_mode_request() {
let mut testbench =
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcSetMode as u8);
let mut app_data: [u8; 4 + ModeAndSubmode::RAW_LEN] = [0; 4 + ModeAndSubmode::RAW_LEN];
let mode_and_submode = ModeAndSubmode::new(2, 1);
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
mode_and_submode
.write_to_be_bytes(&mut app_data[4..])
.unwrap();
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
let token = testbench.add_tc(&tc);
let (_active_req, req) = testbench
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
.expect("conversion has failed");
assert_eq!(req, ModeRequest::SetMode(mode_and_submode));
}
#[test]
fn mode_converter_announce_mode() {
let mut testbench =
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(200, Subservice::TcAnnounceMode as u8);
let mut app_data: [u8; 4] = [0; 4];
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
let token = testbench.add_tc(&tc);
let (_active_req, req) = testbench
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
.expect("conversion has failed");
assert_eq!(req, ModeRequest::AnnounceMode);
}
#[test]
fn mode_converter_announce_mode_recursively() {
let mut testbench =
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ModeRequestConverter::default());
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header =
PusTcSecondaryHeader::new_simple(200, Subservice::TcAnnounceModeRecursive as u8);
let mut app_data: [u8; 4] = [0; 4];
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
let tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
let token = testbench.add_tc(&tc);
let (_active_req, req) = testbench
.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0)
.expect("conversion has failed");
assert_eq!(req, ModeRequest::AnnounceModeRecursive);
}
#[test]
fn reply_handling_unrequested_reply() {
let mut testbench = ReplyHandlerTestbench::new(
TEST_COMPONENT_ID_0.id(),
ModeReplyHandler::new(TEST_COMPONENT_ID_0.id()),
);
let mode_reply = ModeReply::ModeReply(ModeAndSubmode::new(5, 1));
let unrequested_reply =
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), mode_reply);
// Right now this function does not do a lot. We simply check that it does not panic or do
// weird stuff.
let result = testbench.handle_unrequested_reply(&unrequested_reply);
assert!(result.is_ok());
}
#[test]
fn reply_handling_reply_timeout() {
let mut testbench = ReplyHandlerTestbench::new(
TEST_COMPONENT_ID_0.id(),
ModeReplyHandler::new(TEST_COMPONENT_ID_0.id()),
);
let (req_id, active_request) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
let result = testbench.handle_request_timeout(&active_request, &[]);
assert!(result.is_ok());
testbench.verif_reporter.assert_completion_failure(
TEST_COMPONENT_ID_0.raw(),
req_id,
None,
tmtc_err::REQUEST_TIMEOUT.raw() as u64,
);
}
}

View File

@ -1,68 +1,74 @@
use std::sync::mpsc;
use std::time::Duration;
use log::{error, info, warn};
use satrs::pool::{PoolProvider, StaticMemoryPool, StoreAddr};
use crate::pus::create_verification_reporter;
use log::info;
use satrs::pool::{PoolProvider, StaticMemoryPool};
use satrs::pus::scheduler::{PusScheduler, TcInfo};
use satrs::pus::scheduler_srv::PusService11SchedHandler;
use satrs::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::verification::VerificationReportingProvider;
use satrs::pus::scheduler_srv::PusSchedServiceHandler;
use satrs::pus::verification::VerificationReporter;
use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
EcssTcReceiverCore, EcssTmSenderCore, MpscTcReceiver, PusPacketHandlerResult, PusServiceHelper,
TmAsVecSenderWithId, TmAsVecSenderWithMpsc, TmInSharedPoolSenderWithBoundedMpsc,
TmInSharedPoolSenderWithId,
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter,
EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver,
MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper,
};
use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::ChannelId;
use satrs_example::config::{TcReceiverId, TmSenderId, PUS_APID};
use satrs::spacepackets::ecss::PusServiceId;
use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool};
use satrs::ComponentId;
use satrs_example::config::components::PUS_SCHED_SERVICE;
use crate::tmtc::PusTcSourceProviderSharedPool;
use super::{DirectPusService, HandlingStatus};
pub trait TcReleaser {
fn release(&mut self, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool;
fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool;
}
impl TcReleaser for PusTcSourceProviderSharedPool {
fn release(&mut self, enabled: bool, _info: &TcInfo, tc: &[u8]) -> bool {
impl TcReleaser for PacketSenderWithSharedPool {
fn release(
&mut self,
sender_id: ComponentId,
enabled: bool,
_info: &TcInfo,
tc: &[u8],
) -> bool {
if enabled {
let shared_pool = self.shared_pool.get_mut();
// Transfer TC from scheduler TC pool to shared TC pool.
let released_tc_addr = self
.shared_pool
.pool
let released_tc_addr = shared_pool
.0
.write()
.expect("locking pool failed")
.add(tc)
.expect("adding TC to shared pool failed");
self.tc_source
.send(released_tc_addr)
self.sender
.send(PacketInPool::new(sender_id, released_tc_addr))
.expect("sending TC to TC source failed");
}
true
}
}
impl TcReleaser for mpsc::Sender<Vec<u8>> {
fn release(&mut self, enabled: bool, _info: &TcInfo, tc: &[u8]) -> bool {
impl TcReleaser for mpsc::Sender<PacketAsVec> {
fn release(
&mut self,
sender_id: ComponentId,
enabled: bool,
_info: &TcInfo,
tc: &[u8],
) -> bool {
if enabled {
// Send released TC to centralized TC source.
self.send(tc.to_vec())
self.send(PacketAsVec::new(sender_id, tc.to_vec()))
.expect("sending TC to TC source failed");
}
true
}
}
pub struct Pus11Wrapper<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub pus_11_handler: PusService11SchedHandler<
TcReceiver,
pub struct SchedulingServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
{
pub pus_11_handler: PusSchedServiceHandler<
MpscTcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
@ -73,16 +79,68 @@ pub struct Pus11Wrapper<
pub tc_releaser: Box<dyn TcReleaser + Send>,
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> Pus11Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
for SchedulingServiceWrapper<TmSender, TcInMemConverter>
{
const SERVICE_ID: u8 = PusServiceId::Verification as u8;
const SERVICE_STR: &'static str = "verification";
fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus {
let error_handler = |partial_error: &PartialPusHandlingError| {
log::warn!(
"PUS {}({}) partial error: {:?}",
Self::SERVICE_ID,
Self::SERVICE_STR,
partial_error
);
};
let result = self.pus_11_handler.poll_and_handle_next_tc(
error_handler,
time_stamp,
&mut self.sched_tc_pool,
);
if let Err(e) = result {
log::warn!(
"PUS {}({}) error: {:?}",
Self::SERVICE_ID,
Self::SERVICE_STR,
e
);
// To avoid permanent loops on continuous errors.
return HandlingStatus::Empty;
}
match result.unwrap() {
DirectPusPacketHandlerResult::Handled(handling_status) => return handling_status,
DirectPusPacketHandlerResult::CustomSubservice(subservice, _) => {
log::warn!(
"PUS {}({}) subservice {} not implemented",
Self::SERVICE_ID,
Self::SERVICE_STR,
subservice
);
}
DirectPusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
log::warn!(
"PUS {}({}) subservice {} not implemented",
Self::SERVICE_ID,
Self::SERVICE_STR,
subservice
);
}
}
HandlingStatus::HandledOne
}
}
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
SchedulingServiceWrapper<TmSender, TcInMemConverter>
{
pub fn release_tcs(&mut self) {
let id = self.pus_11_handler.service_helper.id();
let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool {
self.tc_releaser.release(enabled, info, tc)
self.tc_releaser.release(id, enabled, info, tc)
};
self.pus_11_handler
@ -102,69 +160,27 @@ impl<
info!("{released_tcs} TC(s) released from scheduler");
}
}
pub fn handle_next_packet(&mut self) -> bool {
match self.pus_11_handler.handle_one_tc(&mut self.sched_tc_pool) {
Ok(result) => match result {
PusPacketHandlerResult::RequestHandled => {}
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
warn!("PUS11 partial packet handling success: {e:?}")
}
PusPacketHandlerResult::CustomSubservice(invalid, _) => {
warn!("PUS11 invalid subservice {invalid}");
}
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
warn!("PUS11: Subservice {subservice} not implemented");
}
PusPacketHandlerResult::Empty => {
return true;
}
},
Err(error) => {
error!("PUS packet handling error: {error:?}")
}
}
false
}
}
pub fn create_scheduler_service_static(
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
tc_releaser: PusTcSourceProviderSharedPool,
tm_sender: PacketSenderWithSharedPool,
tc_releaser: PacketSenderWithSharedPool,
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
sched_tc_pool: StaticMemoryPool,
) -> Pus11Wrapper<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
> {
let sched_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusSched as ChannelId,
"PUS_11_TM_SENDER",
shared_tm_store.clone(),
tm_funnel_tx.clone(),
);
let sched_srv_receiver = MpscTcReceiver::new(
TcReceiverId::PusSched as ChannelId,
"PUS_11_TC_RECV",
pus_sched_rx,
);
) -> SchedulingServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
.expect("Creating PUS Scheduler failed");
let pus_11_handler = PusService11SchedHandler::new(
let pus_11_handler = PusSchedServiceHandler::new(
PusServiceHelper::new(
sched_srv_receiver,
sched_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
EcssTcInSharedStoreConverter::new(tc_releaser.clone_backing_pool(), 2048),
PUS_SCHED_SERVICE.id(),
pus_sched_rx,
tm_sender,
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_releaser.shared_packet_store().0.clone(), 2048),
),
scheduler,
);
Pus11Wrapper {
SchedulingServiceWrapper {
pus_11_handler,
sched_tc_pool,
releaser_buf: [0; 4096],
@ -173,40 +189,26 @@ pub fn create_scheduler_service_static(
}
pub fn create_scheduler_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithVecMpscSender,
tc_source_sender: mpsc::Sender<Vec<u8>>,
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
tc_source_sender: mpsc::Sender<PacketAsVec>,
pus_sched_rx: mpsc::Receiver<EcssTcAndToken>,
sched_tc_pool: StaticMemoryPool,
) -> Pus11Wrapper<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
EcssTcInVecConverter,
VerificationReporterWithVecMpscSender,
> {
let sched_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusSched as ChannelId,
"PUS_11_TM_SENDER",
tm_funnel_tx,
);
let sched_srv_receiver = MpscTcReceiver::new(
TcReceiverId::PusSched as ChannelId,
"PUS_11_TC_RECV",
pus_sched_rx,
);
) -> SchedulingServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
//let sched_srv_receiver =
//MpscTcReceiver::new(PUS_SCHED_SERVICE.raw(), "PUS_11_TC_RECV", pus_sched_rx);
let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
.expect("Creating PUS Scheduler failed");
let pus_11_handler = PusService11SchedHandler::new(
let pus_11_handler = PusSchedServiceHandler::new(
PusServiceHelper::new(
sched_srv_receiver,
sched_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
PUS_SCHED_SERVICE.id(),
pus_sched_rx,
tm_funnel_tx,
create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid),
EcssTcInVecConverter::default(),
),
scheduler,
);
Pus11Wrapper {
SchedulingServiceWrapper {
pus_11_handler,
sched_tc_pool,
releaser_buf: [0; 4096],

View File

@ -1,71 +1,95 @@
use satrs::pus::{
verification::VerificationReportingProvider, EcssTcInMemConverter, EcssTcReceiverCore,
EcssTmSenderCore,
use crate::pus::mode::ModeServiceWrapper;
use derive_new::new;
use satrs::{
pus::{EcssTcInMemConverter, EcssTmSender},
spacepackets::time::{cds, TimeWriter},
};
use super::{
action::Pus8Wrapper, event::Pus5Wrapper, hk::Pus3Wrapper, scheduler::Pus11Wrapper,
test::Service17CustomWrapper,
action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper,
scheduler::SchedulingServiceWrapper, test::TestCustomServiceWrapper, DirectPusService,
HandlingStatus, TargetedPusService,
};
pub struct PusStack<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
event_srv: Pus5Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
hk_srv: Pus3Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
action_srv: Pus8Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
schedule_srv: Pus11Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
test_srv: Service17CustomWrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
// TODO: For better extensibility, we could create 2 vectors: One for direct PUS services and one
// for targeted services..
#[derive(new)]
pub struct PusStack<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> {
test_srv: TestCustomServiceWrapper<TmSender, TcInMemConverter>,
hk_srv_wrapper: HkServiceWrapper<TmSender, TcInMemConverter>,
event_srv: EventServiceWrapper<TmSender, TcInMemConverter>,
action_srv_wrapper: ActionServiceWrapper<TmSender, TcInMemConverter>,
schedule_srv: SchedulingServiceWrapper<TmSender, TcInMemConverter>,
mode_srv: ModeServiceWrapper<TmSender, TcInMemConverter>,
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> PusStack<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
PusStack<TmSender, TcInMemConverter>
{
pub fn new(
hk_srv: Pus3Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
event_srv: Pus5Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
action_srv: Pus8Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
schedule_srv: Pus11Wrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
test_srv: Service17CustomWrapper<
TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
>,
) -> Self {
Self {
event_srv,
action_srv,
schedule_srv,
test_srv,
hk_srv,
}
}
pub fn periodic_operation(&mut self) {
// Release all telecommands which reached their release time before calling the service
// handlers.
self.schedule_srv.release_tcs();
let timestamp = cds::CdsTime::now_with_u16_days()
.expect("time stamp generation error")
.to_vec()
.unwrap();
let mut loop_count = 0_u32;
// Hot loop which will run continuously until all request and reply handling is done.
loop {
let mut all_queues_empty = true;
let mut is_srv_finished = |srv_handler_finished: bool| {
if !srv_handler_finished {
all_queues_empty = false;
}
};
is_srv_finished(self.test_srv.handle_next_packet());
is_srv_finished(self.schedule_srv.handle_next_packet());
is_srv_finished(self.event_srv.handle_next_packet());
is_srv_finished(self.action_srv.handle_next_packet());
is_srv_finished(self.hk_srv.handle_next_packet());
if all_queues_empty {
let mut nothing_to_do = true;
Self::direct_service_checker(&mut self.test_srv, &timestamp, &mut nothing_to_do);
Self::direct_service_checker(&mut self.schedule_srv, &timestamp, &mut nothing_to_do);
Self::direct_service_checker(&mut self.event_srv, &timestamp, &mut nothing_to_do);
Self::targeted_service_checker(
&mut self.action_srv_wrapper,
&timestamp,
&mut nothing_to_do,
);
Self::targeted_service_checker(
&mut self.hk_srv_wrapper,
&timestamp,
&mut nothing_to_do,
);
Self::targeted_service_checker(&mut self.mode_srv, &timestamp, &mut nothing_to_do);
if nothing_to_do {
// Timeout checking is only done once.
self.action_srv_wrapper.check_for_request_timeouts();
self.hk_srv_wrapper.check_for_request_timeouts();
self.mode_srv.check_for_request_timeouts();
break;
}
// Safety mechanism to avoid infinite loops.
loop_count += 1;
if loop_count >= 500 {
log::warn!("reached PUS stack loop count 500, breaking");
break;
}
}
}
pub fn direct_service_checker<S: DirectPusService>(
service: &mut S,
timestamp: &[u8],
nothing_to_do: &mut bool,
) {
let handling_status = service.poll_and_handle_next_tc(timestamp);
if handling_status == HandlingStatus::HandledOne {
*nothing_to_do = false;
}
}
pub fn targeted_service_checker<S: TargetedPusService>(
service: &mut S,
timestamp: &[u8],
nothing_to_do: &mut bool,
) {
let request_handling = service.poll_and_handle_next_tc_default_handler(timestamp);
let reply_handling = service.poll_and_handle_next_reply_default_handler(timestamp);
if request_handling == HandlingStatus::HandledOne
|| reply_handling == HandlingStatus::HandledOne
{
*nothing_to_do = false;
}
}
}

View File

@ -1,175 +1,158 @@
use log::{info, warn};
use satrs::params::Params;
use satrs::pool::{SharedStaticMemoryPool, StoreAddr};
use crate::pus::create_verification_reporter;
use log::info;
use satrs::event_man::{EventMessage, EventMessageU32};
use satrs::pool::SharedStaticMemoryPool;
use satrs::pus::test::PusService17TestHandler;
use satrs::pus::verification::{FailParams, VerificationReportingProvider};
use satrs::pus::verification::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::verification::{FailParams, VerificationReporter, VerificationReportingProvider};
use satrs::pus::{
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, EcssTcReceiverCore,
EcssTmSenderCore, MpscTcReceiver, PusPacketHandlerResult, PusServiceHelper,
TmAsVecSenderWithId, TmAsVecSenderWithMpsc, TmInSharedPoolSenderWithBoundedMpsc,
TmInSharedPoolSenderWithId,
DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter,
EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusServiceHelper,
};
use satrs::pus::{EcssTcInSharedStoreConverter, PartialPusHandlingError};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusPacket;
use satrs::spacepackets::time::cds::TimeProvider;
use satrs::spacepackets::time::TimeWriter;
use satrs::tmtc::tm_helper::SharedTmPool;
use satrs::ChannelId;
use satrs::{events::EventU32, pus::EcssTcInSharedStoreConverter};
use satrs_example::config::{tmtc_err, TcReceiverId, TmSenderId, PUS_APID, TEST_EVENT};
use std::sync::mpsc::{self, Sender};
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use satrs_example::config::components::PUS_TEST_SERVICE;
use satrs_example::config::{tmtc_err, TEST_EVENT};
use std::sync::mpsc;
use super::{DirectPusService, HandlingStatus};
pub fn create_test_service_static(
shared_tm_store: SharedTmPool,
tm_funnel_tx: mpsc::SyncSender<StoreAddr>,
verif_reporter: VerificationReporterWithSharedPoolMpscBoundedSender,
tm_sender: PacketSenderWithSharedPool,
tc_pool: SharedStaticMemoryPool,
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
event_sender: mpsc::SyncSender<EventMessageU32>,
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
) -> Service17CustomWrapper<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
> {
let test_srv_tm_sender = TmInSharedPoolSenderWithId::new(
TmSenderId::PusTest as ChannelId,
"PUS_17_TM_SENDER",
shared_tm_store.clone(),
tm_funnel_tx.clone(),
);
let test_srv_receiver = MpscTcReceiver::new(
TcReceiverId::PusTest as ChannelId,
"PUS_17_TC_RECV",
pus_test_rx,
);
) -> TestCustomServiceWrapper<PacketSenderWithSharedPool, EcssTcInSharedStoreConverter> {
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
test_srv_receiver,
test_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
PUS_TEST_SERVICE.id(),
pus_test_rx,
tm_sender,
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
EcssTcInSharedStoreConverter::new(tc_pool, 2048),
));
Service17CustomWrapper {
pus17_handler,
test_srv_event_sender: event_sender,
TestCustomServiceWrapper {
handler: pus17_handler,
event_tx: event_sender,
}
}
pub fn create_test_service_dynamic(
tm_funnel_tx: mpsc::Sender<Vec<u8>>,
verif_reporter: VerificationReporterWithVecMpscSender,
event_sender: mpsc::Sender<(EventU32, Option<Params>)>,
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
event_sender: mpsc::SyncSender<EventMessageU32>,
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
) -> Service17CustomWrapper<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
EcssTcInVecConverter,
VerificationReporterWithVecMpscSender,
> {
let test_srv_tm_sender = TmAsVecSenderWithId::new(
TmSenderId::PusTest as ChannelId,
"PUS_17_TM_SENDER",
tm_funnel_tx.clone(),
);
let test_srv_receiver = MpscTcReceiver::new(
TcReceiverId::PusTest as ChannelId,
"PUS_17_TC_RECV",
pus_test_rx,
);
) -> TestCustomServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
test_srv_receiver,
test_srv_tm_sender,
PUS_APID,
verif_reporter.clone(),
PUS_TEST_SERVICE.id(),
pus_test_rx,
tm_funnel_tx,
create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid),
EcssTcInVecConverter::default(),
));
Service17CustomWrapper {
pus17_handler,
test_srv_event_sender: event_sender,
TestCustomServiceWrapper {
handler: pus17_handler,
event_tx: event_sender,
}
}
pub struct Service17CustomWrapper<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
pub pus17_handler:
PusService17TestHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
pub test_srv_event_sender: Sender<(EventU32, Option<Params>)>,
pub struct TestCustomServiceWrapper<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter>
{
pub handler:
PusService17TestHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
pub event_tx: mpsc::SyncSender<EventMessageU32>,
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> Service17CustomWrapper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
impl<TmSender: EcssTmSender, TcInMemConverter: EcssTcInMemConverter> DirectPusService
for TestCustomServiceWrapper<TmSender, TcInMemConverter>
{
pub fn handle_next_packet(&mut self) -> bool {
let res = self.pus17_handler.handle_one_tc();
if res.is_err() {
warn!("PUS17 handler failed with error {:?}", res.unwrap_err());
return true;
const SERVICE_ID: u8 = PusServiceId::Test as u8;
const SERVICE_STR: &'static str = "test";
fn poll_and_handle_next_tc(&mut self, timestamp: &[u8]) -> HandlingStatus {
let error_handler = |partial_error: &PartialPusHandlingError| {
log::warn!(
"PUS {}({}) partial error: {:?}",
Self::SERVICE_ID,
Self::SERVICE_STR,
partial_error
);
};
let res = self
.handler
.poll_and_handle_next_tc(error_handler, timestamp);
if let Err(e) = res {
log::warn!(
"PUS {}({}) error: {:?}",
Self::SERVICE_ID,
Self::SERVICE_STR,
e
);
// To avoid permanent loops on continuous errors.
return HandlingStatus::Empty;
}
match res.unwrap() {
PusPacketHandlerResult::RequestHandled => {
info!("Received PUS ping command TC[17,1]");
info!("Sent ping reply PUS TM[17,2]");
DirectPusPacketHandlerResult::Handled(handling_status) => {
if handling_status == HandlingStatus::HandledOne {
info!("Received PUS ping command TC[17,1]");
info!("Sent ping reply PUS TM[17,2]");
}
return handling_status;
}
PusPacketHandlerResult::RequestHandledPartialSuccess(partial_err) => {
warn!(
"Handled PUS ping command with partial success: {:?}",
partial_err
DirectPusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
log::warn!(
"PUS {}({}) subservice {} not implemented",
Self::SERVICE_ID,
Self::SERVICE_STR,
subservice
);
}
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
warn!("PUS17: Subservice {subservice} not implemented")
}
PusPacketHandlerResult::CustomSubservice(subservice, token) => {
DirectPusPacketHandlerResult::CustomSubservice(subservice, token) => {
let (tc, _) = PusTcReader::new(
self.pus17_handler
self.handler
.service_helper
.tc_in_mem_converter
.tc_slice_raw(),
)
.unwrap();
let time_stamper = TimeProvider::from_now_with_u16_days().unwrap();
let mut stamp_buf: [u8; 7] = [0; 7];
time_stamper.write_to_bytes(&mut stamp_buf).unwrap();
if subservice == 128 {
info!("Generating test event");
self.test_srv_event_sender
.send((TEST_EVENT.into(), None))
info!("generating test event");
self.event_tx
.send(EventMessage::new(PUS_TEST_SERVICE.id(), TEST_EVENT.into()))
.expect("Sending test event failed");
let start_token = self
.pus17_handler
.service_helper
.common
.verification_handler
.start_success(token, &stamp_buf)
.expect("Error sending start success");
self.pus17_handler
.service_helper
.common
.verification_handler
.completion_success(start_token, &stamp_buf)
.expect("Error sending completion success");
match self.handler.service_helper.verif_reporter().start_success(
self.handler.service_helper.tm_sender(),
token,
timestamp,
) {
Ok(started_token) => {
if let Err(e) = self
.handler
.service_helper
.verif_reporter()
.completion_success(
self.handler.service_helper.tm_sender(),
started_token,
timestamp,
)
{
error_handler(&PartialPusHandlingError::Verification(e));
}
}
Err(e) => {
error_handler(&PartialPusHandlingError::Verification(e));
}
}
} else {
let fail_data = [tc.subservice()];
self.pus17_handler
self.handler
.service_helper
.common
.verification_handler
.verif_reporter()
.start_failure(
self.handler.service_helper.tm_sender(),
token,
FailParams::new(
&stamp_buf,
timestamp,
&tmtc_err::INVALID_PUS_SUBSERVICE,
&fail_data,
),
@ -177,10 +160,7 @@ impl<
.expect("Sending start failure verification failed");
}
}
PusPacketHandlerResult::Empty => {
return true;
}
}
false
HandlingStatus::HandledOne
}
}

View File

@ -1,45 +0,0 @@
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone)]
pub enum GenericSendError {
RxDisconnected,
QueueFull(Option<u32>),
}
impl Display for GenericSendError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
GenericSendError::RxDisconnected => {
write!(f, "rx side has disconnected")
}
GenericSendError::QueueFull(max_cap) => {
write!(f, "queue with max capacity of {max_cap:?} is full")
}
}
}
}
#[cfg(feature = "std")]
impl Error for GenericSendError {}
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone)]
pub enum GenericRecvError {
Empty,
TxDisconnected,
}
impl Display for GenericRecvError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::TxDisconnected => {
write!(f, "tx side has disconnected")
}
Self::Empty => {
write!(f, "nothing to receive")
}
}
}
}
#[cfg(feature = "std")]
impl Error for GenericRecvError {}

View File

@ -1,94 +1,152 @@
use std::collections::HashMap;
use std::sync::mpsc;
use derive_new::new;
use log::warn;
use satrs::action::ActionRequest;
use satrs::hk::HkRequest;
use satrs::mode::ModeRequest;
use satrs::pus::action::PusActionRequestRouter;
use satrs::pus::hk::PusHkRequestRouter;
use satrs::pus::verification::{TcStateAccepted, VerificationToken};
use satrs::pus::GenericRoutingError;
use satrs::pus::verification::{
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
};
use satrs::pus::{ActiveRequestProvider, EcssTmSender, GenericRoutingError, PusRequestRouter};
use satrs::queue::GenericSendError;
use satrs::TargetId;
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusPacket;
use satrs::ComponentId;
use satrs_example::config::components::PUS_ROUTING_SERVICE;
use satrs_example::config::tmtc_err;
#[allow(dead_code)]
#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Request {
pub enum CompositeRequest {
Hk(HkRequest),
Mode(ModeRequest),
Action(ActionRequest),
}
#[derive(Clone, Eq, PartialEq, Debug, new)]
pub struct TargetedRequest {
pub(crate) target_id: TargetId,
pub(crate) request: Request,
#[derive(Clone)]
pub struct GenericRequestRouter {
pub id: ComponentId,
// All messages which do not have a dedicated queue.
pub composite_router_map: HashMap<ComponentId, mpsc::Sender<GenericMessage<CompositeRequest>>>,
pub mode_router_map: HashMap<ComponentId, mpsc::Sender<GenericMessage<ModeRequest>>>,
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct RequestWithToken {
pub(crate) targeted_request: TargetedRequest,
pub(crate) token: VerificationToken<TcStateAccepted>,
}
impl RequestWithToken {
pub fn new(
target_id: TargetId,
request: Request,
token: VerificationToken<TcStateAccepted>,
) -> Self {
impl Default for GenericRequestRouter {
fn default() -> Self {
Self {
targeted_request: TargetedRequest::new(target_id, request),
token,
id: PUS_ROUTING_SERVICE.raw(),
composite_router_map: Default::default(),
mode_router_map: Default::default(),
}
}
}
#[derive(Default, Clone)]
pub struct GenericRequestRouter(pub HashMap<TargetId, mpsc::Sender<RequestWithToken>>);
impl PusHkRequestRouter for GenericRequestRouter {
impl GenericRequestRouter {
pub(crate) fn handle_error_generic(
&self,
active_request: &impl ActiveRequestProvider,
tc: &PusTcReader,
error: GenericRoutingError,
tm_sender: &(impl EcssTmSender + ?Sized),
verif_reporter: &impl VerificationReportingProvider,
time_stamp: &[u8],
) {
warn!(
"Routing request for service {} failed: {error:?}",
tc.service()
);
let accepted_token: VerificationToken<TcStateAccepted> = active_request
.token()
.try_into()
.expect("token is not in accepted state");
match error {
GenericRoutingError::UnknownTargetId(id) => {
let apid_target_id = UniqueApidTargetId::from(id);
warn!("Target APID for request: {}", apid_target_id.apid);
warn!("Target Unique ID for request: {}", apid_target_id.unique_id);
let mut fail_data: [u8; 8] = [0; 8];
fail_data.copy_from_slice(&id.to_be_bytes());
verif_reporter
.completion_failure(
tm_sender,
accepted_token,
FailParams::new(time_stamp, &tmtc_err::UNKNOWN_TARGET_ID, &fail_data),
)
.expect("Sending start failure failed");
}
GenericRoutingError::Send(_) => {
let mut fail_data: [u8; 8] = [0; 8];
fail_data.copy_from_slice(&active_request.target_id().to_be_bytes());
verif_reporter
.completion_failure(
tm_sender,
accepted_token,
FailParams::new(time_stamp, &tmtc_err::ROUTING_ERROR, &fail_data),
)
.expect("Sending start failure failed");
}
}
}
}
impl PusRequestRouter<HkRequest> for GenericRequestRouter {
type Error = GenericRoutingError;
fn route(
&self,
target_id: TargetId,
requestor_info: MessageMetadata,
target_id: ComponentId,
hk_request: HkRequest,
token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error> {
if let Some(sender) = self.0.get(&target_id) {
if let Some(sender) = self.composite_router_map.get(&target_id) {
sender
.send(RequestWithToken::new(
target_id,
Request::Hk(hk_request),
token,
.send(GenericMessage::new(
requestor_info,
CompositeRequest::Hk(hk_request),
))
.map_err(|_| GenericRoutingError::SendError(GenericSendError::RxDisconnected))?;
.map_err(|_| GenericRoutingError::Send(GenericSendError::RxDisconnected))?;
return Ok(());
}
Ok(())
Err(GenericRoutingError::UnknownTargetId(target_id))
}
}
impl PusActionRequestRouter for GenericRequestRouter {
impl PusRequestRouter<ActionRequest> for GenericRequestRouter {
type Error = GenericRoutingError;
fn route(
&self,
target_id: TargetId,
requestor_info: MessageMetadata,
target_id: ComponentId,
action_request: ActionRequest,
token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error> {
if let Some(sender) = self.0.get(&target_id) {
if let Some(sender) = self.composite_router_map.get(&target_id) {
sender
.send(RequestWithToken::new(
target_id,
Request::Action(action_request),
token,
.send(GenericMessage::new(
requestor_info,
CompositeRequest::Action(action_request),
))
.map_err(|_| GenericRoutingError::SendError(GenericSendError::RxDisconnected))?;
.map_err(|_| GenericRoutingError::Send(GenericSendError::RxDisconnected))?;
return Ok(());
}
Ok(())
Err(GenericRoutingError::UnknownTargetId(target_id))
}
}
impl PusRequestRouter<ModeRequest> for GenericRequestRouter {
type Error = GenericRoutingError;
fn route(
&self,
requestor_info: MessageMetadata,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), Self::Error> {
if let Some(sender) = self.mode_router_map.get(&target_id) {
sender
.send(GenericMessage::new(requestor_info, request))
.map_err(|_| GenericRoutingError::Send(GenericSendError::RxDisconnected))?;
return Ok(());
}
Err(GenericRoutingError::UnknownTargetId(target_id))
}
}

View File

@ -1,133 +0,0 @@
use std::{
collections::VecDeque,
sync::{Arc, Mutex},
};
use log::{info, warn};
use satrs::{
hal::std::tcp_server::{ServerConfig, TcpSpacepacketsServer},
pus::ReceivesEcssPusTc,
spacepackets::PacketId,
tmtc::{CcsdsDistributor, CcsdsError, ReceivesCcsdsTc, TmPacketSourceCore},
};
use satrs_example::config::PUS_APID;
use crate::ccsds::CcsdsReceiver;
pub const PACKET_ID_LOOKUP: &[PacketId] = &[PacketId::const_tc(true, PUS_APID)];
#[derive(Default, Clone)]
pub struct SyncTcpTmSource {
tm_queue: Arc<Mutex<VecDeque<Vec<u8>>>>,
max_packets_stored: usize,
pub silent_packet_overwrite: bool,
}
impl SyncTcpTmSource {
pub fn new(max_packets_stored: usize) -> Self {
Self {
tm_queue: Arc::default(),
max_packets_stored,
silent_packet_overwrite: true,
}
}
pub fn add_tm(&mut self, tm: &[u8]) {
let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failec");
if tm_queue.len() > self.max_packets_stored {
if !self.silent_packet_overwrite {
warn!("TPC TM source is full, deleting oldest packet");
}
tm_queue.pop_front();
}
tm_queue.push_back(tm.to_vec());
}
}
impl TmPacketSourceCore for SyncTcpTmSource {
type Error = ();
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
let mut tm_queue = self.tm_queue.lock().expect("locking tm queue failed");
if !tm_queue.is_empty() {
let next_vec = tm_queue.front().unwrap();
if buffer.len() < next_vec.len() {
panic!(
"provided buffer too small, must be at least {} bytes",
next_vec.len()
);
}
let next_vec = tm_queue.pop_front().unwrap();
buffer[0..next_vec.len()].copy_from_slice(&next_vec);
if next_vec.len() > 9 {
let service = next_vec[7];
let subservice = next_vec[8];
info!("Sending PUS TM[{service},{subservice}]")
} else {
info!("Sending PUS TM");
}
return Ok(next_vec.len());
}
Ok(0)
}
}
pub type TcpServerType<TcSource, MpscErrorType> = TcpSpacepacketsServer<
(),
CcsdsError<MpscErrorType>,
SyncTcpTmSource,
CcsdsDistributor<CcsdsReceiver<TcSource, MpscErrorType>, MpscErrorType>,
>;
pub struct TcpTask<
TcSource: ReceivesCcsdsTc<Error = MpscErrorType>
+ ReceivesEcssPusTc<Error = MpscErrorType>
+ Clone
+ Send
+ 'static,
MpscErrorType: 'static,
> {
server: TcpServerType<TcSource, MpscErrorType>,
}
impl<
TcSource: ReceivesCcsdsTc<Error = MpscErrorType>
+ ReceivesEcssPusTc<Error = MpscErrorType>
+ Clone
+ Send
+ 'static,
MpscErrorType: 'static + core::fmt::Debug,
> TcpTask<TcSource, MpscErrorType>
{
pub fn new(
cfg: ServerConfig,
tm_source: SyncTcpTmSource,
tc_receiver: CcsdsDistributor<CcsdsReceiver<TcSource, MpscErrorType>, MpscErrorType>,
) -> Result<Self, std::io::Error> {
Ok(Self {
server: TcpSpacepacketsServer::new(
cfg,
tm_source,
tc_receiver,
Box::new(PACKET_ID_LOOKUP),
)?,
})
}
pub fn periodic_operation(&mut self) {
loop {
let result = self.server.handle_next_connection();
match result {
Ok(conn_result) => {
info!(
"Served {} TMs and {} TCs for client {:?}",
conn_result.num_sent_tms, conn_result.num_received_tcs, conn_result.addr
);
}
Err(e) => {
warn!("TCP server error: {e:?}");
}
}
}
}
}

View File

@ -1,213 +0,0 @@
use log::warn;
use satrs::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
};
use satrs::pus::{EcssTcAndToken, ReceivesEcssPusTc};
use satrs::spacepackets::SpHeader;
use std::sync::mpsc::{self, Receiver, SendError, Sender, SyncSender, TryRecvError};
use thiserror::Error;
use crate::pus::PusReceiver;
use satrs::pool::{PoolProvider, SharedStaticMemoryPool, StoreAddr, StoreError};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::PusPacket;
use satrs::tmtc::ReceivesCcsdsTc;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum MpscStoreAndSendError {
#[error("Store error: {0}")]
Store(#[from] StoreError),
#[error("TC send error: {0}")]
TcSend(#[from] SendError<EcssTcAndToken>),
#[error("TMTC send error: {0}")]
TmTcSend(#[from] SendError<StoreAddr>),
}
#[derive(Clone)]
pub struct SharedTcPool {
pub pool: SharedStaticMemoryPool,
}
impl SharedTcPool {
pub fn add_pus_tc(&mut self, pus_tc: &PusTcReader) -> Result<StoreAddr, StoreError> {
let mut pg = self.pool.write().expect("error locking TC store");
let addr = pg.free_element(pus_tc.len_packed(), |buf| {
buf[0..pus_tc.len_packed()].copy_from_slice(pus_tc.raw_data());
})?;
Ok(addr)
}
}
#[derive(Clone)]
pub struct PusTcSourceProviderSharedPool {
pub tc_source: SyncSender<StoreAddr>,
pub shared_pool: SharedTcPool,
}
impl PusTcSourceProviderSharedPool {
#[allow(dead_code)]
pub fn clone_backing_pool(&self) -> SharedStaticMemoryPool {
self.shared_pool.pool.clone()
}
}
impl ReceivesEcssPusTc for PusTcSourceProviderSharedPool {
type Error = MpscStoreAndSendError;
fn pass_pus_tc(&mut self, _: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
let addr = self.shared_pool.add_pus_tc(pus_tc)?;
self.tc_source.send(addr)?;
Ok(())
}
}
impl ReceivesCcsdsTc for PusTcSourceProviderSharedPool {
type Error = MpscStoreAndSendError;
fn pass_ccsds(&mut self, _: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
let mut pool = self.shared_pool.pool.write().expect("locking pool failed");
let addr = pool.add(tc_raw)?;
drop(pool);
self.tc_source.send(addr)?;
Ok(())
}
}
// Newtype, can not implement necessary traits on MPSC sender directly because of orphan rules.
#[derive(Clone)]
pub struct PusTcSourceProviderDynamic(pub Sender<Vec<u8>>);
impl ReceivesEcssPusTc for PusTcSourceProviderDynamic {
type Error = SendError<Vec<u8>>;
fn pass_pus_tc(&mut self, _: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
self.0.send(pus_tc.raw_data().to_vec())?;
Ok(())
}
}
impl ReceivesCcsdsTc for PusTcSourceProviderDynamic {
type Error = mpsc::SendError<Vec<u8>>;
fn pass_ccsds(&mut self, _: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
self.0.send(tc_raw.to_vec())?;
Ok(())
}
}
// TC source components where static pools are the backing memory of the received telecommands.
pub struct TcSourceTaskStatic {
shared_tc_pool: SharedTcPool,
tc_receiver: Receiver<StoreAddr>,
tc_buf: [u8; 4096],
pus_receiver: PusReceiver<VerificationReporterWithSharedPoolMpscBoundedSender>,
}
impl TcSourceTaskStatic {
pub fn new(
shared_tc_pool: SharedTcPool,
tc_receiver: Receiver<StoreAddr>,
pus_receiver: PusReceiver<VerificationReporterWithSharedPoolMpscBoundedSender>,
) -> Self {
Self {
shared_tc_pool,
tc_receiver,
tc_buf: [0; 4096],
pus_receiver,
}
}
pub fn periodic_operation(&mut self) {
self.poll_tc();
}
pub fn poll_tc(&mut self) -> bool {
match self.tc_receiver.try_recv() {
Ok(addr) => {
let pool = self
.shared_tc_pool
.pool
.read()
.expect("locking tc pool failed");
pool.read(&addr, &mut self.tc_buf)
.expect("reading pool failed");
drop(pool);
match PusTcReader::new(&self.tc_buf) {
Ok((pus_tc, _)) => {
self.pus_receiver
.handle_tc_packet(
satrs::pus::TcInMemory::StoreAddr(addr),
pus_tc.service(),
&pus_tc,
)
.ok();
true
}
Err(e) => {
warn!("error creating PUS TC from raw data: {e}");
warn!("raw data: {:x?}", self.tc_buf);
true
}
}
}
Err(e) => match e {
TryRecvError::Empty => false,
TryRecvError::Disconnected => {
warn!("tmtc thread: sender disconnected");
false
}
},
}
}
}
// TC source components where the heap is the backing memory of the received telecommands.
pub struct TcSourceTaskDynamic {
pub tc_receiver: Receiver<Vec<u8>>,
pus_receiver: PusReceiver<VerificationReporterWithVecMpscSender>,
}
impl TcSourceTaskDynamic {
pub fn new(
tc_receiver: Receiver<Vec<u8>>,
pus_receiver: PusReceiver<VerificationReporterWithVecMpscSender>,
) -> Self {
Self {
tc_receiver,
pus_receiver,
}
}
pub fn periodic_operation(&mut self) {
self.poll_tc();
}
pub fn poll_tc(&mut self) -> bool {
match self.tc_receiver.try_recv() {
Ok(tc) => match PusTcReader::new(&tc) {
Ok((pus_tc, _)) => {
self.pus_receiver
.handle_tc_packet(
satrs::pus::TcInMemory::Vec(tc.clone()),
pus_tc.service(),
&pus_tc,
)
.ok();
true
}
Err(e) => {
warn!("error creating PUS TC from raw data: {e}");
warn!("raw data: {:x?}", tc);
true
}
},
Err(e) => match e {
TryRecvError::Empty => false,
TryRecvError::Disconnected => {
warn!("tmtc thread: sender disconnected");
false
}
},
}
}
}

View File

@ -0,0 +1,2 @@
pub mod tc_source;
pub mod tm_sink;

View File

@ -0,0 +1,107 @@
use satrs::{
pool::PoolProvider,
pus::HandlingStatus,
tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool},
};
use std::sync::mpsc::{self, TryRecvError};
use satrs::pus::MpscTmAsVecSender;
use crate::pus::PusTcDistributor;
// TC source components where static pools are the backing memory of the received telecommands.
pub struct TcSourceTaskStatic {
shared_tc_pool: SharedPacketPool,
tc_receiver: mpsc::Receiver<PacketInPool>,
tc_buf: [u8; 4096],
pus_distributor: PusTcDistributor<PacketSenderWithSharedPool>,
}
impl TcSourceTaskStatic {
pub fn new(
shared_tc_pool: SharedPacketPool,
tc_receiver: mpsc::Receiver<PacketInPool>,
pus_receiver: PusTcDistributor<PacketSenderWithSharedPool>,
) -> Self {
Self {
shared_tc_pool,
tc_receiver,
tc_buf: [0; 4096],
pus_distributor: pus_receiver,
}
}
pub fn periodic_operation(&mut self) {
self.poll_tc();
}
pub fn poll_tc(&mut self) -> HandlingStatus {
// Right now, we only expect ECSS PUS packets.
// If packets like CFDP are expected, we might have to check the APID first.
match self.tc_receiver.try_recv() {
Ok(packet_in_pool) => {
let pool = self
.shared_tc_pool
.0
.read()
.expect("locking tc pool failed");
pool.read(&packet_in_pool.store_addr, &mut self.tc_buf)
.expect("reading pool failed");
drop(pool);
self.pus_distributor
.handle_tc_packet_in_store(packet_in_pool, &self.tc_buf)
.ok();
HandlingStatus::HandledOne
}
Err(e) => match e {
TryRecvError::Empty => HandlingStatus::Empty,
TryRecvError::Disconnected => {
log::warn!("tmtc thread: sender disconnected");
HandlingStatus::Empty
}
},
}
}
}
// TC source components where the heap is the backing memory of the received telecommands.
pub struct TcSourceTaskDynamic {
pub tc_receiver: mpsc::Receiver<PacketAsVec>,
pus_distributor: PusTcDistributor<MpscTmAsVecSender>,
}
impl TcSourceTaskDynamic {
pub fn new(
tc_receiver: mpsc::Receiver<PacketAsVec>,
pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
) -> Self {
Self {
tc_receiver,
pus_distributor: pus_receiver,
}
}
pub fn periodic_operation(&mut self) {
self.poll_tc();
}
pub fn poll_tc(&mut self) -> HandlingStatus {
// Right now, we only expect ECSS PUS packets.
// If packets like CFDP are expected, we might have to check the APID first.
match self.tc_receiver.try_recv() {
Ok(packet_as_vec) => {
self.pus_distributor
.handle_tc_packet_vec(packet_as_vec)
.ok();
HandlingStatus::HandledOne
}
Err(e) => match e {
TryRecvError::Empty => HandlingStatus::Empty,
TryRecvError::Disconnected => {
log::warn!("tmtc thread: sender disconnected");
HandlingStatus::Empty
}
},
}
}
}

View File

@ -4,18 +4,18 @@ use std::{
};
use log::info;
use satrs::tmtc::{PacketAsVec, PacketInPool, SharedPacketPool};
use satrs::{
pool::{PoolProvider, StoreAddr},
pool::PoolProvider,
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
spacepackets::{
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
time::cds::MIN_CDS_FIELD_LEN,
CcsdsPacket,
},
tmtc::tm_helper::SharedTmPool,
};
use crate::tcp::SyncTcpTmSource;
use crate::interface::tcp::SyncTcpTmSource;
#[derive(Default)]
pub struct CcsdsSeqCounterMap {
@ -70,23 +70,28 @@ impl TmFunnelCommon {
}
fn packet_printout(tm: &PusTmZeroCopyWriter) {
info!("Sending PUS TM[{},{}]", tm.service(), tm.subservice());
info!(
"Sending PUS TM[{},{}] with APID {}",
tm.service(),
tm.subservice(),
tm.apid()
);
}
}
pub struct TmFunnelStatic {
pub struct TmSinkStatic {
common: TmFunnelCommon,
shared_tm_store: SharedTmPool,
tm_funnel_rx: mpsc::Receiver<StoreAddr>,
tm_server_tx: mpsc::SyncSender<StoreAddr>,
shared_tm_store: SharedPacketPool,
tm_funnel_rx: mpsc::Receiver<PacketInPool>,
tm_server_tx: mpsc::SyncSender<PacketInPool>,
}
impl TmFunnelStatic {
impl TmSinkStatic {
pub fn new(
shared_tm_store: SharedTmPool,
shared_tm_store: SharedPacketPool,
sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: mpsc::Receiver<StoreAddr>,
tm_server_tx: mpsc::SyncSender<StoreAddr>,
tm_funnel_rx: mpsc::Receiver<PacketInPool>,
tm_server_tx: mpsc::SyncSender<PacketInPool>,
) -> Self {
Self {
common: TmFunnelCommon::new(sync_tm_tcp_source),
@ -97,14 +102,14 @@ impl TmFunnelStatic {
}
pub fn operation(&mut self) {
if let Ok(addr) = self.tm_funnel_rx.recv() {
if let Ok(pus_tm_in_pool) = self.tm_funnel_rx.recv() {
// Read the TM, set sequence counter and message counter, and finally update
// the CRC.
let shared_pool = self.shared_tm_store.clone_backing_pool();
let shared_pool = self.shared_tm_store.0.clone();
let mut pool_guard = shared_pool.write().expect("Locking TM pool failed");
let mut tm_copy = Vec::new();
pool_guard
.modify(&addr, |buf| {
.modify(&pus_tm_in_pool.store_addr, |buf| {
let zero_copy_writer = PusTmZeroCopyWriter::new(buf, MIN_CDS_FIELD_LEN)
.expect("Creating TM zero copy writer failed");
self.common.apply_packet_processing(zero_copy_writer);
@ -112,7 +117,7 @@ impl TmFunnelStatic {
})
.expect("Reading TM from pool failed");
self.tm_server_tx
.send(addr)
.send(pus_tm_in_pool)
.expect("Sending TM to server failed");
// We could also do this step in the update closure, but I'd rather avoid this, could
// lead to nested locking.
@ -121,17 +126,17 @@ impl TmFunnelStatic {
}
}
pub struct TmFunnelDynamic {
pub struct TmSinkDynamic {
common: TmFunnelCommon,
tm_funnel_rx: mpsc::Receiver<Vec<u8>>,
tm_server_tx: mpsc::Sender<Vec<u8>>,
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
tm_server_tx: mpsc::Sender<PacketAsVec>,
}
impl TmFunnelDynamic {
impl TmSinkDynamic {
pub fn new(
sync_tm_tcp_source: SyncTcpTmSource,
tm_funnel_rx: mpsc::Receiver<Vec<u8>>,
tm_server_tx: mpsc::Sender<Vec<u8>>,
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
tm_server_tx: mpsc::Sender<PacketAsVec>,
) -> Self {
Self {
common: TmFunnelCommon::new(sync_tm_tcp_source),
@ -144,13 +149,13 @@ impl TmFunnelDynamic {
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
// Read the TM, set sequence counter and message counter, and finally update
// the CRC.
let zero_copy_writer = PusTmZeroCopyWriter::new(&mut tm, MIN_CDS_FIELD_LEN)
let zero_copy_writer = PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN)
.expect("Creating TM zero copy writer failed");
self.common.apply_packet_processing(zero_copy_writer);
self.common.sync_tm_tcp_source.add_tm(&tm.packet);
self.tm_server_tx
.send(tm.clone())
.send(tm)
.expect("Sending TM to server failed");
self.common.sync_tm_tcp_source.add_tm(&tm);
}
}
}

View File

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.1.2] 2024-04-17
Allow `satrs-shared` from `v0.1.3` to `<v0.2`.
# [v0.1.1] 2024-02-17
- Bumped `spacepackets` to v0.10.0

View File

@ -1,6 +1,6 @@
[package]
name = "satrs-mib"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
rust-version = "1.61"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
@ -23,12 +23,12 @@ version = "1"
optional = true
[dependencies.satrs-shared]
version = "0.1.2"
version = ">=0.1.3, <0.2"
features = ["serde"]
[dependencies.satrs-mib-codegen]
path = "codegen"
version = "0.1.1"
version = "0.1.2"
[dependencies.serde]
version = "1"

View File

@ -1,6 +1,6 @@
[package]
name = "satrs-mib-codegen"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
description = "satrs-mib proc macro implementation"
homepage = "https://egit.irs.uni-stuttgart.de/rust/sat-rs"
@ -26,7 +26,9 @@ features = ["full"]
[dev-dependencies]
trybuild = { version = "1", features = ["diff"] }
satrs-shared = "0.1.2"
[dev-dependencies.satrs-shared]
version = ">=0.1.3, <0.2"
[dev-dependencies.satrs-mib]
path = ".."

View File

@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.1.4] 2024-04-24
## Added
- `ResultU16::from_be_bytes`
- `From<u16>` impl for `ResultU16`.
- Optional `defmt` support: `defmt::Format` impl on `ResultU16` if the `defmt` feature is
activated.
# [v0.1.3] 2024-04-16
Allow `spacepackets` range starting with v0.10 and v0.11.
# [v0.1.2] 2024-02-17
- Bumped `spacepackets` to v0.10.0 for `UnsignedEnum` trait change.

View File

@ -1,7 +1,7 @@
[package]
name = "satrs-shared"
description = "Components shared by multiple sat-rs crates"
version = "0.1.2"
version = "0.1.4"
edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
@ -17,12 +17,17 @@ version = "1"
default-features = false
optional = true
[dependencies.defmt]
version = "0.3"
optional = true
[dependencies.spacepackets]
version = "0.10"
version = ">0.9, <=0.11"
default-features = false
[features]
serde = ["dep:serde", "spacepackets/serde"]
spacepackets = ["dep:defmt", "spacepackets/defmt"]
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"]
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]

View File

@ -1,3 +1,4 @@
//! This crates contains modules shared among other sat-rs framework crates.
#![no_std]
#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
pub mod res_code;

View File

@ -7,6 +7,7 @@ use spacepackets::ByteConversionError;
/// Simple [u16] based result code type which also allows to group related resultcodes.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ResultU16 {
group_id: u8,
unique_id: u8,
@ -19,15 +20,28 @@ impl ResultU16 {
unique_id,
}
}
pub fn raw(&self) -> u16 {
((self.group_id as u16) << 8) | self.unique_id as u16
}
pub fn group_id(&self) -> u8 {
self.group_id
}
pub fn unique_id(&self) -> u8 {
self.unique_id
}
pub fn from_be_bytes(bytes: [u8; 2]) -> Self {
Self::from(u16::from_be_bytes(bytes))
}
}
impl From<u16> for ResultU16 {
fn from(value: u16) -> Self {
Self::new(((value >> 8) & 0xff) as u8, (value & 0xff) as u8)
}
}
impl From<ResultU16> for EcssEnumU16 {
@ -84,5 +98,14 @@ mod tests {
assert_eq!(written, 2);
assert_eq!(buf[0], 1);
assert_eq!(buf[1], 1);
let read_back = ResultU16::from_be_bytes(buf);
assert_eq!(read_back, result_code);
}
#[test]
fn test_from_u16() {
let result_code = ResultU16::new(1, 1);
let result_code_2 = ResultU16::from(result_code.raw());
assert_eq!(result_code, result_code_2);
}
}

View File

@ -8,8 +8,93 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.2.0] 2024-05-02
## Changed
- Various improvements for the PUS stack components.
## Added
- Added `HandlingStatus` enumeration.
# [v0.2.0-rc.5] 2024-04-24
## Added
- Optional `defmt::Format` support for the event types, if the `defmt` feature is activated.
## Changed
- Removed `MpscEventReceiver`, the `EventReceiveProvider` trait is implemented directly
on `mpsc::Receiver<EventMessage<Event>>`
- Renamed `PusEventDispatcher` to `PusEventTmCreatorWithMap`.
- Renamed `DefaultPusEventU32Dispatcher` to `DefaultPusEventU32EventCreator`.
- Renamed `PusEventMgmtBackendProvider` renamed to `PusEventReportingMap`.
- Reanmed Event `const_new` methods to `new` and the former `new` methods to `new_checked`
# [v0.2.0-rc.4] 2024-04-23
## Changed
- The `parse_for_ccsds_space_packets` method now expects a non-mutable slice and does not copy
broken tail packets anymore. It also does not expect a mutable `next_write_idx` argument anymore.
Instead, a `ParseResult` structure is returned which contains the `packets_found` and an
optional `incomplete_tail_start` value.
## Fixed
- `parse_for_ccsds_space_packets` did not detect CCSDS space packets at the buffer end with the
smallest possible size of 7 bytes.
- TCP server component now re-registers the internal `mio::Poll` object if the client reset
the connection unexpectedly. Not doing so prevented the server from functioning properly
after a re-connect.
# [v0.2.0-rc.3] 2024-04-17
docs-rs hotfix 2
# [v0.2.0-rc.2] 2024-04-17
docs-rs hotfix
# [v0.2.0-rc.1] 2024-04-17
- `spacepackets` v0.11
## Added
- Added `params::WritableToBeBytes::to_vec`.
- New `ComponentId` (`u64` typedef for now) which replaces former `TargetId` as a generic
way to identify components.
- Various abstraction and objects for targeted requests. This includes mode request/reply
types for actions, HK and modes.
- `VerificationReportingProvider::owner_id` method.
- Introduced generic `EventMessage` which is generic over the event type and the additional
parameter type. This message also contains the sender ID which can be useful for debugging
or application layer / FDIR logic.
- Stop signal handling for the TCP servers.
- TCP server now uses `mio` crate to allow non-blocking operation. The server can now handle
multiple connections at once, and the context information about handled transfers is
passed via a callback which is inserted as a generic as well.
## Changed
- Renamed `ReceivesTcCore` to `PacketSenderRaw` to better show its primary purpose. It now contains
a `send_raw_tc` method which is not mutable anymore.
- Renamed `TmPacketSourceCore` to `TmPacketSource`.
- Renamed `EcssTmSenderCore` to `EcssTmSender`.
- Renamed `StoreAddr` to `PoolAddr`.
- Reanmed `StoreError` to `PoolError`.
- TCP server generics order. The error generics come last now.
- `encoding::ccsds::PacketIdValidator` renamed to `ValidatorU16Id`, which lives in the crate root.
It can be used for both CCSDS packet ID and CCSDS APID validation.
- `EventManager::try_event_handling` not expects a mutable error handling closure instead of
returning the occured errors.
- Renamed `EventManagerBase` to `EventReportCreator`
- Renamed `VerificationReporterCore` to `VerificationReportCreator`.
- Removed `VerificationReporterCore`. The high-level API exposed by `VerificationReporter` and
the low level API exposed by `VerificationReportCreator` should be sufficient for all use-cases.
- Refactored `EventManager` to heavily use generics instead of trait objects.
- `SendEventProvider` -> `EventSendProvider`. `id` trait method renamed to `channel_id`.
- `ListenerTable` -> `ListenerMapProvider`
@ -18,16 +103,40 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Refactored ECSS TM sender abstractions to be generic over different message queue backends.
- Refactored Verification Reporter abstractions and implementation to be generic over the sender
instead of using trait objects.
- Renamed `WritableToBeBytes::raw_len` to `WritableToBeBytes::written_len` for consistency.
- `PusServiceProvider` renamed to `PusServiceDistributor` to make the purpose of the object
more clear
- `PusServiceProvider::handle_pus_tc_packet` renamed to `PusServiceDistributor::distribute_packet`.
- `PusServiceDistibutor` and `CcsdsDistributor` now use generics instead of trait objects.
This makes accessing the concrete trait implementations more easy as well.
- Major overhaul of the PUS handling module.
- Replace `TargetId` by `ComponentId`.
- Replace most usages of `ChannelId` by `ComponentId`. A dedicated channel ID has limited usage
due to the nature of typed channels in Rust.
- `CheckTimer` renamed to `CountdownProvider`.
- Renamed `TargetId` to `ComponentId`.
- Replaced most `ChannelId` occurences with `ComponentId`. For typed channels, there is generally
no need for dedicated channel IDs.
- Changed `params::WritableToBeBytes::raw_len` to `written_len` for consistency.
- `EventReporter` caches component ID.
- Renamed `PusService11SchedHandler` to `PusSchedServiceHandler`.
- Fixed general naming of PUS handlers from `handle_one_tc` to `poll_and_handle_next_tc`.
- Reworked verification module: The sender (`impl EcssTmSenderCore`)
now needs to be passed explicitely to the `VerificationReportingProvider` abstraction. This
allows easier sharing of the TM sender component.
## Fixed
- Update deprecated API for `PusScheduler::insert_wrapped_tc_cds_short`
and `PusScheduler::insert_wrapped_tc_cds_long`.
- `EventReporter` uses interior mutability pattern to allow non-mutable API.
## Removed
- Remove `objects` module.
- Removed CCSDS and PUS distributor modules. Their worth is questionable in an architecture
where routing traits are sufficient and the core logic to demultiplex and distribute packets
is simple enough to be application code.
# [v0.2.0-rc.0] 2024-02-21

View File

@ -1,8 +1,8 @@
[package]
name = "satrs"
version = "0.2.0-rc.0"
version = "0.2.0"
edition = "2021"
rust-version = "1.61"
rust-version = "1.71.1"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "A framework to build software for remote systems"
homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/"
@ -15,14 +15,31 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
[dependencies]
delegate = ">0.7, <=0.10"
paste = "1"
derive-new = "0.6"
smallvec = "1"
crc = "3"
satrs-shared = "0.1.2"
[dependencies.satrs-shared]
version = ">=0.1.3, <0.2"
[dependencies.num_enum]
version = ">0.5, <=0.7"
default-features = false
[dependencies.spacepackets]
version = "0.11"
default-features = false
[dependencies.cobs]
git = "https://github.com/robamu/cobs.rs.git"
version = "0.2.3"
branch = "all_features"
default-features = false
[dependencies.num-traits]
version = "0.2"
default-features = false
[dependencies.dyn-clone]
version = "1"
optional = true
@ -35,10 +52,6 @@ optional = true
version = "0.7"
optional = true
[dependencies.num-traits]
version = "0.2"
default-features = false
[dependencies.downcast-rs]
version = "1.2"
default-features = false
@ -67,20 +80,19 @@ version = "0.5.4"
features = ["all"]
optional = true
[dependencies.spacepackets]
version = "0.10"
default-features = false
[dependencies.mio]
version = "0.8"
features = ["os-poll", "net"]
optional = true
[dependencies.cobs]
git = "https://github.com/robamu/cobs.rs.git"
version = "0.2.3"
branch = "all_features"
default-features = false
[dependencies.defmt]
version = "0.3"
optional = true
[dev-dependencies]
serde = "1"
zerocopy = "0.7"
once_cell = "1.13"
once_cell = "1"
serde_json = "1"
rand = "0.8"
tempfile = "3"
@ -100,7 +112,8 @@ std = [
"spacepackets/std",
"num_enum/std",
"thiserror",
"socket2"
"socket2",
"mio"
]
alloc = [
"serde/alloc",
@ -112,8 +125,10 @@ alloc = [
serde = ["dep:serde", "spacepackets/serde", "satrs-shared/serde"]
crossbeam = ["crossbeam-channel"]
heapless = ["dep:heapless"]
defmt = ["dep:defmt", "spacepackets/defmt"]
test_util = []
doc-images = []
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"]
rustdoc-args = ["--cfg", "docs_rs", "--generate-link-to-definition"]

View File

@ -4,11 +4,11 @@ Checklist for new releases
# Pre-Release
1. Make sure any new modules are documented sufficiently enough and check docs with
`cargo +nightly doc --all-features --config 'rustdocflags=["--cfg", "doc_cfg"]' --open`.
`cargo +nightly doc --all-features --config 'build.rustdocflags=["--cfg", "docs_rs"]' --open`.
2. Bump version specifier in `Cargo.toml`.
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
`unreleased` section.
4. Run `cargo test --all-features`.
4. Run `cargo test --all-features` or `cargo nextest r --all-features` and `cargo test --doc`.
5. Run `cargo fmt` and `cargo clippy`. Check `cargo msrv` against MSRV in `Cargo.toml`.
6. Wait for CI/CD results for EGit and Github. These also check cross-compilation for bare-metal
targets.

View File

@ -1,63 +1,68 @@
use crate::{pool::StoreAddr, TargetId};
use crate::{params::Params, pool::PoolAddr};
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
pub type ActionId = u32;
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ActionRequest {
pub action_id: ActionId,
pub variant: ActionRequestVariant,
}
impl ActionRequest {
pub fn new(action_id: ActionId, variant: ActionRequestVariant) -> Self {
Self { action_id, variant }
}
}
#[non_exhaustive]
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum ActionRequest {
UnsignedIdAndStoreData {
action_id: ActionId,
data_addr: StoreAddr,
},
pub enum ActionRequestVariant {
NoData,
StoreData(PoolAddr),
#[cfg(feature = "alloc")]
UnsignedIdAndVecData {
action_id: ActionId,
data: alloc::vec::Vec<u8>,
},
#[cfg(feature = "alloc")]
StringIdAndVecData {
action_id: alloc::string::String,
data: alloc::vec::Vec<u8>,
},
#[cfg(feature = "alloc")]
StringIdAndStoreData {
action_id: alloc::string::String,
data: StoreAddr,
},
VecData(alloc::vec::Vec<u8>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TargetedActionRequest {
target: TargetId,
action_request: ActionRequest,
}
impl TargetedActionRequest {
pub fn new(target: TargetId, action_request: ActionRequest) -> Self {
Self {
target,
action_request,
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct ActionReply {
pub action_id: ActionId,
pub variant: ActionReplyVariant,
}
/// A reply to an action request.
#[non_exhaustive]
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum ActionReply {
CompletionFailed(ActionId),
StepFailed {
id: ActionId,
step: u32,
},
Completed(ActionId),
#[cfg(feature = "alloc")]
CompletedStringId(alloc::string::String),
#[cfg(feature = "alloc")]
CompletionFailedStringId(alloc::string::String),
#[cfg(feature = "alloc")]
StepFailedStringId {
id: alloc::string::String,
step: u32,
},
#[derive(Clone, Debug, PartialEq)]
pub enum ActionReplyVariant {
CompletionFailed(Params),
StepFailed { step: u32, reason: Params },
Completed,
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use super::*;
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ActionRequestStringId {
pub action_id: alloc::string::String,
pub variant: ActionRequestVariant,
}
impl ActionRequestStringId {
pub fn new(action_id: alloc::string::String, variant: ActionRequestVariant) -> Self {
Self { action_id, variant }
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct ActionReplyStringId {
pub action_id: alloc::string::String,
pub variant: ActionReplyVariant,
}
}
#[cfg(test)]
mod tests {}

View File

@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use super::{
filestore::{FilestoreError, VirtualFilestore},
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams},
CheckTimer, CheckTimerCreator, EntityType, LocalEntityConfig, PacketInfo, PacketTarget,
CheckTimerCreator, CountdownProvider, EntityType, LocalEntityConfig, PacketInfo, PacketTarget,
RemoteEntityConfig, RemoteEntityConfigProvider, State, TimerContext, TransactionId,
TransactionStep,
};
@ -54,7 +54,7 @@ struct TransferState {
completion_disposition: CompletionDisposition,
checksum: u32,
current_check_count: u32,
current_check_timer: Option<Box<dyn CheckTimer>>,
current_check_timer: Option<Box<dyn CountdownProvider>>,
}
impl Default for TransferState {
@ -799,9 +799,9 @@ mod tests {
};
use crate::cfdp::{
filestore::NativeFilestore, user::OwnedMetadataRecvdParams, CheckTimer, CheckTimerCreator,
DefaultFaultHandler, IndicationConfig, RemoteEntityConfig, StdRemoteEntityConfigProvider,
UserFaultHandler, CRC_32,
filestore::NativeFilestore, user::OwnedMetadataRecvdParams, CheckTimerCreator,
CountdownProvider, DefaultFaultHandler, IndicationConfig, RemoteEntityConfig,
StdRemoteEntityConfigProvider, UserFaultHandler, CRC_32,
};
use super::*;
@ -1057,7 +1057,7 @@ mod tests {
expired: Arc<AtomicBool>,
}
impl CheckTimer for TestCheckTimer {
impl CountdownProvider for TestCheckTimer {
fn has_expired(&self) -> bool {
self.expired.load(core::sync::atomic::Ordering::Relaxed)
}
@ -1088,7 +1088,10 @@ mod tests {
}
impl CheckTimerCreator for TestCheckTimerCreator {
fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box<dyn CheckTimer> {
fn get_check_timer_provider(
&self,
timer_context: TimerContext,
) -> Box<dyn CountdownProvider> {
match timer_context {
TimerContext::CheckLimit { .. } => {
Box::new(TestCheckTimer::new(self.check_limit_expired_flag.clone()))

View File

@ -17,6 +17,8 @@ use alloc::boxed::Box;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::time::CountdownProvider;
#[cfg(feature = "std")]
pub mod dest;
#[cfg(feature = "alloc")]
@ -45,7 +47,15 @@ pub enum TimerContext {
},
}
/// Generic abstraction for a check timer which is used by 3 mechanisms of the CFDP protocol.
/// A generic trait which allows CFDP entities to create check timers which are required to
/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
/// and 4.6.3.3.
///
/// This trait also allows the creation of different check timers depending on context and purpose
/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or
/// other factors.
///
/// The countdown timer is used by 3 mechanisms of the CFDP protocol.
///
/// ## 1. Check limit handling
///
@ -74,22 +84,9 @@ pub enum TimerContext {
/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in
/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer
/// interval of the remote entity configuration.
pub trait CheckTimer: Debug {
fn has_expired(&self) -> bool;
fn reset(&mut self);
}
/// A generic trait which allows CFDP entities to create check timers which are required to
/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
/// and 4.6.3.3. The [CheckTimer] documentation provides more information about the purpose of the
/// check timer in the context of CFDP.
///
/// This trait also allows the creation of different check timers depending on context and purpose
/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or
/// other factors.
#[cfg(feature = "alloc")]
pub trait CheckTimerCreator {
fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box<dyn CheckTimer>;
fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box<dyn CountdownProvider>;
}
/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime.
@ -112,7 +109,7 @@ impl StdCheckTimer {
}
#[cfg(feature = "std")]
impl CheckTimer for StdCheckTimer {
impl CountdownProvider for StdCheckTimer {
fn has_expired(&self) -> bool {
let elapsed_time = self.start_time.elapsed();
if elapsed_time.as_secs() > self.expiry_time_seconds {

View File

@ -1,163 +1,163 @@
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
use hashbrown::HashSet;
use spacepackets::PacketId;
use spacepackets::{CcsdsPacket, SpHeader};
use crate::tmtc::ReceivesTcCore;
use crate::{tmtc::PacketSenderRaw, ComponentId};
pub trait PacketIdLookup {
fn validate(&self, packet_id: u16) -> bool;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SpValidity {
Valid,
/// The space packet can be assumed to have a valid format, but the packet should
/// be skipped.
Skip,
/// The space packet or space packet header has an invalid format, for example a CRC check
/// failed. In that case, the parser loses the packet synchronization and needs to check for
/// the start of a new space packet header start again. The space packet header
/// [spacepackets::PacketId] can be used as a synchronization marker to detect the start
/// of a possible valid packet again.
Invalid,
}
#[cfg(feature = "alloc")]
impl PacketIdLookup for Vec<u16> {
fn validate(&self, packet_id: u16) -> bool {
self.contains(&packet_id)
}
/// Simple trait to allow user code to check the validity of a space packet.
pub trait SpacePacketValidator {
fn validate(&self, sp_header: &SpHeader, raw_buf: &[u8]) -> SpValidity;
}
#[cfg(feature = "alloc")]
impl PacketIdLookup for HashSet<u16> {
fn validate(&self, packet_id: u16) -> bool {
self.contains(&packet_id)
}
}
impl PacketIdLookup for [u16] {
fn validate(&self, packet_id: u16) -> bool {
self.binary_search(&packet_id).is_ok()
}
}
impl PacketIdLookup for &[u16] {
fn validate(&self, packet_id: u16) -> bool {
self.binary_search(&packet_id).is_ok()
}
}
#[cfg(feature = "alloc")]
impl PacketIdLookup for Vec<PacketId> {
fn validate(&self, packet_id: u16) -> bool {
self.contains(&PacketId::from(packet_id))
}
}
#[cfg(feature = "alloc")]
impl PacketIdLookup for HashSet<PacketId> {
fn validate(&self, packet_id: u16) -> bool {
self.contains(&PacketId::from(packet_id))
}
}
impl PacketIdLookup for [PacketId] {
fn validate(&self, packet_id: u16) -> bool {
self.binary_search(&PacketId::from(packet_id)).is_ok()
}
}
impl PacketIdLookup for &[PacketId] {
fn validate(&self, packet_id: u16) -> bool {
self.binary_search(&PacketId::from(packet_id)).is_ok()
}
#[derive(Default, Debug, PartialEq, Eq)]
pub struct ParseResult {
pub packets_found: u32,
/// If an incomplete space packet was found, its start index is indicated by this value.
pub incomplete_tail_start: Option<usize>,
}
/// This function parses a given buffer for tightly packed CCSDS space packets. It uses the
/// [PacketId] field of the CCSDS packets to detect the start of a CCSDS space packet and then
/// uses the length field of the packet to extract CCSDS packets.
/// [spacepackets::SpHeader] of the CCSDS packets and a user provided [SpacePacketValidator]
/// to check whether a received space packet is relevant for processing.
///
/// This function is also able to deal with broken tail packets at the end as long a the parser
/// can read the full 7 bytes which constitue a space packet header plus one byte minimal size.
/// If broken tail packets are detected, they are moved to the front of the buffer, and the write
/// index for future write operations will be written to the `next_write_idx` argument.
///
/// The parser will write all packets which were decoded successfully to the given `tc_receiver`
/// and return the number of packets found. If the [ReceivesTcCore::pass_tc] calls fails, the
/// error will be returned.
pub fn parse_buffer_for_ccsds_space_packets<E>(
buf: &mut [u8],
packet_id_lookup: &(impl PacketIdLookup + ?Sized),
tc_receiver: &mut (impl ReceivesTcCore<Error = E> + ?Sized),
next_write_idx: &mut usize,
) -> Result<u32, E> {
*next_write_idx = 0;
let mut packets_found = 0;
/// The parses will behave differently based on the [SpValidity] returned from the user provided
/// [SpacePacketValidator]:
///
/// 1. [SpValidity::Valid]: The parser will forward all packets to the given `packet_sender` and
/// return the number of packets found.If the [PacketSenderRaw::send_packet] calls fails, the
/// error will be returned.
/// 2. [SpValidity::Invalid]: The parser assumes that the synchronization is lost and tries to
/// find the start of a new space packet header by scanning all the following bytes.
/// 3. [SpValidity::Skip]: The parser skips the packet using the packet length determined from the
/// space packet header.
pub fn parse_buffer_for_ccsds_space_packets<SendError>(
buf: &[u8],
packet_validator: &(impl SpacePacketValidator + ?Sized),
sender_id: ComponentId,
packet_sender: &(impl PacketSenderRaw<Error = SendError> + ?Sized),
) -> Result<ParseResult, SendError> {
let mut parse_result = ParseResult::default();
let mut current_idx = 0;
let buf_len = buf.len();
loop {
if current_idx + 7 >= buf.len() {
if current_idx + 7 > buf.len() {
break;
}
let packet_id = u16::from_be_bytes(buf[current_idx..current_idx + 2].try_into().unwrap());
if packet_id_lookup.validate(packet_id) {
let length_field =
u16::from_be_bytes(buf[current_idx + 4..current_idx + 6].try_into().unwrap());
let packet_size = length_field + 7;
if (current_idx + packet_size as usize) <= buf_len {
tc_receiver.pass_tc(&buf[current_idx..current_idx + packet_size as usize])?;
packets_found += 1;
} else {
// Move packet to start of buffer if applicable.
if current_idx > 0 {
buf.copy_within(current_idx.., 0);
*next_write_idx = buf.len() - current_idx;
let sp_header = SpHeader::from_be_bytes(&buf[current_idx..]).unwrap().0;
match packet_validator.validate(&sp_header, &buf[current_idx..]) {
SpValidity::Valid => {
let packet_size = sp_header.total_len();
if (current_idx + packet_size) <= buf_len {
packet_sender
.send_packet(sender_id, &buf[current_idx..current_idx + packet_size])?;
parse_result.packets_found += 1;
} else {
// Move packet to start of buffer if applicable.
parse_result.incomplete_tail_start = Some(current_idx);
}
current_idx += packet_size;
continue;
}
SpValidity::Skip => {
current_idx += sp_header.total_len();
}
// We might have lost sync. Try to find the start of a new space packet header.
SpValidity::Invalid => {
current_idx += 1;
}
current_idx += packet_size as usize;
continue;
}
current_idx += 1;
}
Ok(packets_found)
Ok(parse_result)
}
#[cfg(test)]
mod tests {
use spacepackets::{
ecss::{tc::PusTcCreator, WritablePusPacket},
PacketId, SpHeader,
CcsdsPacket, PacketId, PacketSequenceCtrl, PacketType, SequenceFlags, SpHeader,
};
use crate::encoding::tests::TcCacher;
use crate::{encoding::tests::TcCacher, ComponentId};
use super::parse_buffer_for_ccsds_space_packets;
use super::{parse_buffer_for_ccsds_space_packets, SpValidity, SpacePacketValidator};
const PARSER_ID: ComponentId = 0x05;
const TEST_APID_0: u16 = 0x02;
const TEST_APID_1: u16 = 0x10;
const TEST_PACKET_ID_0: PacketId = PacketId::const_tc(true, TEST_APID_0);
const TEST_PACKET_ID_1: PacketId = PacketId::const_tc(true, TEST_APID_1);
const TEST_PACKET_ID_0: PacketId = PacketId::new_for_tc(true, TEST_APID_0);
const TEST_PACKET_ID_1: PacketId = PacketId::new_for_tc(true, TEST_APID_1);
#[derive(Default)]
struct SimpleVerificator {
pub enable_second_id: bool,
}
impl SimpleVerificator {
pub fn new_with_second_id() -> Self {
Self {
enable_second_id: true,
}
}
}
impl SpacePacketValidator for SimpleVerificator {
fn validate(&self, sp_header: &SpHeader, _raw_buf: &[u8]) -> super::SpValidity {
if sp_header.packet_id() == TEST_PACKET_ID_0
|| (self.enable_second_id && sp_header.packet_id() == TEST_PACKET_ID_1)
{
return SpValidity::Valid;
}
SpValidity::Skip
}
}
#[test]
fn test_basic() {
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let sph = SpHeader::new_from_apid(TEST_APID_0);
let ping_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true);
let mut buffer: [u8; 32] = [0; 32];
let packet_len = ping_tc
.write_to_bytes(&mut buffer)
.expect("writing packet failed");
let valid_packet_ids = [TEST_PACKET_ID_0];
let mut tc_cacher = TcCacher::default();
let mut next_write_idx = 0;
let tc_cacher = TcCacher::default();
let parse_result = parse_buffer_for_ccsds_space_packets(
&mut buffer,
valid_packet_ids.as_slice(),
&mut tc_cacher,
&mut next_write_idx,
&buffer,
&SimpleVerificator::default(),
PARSER_ID,
&tc_cacher,
);
assert!(parse_result.is_ok());
let parsed_packets = parse_result.unwrap();
assert_eq!(parsed_packets, 1);
assert_eq!(tc_cacher.tc_queue.len(), 1);
assert_eq!(
tc_cacher.tc_queue.pop_front().unwrap(),
buffer[..packet_len]
);
let parse_result = parse_result.unwrap();
assert_eq!(parse_result.packets_found, 1);
let mut queue = tc_cacher.tc_queue.borrow_mut();
assert_eq!(queue.len(), 1);
let packet_with_sender = queue.pop_front().unwrap();
assert_eq!(packet_with_sender.packet, buffer[..packet_len]);
assert_eq!(packet_with_sender.sender_id, PARSER_ID);
}
#[test]
fn test_multi_packet() {
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
let sph = SpHeader::new_from_apid(TEST_APID_0);
let ping_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true);
let action_tc = PusTcCreator::new_simple(sph, 8, 0, &[], true);
let mut buffer: [u8; 32] = [0; 32];
let packet_len_ping = ping_tc
.write_to_bytes(&mut buffer)
@ -165,35 +165,35 @@ mod tests {
let packet_len_action = action_tc
.write_to_bytes(&mut buffer[packet_len_ping..])
.expect("writing packet failed");
let valid_packet_ids = [TEST_PACKET_ID_0];
let mut tc_cacher = TcCacher::default();
let mut next_write_idx = 0;
let tc_cacher = TcCacher::default();
let parse_result = parse_buffer_for_ccsds_space_packets(
&mut buffer,
valid_packet_ids.as_slice(),
&mut tc_cacher,
&mut next_write_idx,
&buffer,
&SimpleVerificator::default(),
PARSER_ID,
&tc_cacher,
);
assert!(parse_result.is_ok());
let parsed_packets = parse_result.unwrap();
assert_eq!(parsed_packets, 2);
assert_eq!(tc_cacher.tc_queue.len(), 2);
let parse_result = parse_result.unwrap();
assert_eq!(parse_result.packets_found, 2);
let mut queue = tc_cacher.tc_queue.borrow_mut();
assert_eq!(queue.len(), 2);
let packet_with_addr = queue.pop_front().unwrap();
assert_eq!(packet_with_addr.packet, buffer[..packet_len_ping]);
assert_eq!(packet_with_addr.sender_id, PARSER_ID);
let packet_with_addr = queue.pop_front().unwrap();
assert_eq!(packet_with_addr.sender_id, PARSER_ID);
assert_eq!(
tc_cacher.tc_queue.pop_front().unwrap(),
buffer[..packet_len_ping]
);
assert_eq!(
tc_cacher.tc_queue.pop_front().unwrap(),
packet_with_addr.packet,
buffer[packet_len_ping..packet_len_ping + packet_len_action]
);
}
#[test]
fn test_multi_apid() {
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
let sph = SpHeader::new_from_apid(TEST_APID_0);
let ping_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true);
let sph = SpHeader::new_from_apid(TEST_APID_1);
let action_tc = PusTcCreator::new_simple(sph, 8, 0, &[], true);
let mut buffer: [u8; 32] = [0; 32];
let packet_len_ping = ping_tc
.write_to_bytes(&mut buffer)
@ -201,35 +201,30 @@ mod tests {
let packet_len_action = action_tc
.write_to_bytes(&mut buffer[packet_len_ping..])
.expect("writing packet failed");
let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1];
let mut tc_cacher = TcCacher::default();
let mut next_write_idx = 0;
let parse_result = parse_buffer_for_ccsds_space_packets(
&mut buffer,
valid_packet_ids.as_slice(),
&mut tc_cacher,
&mut next_write_idx,
);
let tc_cacher = TcCacher::default();
let verificator = SimpleVerificator::new_with_second_id();
let parse_result =
parse_buffer_for_ccsds_space_packets(&buffer, &verificator, PARSER_ID, &tc_cacher);
assert!(parse_result.is_ok());
let parsed_packets = parse_result.unwrap();
assert_eq!(parsed_packets, 2);
assert_eq!(tc_cacher.tc_queue.len(), 2);
let parse_result = parse_result.unwrap();
assert_eq!(parse_result.packets_found, 2);
let mut queue = tc_cacher.tc_queue.borrow_mut();
assert_eq!(queue.len(), 2);
let packet_with_addr = queue.pop_front().unwrap();
assert_eq!(packet_with_addr.packet, buffer[..packet_len_ping]);
let packet_with_addr = queue.pop_front().unwrap();
assert_eq!(
tc_cacher.tc_queue.pop_front().unwrap(),
buffer[..packet_len_ping]
);
assert_eq!(
tc_cacher.tc_queue.pop_front().unwrap(),
packet_with_addr.packet,
buffer[packet_len_ping..packet_len_ping + packet_len_action]
);
}
#[test]
fn test_split_packet_multi() {
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
let ping_tc =
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_0), 17, 1, &[], true);
let action_tc =
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_1), 8, 0, &[], true);
let mut buffer: [u8; 32] = [0; 32];
let packet_len_ping = ping_tc
.write_to_bytes(&mut buffer)
@ -237,45 +232,68 @@ mod tests {
let packet_len_action = action_tc
.write_to_bytes(&mut buffer[packet_len_ping..])
.expect("writing packet failed");
let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1];
let mut tc_cacher = TcCacher::default();
let mut next_write_idx = 0;
let tc_cacher = TcCacher::default();
let verificator = SimpleVerificator::new_with_second_id();
let parse_result = parse_buffer_for_ccsds_space_packets(
&mut buffer[..packet_len_ping + packet_len_action - 4],
valid_packet_ids.as_slice(),
&mut tc_cacher,
&mut next_write_idx,
&buffer[..packet_len_ping + packet_len_action - 4],
&verificator,
PARSER_ID,
&tc_cacher,
);
assert!(parse_result.is_ok());
let parsed_packets = parse_result.unwrap();
assert_eq!(parsed_packets, 1);
assert_eq!(tc_cacher.tc_queue.len(), 1);
let parse_result = parse_result.unwrap();
assert_eq!(parse_result.packets_found, 1);
assert!(parse_result.incomplete_tail_start.is_some());
let incomplete_tail_idx = parse_result.incomplete_tail_start.unwrap();
assert_eq!(incomplete_tail_idx, packet_len_ping);
let queue = tc_cacher.tc_queue.borrow();
assert_eq!(queue.len(), 1);
// The broken packet was moved to the start, so the next write index should be after the
// last segment missing 4 bytes.
assert_eq!(next_write_idx, packet_len_action - 4);
}
#[test]
fn test_one_split_packet() {
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let ping_tc =
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_0), 17, 1, &[], true);
let mut buffer: [u8; 32] = [0; 32];
let packet_len_ping = ping_tc
.write_to_bytes(&mut buffer)
.expect("writing packet failed");
let valid_packet_ids = [TEST_PACKET_ID_0, TEST_PACKET_ID_1];
let mut tc_cacher = TcCacher::default();
let mut next_write_idx = 0;
let tc_cacher = TcCacher::default();
let verificator = SimpleVerificator::new_with_second_id();
let parse_result = parse_buffer_for_ccsds_space_packets(
&mut buffer[..packet_len_ping - 4],
valid_packet_ids.as_slice(),
&mut tc_cacher,
&mut next_write_idx,
&buffer[..packet_len_ping - 4],
&verificator,
PARSER_ID,
&tc_cacher,
);
assert_eq!(next_write_idx, 0);
assert!(parse_result.is_ok());
let parsed_packets = parse_result.unwrap();
assert_eq!(parsed_packets, 0);
assert_eq!(tc_cacher.tc_queue.len(), 0);
let parse_result = parse_result.unwrap();
assert_eq!(parse_result.packets_found, 0);
let queue = tc_cacher.tc_queue.borrow();
assert_eq!(queue.len(), 0);
}
#[test]
fn test_smallest_packet() {
let ccsds_header_only = SpHeader::new(
PacketId::new(PacketType::Tc, true, TEST_APID_0),
PacketSequenceCtrl::new(SequenceFlags::Unsegmented, 0),
0,
);
let mut buf: [u8; 7] = [0; 7];
ccsds_header_only
.write_to_be_bytes(&mut buf)
.expect("writing failed");
let verificator = SimpleVerificator::default();
let tc_cacher = TcCacher::default();
let parse_result =
parse_buffer_for_ccsds_space_packets(&buf, &verificator, PARSER_ID, &tc_cacher);
assert!(parse_result.is_ok());
let parse_result = parse_result.unwrap();
assert_eq!(parse_result.packets_found, 1);
}
}

View File

@ -1,4 +1,4 @@
use crate::tmtc::ReceivesTcCore;
use crate::{tmtc::PacketSenderRaw, ComponentId};
use cobs::{decode_in_place, encode, max_encoding_length};
/// This function encodes the given packet with COBS and also wraps the encoded packet with
@ -55,11 +55,12 @@ pub fn encode_packet_with_cobs(
/// future write operations will be written to the `next_write_idx` argument.
///
/// The parser will write all packets which were decoded successfully to the given `tc_receiver`.
pub fn parse_buffer_for_cobs_encoded_packets<E>(
pub fn parse_buffer_for_cobs_encoded_packets<SendError>(
buf: &mut [u8],
tc_receiver: &mut dyn ReceivesTcCore<Error = E>,
sender_id: ComponentId,
packet_sender: &(impl PacketSenderRaw<Error = SendError> + ?Sized),
next_write_idx: &mut usize,
) -> Result<u32, E> {
) -> Result<u32, SendError> {
let mut start_index_packet = 0;
let mut start_found = false;
let mut last_byte = false;
@ -78,8 +79,10 @@ pub fn parse_buffer_for_cobs_encoded_packets<E>(
let decode_result = decode_in_place(&mut buf[start_index_packet..i]);
if let Ok(packet_len) = decode_result {
packets_found += 1;
tc_receiver
.pass_tc(&buf[start_index_packet..start_index_packet + packet_len])?;
packet_sender.send_packet(
sender_id,
&buf[start_index_packet..start_index_packet + packet_len],
)?;
}
start_found = false;
} else {
@ -100,32 +103,39 @@ pub fn parse_buffer_for_cobs_encoded_packets<E>(
pub(crate) mod tests {
use cobs::encode;
use crate::encoding::tests::{encode_simple_packet, TcCacher, INVERTED_PACKET, SIMPLE_PACKET};
use crate::{
encoding::tests::{encode_simple_packet, TcCacher, INVERTED_PACKET, SIMPLE_PACKET},
ComponentId,
};
use super::parse_buffer_for_cobs_encoded_packets;
const PARSER_ID: ComponentId = 0x05;
#[test]
fn test_parsing_simple_packet() {
let mut test_sender = TcCacher::default();
let test_sender = TcCacher::default();
let mut encoded_buf: [u8; 16] = [0; 16];
let mut current_idx = 0;
encode_simple_packet(&mut encoded_buf, &mut current_idx);
let mut next_read_idx = 0;
let packets = parse_buffer_for_cobs_encoded_packets(
&mut encoded_buf[0..current_idx],
&mut test_sender,
PARSER_ID,
&test_sender,
&mut next_read_idx,
)
.unwrap();
assert_eq!(packets, 1);
assert_eq!(test_sender.tc_queue.len(), 1);
let packet = &test_sender.tc_queue[0];
assert_eq!(packet, &SIMPLE_PACKET);
let queue = test_sender.tc_queue.borrow();
assert_eq!(queue.len(), 1);
let packet = &queue[0];
assert_eq!(packet.packet, &SIMPLE_PACKET);
}
#[test]
fn test_parsing_consecutive_packets() {
let mut test_sender = TcCacher::default();
let test_sender = TcCacher::default();
let mut encoded_buf: [u8; 16] = [0; 16];
let mut current_idx = 0;
encode_simple_packet(&mut encoded_buf, &mut current_idx);
@ -139,21 +149,23 @@ pub(crate) mod tests {
let mut next_read_idx = 0;
let packets = parse_buffer_for_cobs_encoded_packets(
&mut encoded_buf[0..current_idx],
&mut test_sender,
PARSER_ID,
&test_sender,
&mut next_read_idx,
)
.unwrap();
assert_eq!(packets, 2);
assert_eq!(test_sender.tc_queue.len(), 2);
let packet0 = &test_sender.tc_queue[0];
assert_eq!(packet0, &SIMPLE_PACKET);
let packet1 = &test_sender.tc_queue[1];
assert_eq!(packet1, &INVERTED_PACKET);
let queue = test_sender.tc_queue.borrow();
assert_eq!(queue.len(), 2);
let packet0 = &queue[0];
assert_eq!(packet0.packet, &SIMPLE_PACKET);
let packet1 = &queue[1];
assert_eq!(packet1.packet, &INVERTED_PACKET);
}
#[test]
fn test_split_tail_packet_only() {
let mut test_sender = TcCacher::default();
let test_sender = TcCacher::default();
let mut encoded_buf: [u8; 16] = [0; 16];
let mut current_idx = 0;
encode_simple_packet(&mut encoded_buf, &mut current_idx);
@ -161,17 +173,19 @@ pub(crate) mod tests {
let packets = parse_buffer_for_cobs_encoded_packets(
// Cut off the sentinel byte at the end.
&mut encoded_buf[0..current_idx - 1],
&mut test_sender,
PARSER_ID,
&test_sender,
&mut next_read_idx,
)
.unwrap();
assert_eq!(packets, 0);
assert_eq!(test_sender.tc_queue.len(), 0);
let queue = test_sender.tc_queue.borrow();
assert_eq!(queue.len(), 0);
assert_eq!(next_read_idx, 0);
}
fn generic_test_split_packet(cut_off: usize) {
let mut test_sender = TcCacher::default();
let test_sender = TcCacher::default();
let mut encoded_buf: [u8; 16] = [0; 16];
assert!(cut_off < INVERTED_PACKET.len() + 1);
let mut current_idx = 0;
@ -193,13 +207,15 @@ pub(crate) mod tests {
let packets = parse_buffer_for_cobs_encoded_packets(
// Cut off the sentinel byte at the end.
&mut encoded_buf[0..current_idx - cut_off],
&mut test_sender,
PARSER_ID,
&test_sender,
&mut next_write_idx,
)
.unwrap();
assert_eq!(packets, 1);
assert_eq!(test_sender.tc_queue.len(), 1);
assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET);
let queue = test_sender.tc_queue.borrow();
assert_eq!(queue.len(), 1);
assert_eq!(&queue[0].packet, &SIMPLE_PACKET);
assert_eq!(next_write_idx, next_expected_write_idx);
assert_eq!(encoded_buf[..next_expected_write_idx], expected_at_start);
}
@ -221,7 +237,7 @@ pub(crate) mod tests {
#[test]
fn test_zero_at_end() {
let mut test_sender = TcCacher::default();
let test_sender = TcCacher::default();
let mut encoded_buf: [u8; 16] = [0; 16];
let mut next_write_idx = 0;
let mut current_idx = 0;
@ -233,31 +249,35 @@ pub(crate) mod tests {
let packets = parse_buffer_for_cobs_encoded_packets(
// Cut off the sentinel byte at the end.
&mut encoded_buf[0..current_idx],
&mut test_sender,
PARSER_ID,
&test_sender,
&mut next_write_idx,
)
.unwrap();
assert_eq!(packets, 1);
assert_eq!(test_sender.tc_queue.len(), 1);
assert_eq!(&test_sender.tc_queue[0], &SIMPLE_PACKET);
let queue = test_sender.tc_queue.borrow_mut();
assert_eq!(queue.len(), 1);
assert_eq!(&queue[0].packet, &SIMPLE_PACKET);
assert_eq!(next_write_idx, 1);
assert_eq!(encoded_buf[0], 0);
}
#[test]
fn test_all_zeroes() {
let mut test_sender = TcCacher::default();
let test_sender = TcCacher::default();
let mut all_zeroes: [u8; 5] = [0; 5];
let mut next_write_idx = 0;
let packets = parse_buffer_for_cobs_encoded_packets(
// Cut off the sentinel byte at the end.
&mut all_zeroes,
&mut test_sender,
PARSER_ID,
&test_sender,
&mut next_write_idx,
)
.unwrap();
assert_eq!(packets, 0);
assert!(test_sender.tc_queue.is_empty());
let queue = test_sender.tc_queue.borrow();
assert!(queue.is_empty());
assert_eq!(next_write_idx, 0);
}
}

View File

@ -6,9 +6,14 @@ pub use crate::encoding::cobs::{encode_packet_with_cobs, parse_buffer_for_cobs_e
#[cfg(test)]
pub(crate) mod tests {
use alloc::{collections::VecDeque, vec::Vec};
use core::cell::RefCell;
use crate::tmtc::ReceivesTcCore;
use alloc::collections::VecDeque;
use crate::{
tmtc::{PacketAsVec, PacketSenderRaw},
ComponentId,
};
use super::cobs::encode_packet_with_cobs;
@ -17,14 +22,15 @@ pub(crate) mod tests {
#[derive(Default)]
pub(crate) struct TcCacher {
pub(crate) tc_queue: VecDeque<Vec<u8>>,
pub(crate) tc_queue: RefCell<VecDeque<PacketAsVec>>,
}
impl ReceivesTcCore for TcCacher {
impl PacketSenderRaw for TcCacher {
type Error = ();
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
self.tc_queue.push_back(tc_raw.to_vec());
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
let mut mut_queue = self.tc_queue.borrow_mut();
mut_queue.push_back(PacketAsVec::new(sender_id, tc_raw.to_vec()));
Ok(())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -20,12 +20,12 @@
//! ```
//! use satrs::events::{EventU16, EventU32, EventU32TypedSev, Severity, SeverityHigh, SeverityInfo};
//!
//! const MSG_RECVD: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::const_new(1, 0);
//! const MSG_FAILED: EventU32 = EventU32::const_new(Severity::LOW, 1, 1);
//! const MSG_RECVD: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::new(1, 0);
//! const MSG_FAILED: EventU32 = EventU32::new(Severity::Low, 1, 1);
//!
//! const TEMPERATURE_HIGH: EventU32TypedSev<SeverityHigh> = EventU32TypedSev::const_new(2, 0);
//! const TEMPERATURE_HIGH: EventU32TypedSev<SeverityHigh> = EventU32TypedSev::new(2, 0);
//!
//! let small_event = EventU16::new(Severity::INFO, 3, 0);
//! let small_event = EventU16::new(Severity::Info, 3, 0);
//! ```
use core::fmt::Debug;
use core::hash::Hash;
@ -40,12 +40,17 @@ pub type LargestEventRaw = u32;
/// Using a type definition allows to change this to u32 in the future more easily
pub type LargestGroupIdRaw = u16;
pub const MAX_GROUP_ID_U32_EVENT: u16 = 2_u16.pow(14) - 1;
pub const MAX_GROUP_ID_U16_EVENT: u16 = 2_u16.pow(6) - 1;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Severity {
INFO = 0,
LOW = 1,
MEDIUM = 2,
HIGH = 3,
Info = 0,
Low = 1,
Medium = 2,
High = 3,
}
pub trait HasSeverity: Debug + PartialEq + Eq + Copy + Clone {
@ -56,31 +61,31 @@ pub trait HasSeverity: Debug + PartialEq + Eq + Copy + Clone {
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct SeverityInfo {}
impl HasSeverity for SeverityInfo {
const SEVERITY: Severity = Severity::INFO;
const SEVERITY: Severity = Severity::Info;
}
/// Type level support struct
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct SeverityLow {}
impl HasSeverity for SeverityLow {
const SEVERITY: Severity = Severity::LOW;
const SEVERITY: Severity = Severity::Low;
}
/// Type level support struct
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct SeverityMedium {}
impl HasSeverity for SeverityMedium {
const SEVERITY: Severity = Severity::MEDIUM;
const SEVERITY: Severity = Severity::Medium;
}
/// Type level support struct
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct SeverityHigh {}
impl HasSeverity for SeverityHigh {
const SEVERITY: Severity = Severity::HIGH;
const SEVERITY: Severity = Severity::High;
}
pub trait GenericEvent: EcssEnumeration {
pub trait GenericEvent: EcssEnumeration + Copy + Clone {
type Raw;
type GroupId;
type UniqueId;
@ -99,27 +104,29 @@ impl TryFrom<u8> for Severity {
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
x if x == Severity::INFO as u8 => Ok(Severity::INFO),
x if x == Severity::LOW as u8 => Ok(Severity::LOW),
x if x == Severity::MEDIUM as u8 => Ok(Severity::MEDIUM),
x if x == Severity::HIGH as u8 => Ok(Severity::HIGH),
x if x == Severity::Info as u8 => Ok(Severity::Info),
x if x == Severity::Low as u8 => Ok(Severity::Low),
x if x == Severity::Medium as u8 => Ok(Severity::Medium),
x if x == Severity::High as u8 => Ok(Severity::High),
_ => Err(()),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
struct EventBase<RAW, GID, UID> {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct EventBase<Raw, GroupId, UniqueId> {
severity: Severity,
group_id: GID,
unique_id: UID,
phantom: PhantomData<RAW>,
group_id: GroupId,
unique_id: UniqueId,
phantom: PhantomData<Raw>,
}
impl<RAW: ToBeBytes, GID, UID> EventBase<RAW, GID, UID> {
impl<Raw: ToBeBytes, GroupId, UniqueId> EventBase<Raw, GroupId, UniqueId> {
fn write_to_bytes(
&self,
raw: RAW,
raw: Raw,
buf: &mut [u8],
width: usize,
) -> Result<usize, ByteConversionError> {
@ -267,6 +274,7 @@ macro_rules! const_from_fn {
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EventU32 {
base: EventBase<u32, u16, u16>,
}
@ -309,12 +317,12 @@ impl EventU32 {
/// next 14 bits after the severity. Therefore, the size is limited by dec 16383 hex 0x3FFF.
/// * `unique_id`: Each event has a unique 16 bit ID occupying the last 16 bits of the
/// raw event ID
pub fn new(
pub fn new_checked(
severity: Severity,
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Option<Self> {
if group_id > (2u16.pow(14) - 1) {
if group_id > MAX_GROUP_ID_U32_EVENT {
return None;
}
Some(Self {
@ -326,12 +334,14 @@ impl EventU32 {
},
})
}
pub const fn const_new(
/// This constructor will panic if the passed group is is larger than [MAX_GROUP_ID_U32_EVENT].
pub const fn new(
severity: Severity,
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Self {
if group_id > (2u16.pow(14) - 1) {
if group_id > MAX_GROUP_ID_U32_EVENT {
panic!("Group ID too large");
}
Self {
@ -344,50 +354,16 @@ impl EventU32 {
}
}
pub fn from_be_bytes(bytes: [u8; 4]) -> Self {
Self::from(u32::from_be_bytes(bytes))
}
const_from_fn!(const_from_info, EventU32TypedSev, SeverityInfo);
const_from_fn!(const_from_low, EventU32TypedSev, SeverityLow);
const_from_fn!(const_from_medium, EventU32TypedSev, SeverityMedium);
const_from_fn!(const_from_high, EventU32TypedSev, SeverityHigh);
}
impl<SEVERITY: HasSeverity> EventU32TypedSev<SEVERITY> {
/// This is similar to [EventU32::new] but the severity is a type generic, which allows to
/// have distinct types for events with different severities
pub fn new(
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Option<Self> {
let event = EventU32::new(SEVERITY::SEVERITY, group_id, unique_id)?;
Some(Self {
event,
phantom: PhantomData,
})
}
/// Const version of [Self::new], but panics on invalid group ID input values.
pub const fn const_new(
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Self {
let event = EventU32::const_new(SEVERITY::SEVERITY, group_id, unique_id);
Self {
event,
phantom: PhantomData,
}
}
fn try_from_generic(expected: Severity, raw: u32) -> Result<Self, Severity> {
let severity = Severity::try_from(((raw >> 30) & 0b11) as u8).unwrap();
if severity != expected {
return Err(severity);
}
Ok(Self::const_new(
((raw >> 16) & 0x3FFF) as u16,
(raw & 0xFFFF) as u16,
))
}
}
impl From<u32> for EventU32 {
fn from(raw: u32) -> Self {
// Severity conversion from u8 should never fail
@ -395,15 +371,10 @@ impl From<u32> for EventU32 {
let group_id = ((raw >> 16) & 0x3FFF) as u16;
let unique_id = (raw & 0xFFFF) as u16;
// Sanitized input, should never fail
Self::const_new(severity, group_id, unique_id)
Self::new(severity, group_id, unique_id)
}
}
try_from_impls!(SeverityInfo, Severity::INFO, u32, EventU32TypedSev);
try_from_impls!(SeverityLow, Severity::LOW, u32, EventU32TypedSev);
try_from_impls!(SeverityMedium, Severity::MEDIUM, u32, EventU32TypedSev);
try_from_impls!(SeverityHigh, Severity::HIGH, u32, EventU32TypedSev);
impl UnsignedEnum for EventU32 {
fn size(&self) -> usize {
core::mem::size_of::<u32>()
@ -424,6 +395,49 @@ impl EcssEnumeration for EventU32 {
}
}
impl<SEVERITY: HasSeverity> EventU32TypedSev<SEVERITY> {
/// This is similar to [EventU32::new] but the severity is a type generic, which allows to
/// have distinct types for events with different severities
pub fn new_checked(
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Option<Self> {
let event = EventU32::new_checked(SEVERITY::SEVERITY, group_id, unique_id)?;
Some(Self {
event,
phantom: PhantomData,
})
}
/// This constructor will panic if the `group_id` is larger than [MAX_GROUP_ID_U32_EVENT].
pub const fn new(
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Self {
let event = EventU32::new(SEVERITY::SEVERITY, group_id, unique_id);
Self {
event,
phantom: PhantomData,
}
}
fn try_from_generic(expected: Severity, raw: u32) -> Result<Self, Severity> {
let severity = Severity::try_from(((raw >> 30) & 0b11) as u8).unwrap();
if severity != expected {
return Err(severity);
}
Ok(Self::new(
((raw >> 16) & 0x3FFF) as u16,
(raw & 0xFFFF) as u16,
))
}
}
try_from_impls!(SeverityInfo, Severity::Info, u32, EventU32TypedSev);
try_from_impls!(SeverityLow, Severity::Low, u32, EventU32TypedSev);
try_from_impls!(SeverityMedium, Severity::Medium, u32, EventU32TypedSev);
try_from_impls!(SeverityHigh, Severity::High, u32, EventU32TypedSev);
//noinspection RsTraitImplementation
impl<SEVERITY: HasSeverity> UnsignedEnum for EventU32TypedSev<SEVERITY> {
delegate!(to self.event {
@ -441,6 +455,8 @@ impl<SEVERITY: HasSeverity> EcssEnumeration for EventU32TypedSev<SEVERITY> {
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EventU16 {
base: EventBase<u16, u8, u8>,
}
@ -475,7 +491,7 @@ impl EventU16 {
/// next 6 bits after the severity. Therefore, the size is limited by dec 63 hex 0x3F.
/// * `unique_id`: Each event has a unique 8 bit ID occupying the last 8 bits of the
/// raw event ID
pub fn new(
pub fn new_checked(
severity: Severity,
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
@ -493,8 +509,8 @@ impl EventU16 {
})
}
/// Const version of [Self::new], but panics on invalid group ID input values.
pub const fn const_new(
/// This constructor will panic if the `group_id` is larger than [MAX_GROUP_ID_U16_EVENT].
pub const fn new(
severity: Severity,
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
@ -511,52 +527,26 @@ impl EventU16 {
},
}
}
pub fn from_be_bytes(bytes: [u8; 2]) -> Self {
Self::from(u16::from_be_bytes(bytes))
}
const_from_fn!(const_from_info, EventU16TypedSev, SeverityInfo);
const_from_fn!(const_from_low, EventU16TypedSev, SeverityLow);
const_from_fn!(const_from_medium, EventU16TypedSev, SeverityMedium);
const_from_fn!(const_from_high, EventU16TypedSev, SeverityHigh);
}
impl<SEVERITY: HasSeverity> EventU16TypedSev<SEVERITY> {
/// This is similar to [EventU16::new] but the severity is a type generic, which allows to
/// have distinct types for events with different severities
pub fn new(
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Option<Self> {
let event = EventU16::new(SEVERITY::SEVERITY, group_id, unique_id)?;
Some(Self {
event,
phantom: PhantomData,
})
}
/// Const version of [Self::new], but panics on invalid group ID input values.
pub const fn const_new(
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Self {
let event = EventU16::const_new(SEVERITY::SEVERITY, group_id, unique_id);
Self {
event,
phantom: PhantomData,
}
}
fn try_from_generic(expected: Severity, raw: u16) -> Result<Self, Severity> {
impl From<u16> for EventU16 {
fn from(raw: <Self as GenericEvent>::Raw) -> Self {
let severity = Severity::try_from(((raw >> 14) & 0b11) as u8).unwrap();
if severity != expected {
return Err(severity);
}
Ok(Self::const_new(
((raw >> 8) & 0x3F) as u8,
(raw & 0xFF) as u8,
))
let group_id = ((raw >> 8) & 0x3F) as u8;
let unique_id = (raw & 0xFF) as u8;
// Sanitized input, new call should never fail
Self::new(severity, group_id, unique_id)
}
}
impl_event_provider!(EventU16, EventU16TypedSev, u16, u8, u8);
impl UnsignedEnum for EventU16 {
fn size(&self) -> usize {
core::mem::size_of::<u16>()
@ -577,6 +567,43 @@ impl EcssEnumeration for EventU16 {
}
}
impl<SEVERITY: HasSeverity> EventU16TypedSev<SEVERITY> {
/// This is similar to [EventU16::new] but the severity is a type generic, which allows to
/// have distinct types for events with different severities
pub fn new_checked(
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Option<Self> {
let event = EventU16::new_checked(SEVERITY::SEVERITY, group_id, unique_id)?;
Some(Self {
event,
phantom: PhantomData,
})
}
/// This constructor will panic if the `group_id` is larger than [MAX_GROUP_ID_U16_EVENT].
pub const fn new(
group_id: <Self as GenericEvent>::GroupId,
unique_id: <Self as GenericEvent>::UniqueId,
) -> Self {
let event = EventU16::new(SEVERITY::SEVERITY, group_id, unique_id);
Self {
event,
phantom: PhantomData,
}
}
fn try_from_generic(expected: Severity, raw: u16) -> Result<Self, Severity> {
let severity = Severity::try_from(((raw >> 14) & 0b11) as u8).unwrap();
if severity != expected {
return Err(severity);
}
Ok(Self::new(((raw >> 8) & 0x3F) as u8, (raw & 0xFF) as u8))
}
}
impl_event_provider!(EventU16, EventU16TypedSev, u16, u8, u8);
//noinspection RsTraitImplementation
impl<SEVERITY: HasSeverity> UnsignedEnum for EventU16TypedSev<SEVERITY> {
delegate!(to self.event {
@ -593,20 +620,10 @@ impl<SEVERITY: HasSeverity> EcssEnumeration for EventU16TypedSev<SEVERITY> {
});
}
impl From<u16> for EventU16 {
fn from(raw: <Self as GenericEvent>::Raw) -> Self {
let severity = Severity::try_from(((raw >> 14) & 0b11) as u8).unwrap();
let group_id = ((raw >> 8) & 0x3F) as u8;
let unique_id = (raw & 0xFF) as u8;
// Sanitized input, new call should never fail
Self::const_new(severity, group_id, unique_id)
}
}
try_from_impls!(SeverityInfo, Severity::INFO, u16, EventU16TypedSev);
try_from_impls!(SeverityLow, Severity::LOW, u16, EventU16TypedSev);
try_from_impls!(SeverityMedium, Severity::MEDIUM, u16, EventU16TypedSev);
try_from_impls!(SeverityHigh, Severity::HIGH, u16, EventU16TypedSev);
try_from_impls!(SeverityInfo, Severity::Info, u16, EventU16TypedSev);
try_from_impls!(SeverityLow, Severity::Low, u16, EventU16TypedSev);
try_from_impls!(SeverityMedium, Severity::Medium, u16, EventU16TypedSev);
try_from_impls!(SeverityHigh, Severity::High, u16, EventU16TypedSev);
impl<Severity: HasSeverity> PartialEq<EventU32> for EventU32TypedSev<Severity> {
#[inline]
@ -647,12 +664,10 @@ mod tests {
assert_eq!(size_of::<T>(), val);
}
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::const_new(0, 0);
const INFO_EVENT_SMALL: EventU16TypedSev<SeverityInfo> = EventU16TypedSev::const_new(0, 0);
const HIGH_SEV_EVENT: EventU32TypedSev<SeverityHigh> =
EventU32TypedSev::const_new(0x3FFF, 0xFFFF);
const HIGH_SEV_EVENT_SMALL: EventU16TypedSev<SeverityHigh> =
EventU16TypedSev::const_new(0x3F, 0xff);
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::new(0, 0);
const INFO_EVENT_SMALL: EventU16TypedSev<SeverityInfo> = EventU16TypedSev::new(0, 0);
const HIGH_SEV_EVENT: EventU32TypedSev<SeverityHigh> = EventU32TypedSev::new(0x3FFF, 0xFFFF);
const HIGH_SEV_EVENT_SMALL: EventU16TypedSev<SeverityHigh> = EventU16TypedSev::new(0x3F, 0xff);
/// This working is a test in itself.
const INFO_REDUCED: EventU32 = EventU32::const_from_info(INFO_EVENT);
@ -683,7 +698,7 @@ mod tests {
#[test]
fn test_normal_event_getters() {
assert_eq!(INFO_EVENT.severity(), Severity::INFO);
assert_eq!(INFO_EVENT.severity(), Severity::Info);
assert_eq!(INFO_EVENT.unique_id(), 0);
assert_eq!(INFO_EVENT.group_id(), 0);
let raw_event = INFO_EVENT.raw();
@ -692,7 +707,7 @@ mod tests {
#[test]
fn test_small_event_getters() {
assert_eq!(INFO_EVENT_SMALL.severity(), Severity::INFO);
assert_eq!(INFO_EVENT_SMALL.severity(), Severity::Info);
assert_eq!(INFO_EVENT_SMALL.unique_id(), 0);
assert_eq!(INFO_EVENT_SMALL.group_id(), 0);
let raw_event = INFO_EVENT_SMALL.raw();
@ -701,7 +716,7 @@ mod tests {
#[test]
fn all_ones_event_regular() {
assert_eq!(HIGH_SEV_EVENT.severity(), Severity::HIGH);
assert_eq!(HIGH_SEV_EVENT.severity(), Severity::High);
assert_eq!(HIGH_SEV_EVENT.group_id(), 0x3FFF);
assert_eq!(HIGH_SEV_EVENT.unique_id(), 0xFFFF);
let raw_event = HIGH_SEV_EVENT.raw();
@ -710,7 +725,7 @@ mod tests {
#[test]
fn all_ones_event_small() {
assert_eq!(HIGH_SEV_EVENT_SMALL.severity(), Severity::HIGH);
assert_eq!(HIGH_SEV_EVENT_SMALL.severity(), Severity::High);
assert_eq!(HIGH_SEV_EVENT_SMALL.group_id(), 0x3F);
assert_eq!(HIGH_SEV_EVENT_SMALL.unique_id(), 0xFF);
let raw_event = HIGH_SEV_EVENT_SMALL.raw();
@ -719,18 +734,19 @@ mod tests {
#[test]
fn invalid_group_id_normal() {
assert!(EventU32TypedSev::<SeverityMedium>::new(2_u16.pow(14), 0).is_none());
assert!(EventU32TypedSev::<SeverityMedium>::new_checked(2_u16.pow(14), 0).is_none());
}
#[test]
fn invalid_group_id_small() {
assert!(EventU16TypedSev::<SeverityMedium>::new(2_u8.pow(6), 0).is_none());
assert!(EventU16TypedSev::<SeverityMedium>::new_checked(2_u8.pow(6), 0).is_none());
}
#[test]
fn regular_new() {
assert_eq!(
EventU32TypedSev::<SeverityInfo>::new(0, 0).expect("Creating regular event failed"),
EventU32TypedSev::<SeverityInfo>::new_checked(0, 0)
.expect("Creating regular event failed"),
INFO_EVENT
);
}
@ -738,7 +754,8 @@ mod tests {
#[test]
fn small_new() {
assert_eq!(
EventU16TypedSev::<SeverityInfo>::new(0, 0).expect("Creating regular event failed"),
EventU16TypedSev::<SeverityInfo>::new_checked(0, 0)
.expect("Creating regular event failed"),
INFO_EVENT_SMALL
);
}
@ -777,6 +794,8 @@ mod tests {
assert!(HIGH_SEV_EVENT.write_to_be_bytes(&mut buf).is_ok());
let val_from_raw = u32::from_be_bytes(buf);
assert_eq!(val_from_raw, 0xFFFFFFFF);
let event_read_back = EventU32::from_be_bytes(buf);
assert_eq!(event_read_back, HIGH_SEV_EVENT);
}
#[test]
@ -785,6 +804,8 @@ mod tests {
assert!(HIGH_SEV_EVENT_SMALL.write_to_be_bytes(&mut buf).is_ok());
let val_from_raw = u16::from_be_bytes(buf);
assert_eq!(val_from_raw, 0xFFFF);
let event_read_back = EventU16::from_be_bytes(buf);
assert_eq!(event_read_back, HIGH_SEV_EVENT_SMALL);
}
#[test]
@ -815,13 +836,13 @@ mod tests {
fn severity_from_invalid_raw_val() {
let invalid = 0xFF;
assert!(Severity::try_from(invalid).is_err());
let invalid = Severity::HIGH as u8 + 1;
let invalid = Severity::High as u8 + 1;
assert!(Severity::try_from(invalid).is_err());
}
#[test]
fn reduction() {
let event = EventU32TypedSev::<SeverityInfo>::const_new(1, 1);
let event = EventU32TypedSev::<SeverityInfo>::new(1, 1);
let raw = event.raw();
let reduced: EventU32 = event.into();
assert_eq!(reduced.group_id(), 1);

View File

@ -1,4 +1,3 @@
//! # Hardware Abstraction Layer module
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std;

View File

@ -1,19 +1,25 @@
use alloc::sync::Arc;
use alloc::vec;
use cobs::encode;
use core::sync::atomic::AtomicBool;
use core::time::Duration;
use delegate::delegate;
use mio::net::{TcpListener, TcpStream};
use std::io::Write;
use std::net::SocketAddr;
use std::net::TcpListener;
use std::net::TcpStream;
use std::vec::Vec;
use crate::encoding::parse_buffer_for_cobs_encoded_packets;
use crate::tmtc::ReceivesTc;
use crate::tmtc::TmPacketSource;
use crate::tmtc::PacketSenderRaw;
use crate::tmtc::PacketSource;
use crate::hal::std::tcp_server::{
ConnectionResult, ServerConfig, TcpTcParser, TcpTmSender, TcpTmtcError, TcpTmtcGenericServer,
};
use crate::ComponentId;
use super::tcp_server::HandledConnectionHandler;
use super::tcp_server::HandledConnectionInfo;
/// Concrete [TcpTcParser] implementation for the [TcpTmtcInCobsServer].
#[derive(Default)]
@ -23,14 +29,16 @@ impl<TmError, TcError: 'static> TcpTcParser<TmError, TcError> for CobsTcParser {
fn handle_tc_parsing(
&mut self,
tc_buffer: &mut [u8],
tc_receiver: &mut (impl ReceivesTc<Error = TcError> + ?Sized),
conn_result: &mut ConnectionResult,
sender_id: ComponentId,
tc_sender: &(impl PacketSenderRaw<Error = TcError> + ?Sized),
conn_result: &mut HandledConnectionInfo,
current_write_idx: usize,
next_write_idx: &mut usize,
) -> Result<(), TcpTmtcError<TmError, TcError>> {
conn_result.num_received_tcs += parse_buffer_for_cobs_encoded_packets(
&mut tc_buffer[..current_write_idx],
tc_receiver.upcast_mut(),
sender_id,
tc_sender,
next_write_idx,
)
.map_err(|e| TcpTmtcError::TcError(e))?;
@ -57,8 +65,8 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for CobsTmSender {
fn handle_tm_sending(
&mut self,
tm_buffer: &mut [u8],
tm_source: &mut (impl TmPacketSource<Error = TmError> + ?Sized),
conn_result: &mut ConnectionResult,
tm_source: &mut (impl PacketSource<Error = TmError> + ?Sized),
conn_result: &mut HandledConnectionInfo,
stream: &mut TcpStream,
) -> Result<bool, TcpTmtcError<TmError, TcError>> {
let mut tm_was_sent = false;
@ -96,7 +104,7 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for CobsTmSender {
/// Telemetry will be encoded with the COBS protocol using [cobs::encode] in addition to being
/// wrapped with the sentinel value 0 as the packet delimiter as well before being sent back to
/// the client. Please note that the server will send as much data as it can retrieve from the
/// [TmPacketSource] in its current implementation.
/// [PacketSource] in its current implementation.
///
/// Using a framing protocol like COBS imposes minimal restrictions on the type of TMTC data
/// exchanged while also allowing packets with flexible size and a reliable way to reconstruct full
@ -110,21 +118,30 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for CobsTmSender {
/// The [TCP integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs/tests/tcp_servers.rs)
/// test also serves as the example application for this module.
pub struct TcpTmtcInCobsServer<
TmSource: PacketSource<Error = TmError>,
TcSender: PacketSenderRaw<Error = SendError>,
HandledConnection: HandledConnectionHandler,
TmError,
TcError: 'static,
TmSource: TmPacketSource<Error = TmError>,
TcReceiver: ReceivesTc<Error = TcError>,
SendError: 'static,
> {
generic_server:
TcpTmtcGenericServer<TmError, TcError, TmSource, TcReceiver, CobsTmSender, CobsTcParser>,
pub generic_server: TcpTmtcGenericServer<
TmSource,
TcSender,
CobsTmSender,
CobsTcParser,
HandledConnection,
TmError,
SendError,
>,
}
impl<
TmSource: PacketSource<Error = TmError>,
TcReceiver: PacketSenderRaw<Error = TcError>,
HandledConnection: HandledConnectionHandler,
TmError: 'static,
TcError: 'static,
TmSource: TmPacketSource<Error = TmError>,
TcReceiver: ReceivesTc<Error = TcError>,
> TcpTmtcInCobsServer<TmError, TcError, TmSource, TcReceiver>
> TcpTmtcInCobsServer<TmSource, TcReceiver, HandledConnection, TmError, TcError>
{
/// Create a new TCP TMTC server which exchanges TMTC packets encoded with
/// [COBS protocol](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
@ -140,6 +157,8 @@ impl<
cfg: ServerConfig,
tm_source: TmSource,
tc_receiver: TcReceiver,
handled_connection: HandledConnection,
stop_signal: Option<Arc<AtomicBool>>,
) -> Result<Self, std::io::Error> {
Ok(Self {
generic_server: TcpTmtcGenericServer::new(
@ -148,6 +167,8 @@ impl<
CobsTmSender::new(cfg.tm_buffer_size),
tm_source,
tc_receiver,
handled_connection,
stop_signal,
)?,
})
}
@ -160,9 +181,10 @@ impl<
/// useful if using the port number 0 for OS auto-assignment.
pub fn local_addr(&self) -> std::io::Result<SocketAddr>;
/// Delegation to the [TcpTmtcGenericServer::handle_next_connection] call.
pub fn handle_next_connection(
/// Delegation to the [TcpTmtcGenericServer::handle_all_connections] call.
pub fn handle_all_connections(
&mut self,
poll_duration: Option<Duration>,
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcError>>;
}
}
@ -177,21 +199,29 @@ mod tests {
use std::{
io::{Read, Write},
net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream},
panic,
sync::mpsc,
thread,
time::Instant,
};
use crate::{
encoding::tests::{INVERTED_PACKET, SIMPLE_PACKET},
hal::std::tcp_server::{
tests::{SyncTcCacher, SyncTmSource},
ServerConfig,
tests::{ConnectionFinishedHandler, SyncTmSource},
ConnectionResult, ServerConfig,
},
queue::GenericSendError,
tmtc::PacketAsVec,
ComponentId,
};
use alloc::sync::Arc;
use cobs::encode;
use super::TcpTmtcInCobsServer;
const TCP_SERVER_ID: ComponentId = 0x05;
fn encode_simple_packet(encoded_buf: &mut [u8], current_idx: &mut usize) {
encode_packet(&SIMPLE_PACKET, encoded_buf, current_idx)
}
@ -210,13 +240,22 @@ mod tests {
fn generic_tmtc_server(
addr: &SocketAddr,
tc_receiver: SyncTcCacher,
tc_sender: mpsc::Sender<PacketAsVec>,
tm_source: SyncTmSource,
) -> TcpTmtcInCobsServer<(), (), SyncTmSource, SyncTcCacher> {
stop_signal: Option<Arc<AtomicBool>>,
) -> TcpTmtcInCobsServer<
SyncTmSource,
mpsc::Sender<PacketAsVec>,
ConnectionFinishedHandler,
(),
GenericSendError,
> {
TcpTmtcInCobsServer::new(
ServerConfig::new(*addr, Duration::from_millis(2), 1024, 1024),
ServerConfig::new(TCP_SERVER_ID, *addr, Duration::from_millis(2), 1024, 1024),
tm_source,
tc_receiver,
tc_sender,
ConnectionFinishedHandler::default(),
stop_signal,
)
.expect("TCP server generation failed")
}
@ -224,9 +263,10 @@ mod tests {
#[test]
fn test_server_basic_no_tm() {
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
let tc_receiver = SyncTcCacher::default();
let (tc_sender, tc_receiver) = mpsc::channel();
let tm_source = SyncTmSource::default();
let mut tcp_server = generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source);
let mut tcp_server =
generic_tmtc_server(&auto_port_addr, tc_sender.clone(), tm_source, None);
let dest_addr = tcp_server
.local_addr()
.expect("retrieving dest addr failed");
@ -234,13 +274,20 @@ mod tests {
let set_if_done = conn_handled.clone();
// Call the connection handler in separate thread, does block.
thread::spawn(move || {
let result = tcp_server.handle_next_connection();
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(100)));
if result.is_err() {
panic!("handling connection failed: {:?}", result.unwrap_err());
}
let conn_result = result.unwrap();
assert_eq!(conn_result.num_received_tcs, 1);
assert_eq!(conn_result.num_sent_tms, 0);
let result = result.unwrap();
assert_eq!(result, ConnectionResult::HandledConnections(1));
tcp_server
.generic_server
.finished_handler
.check_last_connection(0, 1);
tcp_server
.generic_server
.finished_handler
.check_no_connections_left();
set_if_done.store(true, Ordering::Relaxed);
});
// Send TC to server now.
@ -262,24 +309,20 @@ mod tests {
panic!("connection was not handled properly");
}
// Check that the packet was received and decoded successfully.
let mut tc_queue = tc_receiver
.tc_queue
.lock()
.expect("locking tc queue failed");
assert_eq!(tc_queue.len(), 1);
assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET);
drop(tc_queue);
let packet_with_sender = tc_receiver.recv().expect("receiving TC failed");
assert_eq!(packet_with_sender.packet, &SIMPLE_PACKET);
matches!(tc_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
}
#[test]
fn test_server_basic_multi_tm_multi_tc() {
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
let tc_receiver = SyncTcCacher::default();
let (tc_sender, tc_receiver) = mpsc::channel();
let mut tm_source = SyncTmSource::default();
tm_source.add_tm(&INVERTED_PACKET);
tm_source.add_tm(&SIMPLE_PACKET);
let mut tcp_server =
generic_tmtc_server(&auto_port_addr, tc_receiver.clone(), tm_source.clone());
generic_tmtc_server(&auto_port_addr, tc_sender.clone(), tm_source.clone(), None);
let dest_addr = tcp_server
.local_addr()
.expect("retrieving dest addr failed");
@ -287,13 +330,20 @@ mod tests {
let set_if_done = conn_handled.clone();
// Call the connection handler in separate thread, does block.
thread::spawn(move || {
let result = tcp_server.handle_next_connection();
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(100)));
if result.is_err() {
panic!("handling connection failed: {:?}", result.unwrap_err());
}
let conn_result = result.unwrap();
assert_eq!(conn_result.num_received_tcs, 2, "Not enough TCs received");
assert_eq!(conn_result.num_sent_tms, 2, "Not enough TMs received");
let result = result.unwrap();
assert_eq!(result, ConnectionResult::HandledConnections(1));
tcp_server
.generic_server
.finished_handler
.check_last_connection(2, 2);
tcp_server
.generic_server
.finished_handler
.check_no_connections_left();
set_if_done.store(true, Ordering::Relaxed);
});
// Send TC to server now.
@ -367,13 +417,78 @@ mod tests {
panic!("connection was not handled properly");
}
// Check that the packet was received and decoded successfully.
let mut tc_queue = tc_receiver
.tc_queue
.lock()
.expect("locking tc queue failed");
assert_eq!(tc_queue.len(), 2);
assert_eq!(tc_queue.pop_front().unwrap(), &SIMPLE_PACKET);
assert_eq!(tc_queue.pop_front().unwrap(), &INVERTED_PACKET);
drop(tc_queue);
let packet_with_sender = tc_receiver.recv().expect("receiving TC failed");
let packet = &packet_with_sender.packet;
assert_eq!(packet, &SIMPLE_PACKET);
let packet_with_sender = tc_receiver.recv().expect("receiving TC failed");
let packet = &packet_with_sender.packet;
assert_eq!(packet, &INVERTED_PACKET);
matches!(tc_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
}
#[test]
fn test_server_accept_timeout() {
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
let (tc_sender, _tc_receiver) = mpsc::channel();
let tm_source = SyncTmSource::default();
let mut tcp_server =
generic_tmtc_server(&auto_port_addr, tc_sender.clone(), tm_source, None);
let start = Instant::now();
// Call the connection handler in separate thread, does block.
let thread_jh = thread::spawn(move || loop {
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(20)));
if result.is_err() {
panic!("handling connection failed: {:?}", result.unwrap_err());
}
let result = result.unwrap();
if result == ConnectionResult::AcceptTimeout {
break;
}
if Instant::now() - start > Duration::from_millis(100) {
panic!("regular stop signal handling failed");
}
});
thread_jh.join().expect("thread join failed");
}
#[test]
fn test_server_stop_signal() {
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
let (tc_sender, _tc_receiver) = mpsc::channel();
let tm_source = SyncTmSource::default();
let stop_signal = Arc::new(AtomicBool::new(false));
let mut tcp_server = generic_tmtc_server(
&auto_port_addr,
tc_sender.clone(),
tm_source,
Some(stop_signal.clone()),
);
let dest_addr = tcp_server
.local_addr()
.expect("retrieving dest addr failed");
let stop_signal_copy = stop_signal.clone();
let start = Instant::now();
// Call the connection handler in separate thread, does block.
let thread_jh = thread::spawn(move || loop {
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(20)));
if result.is_err() {
panic!("handling connection failed: {:?}", result.unwrap_err());
}
let result = result.unwrap();
if result == ConnectionResult::AcceptTimeout {
panic!("unexpected accept timeout");
}
if stop_signal_copy.load(Ordering::Relaxed) {
break;
}
if Instant::now() - start > Duration::from_millis(100) {
panic!("regular stop signal handling failed");
}
});
// We connect but do not do anything.
let _stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed");
stop_signal.store(true, Ordering::Relaxed);
// No need to drop the connection, the stop signal should take take of everything.
thread_jh.join().expect("thread join failed");
}
}

View File

@ -1,21 +1,23 @@
//! Generic TCP TMTC servers with different TMTC format flavours.
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::sync::atomic::AtomicBool;
use core::time::Duration;
use mio::net::{TcpListener, TcpStream};
use mio::{Events, Interest, Poll, Token};
use socket2::{Domain, Socket, Type};
use std::io::Read;
use std::net::TcpListener;
use std::net::{SocketAddr, TcpStream};
use std::io::{self, Read};
use std::net::SocketAddr;
use std::thread;
use crate::tmtc::{ReceivesTc, TmPacketSource};
use crate::tmtc::{PacketSenderRaw, PacketSource};
use crate::ComponentId;
use thiserror::Error;
// Re-export the TMTC in COBS server.
pub use crate::hal::std::tcp_cobs_server::{CobsTcParser, CobsTmSender, TcpTmtcInCobsServer};
pub use crate::hal::std::tcp_spacepackets_server::{
SpacepacketsTcParser, SpacepacketsTmSender, TcpSpacepacketsServer,
};
pub use crate::hal::std::tcp_spacepackets_server::{SpacepacketsTmSender, TcpSpacepacketsServer};
/// Configuration struct for the generic TCP TMTC server
///
@ -25,7 +27,7 @@ pub use crate::hal::std::tcp_spacepackets_server::{
/// * `inner_loop_delay` - If a client connects for a longer period, but no TC is received or
/// no TM needs to be sent, the TCP server will delay for the specified amount of time
/// to reduce CPU load.
/// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [TmPacketSource] and
/// * `tm_buffer_size` - Size of the TM buffer used to read TM from the [PacketSource] and
/// encoding of that data. This buffer should at large enough to hold the maximum expected
/// TM size read from the packet source.
/// * `tc_buffer_size` - Size of the TC buffer used to read encoded telecommands sent from
@ -41,6 +43,7 @@ pub use crate::hal::std::tcp_spacepackets_server::{
/// default.
#[derive(Debug, Copy, Clone)]
pub struct ServerConfig {
pub id: ComponentId,
pub addr: SocketAddr,
pub inner_loop_delay: Duration,
pub tm_buffer_size: usize,
@ -51,12 +54,14 @@ pub struct ServerConfig {
impl ServerConfig {
pub fn new(
id: ComponentId,
addr: SocketAddr,
inner_loop_delay: Duration,
tm_buffer_size: usize,
tc_buffer_size: usize,
) -> Self {
Self {
id,
addr,
inner_loop_delay,
tm_buffer_size,
@ -79,37 +84,62 @@ pub enum TcpTmtcError<TmError, TcError> {
/// Result of one connection attempt. Contains the client address if a connection was established,
/// in addition to the number of telecommands and telemetry packets exchanged.
#[derive(Debug, Default)]
pub struct ConnectionResult {
pub addr: Option<SocketAddr>,
#[derive(Debug, PartialEq, Eq)]
pub enum ConnectionResult {
AcceptTimeout,
HandledConnections(u32),
}
#[derive(Debug)]
pub struct HandledConnectionInfo {
pub addr: SocketAddr,
pub num_received_tcs: u32,
pub num_sent_tms: u32,
/// The generic TCP server can be stopped using an external signal. If this happened, this
/// boolean will be set to true.
pub stopped_by_signal: bool,
}
impl HandledConnectionInfo {
pub fn new(addr: SocketAddr) -> Self {
Self {
addr,
num_received_tcs: 0,
num_sent_tms: 0,
stopped_by_signal: false,
}
}
}
pub trait HandledConnectionHandler {
fn handled_connection(&mut self, info: HandledConnectionInfo);
}
/// Generic parser abstraction for an object which can parse for telecommands given a raw
/// bytestream received from a TCP socket and send them to a generic [ReceivesTc] telecommand
/// receiver. This allows different encoding schemes for telecommands.
pub trait TcpTcParser<TmError, TcError> {
/// bytestream received from a TCP socket and send them using a generic [PacketSenderRaw]
/// implementation. This allows different encoding schemes for telecommands.
pub trait TcpTcParser<TmError, SendError> {
fn handle_tc_parsing(
&mut self,
tc_buffer: &mut [u8],
tc_receiver: &mut (impl ReceivesTc<Error = TcError> + ?Sized),
conn_result: &mut ConnectionResult,
sender_id: ComponentId,
tc_sender: &(impl PacketSenderRaw<Error = SendError> + ?Sized),
conn_result: &mut HandledConnectionInfo,
current_write_idx: usize,
next_write_idx: &mut usize,
) -> Result<(), TcpTmtcError<TmError, TcError>>;
) -> Result<(), TcpTmtcError<TmError, SendError>>;
}
/// Generic sender abstraction for an object which can pull telemetry from a given TM source
/// using a [TmPacketSource] and then send them back to a client using a given [TcpStream].
/// using a [PacketSource] and then send them back to a client using a given [TcpStream].
/// The concrete implementation can also perform any encoding steps which are necessary before
/// sending back the data to a client.
pub trait TcpTmSender<TmError, TcError> {
fn handle_tm_sending(
&mut self,
tm_buffer: &mut [u8],
tm_source: &mut (impl TmPacketSource<Error = TmError> + ?Sized),
conn_result: &mut ConnectionResult,
tm_source: &mut (impl PacketSource<Error = TmError> + ?Sized),
conn_result: &mut HandledConnectionInfo,
stream: &mut TcpStream,
) -> Result<bool, TcpTmtcError<TmError, TcError>>;
}
@ -121,9 +151,9 @@ pub trait TcpTmSender<TmError, TcError> {
/// through the following 4 core abstractions:
///
/// 1. [TcpTcParser] to parse for telecommands from the raw bytestream received from a client.
/// 2. Parsed telecommands will be sent to the [ReceivesTc] telecommand receiver.
/// 2. Parsed telecommands will be sent using the [PacketSenderRaw] object.
/// 3. [TcpTmSender] to send telemetry pulled from a TM source back to the client.
/// 4. [TmPacketSource] as a generic TM source used by the [TcpTmSender].
/// 4. [PacketSource] as a generic TM source used by the [TcpTmSender].
///
/// It is possible to specify custom abstractions to build a dedicated TCP TMTC server without
/// having to re-implement common logic.
@ -131,32 +161,49 @@ pub trait TcpTmSender<TmError, TcError> {
/// Currently, this framework offers the following concrete implementations:
///
/// 1. [TcpTmtcInCobsServer] to exchange TMTC wrapped inside the COBS framing protocol.
/// 2. [TcpSpacepacketsServer] to exchange space packets via TCP.
pub struct TcpTmtcGenericServer<
TmSource: PacketSource<Error = TmError>,
TcSender: PacketSenderRaw<Error = TcSendError>,
TmSender: TcpTmSender<TmError, TcSendError>,
TcParser: TcpTcParser<TmError, TcSendError>,
HandledConnection: HandledConnectionHandler,
TmError,
TcError,
TmSource: TmPacketSource<Error = TmError>,
TcReceiver: ReceivesTc<Error = TcError>,
TmSender: TcpTmSender<TmError, TcError>,
TcParser: TcpTcParser<TmError, TcError>,
TcSendError,
> {
pub id: ComponentId,
pub finished_handler: HandledConnection,
pub(crate) listener: TcpListener,
pub(crate) inner_loop_delay: Duration,
pub(crate) tm_source: TmSource,
pub(crate) tm_buffer: Vec<u8>,
pub(crate) tc_receiver: TcReceiver,
pub(crate) tc_sender: TcSender,
pub(crate) tc_buffer: Vec<u8>,
tc_handler: TcParser,
tm_handler: TmSender,
poll: Poll,
events: Events,
pub tc_handler: TcParser,
pub tm_handler: TmSender,
stop_signal: Option<Arc<AtomicBool>>,
}
impl<
TmSource: PacketSource<Error = TmError>,
TcSender: PacketSenderRaw<Error = TcSendError>,
TmSender: TcpTmSender<TmError, TcSendError>,
TcParser: TcpTcParser<TmError, TcSendError>,
HandledConnection: HandledConnectionHandler,
TmError: 'static,
TcError: 'static,
TmSource: TmPacketSource<Error = TmError>,
TcReceiver: ReceivesTc<Error = TcError>,
TmSender: TcpTmSender<TmError, TcError>,
TcParser: TcpTcParser<TmError, TcError>,
> TcpTmtcGenericServer<TmError, TcError, TmSource, TcReceiver, TmSender, TcParser>
TcSendError: 'static,
>
TcpTmtcGenericServer<
TmSource,
TcSender,
TmSender,
TcParser,
HandledConnection,
TmError,
TcSendError,
>
{
/// Create a new generic TMTC server instance.
///
@ -168,32 +215,58 @@ impl<
/// * `tm_sender` - Sends back telemetry to the client using the specified TM source.
/// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are
/// then sent back to the client.
/// * `tc_receiver` - Any received telecommand which was decoded successfully will be forwarded
/// to this TC receiver.
/// * `tc_sender` - Any received telecommand which was decoded successfully will be forwarded
/// using this TC sender.
/// * `stop_signal` - Can be used to stop the server even if a connection is ongoing.
pub fn new(
cfg: ServerConfig,
tc_parser: TcParser,
tm_sender: TmSender,
tm_source: TmSource,
tc_receiver: TcReceiver,
tc_receiver: TcSender,
finished_handler: HandledConnection,
stop_signal: Option<Arc<AtomicBool>>,
) -> Result<Self, std::io::Error> {
// Create a TCP listener bound to two addresses.
let socket = Socket::new(Domain::IPV4, Type::STREAM, None)?;
socket.set_reuse_address(cfg.reuse_addr)?;
#[cfg(unix)]
socket.set_reuse_port(cfg.reuse_port)?;
// MIO does not do this for us. We want the accept calls to be non-blocking.
socket.set_nonblocking(true)?;
let addr = (cfg.addr).into();
socket.bind(&addr)?;
socket.listen(128)?;
// Create a poll instance.
let poll = Poll::new()?;
// Create storage for events.
let events = Events::with_capacity(32);
let listener: std::net::TcpListener = socket.into();
let mut mio_listener = TcpListener::from_std(listener);
// Start listening for incoming connections.
poll.registry().register(
&mut mio_listener,
Token(0),
Interest::READABLE | Interest::WRITABLE,
)?;
Ok(Self {
id: cfg.id,
tc_handler: tc_parser,
tm_handler: tm_sender,
listener: socket.into(),
poll,
events,
listener: mio_listener,
inner_loop_delay: cfg.inner_loop_delay,
tm_source,
tm_buffer: vec![0; cfg.tm_buffer_size],
tc_receiver,
tc_sender: tc_receiver,
tc_buffer: vec![0; cfg.tc_buffer_size],
stop_signal,
finished_handler,
})
}
@ -208,11 +281,11 @@ impl<
self.listener.local_addr()
}
/// This call is used to handle the next connection to a client. Right now, it performs
/// This call is used to handle all connection from clients. Right now, it performs
/// the following steps:
///
/// 1. It calls the [std::net::TcpListener::accept] method internally using the blocking API
/// until a client connects.
/// 1. It calls the [std::net::TcpListener::accept] method until a client connects. An optional
/// timeout can be specified for non-blocking acceptance.
/// 2. It reads all the telecommands from the client and parses all received data using the
/// user specified [TcpTcParser].
/// 3. After reading and parsing all telecommands, it sends back all telemetry using the
@ -221,15 +294,66 @@ impl<
/// The server will delay for a user-specified period if the client connects to the server
/// for prolonged periods and there is no traffic for the server. This is the case if the
/// client does not send any telecommands and no telemetry needs to be sent back to the client.
pub fn handle_next_connection(
pub fn handle_all_connections(
&mut self,
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcError>> {
let mut connection_result = ConnectionResult::default();
poll_timeout: Option<Duration>,
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcSendError>> {
let mut handled_connections = 0;
// Poll Mio for events.
self.poll.poll(&mut self.events, poll_timeout)?;
let mut acceptable_connection = false;
// Process each event.
for event in self.events.iter() {
if event.token() == Token(0) {
acceptable_connection = true;
} else {
// Should never happen..
panic!("unexpected TCP event token");
}
}
// I'd love to do this in the loop above, but there are issues with multiple borrows.
if acceptable_connection {
// There might be mutliple connections available. Accept until all of them have
// been handled.
loop {
match self.listener.accept() {
Ok((stream, addr)) => {
if let Err(e) = self.handle_accepted_connection(stream, addr) {
self.reregister_poll_interest()?;
return Err(e);
}
handled_connections += 1;
}
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => break,
Err(err) => {
self.reregister_poll_interest()?;
return Err(TcpTmtcError::Io(err));
}
}
}
}
if handled_connections > 0 {
return Ok(ConnectionResult::HandledConnections(handled_connections));
}
Ok(ConnectionResult::AcceptTimeout)
}
fn reregister_poll_interest(&mut self) -> io::Result<()> {
self.poll.registry().reregister(
&mut self.listener,
Token(0),
Interest::READABLE | Interest::WRITABLE,
)
}
fn handle_accepted_connection(
&mut self,
mut stream: TcpStream,
addr: SocketAddr,
) -> Result<(), TcpTmtcError<TmError, TcSendError>> {
let mut current_write_idx;
let mut next_write_idx = 0;
let (mut stream, addr) = self.listener.accept()?;
stream.set_nonblocking(true)?;
connection_result.addr = Some(addr);
let mut connection_result = HandledConnectionInfo::new(addr);
current_write_idx = next_write_idx;
loop {
let read_result = stream.read(&mut self.tc_buffer[current_write_idx..]);
@ -240,7 +364,8 @@ impl<
if current_write_idx > 0 {
self.tc_handler.handle_tc_parsing(
&mut self.tc_buffer,
&mut self.tc_receiver,
self.id,
&self.tc_sender,
&mut connection_result,
current_write_idx,
&mut next_write_idx,
@ -254,7 +379,8 @@ impl<
if current_write_idx == self.tc_buffer.capacity() {
self.tc_handler.handle_tc_parsing(
&mut self.tc_buffer,
&mut self.tc_receiver,
self.id,
&self.tc_sender,
&mut connection_result,
current_write_idx,
&mut next_write_idx,
@ -268,7 +394,8 @@ impl<
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => {
self.tc_handler.handle_tc_parsing(
&mut self.tc_buffer,
&mut self.tc_receiver,
self.id,
&self.tc_sender,
&mut connection_result,
current_write_idx,
&mut next_write_idx,
@ -284,6 +411,18 @@ impl<
// No TC read, no TM was sent, but the client has not disconnected.
// Perform an inner delay to avoid burning CPU time.
thread::sleep(self.inner_loop_delay);
// Optional stop signal handling.
if self.stop_signal.is_some()
&& self
.stop_signal
.as_ref()
.unwrap()
.load(std::sync::atomic::Ordering::Relaxed)
{
connection_result.stopped_by_signal = true;
self.finished_handler.handled_connection(connection_result);
return Ok(());
}
}
}
_ => {
@ -298,7 +437,8 @@ impl<
&mut connection_result,
&mut stream,
)?;
Ok(connection_result)
self.finished_handler.handled_connection(connection_result);
Ok(())
}
}
@ -308,21 +448,9 @@ pub(crate) mod tests {
use alloc::{collections::VecDeque, sync::Arc, vec::Vec};
use crate::tmtc::{ReceivesTcCore, TmPacketSourceCore};
use crate::tmtc::PacketSource;
#[derive(Default, Clone)]
pub(crate) struct SyncTcCacher {
pub(crate) tc_queue: Arc<Mutex<VecDeque<Vec<u8>>>>,
}
impl ReceivesTcCore for SyncTcCacher {
type Error = ();
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
let mut tc_queue = self.tc_queue.lock().expect("tc forwarder failed");
tc_queue.push_back(tc_raw.to_vec());
Ok(())
}
}
use super::*;
#[derive(Default, Clone)]
pub(crate) struct SyncTmSource {
@ -336,7 +464,7 @@ pub(crate) mod tests {
}
}
impl TmPacketSourceCore for SyncTmSource {
impl PacketSource for SyncTmSource {
type Error = ();
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
@ -356,4 +484,30 @@ pub(crate) mod tests {
Ok(0)
}
}
#[derive(Default)]
pub struct ConnectionFinishedHandler {
connection_info: VecDeque<HandledConnectionInfo>,
}
impl HandledConnectionHandler for ConnectionFinishedHandler {
fn handled_connection(&mut self, info: HandledConnectionInfo) {
self.connection_info.push_back(info);
}
}
impl ConnectionFinishedHandler {
pub fn check_last_connection(&mut self, num_tms: u32, num_tcs: u32) {
let last_conn_result = self
.connection_info
.pop_back()
.expect("no connection info available");
assert_eq!(last_conn_result.num_received_tcs, num_tcs);
assert_eq!(last_conn_result.num_sent_tms, num_tms);
}
pub fn check_no_connections_left(&self) {
assert!(self.connection_info.is_empty());
}
}
}

View File

@ -1,48 +1,44 @@
use alloc::sync::Arc;
use core::{sync::atomic::AtomicBool, time::Duration};
use delegate::delegate;
use std::{
io::Write,
net::{SocketAddr, TcpListener, TcpStream},
};
use alloc::boxed::Box;
use mio::net::{TcpListener, TcpStream};
use std::{io::Write, net::SocketAddr};
use crate::{
encoding::{ccsds::PacketIdLookup, parse_buffer_for_ccsds_space_packets},
tmtc::{ReceivesTc, TmPacketSource},
encoding::{ccsds::SpacePacketValidator, parse_buffer_for_ccsds_space_packets},
tmtc::{PacketSenderRaw, PacketSource},
ComponentId,
};
use super::tcp_server::{
ConnectionResult, ServerConfig, TcpTcParser, TcpTmSender, TcpTmtcError, TcpTmtcGenericServer,
ConnectionResult, HandledConnectionHandler, HandledConnectionInfo, ServerConfig, TcpTcParser,
TcpTmSender, TcpTmtcError, TcpTmtcGenericServer,
};
/// Concrete [TcpTcParser] implementation for the [TcpSpacepacketsServer].
pub struct SpacepacketsTcParser {
packet_id_lookup: Box<dyn PacketIdLookup + Send>,
}
impl SpacepacketsTcParser {
pub fn new(packet_id_lookup: Box<dyn PacketIdLookup + Send>) -> Self {
Self { packet_id_lookup }
}
}
impl<TmError, TcError: 'static> TcpTcParser<TmError, TcError> for SpacepacketsTcParser {
impl<T: SpacePacketValidator, TmError, TcError: 'static> TcpTcParser<TmError, TcError> for T {
fn handle_tc_parsing(
&mut self,
tc_buffer: &mut [u8],
tc_receiver: &mut (impl ReceivesTc<Error = TcError> + ?Sized),
conn_result: &mut ConnectionResult,
sender_id: ComponentId,
tc_sender: &(impl PacketSenderRaw<Error = TcError> + ?Sized),
conn_result: &mut HandledConnectionInfo,
current_write_idx: usize,
next_write_idx: &mut usize,
) -> Result<(), TcpTmtcError<TmError, TcError>> {
// Reader vec full, need to parse for packets.
conn_result.num_received_tcs += parse_buffer_for_ccsds_space_packets(
&mut tc_buffer[..current_write_idx],
self.packet_id_lookup.as_ref(),
tc_receiver.upcast_mut(),
next_write_idx,
let parse_result = parse_buffer_for_ccsds_space_packets(
&tc_buffer[..current_write_idx],
self,
sender_id,
tc_sender,
)
.map_err(|e| TcpTmtcError::TcError(e))?;
if let Some(broken_tail_start) = parse_result.incomplete_tail_start {
// Copy broken tail to front of buffer.
tc_buffer.copy_within(broken_tail_start..current_write_idx, 0);
*next_write_idx = current_write_idx - broken_tail_start;
}
conn_result.num_received_tcs += parse_result.packets_found;
Ok(())
}
}
@ -55,8 +51,8 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for SpacepacketsTmSender {
fn handle_tm_sending(
&mut self,
tm_buffer: &mut [u8],
tm_source: &mut (impl TmPacketSource<Error = TmError> + ?Sized),
conn_result: &mut ConnectionResult,
tm_source: &mut (impl PacketSource<Error = TmError> + ?Sized),
conn_result: &mut HandledConnectionInfo,
stream: &mut TcpStream,
) -> Result<bool, TcpTmtcError<TmError, TcError>> {
let mut tm_was_sent = false;
@ -83,35 +79,41 @@ impl<TmError, TcError> TcpTmSender<TmError, TcError> for SpacepacketsTmSender {
///
/// This serves only works if
/// [CCSDS 133.0-B-2 space packets](https://public.ccsds.org/Pubs/133x0b2e1.pdf) are the only
/// packet type being exchanged. It uses the CCSDS [spacepackets::PacketId] as the packet delimiter
/// and start marker when parsing for packets. The user specifies a set of expected
/// [spacepackets::PacketId]s as part of the server configuration for that purpose.
/// packet type being exchanged. It uses the CCSDS space packet header [spacepackets::SpHeader] and
/// a user specified [SpacePacketValidator] to determine the space packets relevant for further
/// processing.
///
/// ## Example
///
/// The [TCP server integration tests](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs/tests/tcp_servers.rs)
/// also serves as the example application for this module.
pub struct TcpSpacepacketsServer<
TmSource: PacketSource<Error = TmError>,
TcSender: PacketSenderRaw<Error = SendError>,
Validator: SpacePacketValidator,
HandledConnection: HandledConnectionHandler,
TmError,
TcError: 'static,
TmSource: TmPacketSource<Error = TmError>,
TcReceiver: ReceivesTc<Error = TcError>,
SendError: 'static,
> {
generic_server: TcpTmtcGenericServer<
TmError,
TcError,
pub generic_server: TcpTmtcGenericServer<
TmSource,
TcReceiver,
TcSender,
SpacepacketsTmSender,
SpacepacketsTcParser,
Validator,
HandledConnection,
TmError,
SendError,
>,
}
impl<
TmSource: PacketSource<Error = TmError>,
TcSender: PacketSenderRaw<Error = TcError>,
Validator: SpacePacketValidator,
HandledConnection: HandledConnectionHandler,
TmError: 'static,
TcError: 'static,
TmSource: TmPacketSource<Error = TmError>,
TcReceiver: ReceivesTc<Error = TcError>,
> TcpSpacepacketsServer<TmError, TcError, TmSource, TcReceiver>
> TcpSpacepacketsServer<TmSource, TcSender, Validator, HandledConnection, TmError, TcError>
{
///
/// ## Parameter
@ -119,23 +121,31 @@ impl<
/// * `cfg` - Configuration of the server.
/// * `tm_source` - Generic TM source used by the server to pull telemetry packets which are
/// then sent back to the client.
/// * `tc_receiver` - Any received telecommands which were decoded successfully will be
/// forwarded to this TC receiver.
/// * `packet_id_lookup` - This lookup table contains the relevant packets IDs for packet
/// parsing. This mechanism is used to have a start marker for finding CCSDS packets.
/// * `tc_sender` - Any received telecommands which were decoded successfully will be
/// forwarded using this [PacketSenderRaw].
/// * `validator` - Used to determine the space packets relevant for further processing and
/// to detect broken space packets.
/// * `handled_connection_hook` - Called to notify the user about a succesfully handled
/// connection.
/// * `stop_signal` - Can be used to shut down the TCP server even for longer running
/// connections.
pub fn new(
cfg: ServerConfig,
tm_source: TmSource,
tc_receiver: TcReceiver,
packet_id_lookup: Box<dyn PacketIdLookup + Send>,
tc_sender: TcSender,
validator: Validator,
handled_connection_hook: HandledConnection,
stop_signal: Option<Arc<AtomicBool>>,
) -> Result<Self, std::io::Error> {
Ok(Self {
generic_server: TcpTmtcGenericServer::new(
cfg,
SpacepacketsTcParser::new(packet_id_lookup),
validator,
SpacepacketsTmSender::default(),
tm_source,
tc_receiver,
tc_sender,
handled_connection_hook,
stop_signal,
)?,
})
}
@ -148,9 +158,10 @@ impl<
/// useful if using the port number 0 for OS auto-assignment.
pub fn local_addr(&self) -> std::io::Result<SocketAddr>;
/// Delegation to the [TcpTmtcGenericServer::handle_next_connection] call.
pub fn handle_next_connection(
/// Delegation to the [TcpTmtcGenericServer::handle_all_connections] call.
pub fn handle_all_connections(
&mut self,
poll_timeout: Option<Duration>
) -> Result<ConnectionResult, TcpTmtcError<TmError, TcError>>;
}
}
@ -167,39 +178,70 @@ mod tests {
use std::{
io::{Read, Write},
net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream},
sync::mpsc,
thread,
};
use alloc::{boxed::Box, sync::Arc};
use alloc::sync::Arc;
use hashbrown::HashSet;
use spacepackets::{
ecss::{tc::PusTcCreator, WritablePusPacket},
PacketId, SpHeader,
CcsdsPacket, PacketId, SpHeader,
};
use crate::hal::std::tcp_server::{
tests::{SyncTcCacher, SyncTmSource},
ServerConfig,
use crate::{
encoding::ccsds::{SpValidity, SpacePacketValidator},
hal::std::tcp_server::{
tests::{ConnectionFinishedHandler, SyncTmSource},
ConnectionResult, ServerConfig,
},
queue::GenericSendError,
tmtc::PacketAsVec,
ComponentId,
};
use super::TcpSpacepacketsServer;
const TCP_SERVER_ID: ComponentId = 0x05;
const TEST_APID_0: u16 = 0x02;
const TEST_PACKET_ID_0: PacketId = PacketId::const_tc(true, TEST_APID_0);
const TEST_PACKET_ID_0: PacketId = PacketId::new_for_tc(true, TEST_APID_0);
const TEST_APID_1: u16 = 0x10;
const TEST_PACKET_ID_1: PacketId = PacketId::const_tc(true, TEST_APID_1);
const TEST_PACKET_ID_1: PacketId = PacketId::new_for_tc(true, TEST_APID_1);
#[derive(Default)]
pub struct SimpleValidator(pub HashSet<PacketId>);
impl SpacePacketValidator for SimpleValidator {
fn validate(&self, sp_header: &SpHeader, _raw_buf: &[u8]) -> SpValidity {
if self.0.contains(&sp_header.packet_id()) {
return SpValidity::Valid;
}
// Simple case: Assume that the interface always contains valid space packets.
SpValidity::Skip
}
}
fn generic_tmtc_server(
addr: &SocketAddr,
tc_receiver: SyncTcCacher,
tc_sender: mpsc::Sender<PacketAsVec>,
tm_source: SyncTmSource,
packet_id_lookup: HashSet<PacketId>,
) -> TcpSpacepacketsServer<(), (), SyncTmSource, SyncTcCacher> {
validator: SimpleValidator,
stop_signal: Option<Arc<AtomicBool>>,
) -> TcpSpacepacketsServer<
SyncTmSource,
mpsc::Sender<PacketAsVec>,
SimpleValidator,
ConnectionFinishedHandler,
(),
GenericSendError,
> {
TcpSpacepacketsServer::new(
ServerConfig::new(*addr, Duration::from_millis(2), 1024, 1024),
ServerConfig::new(TCP_SERVER_ID, *addr, Duration::from_millis(2), 1024, 1024),
tm_source,
tc_receiver,
Box::new(packet_id_lookup),
tc_sender,
validator,
ConnectionFinishedHandler::default(),
stop_signal,
)
.expect("TCP server generation failed")
}
@ -207,15 +249,16 @@ mod tests {
#[test]
fn test_basic_tc_only() {
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
let tc_receiver = SyncTcCacher::default();
let (tc_sender, tc_receiver) = mpsc::channel();
let tm_source = SyncTmSource::default();
let mut packet_id_lookup = HashSet::new();
packet_id_lookup.insert(TEST_PACKET_ID_0);
let mut validator = SimpleValidator::default();
validator.0.insert(TEST_PACKET_ID_0);
let mut tcp_server = generic_tmtc_server(
&auto_port_addr,
tc_receiver.clone(),
tc_sender.clone(),
tm_source,
packet_id_lookup,
validator,
None,
);
let dest_addr = tcp_server
.local_addr()
@ -224,17 +267,24 @@ mod tests {
let set_if_done = conn_handled.clone();
// Call the connection handler in separate thread, does block.
thread::spawn(move || {
let result = tcp_server.handle_next_connection();
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(100)));
if result.is_err() {
panic!("handling connection failed: {:?}", result.unwrap_err());
}
let conn_result = result.unwrap();
assert_eq!(conn_result.num_received_tcs, 1);
assert_eq!(conn_result.num_sent_tms, 0);
matches!(conn_result, ConnectionResult::HandledConnections(1));
tcp_server
.generic_server
.finished_handler
.check_last_connection(0, 1);
tcp_server
.generic_server
.finished_handler
.check_no_connections_left();
set_if_done.store(true, Ordering::Relaxed);
});
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let ping_tc =
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_0), 17, 1, &[], true);
let tc_0 = ping_tc.to_vec().expect("packet generation failed");
let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed");
stream
@ -251,40 +301,40 @@ mod tests {
if !conn_handled.load(Ordering::Relaxed) {
panic!("connection was not handled properly");
}
// Check that TC has arrived.
let mut tc_queue = tc_receiver.tc_queue.lock().unwrap();
assert_eq!(tc_queue.len(), 1);
assert_eq!(tc_queue.pop_front().unwrap(), tc_0);
let packet = tc_receiver.try_recv().expect("receiving TC failed");
assert_eq!(packet.packet, tc_0);
matches!(tc_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
}
#[test]
fn test_multi_tc_multi_tm() {
let auto_port_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
let tc_receiver = SyncTcCacher::default();
let (tc_sender, tc_receiver) = mpsc::channel();
let mut tm_source = SyncTmSource::default();
// Add telemetry
let mut total_tm_len = 0;
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
let verif_tm = PusTcCreator::new_simple(&mut sph, 1, 1, None, true);
let verif_tm =
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_0), 1, 1, &[], true);
let tm_0 = verif_tm.to_vec().expect("writing packet failed");
total_tm_len += tm_0.len();
tm_source.add_tm(&tm_0);
let mut sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
let verif_tm = PusTcCreator::new_simple(&mut sph, 1, 3, None, true);
let verif_tm =
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_1), 1, 3, &[], true);
let tm_1 = verif_tm.to_vec().expect("writing packet failed");
total_tm_len += tm_1.len();
tm_source.add_tm(&tm_1);
// Set up server
let mut packet_id_lookup = HashSet::new();
packet_id_lookup.insert(TEST_PACKET_ID_0);
packet_id_lookup.insert(TEST_PACKET_ID_1);
let mut validator = SimpleValidator::default();
validator.0.insert(TEST_PACKET_ID_0);
validator.0.insert(TEST_PACKET_ID_1);
let mut tcp_server = generic_tmtc_server(
&auto_port_addr,
tc_receiver.clone(),
tc_sender.clone(),
tm_source,
packet_id_lookup,
validator,
None,
);
let dest_addr = tcp_server
.local_addr()
@ -294,16 +344,20 @@ mod tests {
// Call the connection handler in separate thread, does block.
thread::spawn(move || {
let result = tcp_server.handle_next_connection();
let result = tcp_server.handle_all_connections(Some(Duration::from_millis(100)));
if result.is_err() {
panic!("handling connection failed: {:?}", result.unwrap_err());
}
let conn_result = result.unwrap();
assert_eq!(
conn_result.num_received_tcs, 2,
"wrong number of received TCs"
);
assert_eq!(conn_result.num_sent_tms, 2, "wrong number of sent TMs");
matches!(conn_result, ConnectionResult::HandledConnections(1));
tcp_server
.generic_server
.finished_handler
.check_last_connection(2, 2);
tcp_server
.generic_server
.finished_handler
.check_no_connections_left();
set_if_done.store(true, Ordering::Relaxed);
});
let mut stream = TcpStream::connect(dest_addr).expect("connecting to TCP server failed");
@ -312,14 +366,14 @@ mod tests {
.expect("setting reas timeout failed");
// Send telecommands
let mut sph = SpHeader::tc_unseg(TEST_APID_0, 0, 0).unwrap();
let ping_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let ping_tc =
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_0), 17, 1, &[], true);
let tc_0 = ping_tc.to_vec().expect("ping tc creation failed");
stream
.write_all(&tc_0)
.expect("writing to TCP server failed");
let mut sph = SpHeader::tc_unseg(TEST_APID_1, 0, 0).unwrap();
let action_tc = PusTcCreator::new_simple(&mut sph, 8, 0, None, true);
let action_tc =
PusTcCreator::new_simple(SpHeader::new_from_apid(TEST_APID_1), 8, 0, &[], true);
let tc_1 = action_tc.to_vec().expect("action tc creation failed");
stream
.write_all(&tc_1)
@ -354,9 +408,10 @@ mod tests {
panic!("connection was not handled properly");
}
// Check that TC has arrived.
let mut tc_queue = tc_receiver.tc_queue.lock().unwrap();
assert_eq!(tc_queue.len(), 2);
assert_eq!(tc_queue.pop_front().unwrap(), tc_0);
assert_eq!(tc_queue.pop_front().unwrap(), tc_1);
let packet_0 = tc_receiver.try_recv().expect("receiving TC failed");
assert_eq!(packet_0.packet, tc_0);
let packet_1 = tc_receiver.try_recv().expect("receiving TC failed");
assert_eq!(packet_1.packet, tc_1);
matches!(tc_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
}
}

View File

@ -1,7 +1,8 @@
//! Generic UDP TC server.
use crate::tmtc::{ReceivesTc, ReceivesTcCore};
use std::boxed::Box;
use std::io::{Error, ErrorKind};
use crate::tmtc::PacketSenderRaw;
use crate::ComponentId;
use core::fmt::Debug;
use std::io::{self, ErrorKind};
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::vec;
use std::vec::Vec;
@ -11,45 +12,46 @@ use std::vec::Vec;
///
/// It caches all received telecomands into a vector. The maximum expected telecommand size should
/// be declared upfront. This avoids dynamic allocation during run-time. The user can specify a TC
/// receiver in form of a special trait object which implements [ReceivesTc]. Please note that the
/// receiver should copy out the received data if it the data is required past the
/// [ReceivesTcCore::pass_tc] call.
/// sender in form of a special trait object which implements [PacketSenderRaw]. For example, this
/// can be used to send the telecommands to a centralized TC source component for further
/// processing and routing.
///
/// # Examples
///
/// ```
/// use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
/// use std::sync::mpsc;
/// use spacepackets::ecss::WritablePusPacket;
/// use satrs::hal::std::udp_server::UdpTcServer;
/// use satrs::tmtc::{ReceivesTc, ReceivesTcCore};
/// use satrs::ComponentId;
/// use satrs::tmtc::PacketSenderRaw;
/// use spacepackets::SpHeader;
/// use spacepackets::ecss::tc::PusTcCreator;
///
/// #[derive (Default)]
/// struct PingReceiver {}
/// impl ReceivesTcCore for PingReceiver {
/// type Error = ();
/// fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
/// assert_eq!(tc_raw.len(), 13);
/// Ok(())
/// }
/// }
/// const UDP_SERVER_ID: ComponentId = 0x05;
///
/// let mut buf = [0; 32];
/// let (packet_sender, packet_receiver) = mpsc::channel();
/// let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777);
/// let ping_receiver = PingReceiver::default();
/// let mut udp_tc_server = UdpTcServer::new(dest_addr, 2048, Box::new(ping_receiver))
/// let mut udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, dest_addr, 2048, packet_sender)
/// .expect("Creating UDP TMTC server failed");
/// let mut sph = SpHeader::tc_unseg(0x02, 0, 0).unwrap();
/// let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
/// let len = pus_tc
/// .write_to_bytes(&mut buf)
/// .expect("Error writing PUS TC packet");
/// assert_eq!(len, 13);
/// let client = UdpSocket::bind("127.0.0.1:7778").expect("Connecting to UDP server failed");
/// let sph = SpHeader::new_from_apid(0x02);
/// let pus_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true);
/// // Can not fail.
/// let ping_tc_raw = pus_tc.to_vec().unwrap();
///
/// // Now create a UDP client and send the ping telecommand to the server.
/// let client = UdpSocket::bind("127.0.0.1:0").expect("creating UDP client failed");
/// client
/// .send_to(&buf[0..len], dest_addr)
/// .send_to(&ping_tc_raw, dest_addr)
/// .expect("Error sending PUS TC via UDP");
/// let recv_result = udp_tc_server.try_recv_tc();
/// assert!(recv_result.is_ok());
/// // The packet is received by the UDP TC server and sent via the mpsc channel.
/// let sent_packet_with_sender = packet_receiver.try_recv().expect("expected telecommand");
/// assert_eq!(sent_packet_with_sender.packet, ping_tc_raw);
/// assert_eq!(sent_packet_with_sender.sender_id, UDP_SERVER_ID);
/// // No more packets received.
/// matches!(packet_receiver.try_recv(), Err(mpsc::TryRecvError::Empty));
/// ```
///
/// The [satrs-example crate](https://egit.irs.uni-stuttgart.de/rust/fsrc-launchpad/src/branch/main/satrs-example)
@ -57,65 +59,45 @@ use std::vec::Vec;
/// [example code](https://egit.irs.uni-stuttgart.de/rust/sat-rs/src/branch/main/satrs-example/src/tmtc.rs#L67)
/// on how to use this TC server. It uses the server to receive PUS telecommands on a specific port
/// and then forwards them to a generic CCSDS packet receiver.
pub struct UdpTcServer<E> {
pub struct UdpTcServer<TcSender: PacketSenderRaw<Error = SendError>, SendError> {
pub id: ComponentId,
pub socket: UdpSocket,
recv_buf: Vec<u8>,
sender_addr: Option<SocketAddr>,
tc_receiver: Box<dyn ReceivesTc<Error = E>>,
pub tc_sender: TcSender,
}
#[derive(Debug)]
pub enum ReceiveResult<E> {
#[derive(Debug, thiserror::Error)]
pub enum ReceiveResult<SendError: Debug + 'static> {
#[error("nothing was received")]
NothingReceived,
IoError(Error),
ReceiverError(E),
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Send(SendError),
}
impl<E> From<Error> for ReceiveResult<E> {
fn from(e: Error) -> Self {
ReceiveResult::IoError(e)
}
}
impl<E: PartialEq> PartialEq for ReceiveResult<E> {
fn eq(&self, other: &Self) -> bool {
use ReceiveResult::*;
match (self, other) {
(IoError(ref e), IoError(ref other_e)) => e.kind() == other_e.kind(),
(NothingReceived, NothingReceived) => true,
(ReceiverError(e), ReceiverError(other_e)) => e == other_e,
_ => false,
}
}
}
impl<E: Eq + PartialEq> Eq for ReceiveResult<E> {}
impl<E: 'static> ReceivesTcCore for UdpTcServer<E> {
type Error = E;
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
self.tc_receiver.pass_tc(tc_raw)
}
}
impl<E: 'static> UdpTcServer<E> {
impl<TcSender: PacketSenderRaw<Error = SendError>, SendError: Debug + 'static>
UdpTcServer<TcSender, SendError>
{
pub fn new<A: ToSocketAddrs>(
id: ComponentId,
addr: A,
max_recv_size: usize,
tc_receiver: Box<dyn ReceivesTc<Error = E>>,
) -> Result<Self, Error> {
tc_sender: TcSender,
) -> Result<Self, io::Error> {
let server = Self {
id,
socket: UdpSocket::bind(addr)?,
recv_buf: vec![0; max_recv_size],
sender_addr: None,
tc_receiver,
tc_sender,
};
server.socket.set_nonblocking(true)?;
Ok(server)
}
pub fn try_recv_tc(&mut self) -> Result<(usize, SocketAddr), ReceiveResult<E>> {
pub fn try_recv_tc(&mut self) -> Result<(usize, SocketAddr), ReceiveResult<SendError>> {
let res = match self.socket.recv_from(&mut self.recv_buf) {
Ok(res) => res,
Err(e) => {
@ -128,9 +110,9 @@ impl<E: 'static> UdpTcServer<E> {
};
let (num_bytes, from) = res;
self.sender_addr = Some(from);
self.tc_receiver
.pass_tc(&self.recv_buf[0..num_bytes])
.map_err(|e| ReceiveResult::ReceiverError(e))?;
self.tc_sender
.send_packet(self.id, &self.recv_buf[0..num_bytes])
.map_err(ReceiveResult::Send)?;
Ok(res)
}
@ -142,29 +124,35 @@ impl<E: 'static> UdpTcServer<E> {
#[cfg(test)]
mod tests {
use crate::hal::std::udp_server::{ReceiveResult, UdpTcServer};
use crate::tmtc::ReceivesTcCore;
use crate::queue::GenericSendError;
use crate::tmtc::PacketSenderRaw;
use crate::ComponentId;
use core::cell::RefCell;
use spacepackets::ecss::tc::PusTcCreator;
use spacepackets::ecss::WritablePusPacket;
use spacepackets::SpHeader;
use std::boxed::Box;
use std::collections::VecDeque;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::vec::Vec;
fn is_send<T: Send>(_: &T) {}
const UDP_SERVER_ID: ComponentId = 0x05;
#[derive(Default)]
struct PingReceiver {
pub sent_cmds: VecDeque<Vec<u8>>,
pub sent_cmds: RefCell<VecDeque<Vec<u8>>>,
}
impl ReceivesTcCore for PingReceiver {
type Error = ();
impl PacketSenderRaw for PingReceiver {
type Error = GenericSendError;
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
assert_eq!(sender_id, UDP_SERVER_ID);
let mut sent_data = Vec::new();
sent_data.extend_from_slice(tc_raw);
self.sent_cmds.push_back(sent_data);
let mut queue = self.sent_cmds.borrow_mut();
queue.push_back(sent_data);
Ok(())
}
}
@ -175,11 +163,11 @@ mod tests {
let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7777);
let ping_receiver = PingReceiver::default();
is_send(&ping_receiver);
let mut udp_tc_server = UdpTcServer::new(dest_addr, 2048, Box::new(ping_receiver))
let mut udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, dest_addr, 2048, ping_receiver)
.expect("Creating UDP TMTC server failed");
is_send(&udp_tc_server);
let mut sph = SpHeader::tc_unseg(0x02, 0, 0).unwrap();
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let sph = SpHeader::new_from_apid(0x02);
let pus_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true);
let len = pus_tc
.write_to_bytes(&mut buf)
.expect("Error writing PUS TC packet");
@ -195,9 +183,10 @@ mod tests {
udp_tc_server.last_sender().expect("No sender set"),
local_addr
);
let ping_receiver: &mut PingReceiver = udp_tc_server.tc_receiver.downcast_mut().unwrap();
assert_eq!(ping_receiver.sent_cmds.len(), 1);
let sent_cmd = ping_receiver.sent_cmds.pop_front().unwrap();
let ping_receiver = &mut udp_tc_server.tc_sender;
let mut queue = ping_receiver.sent_cmds.borrow_mut();
assert_eq!(queue.len(), 1);
let sent_cmd = queue.pop_front().unwrap();
assert_eq!(sent_cmd, buf[0..len]);
}
@ -205,11 +194,11 @@ mod tests {
fn test_nothing_received() {
let dest_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7779);
let ping_receiver = PingReceiver::default();
let mut udp_tc_server = UdpTcServer::new(dest_addr, 2048, Box::new(ping_receiver))
let mut udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, dest_addr, 2048, ping_receiver)
.expect("Creating UDP TMTC server failed");
let res = udp_tc_server.try_recv_tc();
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(err, ReceiveResult::NothingReceived);
matches!(err, ReceiveResult::NothingReceived);
}
}

View File

@ -1,40 +1,40 @@
use crate::{
pus::verification::{TcStateAccepted, VerificationToken},
TargetId,
};
use crate::ComponentId;
pub type CollectionIntervalFactor = u32;
/// Unique Identifier for a certain housekeeping dataset.
pub type UniqueId = u32;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum HkRequest {
OneShot(UniqueId),
Enable(UniqueId),
Disable(UniqueId),
ModifyCollectionInterval(UniqueId, CollectionIntervalFactor),
pub struct HkRequest {
pub unique_id: UniqueId,
pub variant: HkRequestVariant,
}
impl HkRequest {
pub fn new(unique_id: UniqueId, variant: HkRequestVariant) -> Self {
Self { unique_id, variant }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum HkRequestVariant {
OneShot,
EnablePeriodic,
DisablePeriodic,
ModifyCollectionInterval(CollectionIntervalFactor),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TargetedHkRequest {
pub target_id: TargetId,
pub hk_request: HkRequest,
pub target_id: ComponentId,
pub hk_request: HkRequestVariant,
}
impl TargetedHkRequest {
pub fn new(target_id: TargetId, hk_request: HkRequest) -> Self {
pub fn new(target_id: ComponentId, hk_request: HkRequestVariant) -> Self {
Self {
target_id,
hk_request,
}
}
}
pub trait PusHkRequestRouter {
type Error;
fn route(
&self,
target_id: TargetId,
hk_request: HkRequest,
token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error>;
}

View File

@ -14,7 +14,7 @@
//! - 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/).
#![no_std]
#![cfg_attr(doc_cfg, feature(doc_cfg))]
#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
@ -23,16 +23,15 @@ extern crate downcast_rs;
extern crate std;
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub mod cfdp;
pub mod encoding;
pub mod event_man;
pub mod events;
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod executable;
pub mod hal;
pub mod objects;
#[cfg(feature = "std")]
pub mod mode_tree;
pub mod pool;
pub mod power;
pub mod pus;
@ -40,6 +39,7 @@ pub mod queue;
pub mod request;
pub mod res_code;
pub mod seq_count;
pub mod time;
pub mod tmtc;
pub mod action;
@ -49,8 +49,82 @@ pub mod params;
pub use spacepackets;
/// Generic channel ID type.
pub type ChannelId = u32;
use spacepackets::PacketId;
/// Generic target ID type.
pub type TargetId = u64;
/// Generic component ID type.
pub type ComponentId = u64;
pub trait ValidatorU16Id {
fn validate(&self, id: u16) -> bool;
}
#[cfg(feature = "alloc")]
impl ValidatorU16Id for alloc::vec::Vec<u16> {
fn validate(&self, id: u16) -> bool {
self.contains(&id)
}
}
#[cfg(feature = "alloc")]
impl ValidatorU16Id for hashbrown::HashSet<u16> {
fn validate(&self, id: u16) -> bool {
self.contains(&id)
}
}
impl ValidatorU16Id for u16 {
fn validate(&self, id: u16) -> bool {
id == *self
}
}
impl ValidatorU16Id for &u16 {
fn validate(&self, id: u16) -> bool {
id == **self
}
}
impl ValidatorU16Id for [u16] {
fn validate(&self, id: u16) -> bool {
self.binary_search(&id).is_ok()
}
}
impl ValidatorU16Id for &[u16] {
fn validate(&self, id: u16) -> bool {
self.binary_search(&id).is_ok()
}
}
#[cfg(feature = "alloc")]
impl ValidatorU16Id for alloc::vec::Vec<spacepackets::PacketId> {
fn validate(&self, packet_id: u16) -> bool {
self.contains(&PacketId::from(packet_id))
}
}
#[cfg(feature = "alloc")]
impl ValidatorU16Id for hashbrown::HashSet<spacepackets::PacketId> {
fn validate(&self, packet_id: u16) -> bool {
self.contains(&PacketId::from(packet_id))
}
}
#[cfg(feature = "std")]
impl ValidatorU16Id for std::collections::HashSet<PacketId> {
fn validate(&self, packet_id: u16) -> bool {
self.contains(&PacketId::from(packet_id))
}
}
impl ValidatorU16Id for [PacketId] {
fn validate(&self, packet_id: u16) -> bool {
self.binary_search(&PacketId::from(packet_id)).is_ok()
}
}
impl ValidatorU16Id for &[PacketId] {
fn validate(&self, packet_id: u16) -> bool {
self.binary_search(&PacketId::from(packet_id)).is_ok()
}
}

View File

@ -1,67 +1,95 @@
use core::mem::size_of;
use satrs_shared::res_code::ResultU16;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use spacepackets::ByteConversionError;
use crate::TargetId;
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
#[cfg(feature = "std")]
pub use std_mod::*;
use crate::{
queue::GenericTargetedMessagingError,
request::{GenericMessage, MessageMetadata, MessageReceiver, MessageReceiverWithId, RequestId},
ComponentId,
};
pub type Mode = u32;
pub type Submode = u16;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModeAndSubmode {
mode: u32,
submode: u16,
mode: Mode,
submode: Submode,
}
impl ModeAndSubmode {
pub const fn new_mode_only(mode: u32) -> Self {
pub const RAW_LEN: usize = size_of::<Mode>() + size_of::<Submode>();
pub const fn new_mode_only(mode: Mode) -> Self {
Self { mode, submode: 0 }
}
pub const fn new(mode: u32, submode: u16) -> Self {
pub const fn new(mode: Mode, submode: Submode) -> Self {
Self { mode, submode }
}
pub fn raw_len() -> usize {
size_of::<u32>() + size_of::<u16>()
}
pub fn from_be_bytes(buf: &[u8]) -> Result<Self, ByteConversionError> {
if buf.len() < 6 {
return Err(ByteConversionError::FromSliceTooSmall {
expected: 6,
expected: Self::RAW_LEN,
found: buf.len(),
});
}
Ok(Self {
mode: u32::from_be_bytes(buf[0..4].try_into().unwrap()),
submode: u16::from_be_bytes(buf[4..6].try_into().unwrap()),
mode: Mode::from_be_bytes(buf[0..size_of::<Mode>()].try_into().unwrap()),
submode: Submode::from_be_bytes(
buf[size_of::<Mode>()..size_of::<Mode>() + size_of::<Submode>()]
.try_into()
.unwrap(),
),
})
}
pub fn mode(&self) -> u32 {
pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
if buf.len() < Self::RAW_LEN {
return Err(ByteConversionError::ToSliceTooSmall {
expected: Self::RAW_LEN,
found: buf.len(),
});
}
buf[0..size_of::<Mode>()].copy_from_slice(&self.mode.to_be_bytes());
buf[size_of::<Mode>()..Self::RAW_LEN].copy_from_slice(&self.submode.to_be_bytes());
Ok(Self::RAW_LEN)
}
pub fn mode(&self) -> Mode {
self.mode
}
pub fn submode(&self) -> u16 {
pub fn submode(&self) -> Submode {
self.submode
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TargetedModeCommand {
pub address: TargetId,
pub address: ComponentId,
pub mode_submode: ModeAndSubmode,
}
impl TargetedModeCommand {
pub const fn new(address: TargetId, mode_submode: ModeAndSubmode) -> Self {
pub const fn new(address: ComponentId, mode_submode: ModeAndSubmode) -> Self {
Self {
address,
mode_submode,
}
}
pub fn address(&self) -> TargetId {
pub fn address(&self) -> ComponentId {
self.address
}
@ -81,6 +109,8 @@ impl TargetedModeCommand {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ModeRequest {
/// Mode information. Can be used to notify other components of changed modes.
ModeInfo(ModeAndSubmode),
SetMode(ModeAndSubmode),
ReadMode,
AnnounceMode,
@ -90,6 +120,471 @@ pub enum ModeRequest {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TargetedModeRequest {
target_id: TargetId,
target_id: ComponentId,
mode_request: ModeRequest,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ModeReply {
/// Reply to a mode request to confirm the commanded mode was reached.
ModeReply(ModeAndSubmode),
// Can not reach the commanded mode. Contains a reason as a [ResultU16].
CantReachMode(ResultU16),
/// We are in the wrong mode for unknown reasons. Contains the expected and reached mode.
WrongMode {
expected: ModeAndSubmode,
reached: ModeAndSubmode,
},
}
pub type GenericModeReply = GenericMessage<ModeReply>;
pub trait ModeRequestSender {
fn local_channel_id(&self) -> ComponentId;
fn send_mode_request(
&self,
request_id: RequestId,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError>;
}
pub trait ModeRequestReceiver {
fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError>;
}
impl<R: MessageReceiver<ModeRequest>> ModeRequestReceiver
for MessageReceiverWithId<ModeRequest, R>
{
fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
self.try_recv_message()
}
}
#[derive(Debug, Clone)]
pub enum ModeError {
Messaging(GenericTargetedMessagingError),
}
impl From<GenericTargetedMessagingError> for ModeError {
fn from(value: GenericTargetedMessagingError) -> Self {
Self::Messaging(value)
}
}
pub trait ModeProvider {
fn mode_and_submode(&self) -> ModeAndSubmode;
fn mode(&self) -> Mode {
self.mode_and_submode().mode()
}
fn submode(&self) -> Submode {
self.mode_and_submode().submode()
}
}
pub trait ModeRequestHandler: ModeProvider {
type Error;
fn start_transition(
&mut self,
requestor: MessageMetadata,
mode_and_submode: ModeAndSubmode,
) -> Result<(), Self::Error>;
fn announce_mode(&self, requestor_info: Option<MessageMetadata>, recursive: bool);
fn handle_mode_reached(
&mut self,
requestor_info: Option<MessageMetadata>,
) -> Result<(), Self::Error>;
fn handle_mode_info(
&mut self,
requestor_info: MessageMetadata,
info: ModeAndSubmode,
) -> Result<(), Self::Error>;
fn send_mode_reply(
&self,
requestor_info: MessageMetadata,
reply: ModeReply,
) -> Result<(), Self::Error>;
fn handle_mode_request(
&mut self,
request: GenericMessage<ModeRequest>,
) -> Result<(), Self::Error> {
match request.message {
ModeRequest::SetMode(mode_and_submode) => {
self.start_transition(request.requestor_info, mode_and_submode)
}
ModeRequest::ReadMode => self.send_mode_reply(
request.requestor_info,
ModeReply::ModeReply(self.mode_and_submode()),
),
ModeRequest::AnnounceMode => {
self.announce_mode(Some(request.requestor_info), false);
Ok(())
}
ModeRequest::AnnounceModeRecursive => {
self.announce_mode(Some(request.requestor_info), true);
Ok(())
}
ModeRequest::ModeInfo(info) => self.handle_mode_info(request.requestor_info, info),
}
}
}
pub trait ModeReplyReceiver {
fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError>;
}
impl<R: MessageReceiver<ModeReply>> ModeReplyReceiver for MessageReceiverWithId<ModeReply, R> {
fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
self.try_recv_message()
}
}
pub trait ModeReplySender {
fn local_channel_id(&self) -> ComponentId;
/// The requestor is assumed to be the target of the reply.
fn send_mode_reply(
&self,
requestor_info: MessageMetadata,
reply: ModeReply,
) -> Result<(), GenericTargetedMessagingError>;
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use crate::request::{
MessageSender, MessageSenderAndReceiver, MessageSenderMap, RequestAndReplySenderAndReceiver,
};
use super::*;
impl<S: MessageSender<ModeReply>> MessageSenderMap<ModeReply, S> {
pub fn send_mode_reply(
&self,
requestor_info: MessageMetadata,
target_id: ComponentId,
request: ModeReply,
) -> Result<(), GenericTargetedMessagingError> {
self.send_message(requestor_info, target_id, request)
}
pub fn add_reply_target(&mut self, target_id: ComponentId, request_sender: S) {
self.add_message_target(target_id, request_sender)
}
}
impl<FROM, S: MessageSender<ModeReply>, R: MessageReceiver<FROM>> ModeReplySender
for MessageSenderAndReceiver<ModeReply, FROM, S, R>
{
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id_generic()
}
fn send_mode_reply(
&self,
requestor_info: MessageMetadata,
request: ModeReply,
) -> Result<(), GenericTargetedMessagingError> {
self.message_sender_map.send_mode_reply(
MessageMetadata::new(requestor_info.request_id(), self.local_channel_id()),
requestor_info.sender_id(),
request,
)
}
}
impl<TO, S: MessageSender<TO>, R: MessageReceiver<ModeReply>> ModeReplyReceiver
for MessageSenderAndReceiver<TO, ModeReply, S, R>
{
fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
self.message_receiver.try_recv_message()
}
}
impl<
REQUEST,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<ModeReply>,
S1: MessageSender<ModeReply>,
R1: MessageReceiver<REQUEST>,
> RequestAndReplySenderAndReceiver<REQUEST, ModeReply, S0, R0, S1, R1>
{
pub fn add_reply_target(&mut self, target_id: ComponentId, reply_sender: S1) {
self.reply_sender_map
.add_message_target(target_id, reply_sender)
}
}
impl<
REQUEST,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<ModeReply>,
S1: MessageSender<ModeReply>,
R1: MessageReceiver<REQUEST>,
> ModeReplySender for RequestAndReplySenderAndReceiver<REQUEST, ModeReply, S0, R0, S1, R1>
{
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id_generic()
}
fn send_mode_reply(
&self,
requestor_info: MessageMetadata,
request: ModeReply,
) -> Result<(), GenericTargetedMessagingError> {
self.reply_sender_map.send_mode_reply(
MessageMetadata::new(requestor_info.request_id(), self.local_channel_id()),
requestor_info.sender_id(),
request,
)
}
}
impl<
REQUEST,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<ModeReply>,
S1: MessageSender<ModeReply>,
R1: MessageReceiver<REQUEST>,
> ModeReplyReceiver
for RequestAndReplySenderAndReceiver<REQUEST, ModeReply, S0, R0, S1, R1>
{
fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
self.reply_receiver.try_recv_message()
}
}
/// Helper type definition for a mode handler which can handle mode requests.
pub type ModeRequestHandlerInterface<S, R> =
MessageSenderAndReceiver<ModeReply, ModeRequest, S, R>;
impl<S: MessageSender<ModeReply>, R: MessageReceiver<ModeRequest>>
ModeRequestHandlerInterface<S, R>
{
pub fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
self.try_recv_message()
}
pub fn send_mode_reply(
&self,
requestor_info: MessageMetadata,
reply: ModeReply,
) -> Result<(), GenericTargetedMessagingError> {
self.send_message(
requestor_info.request_id(),
requestor_info.sender_id(),
reply,
)
}
}
/// Helper type defintion for a mode handler object which can send mode requests and receive
/// mode replies.
pub type ModeRequestorInterface<S, R> = MessageSenderAndReceiver<ModeRequest, ModeReply, S, R>;
impl<S: MessageSender<ModeRequest>, R: MessageReceiver<ModeReply>> ModeRequestorInterface<S, R> {
pub fn try_recv_mode_reply(
&self,
) -> Result<Option<GenericMessage<ModeReply>>, GenericTargetedMessagingError> {
self.try_recv_message()
}
pub fn send_mode_request(
&self,
request_id: RequestId,
target_id: ComponentId,
reply: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
self.send_message(request_id, target_id, reply)
}
}
/// Helper type defintion for a mode handler object which can both send mode requests and
/// process mode requests.
pub type ModeInterface<S0, R0, S1, R1> =
RequestAndReplySenderAndReceiver<ModeRequest, ModeReply, S0, R0, S1, R1>;
impl<S: MessageSender<ModeRequest>> MessageSenderMap<ModeRequest, S> {
pub fn send_mode_request(
&self,
requestor_info: MessageMetadata,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
self.send_message(requestor_info, target_id, request)
}
pub fn add_request_target(&mut self, target_id: ComponentId, request_sender: S) {
self.add_message_target(target_id, request_sender)
}
}
/*
impl<S: MessageSender<ModeRequest>> ModeRequestSender for MessageSenderMapWithId<ModeRequest, S> {
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id
}
fn send_mode_request(
&self,
request_id: RequestId,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
self.send_message(request_id, target_id, request)
}
}
*/
impl<TO, S: MessageSender<TO>, R: MessageReceiver<ModeRequest>> ModeRequestReceiver
for MessageSenderAndReceiver<TO, ModeRequest, S, R>
{
fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
self.message_receiver.try_recv_message()
}
}
impl<FROM, S: MessageSender<ModeRequest>, R: MessageReceiver<FROM>> ModeRequestSender
for MessageSenderAndReceiver<ModeRequest, FROM, S, R>
{
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id_generic()
}
fn send_mode_request(
&self,
request_id: RequestId,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
self.message_sender_map.send_mode_request(
MessageMetadata::new(request_id, self.local_channel_id()),
target_id,
request,
)
}
}
impl<
REPLY,
S0: MessageSender<ModeRequest>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<ModeRequest>,
> RequestAndReplySenderAndReceiver<ModeRequest, REPLY, S0, R0, S1, R1>
{
pub fn add_request_target(&mut self, target_id: ComponentId, request_sender: S0) {
self.request_sender_map
.add_message_target(target_id, request_sender)
}
}
impl<
REPLY,
S0: MessageSender<ModeRequest>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<ModeRequest>,
> ModeRequestSender
for RequestAndReplySenderAndReceiver<ModeRequest, REPLY, S0, R0, S1, R1>
{
fn local_channel_id(&self) -> ComponentId {
self.local_channel_id_generic()
}
fn send_mode_request(
&self,
request_id: RequestId,
target_id: ComponentId,
request: ModeRequest,
) -> Result<(), GenericTargetedMessagingError> {
self.request_sender_map.send_mode_request(
MessageMetadata::new(request_id, self.local_channel_id()),
target_id,
request,
)
}
}
impl<
REPLY,
S0: MessageSender<ModeRequest>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<ModeRequest>,
> ModeRequestReceiver
for RequestAndReplySenderAndReceiver<ModeRequest, REPLY, S0, R0, S1, R1>
{
fn try_recv_mode_request(
&self,
) -> Result<Option<GenericMessage<ModeRequest>>, GenericTargetedMessagingError> {
self.request_receiver.try_recv_message()
}
}
}
#[cfg(feature = "std")]
pub mod std_mod {
use std::sync::mpsc;
use super::*;
pub type ModeRequestHandlerMpsc = ModeRequestHandlerInterface<
mpsc::Sender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeRequest>>,
>;
pub type ModeRequestHandlerMpscBounded = ModeRequestHandlerInterface<
mpsc::SyncSender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeRequest>>,
>;
pub type ModeRequestorMpsc = ModeRequestorInterface<
mpsc::Sender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
>;
pub type ModeRequestorBoundedMpsc = ModeRequestorInterface<
mpsc::SyncSender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
>;
pub type ModeRequestorAndHandlerMpsc = ModeInterface<
mpsc::Sender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
mpsc::Sender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeRequest>>,
>;
pub type ModeRequestorAndHandlerMpscBounded = ModeInterface<
mpsc::SyncSender<GenericMessage<ModeRequest>>,
mpsc::Receiver<GenericMessage<ModeReply>>,
mpsc::SyncSender<GenericMessage<ModeReply>>,
mpsc::Receiver<GenericMessage<ModeRequest>>,
>;
}
#[cfg(test)]
mod tests {}

37
satrs/src/mode_tree.rs Normal file
View File

@ -0,0 +1,37 @@
use alloc::vec::Vec;
use hashbrown::HashMap;
use crate::{
mode::{Mode, ModeAndSubmode, Submode},
ComponentId,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TableEntryType {
/// Target table containing information of the expected children modes for given mode.
Target,
/// Sequence table which contains information about how to reach a target table, including
/// the order of the sequences.
Sequence,
}
pub struct ModeTableEntry {
/// Name of respective table entry.
pub name: &'static str,
/// Target channel ID.
pub channel_id: ComponentId,
pub mode_submode: ModeAndSubmode,
pub allowed_submode_mask: Option<Submode>,
pub check_success: bool,
}
pub struct ModeTableMapValue {
/// Name for a given mode table entry.
pub name: &'static str,
pub entries: Vec<ModeTableEntry>,
}
pub type ModeTable = HashMap<Mode, ModeTableMapValue>;
#[cfg(test)]
mod tests {}

View File

@ -1,308 +0,0 @@
//! # Module providing addressable object support and a manager for them
//!
//! Each addressable object can be identified using an [object ID][ObjectId].
//! The [system object][ManagedSystemObject] trait also allows storing these objects into the
//! [object manager][ObjectManager]. They can then be retrieved and casted back to a known type
//! using the object ID.
//!
//! # Examples
//!
//! ```rust
//! use std::any::Any;
//! use std::error::Error;
//! use satrs::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
//!
//! struct ExampleSysObj {
//! id: ObjectId,
//! dummy: u32,
//! was_initialized: bool,
//! }
//!
//! impl ExampleSysObj {
//! fn new(id: ObjectId, dummy: u32) -> ExampleSysObj {
//! ExampleSysObj {
//! id,
//! dummy,
//! was_initialized: false,
//! }
//! }
//! }
//!
//! impl SystemObject for ExampleSysObj {
//! type Error = ();
//! fn get_object_id(&self) -> &ObjectId {
//! &self.id
//! }
//!
//! fn initialize(&mut self) -> Result<(), Self::Error> {
//! self.was_initialized = true;
//! Ok(())
//! }
//! }
//!
//! impl ManagedSystemObject for ExampleSysObj {}
//!
//! let mut obj_manager = ObjectManager::default();
//! let obj_id = ObjectId { id: 0, name: "Example 0"};
//! let example_obj = ExampleSysObj::new(obj_id, 42);
//! obj_manager.insert(Box::new(example_obj));
//! let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&obj_id);
//! let example_obj = obj_back_casted.unwrap();
//! assert_eq!(example_obj.id, obj_id);
//! assert_eq!(example_obj.dummy, 42);
//! ```
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
#[cfg(feature = "alloc")]
use downcast_rs::Downcast;
#[cfg(feature = "alloc")]
use hashbrown::HashMap;
#[cfg(feature = "std")]
use std::error::Error;
use crate::TargetId;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct ObjectId {
pub id: TargetId,
pub name: &'static str,
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use super::*;
/// Each object which is stored inside the [object manager][ObjectManager] needs to implemented
/// this trait
pub trait SystemObject: Downcast {
type Error;
fn get_object_id(&self) -> &ObjectId;
fn initialize(&mut self) -> Result<(), Self::Error>;
}
downcast_rs::impl_downcast!(SystemObject assoc Error);
pub trait ManagedSystemObject: SystemObject + Send {}
downcast_rs::impl_downcast!(ManagedSystemObject assoc Error);
/// Helper module to manage multiple [ManagedSystemObjects][ManagedSystemObject] by mapping them
/// using an [object ID][ObjectId]
#[cfg(feature = "alloc")]
pub struct ObjectManager<E> {
obj_map: HashMap<ObjectId, Box<dyn ManagedSystemObject<Error = E>>>,
}
#[cfg(feature = "alloc")]
impl<E: 'static> Default for ObjectManager<E> {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "alloc")]
impl<E: 'static> ObjectManager<E> {
pub fn new() -> Self {
ObjectManager {
obj_map: HashMap::new(),
}
}
pub fn insert(&mut self, sys_obj: Box<dyn ManagedSystemObject<Error = E>>) -> bool {
let obj_id = sys_obj.get_object_id();
if self.obj_map.contains_key(obj_id) {
return false;
}
self.obj_map.insert(*obj_id, sys_obj).is_none()
}
/// Initializes all System Objects in the hash map and returns the number of successful
/// initializations
pub fn initialize(&mut self) -> Result<u32, Box<dyn Error>> {
let mut init_success = 0;
for val in self.obj_map.values_mut() {
if val.initialize().is_ok() {
init_success += 1
}
}
Ok(init_success)
}
/// Retrieve a reference to an object stored inside the manager. The type to retrieve needs to
/// be explicitly passed as a generic parameter or specified on the left hand side of the
/// expression.
pub fn get_ref<T: ManagedSystemObject<Error = E>>(&self, key: &ObjectId) -> Option<&T> {
self.obj_map.get(key).and_then(|o| o.downcast_ref::<T>())
}
/// Retrieve a mutable reference to an object stored inside the manager. The type to retrieve
/// needs to be explicitly passed as a generic parameter or specified on the left hand side
/// of the expression.
pub fn get_mut<T: ManagedSystemObject<Error = E>>(
&mut self,
key: &ObjectId,
) -> Option<&mut T> {
self.obj_map
.get_mut(key)
.and_then(|o| o.downcast_mut::<T>())
}
}
}
#[cfg(test)]
mod tests {
use crate::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
use std::boxed::Box;
use std::string::String;
use std::sync::{Arc, Mutex};
use std::thread;
struct ExampleSysObj {
id: ObjectId,
dummy: u32,
was_initialized: bool,
}
impl ExampleSysObj {
fn new(id: ObjectId, dummy: u32) -> ExampleSysObj {
ExampleSysObj {
id,
dummy,
was_initialized: false,
}
}
}
impl SystemObject for ExampleSysObj {
type Error = ();
fn get_object_id(&self) -> &ObjectId {
&self.id
}
fn initialize(&mut self) -> Result<(), Self::Error> {
self.was_initialized = true;
Ok(())
}
}
impl ManagedSystemObject for ExampleSysObj {}
struct OtherExampleObject {
id: ObjectId,
string: String,
was_initialized: bool,
}
impl SystemObject for OtherExampleObject {
type Error = ();
fn get_object_id(&self) -> &ObjectId {
&self.id
}
fn initialize(&mut self) -> Result<(), Self::Error> {
self.was_initialized = true;
Ok(())
}
}
impl ManagedSystemObject for OtherExampleObject {}
#[test]
fn test_obj_manager_simple() {
let mut obj_manager = ObjectManager::default();
let expl_obj_id = ObjectId {
id: 0,
name: "Example 0",
};
let example_obj = ExampleSysObj::new(expl_obj_id, 42);
assert!(obj_manager.insert(Box::new(example_obj)));
let res = obj_manager.initialize();
assert!(res.is_ok());
assert_eq!(res.unwrap(), 1);
let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&expl_obj_id);
assert!(obj_back_casted.is_some());
let expl_obj_back_casted = obj_back_casted.unwrap();
assert_eq!(expl_obj_back_casted.dummy, 42);
assert!(expl_obj_back_casted.was_initialized);
let second_obj_id = ObjectId {
id: 12,
name: "Example 1",
};
let second_example_obj = OtherExampleObject {
id: second_obj_id,
string: String::from("Hello Test"),
was_initialized: false,
};
assert!(obj_manager.insert(Box::new(second_example_obj)));
let res = obj_manager.initialize();
assert!(res.is_ok());
assert_eq!(res.unwrap(), 2);
let obj_back_casted: Option<&OtherExampleObject> = obj_manager.get_ref(&second_obj_id);
assert!(obj_back_casted.is_some());
let expl_obj_back_casted = obj_back_casted.unwrap();
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));
assert!(expl_obj_back_casted.was_initialized);
let existing_obj_id = ObjectId {
id: 12,
name: "Example 1",
};
let invalid_obj = OtherExampleObject {
id: existing_obj_id,
string: String::from("Hello Test"),
was_initialized: false,
};
assert!(!obj_manager.insert(Box::new(invalid_obj)));
}
#[test]
fn object_man_threaded() {
let obj_manager = Arc::new(Mutex::new(ObjectManager::new()));
let expl_obj_id = ObjectId {
id: 0,
name: "Example 0",
};
let example_obj = ExampleSysObj::new(expl_obj_id, 42);
let second_obj_id = ObjectId {
id: 12,
name: "Example 1",
};
let second_example_obj = OtherExampleObject {
id: second_obj_id,
string: String::from("Hello Test"),
was_initialized: false,
};
let mut obj_man_handle = obj_manager.lock().expect("Mutex lock failed");
assert!(obj_man_handle.insert(Box::new(example_obj)));
assert!(obj_man_handle.insert(Box::new(second_example_obj)));
let res = obj_man_handle.initialize();
std::mem::drop(obj_man_handle);
assert!(res.is_ok());
assert_eq!(res.unwrap(), 2);
let obj_man_0 = obj_manager.clone();
let jh0 = thread::spawn(move || {
let locked_man = obj_man_0.lock().expect("Mutex lock failed");
let obj_back_casted: Option<&ExampleSysObj> = locked_man.get_ref(&expl_obj_id);
assert!(obj_back_casted.is_some());
let expl_obj_back_casted = obj_back_casted.unwrap();
assert_eq!(expl_obj_back_casted.dummy, 42);
assert!(expl_obj_back_casted.was_initialized);
std::mem::drop(locked_man)
});
let jh1 = thread::spawn(move || {
let locked_man = obj_manager.lock().expect("Mutex lock failed");
let obj_back_casted: Option<&OtherExampleObject> = locked_man.get_ref(&second_obj_id);
assert!(obj_back_casted.is_some());
let expl_obj_back_casted = obj_back_casted.unwrap();
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));
assert!(expl_obj_back_casted.was_initialized);
std::mem::drop(locked_man)
});
jh0.join().expect("Joining thread 0 failed");
jh1.join().expect("Joining thread 1 failed");
}
}

View File

@ -43,7 +43,7 @@
//! This includes the [ParamsHeapless] enumeration for contained values which do not require heap
//! allocation, and the [Params] which enumerates [ParamsHeapless] and some additional types which
//! require [alloc] support but allow for more flexbility.
use crate::pool::StoreAddr;
use crate::pool::PoolAddr;
use core::fmt::Debug;
use core::mem::size_of;
use paste::paste;
@ -60,21 +60,28 @@ use alloc::vec::Vec;
/// Generic trait which is used for objects which can be converted into a raw network (big) endian
/// byte format.
pub trait WritableToBeBytes {
fn raw_len(&self) -> usize;
fn written_len(&self) -> usize;
/// Writes the object to a raw buffer in network endianness (big)
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
#[cfg(feature = "alloc")]
fn to_vec(&self) -> Result<Vec<u8>, ByteConversionError> {
let mut vec = alloc::vec![0; self.written_len()];
self.write_to_be_bytes(&mut vec)?;
Ok(vec)
}
}
macro_rules! param_to_be_bytes_impl {
($Newtype: ident) => {
impl WritableToBeBytes for $Newtype {
#[inline]
fn raw_len(&self) -> usize {
fn written_len(&self) -> usize {
size_of::<<Self as ToBeBytes>::ByteArray>()
}
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
let raw_len = self.raw_len();
let raw_len = WritableToBeBytes::written_len(self);
if buf.len() < raw_len {
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
@ -382,32 +389,32 @@ pub enum ParamsRaw {
}
impl WritableToBeBytes for ParamsRaw {
fn raw_len(&self) -> usize {
fn written_len(&self) -> usize {
match self {
ParamsRaw::U8(v) => v.raw_len(),
ParamsRaw::U8Pair(v) => v.raw_len(),
ParamsRaw::U8Triplet(v) => v.raw_len(),
ParamsRaw::I8(v) => v.raw_len(),
ParamsRaw::I8Pair(v) => v.raw_len(),
ParamsRaw::I8Triplet(v) => v.raw_len(),
ParamsRaw::U16(v) => v.raw_len(),
ParamsRaw::U16Pair(v) => v.raw_len(),
ParamsRaw::U16Triplet(v) => v.raw_len(),
ParamsRaw::I16(v) => v.raw_len(),
ParamsRaw::I16Pair(v) => v.raw_len(),
ParamsRaw::I16Triplet(v) => v.raw_len(),
ParamsRaw::U32(v) => v.raw_len(),
ParamsRaw::U32Pair(v) => v.raw_len(),
ParamsRaw::U32Triplet(v) => v.raw_len(),
ParamsRaw::I32(v) => v.raw_len(),
ParamsRaw::I32Pair(v) => v.raw_len(),
ParamsRaw::I32Triplet(v) => v.raw_len(),
ParamsRaw::F32(v) => v.raw_len(),
ParamsRaw::F32Pair(v) => v.raw_len(),
ParamsRaw::F32Triplet(v) => v.raw_len(),
ParamsRaw::U64(v) => v.raw_len(),
ParamsRaw::I64(v) => v.raw_len(),
ParamsRaw::F64(v) => v.raw_len(),
ParamsRaw::U8(v) => WritableToBeBytes::written_len(v),
ParamsRaw::U8Pair(v) => WritableToBeBytes::written_len(v),
ParamsRaw::U8Triplet(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I8(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I8Pair(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I8Triplet(v) => WritableToBeBytes::written_len(v),
ParamsRaw::U16(v) => WritableToBeBytes::written_len(v),
ParamsRaw::U16Pair(v) => WritableToBeBytes::written_len(v),
ParamsRaw::U16Triplet(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I16(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I16Pair(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I16Triplet(v) => WritableToBeBytes::written_len(v),
ParamsRaw::U32(v) => WritableToBeBytes::written_len(v),
ParamsRaw::U32Pair(v) => WritableToBeBytes::written_len(v),
ParamsRaw::U32Triplet(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I32(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I32Pair(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I32Triplet(v) => WritableToBeBytes::written_len(v),
ParamsRaw::F32(v) => WritableToBeBytes::written_len(v),
ParamsRaw::F32Pair(v) => WritableToBeBytes::written_len(v),
ParamsRaw::F32Triplet(v) => WritableToBeBytes::written_len(v),
ParamsRaw::U64(v) => WritableToBeBytes::written_len(v),
ParamsRaw::I64(v) => WritableToBeBytes::written_len(v),
ParamsRaw::F64(v) => WritableToBeBytes::written_len(v),
}
}
@ -460,7 +467,7 @@ params_raw_from_newtype!(
);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum EcssEnumParams {
pub enum ParamsEcssEnum {
U8(EcssEnumU8),
U16(EcssEnumU16),
U32(EcssEnumU32),
@ -468,40 +475,46 @@ pub enum EcssEnumParams {
}
macro_rules! writable_as_be_bytes_ecss_enum_impl {
($EnumIdent: ident) => {
($EnumIdent: ident, $Ty: ident) => {
impl From<$EnumIdent> for ParamsEcssEnum {
fn from(e: $EnumIdent) -> Self {
Self::$Ty(e)
}
}
impl WritableToBeBytes for $EnumIdent {
fn raw_len(&self) -> usize {
fn written_len(&self) -> usize {
self.size()
}
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
<Self as UnsignedEnum>::write_to_be_bytes(self, buf).map(|_| self.raw_len())
<Self as UnsignedEnum>::write_to_be_bytes(self, buf).map(|_| self.written_len())
}
}
};
}
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU8);
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU16);
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU32);
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU64);
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU8, U8);
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU16, U16);
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU32, U32);
writable_as_be_bytes_ecss_enum_impl!(EcssEnumU64, U64);
impl WritableToBeBytes for EcssEnumParams {
fn raw_len(&self) -> usize {
impl WritableToBeBytes for ParamsEcssEnum {
fn written_len(&self) -> usize {
match self {
EcssEnumParams::U8(e) => e.raw_len(),
EcssEnumParams::U16(e) => e.raw_len(),
EcssEnumParams::U32(e) => e.raw_len(),
EcssEnumParams::U64(e) => e.raw_len(),
ParamsEcssEnum::U8(e) => e.written_len(),
ParamsEcssEnum::U16(e) => e.written_len(),
ParamsEcssEnum::U32(e) => e.written_len(),
ParamsEcssEnum::U64(e) => e.written_len(),
}
}
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
match self {
EcssEnumParams::U8(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
EcssEnumParams::U16(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
EcssEnumParams::U32(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
EcssEnumParams::U64(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
ParamsEcssEnum::U8(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
ParamsEcssEnum::U16(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
ParamsEcssEnum::U32(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
ParamsEcssEnum::U64(e) => WritableToBeBytes::write_to_be_bytes(e, buf),
}
}
}
@ -510,7 +523,19 @@ impl WritableToBeBytes for EcssEnumParams {
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ParamsHeapless {
Raw(ParamsRaw),
EcssEnum(EcssEnumParams),
EcssEnum(ParamsEcssEnum),
}
impl From<ParamsRaw> for ParamsHeapless {
fn from(v: ParamsRaw) -> Self {
Self::Raw(v)
}
}
impl From<ParamsEcssEnum> for ParamsHeapless {
fn from(v: ParamsEcssEnum) -> Self {
Self::EcssEnum(v)
}
}
macro_rules! from_conversions_for_raw {
@ -559,21 +584,19 @@ from_conversions_for_raw!(
/// Generic enumeration for additional parameters, including parameters which rely on heap
/// allocations.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Params {
Heapless(ParamsHeapless),
Store(StoreAddr),
Store(PoolAddr),
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
Vec(Vec<u8>),
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
String(String),
}
impl From<StoreAddr> for Params {
fn from(x: StoreAddr) -> Self {
impl From<PoolAddr> for Params {
fn from(x: PoolAddr) -> Self {
Self::Store(x)
}
}
@ -584,8 +607,13 @@ impl From<ParamsHeapless> for Params {
}
}
impl From<ParamsRaw> for Params {
fn from(x: ParamsRaw) -> Self {
Self::Heapless(ParamsHeapless::Raw(x))
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
impl From<Vec<u8>> for Params {
fn from(val: Vec<u8>) -> Self {
Self::Vec(val)
@ -594,7 +622,6 @@ impl From<Vec<u8>> for Params {
/// Converts a byte slice into the [Params::Vec] variant
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
impl From<&[u8]> for Params {
fn from(val: &[u8]) -> Self {
Self::Vec(val.to_vec())
@ -602,7 +629,6 @@ impl From<&[u8]> for Params {
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
impl From<String> for Params {
fn from(val: String) -> Self {
Self::String(val)
@ -610,7 +636,6 @@ impl From<String> for Params {
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
/// Converts a string slice into the [Params::String] variant
impl From<&str> for Params {
fn from(val: &str) -> Self {
@ -618,10 +643,56 @@ impl From<&str> for Params {
}
}
impl WritableToBeBytes for ParamsHeapless {
fn written_len(&self) -> usize {
match self {
ParamsHeapless::Raw(raw) => raw.written_len(),
ParamsHeapless::EcssEnum(ecss_enum) => ecss_enum.written_len(),
}
}
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
match self {
ParamsHeapless::Raw(raw) => raw.write_to_be_bytes(buf),
ParamsHeapless::EcssEnum(ecss_enum) => ecss_enum.write_to_be_bytes(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_cloning_works(param_raw: &impl WritableToBeBytes) {
let _new_param = param_raw;
}
fn test_writing_fails(param_raw: &(impl WritableToBeBytes + ToBeBytes)) {
let pair_size = WritableToBeBytes::written_len(param_raw);
assert_eq!(pair_size, ToBeBytes::written_len(param_raw));
let mut vec = alloc::vec![0; pair_size - 1];
let result = param_raw.write_to_be_bytes(&mut vec);
if let Err(ByteConversionError::ToSliceTooSmall { found, expected }) = result {
assert_eq!(found, pair_size - 1);
assert_eq!(expected, pair_size);
} else {
panic!("Expected ByteConversionError::ToSliceTooSmall");
}
}
fn test_writing(params_raw: &ParamsRaw, writeable: &impl WritableToBeBytes) {
assert_eq!(params_raw.written_len(), writeable.written_len());
let mut vec = alloc::vec![0; writeable.written_len()];
writeable
.write_to_be_bytes(&mut vec)
.expect("writing parameter to buffer failed");
let mut other_vec = alloc::vec![0; writeable.written_len()];
params_raw
.write_to_be_bytes(&mut other_vec)
.expect("writing parameter to buffer failed");
assert_eq!(vec, other_vec);
}
#[test]
fn test_basic_u32_pair() {
let u32_pair = U32Pair(4, 8);
@ -632,10 +703,32 @@ mod tests {
assert_eq!(u32_conv_back, 4);
u32_conv_back = u32::from_be_bytes(raw[4..8].try_into().unwrap());
assert_eq!(u32_conv_back, 8);
test_writing_fails(&u32_pair);
test_cloning_works(&u32_pair);
let u32_praw = ParamsRaw::from(u32_pair);
test_writing(&u32_praw, &u32_pair);
}
#[test]
fn basic_signed_test_pair() {
fn test_u16_pair_writing_fails() {
let u16_pair = U16Pair(4, 8);
test_writing_fails(&u16_pair);
test_cloning_works(&u16_pair);
let u16_praw = ParamsRaw::from(u16_pair);
test_writing(&u16_praw, &u16_pair);
}
#[test]
fn test_u8_pair_writing_fails() {
let u8_pair = U8Pair(4, 8);
test_writing_fails(&u8_pair);
test_cloning_works(&u8_pair);
let u8_praw = ParamsRaw::from(u8_pair);
test_writing(&u8_praw, &u8_pair);
}
#[test]
fn basic_i8_test() {
let i8_pair = I8Pair(-3, -16);
assert_eq!(i8_pair.0, -3);
assert_eq!(i8_pair.1, -16);
@ -644,10 +737,31 @@ mod tests {
assert_eq!(i8_conv_back, -3);
i8_conv_back = i8::from_be_bytes(raw[1..2].try_into().unwrap());
assert_eq!(i8_conv_back, -16);
test_writing_fails(&i8_pair);
test_cloning_works(&i8_pair);
let i8_praw = ParamsRaw::from(i8_pair);
test_writing(&i8_praw, &i8_pair);
}
#[test]
fn basic_signed_test_triplet() {
fn test_from_u32_triplet() {
let raw_params = U32Triplet::from((1, 2, 3));
assert_eq!(raw_params.0, 1);
assert_eq!(raw_params.1, 2);
assert_eq!(raw_params.2, 3);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 12);
assert_eq!(
raw_params.to_be_bytes(),
[0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]
);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let u32_triplet = ParamsRaw::from(raw_params);
test_writing(&u32_triplet, &raw_params);
}
#[test]
fn test_i8_triplet() {
let i8_triplet = I8Triplet(-3, -16, -126);
assert_eq!(i8_triplet.0, -3);
assert_eq!(i8_triplet.1, -16);
@ -659,6 +773,10 @@ mod tests {
assert_eq!(i8_conv_back, -16);
i8_conv_back = i8::from_be_bytes(raw[2..3].try_into().unwrap());
assert_eq!(i8_conv_back, -126);
test_writing_fails(&i8_triplet);
test_cloning_works(&i8_triplet);
let i8_praw = ParamsRaw::from(i8_triplet);
test_writing(&i8_praw, &i8_triplet);
}
#[test]
@ -681,4 +799,358 @@ mod tests {
panic!("Params type is not a vector")
}
}
#[test]
fn test_params_written_len_raw() {
let param_raw = ParamsRaw::from((500_u32, 1000_u32));
assert_eq!(param_raw.written_len(), 8);
let mut buf: [u8; 8] = [0; 8];
param_raw
.write_to_be_bytes(&mut buf)
.expect("writing to buffer failed");
assert_eq!(u32::from_be_bytes(buf[0..4].try_into().unwrap()), 500);
assert_eq!(u32::from_be_bytes(buf[4..8].try_into().unwrap()), 1000);
}
#[test]
fn test_heapless_param_writable_trait_raw() {
let param_heapless = ParamsHeapless::Raw(ParamsRaw::from((500_u32, 1000_u32)));
assert_eq!(param_heapless.written_len(), 8);
let mut buf: [u8; 8] = [0; 8];
let size = param_heapless
.write_to_be_bytes(&mut buf)
.expect("writing failed");
assert_eq!(size, 8);
assert_eq!(u32::from_be_bytes(buf[0..4].try_into().unwrap()), 500);
assert_eq!(u32::from_be_bytes(buf[4..8].try_into().unwrap()), 1000);
}
#[test]
fn test_heapless_param_writable_trait_ecss_enum() {
let param_heapless = ParamsHeapless::EcssEnum(ParamsEcssEnum::U16(5.into()));
assert_eq!(param_heapless.written_len(), 2);
let mut buf: [u8; 2] = [0; 2];
let size = param_heapless
.write_to_be_bytes(&mut buf)
.expect("writing failed");
assert_eq!(size, 2);
assert_eq!(u16::from_be_bytes(buf[0..2].try_into().unwrap()), 5);
}
#[test]
fn test_u32_single() {
let raw_params = U32::from(20);
assert_eq!(raw_params.0, 20);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 4);
assert_eq!(raw_params.to_be_bytes(), [0, 0, 0, 20]);
let other = U32::from(20);
assert_eq!(raw_params, other);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let u32_praw = ParamsRaw::from(raw_params);
test_writing(&u32_praw, &raw_params);
}
#[test]
fn test_i8_single() {
let neg_number: i8 = -5_i8;
let raw_params = I8::from(neg_number);
assert_eq!(raw_params.0, neg_number);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 1);
assert_eq!(raw_params.to_be_bytes(), neg_number.to_be_bytes());
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let u8_praw = ParamsRaw::from(raw_params);
test_writing(&u8_praw, &raw_params);
}
#[test]
fn test_u8_single() {
let raw_params = U8::from(20);
assert_eq!(raw_params.0, 20);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 1);
assert_eq!(raw_params.to_be_bytes(), [20]);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let u32_praw = ParamsRaw::from(raw_params);
test_writing(&u32_praw, &raw_params);
}
#[test]
fn test_u16_single() {
let raw_params = U16::from(0x123);
assert_eq!(raw_params.0, 0x123);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 2);
assert_eq!(raw_params.to_be_bytes(), [0x01, 0x23]);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let u16_praw = ParamsRaw::from(raw_params);
test_writing(&u16_praw, &raw_params);
}
#[test]
fn test_u16_triplet() {
let raw_params = U16Triplet::from((1, 2, 3));
assert_eq!(raw_params.0, 1);
assert_eq!(raw_params.1, 2);
assert_eq!(raw_params.2, 3);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 6);
assert_eq!(raw_params.to_be_bytes(), [0, 1, 0, 2, 0, 3]);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let u16_praw = ParamsRaw::from(raw_params);
test_writing(&u16_praw, &raw_params);
}
#[test]
fn test_u8_triplet() {
let raw_params = U8Triplet::from((1, 2, 3));
assert_eq!(raw_params.0, 1);
assert_eq!(raw_params.1, 2);
assert_eq!(raw_params.2, 3);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 3);
assert_eq!(raw_params.to_be_bytes(), [1, 2, 3]);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let u8_praw = ParamsRaw::from(raw_params);
test_writing(&u8_praw, &raw_params);
}
#[test]
fn test_i16_single() {
let value = -300_i16;
let raw_params = I16::from(value);
assert_eq!(raw_params.0, value);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 2);
assert_eq!(raw_params.to_be_bytes(), value.to_be_bytes());
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let i16_praw = ParamsRaw::from(raw_params);
test_writing(&i16_praw, &raw_params);
}
#[test]
fn test_i16_pair() {
let raw_params = I16Pair::from((-300, -400));
assert_eq!(raw_params.0, -300);
assert_eq!(raw_params.1, -400);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 4);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let i16_praw = ParamsRaw::from(raw_params);
test_writing(&i16_praw, &raw_params);
}
#[test]
fn test_i16_triplet() {
let raw_params = I16Triplet::from((-300, -400, -350));
assert_eq!(raw_params.0, -300);
assert_eq!(raw_params.1, -400);
assert_eq!(raw_params.2, -350);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 6);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let i16_praw = ParamsRaw::from(raw_params);
test_writing(&i16_praw, &raw_params);
}
#[test]
fn test_i32_single() {
let raw_params = I32::from(-80000);
assert_eq!(raw_params.0, -80000);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 4);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let i32_praw = ParamsRaw::from(raw_params);
test_writing(&i32_praw, &raw_params);
}
#[test]
fn test_i32_pair() {
let raw_params = I32Pair::from((-80000, -200));
assert_eq!(raw_params.0, -80000);
assert_eq!(raw_params.1, -200);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 8);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let i32_praw = ParamsRaw::from(raw_params);
test_writing(&i32_praw, &raw_params);
}
#[test]
fn test_i32_triplet() {
let raw_params = I32Triplet::from((-80000, -5, -200));
assert_eq!(raw_params.0, -80000);
assert_eq!(raw_params.1, -5);
assert_eq!(raw_params.2, -200);
assert_eq!(WritableToBeBytes::written_len(&raw_params), 12);
test_writing_fails(&raw_params);
test_cloning_works(&raw_params);
let i32_praw = ParamsRaw::from(raw_params);
test_writing(&i32_praw, &raw_params);
}
#[test]
fn test_f32_single() {
let param = F32::from(0.1);
assert_eq!(param.0, 0.1);
assert_eq!(WritableToBeBytes::written_len(&param), 4);
let f32_pair_raw = param.to_be_bytes();
let f32_0 = f32::from_be_bytes(f32_pair_raw[0..4].try_into().unwrap());
assert_eq!(f32_0, 0.1);
test_writing_fails(&param);
test_cloning_works(&param);
let praw = ParamsRaw::from(param);
test_writing(&praw, &param);
let p_try_from = F32::try_from(param.to_be_bytes().as_ref()).expect("try_from failed");
assert_eq!(p_try_from, param);
}
#[test]
fn test_f32_pair() {
let param = F32Pair::from((0.1, 0.2));
assert_eq!(param.0, 0.1);
assert_eq!(param.1, 0.2);
assert_eq!(WritableToBeBytes::written_len(&param), 8);
let f32_pair_raw = param.to_be_bytes();
let f32_0 = f32::from_be_bytes(f32_pair_raw[0..4].try_into().unwrap());
assert_eq!(f32_0, 0.1);
let f32_1 = f32::from_be_bytes(f32_pair_raw[4..8].try_into().unwrap());
assert_eq!(f32_1, 0.2);
let other_pair = F32Pair::from((0.1, 0.2));
assert_eq!(param, other_pair);
test_writing_fails(&param);
test_cloning_works(&param);
let praw = ParamsRaw::from(param);
test_writing(&praw, &param);
let p_try_from = F32Pair::try_from(param.to_be_bytes().as_ref()).expect("try_from failed");
assert_eq!(p_try_from, param);
}
#[test]
fn test_f32_triplet() {
let f32 = F32Triplet::from((0.1, -0.1, -5.2));
assert_eq!(f32.0, 0.1);
assert_eq!(f32.1, -0.1);
assert_eq!(f32.2, -5.2);
assert_eq!(WritableToBeBytes::written_len(&f32), 12);
let f32_pair_raw = f32.to_be_bytes();
let f32_0 = f32::from_be_bytes(f32_pair_raw[0..4].try_into().unwrap());
assert_eq!(f32_0, 0.1);
let f32_1 = f32::from_be_bytes(f32_pair_raw[4..8].try_into().unwrap());
assert_eq!(f32_1, -0.1);
let f32_2 = f32::from_be_bytes(f32_pair_raw[8..12].try_into().unwrap());
assert_eq!(f32_2, -5.2);
test_writing_fails(&f32);
test_cloning_works(&f32);
let f32_praw = ParamsRaw::from(f32);
test_writing(&f32_praw, &f32);
let f32_try_from =
F32Triplet::try_from(f32.to_be_bytes().as_ref()).expect("try_from failed");
assert_eq!(f32_try_from, f32);
}
#[test]
fn test_u64_single() {
let u64 = U64::from(0x1010101010);
assert_eq!(u64.0, 0x1010101010);
assert_eq!(WritableToBeBytes::written_len(&u64), 8);
test_writing_fails(&u64);
test_cloning_works(&u64);
let praw = ParamsRaw::from(u64);
test_writing(&praw, &u64);
}
#[test]
fn test_i64_single() {
let i64 = I64::from(-0xfffffffff);
assert_eq!(i64.0, -0xfffffffff);
assert_eq!(WritableToBeBytes::written_len(&i64), 8);
test_writing_fails(&i64);
test_cloning_works(&i64);
let praw = ParamsRaw::from(i64);
test_writing(&praw, &i64);
}
#[test]
fn test_f64_single() {
let value = 823_823_812_832.232_3;
let f64 = F64::from(value);
assert_eq!(f64.0, value);
assert_eq!(WritableToBeBytes::written_len(&f64), 8);
test_writing_fails(&f64);
test_cloning_works(&f64);
let praw = ParamsRaw::from(f64);
test_writing(&praw, &f64);
}
#[test]
fn test_f64_triplet() {
let f64_triplet = F64Triplet::from((0.1, 0.2, 0.3));
assert_eq!(f64_triplet.0, 0.1);
assert_eq!(f64_triplet.1, 0.2);
assert_eq!(f64_triplet.2, 0.3);
assert_eq!(WritableToBeBytes::written_len(&f64_triplet), 24);
let f64_triplet_raw = f64_triplet.to_be_bytes();
let f64_0 = f64::from_be_bytes(f64_triplet_raw[0..8].try_into().unwrap());
assert_eq!(f64_0, 0.1);
let f64_1 = f64::from_be_bytes(f64_triplet_raw[8..16].try_into().unwrap());
assert_eq!(f64_1, 0.2);
let f64_2 = f64::from_be_bytes(f64_triplet_raw[16..24].try_into().unwrap());
assert_eq!(f64_2, 0.3);
test_writing_fails(&f64_triplet);
test_cloning_works(&f64_triplet);
}
#[test]
fn test_u8_ecss_enum() {
let value = 200;
let u8p = EcssEnumU8::new(value);
test_cloning_works(&u8p);
let praw = ParamsEcssEnum::from(u8p);
assert_eq!(praw.written_len(), 1);
let mut buf = [0; 1];
praw.write_to_be_bytes(&mut buf)
.expect("writing to buffer failed");
buf[0] = 200;
}
#[test]
fn test_u16_ecss_enum() {
let value = 60000;
let u16p = EcssEnumU16::new(value);
test_cloning_works(&u16p);
let praw = ParamsEcssEnum::from(u16p);
assert_eq!(praw.written_len(), 2);
let mut buf = [0; 2];
praw.write_to_be_bytes(&mut buf)
.expect("writing to buffer failed");
assert_eq!(u16::from_be_bytes(buf), value);
}
#[test]
fn test_u32_ecss_enum() {
let value = 70000;
let u32p = EcssEnumU32::new(value);
test_cloning_works(&u32p);
let praw = ParamsEcssEnum::from(u32p);
assert_eq!(praw.written_len(), 4);
let mut buf = [0; 4];
praw.write_to_be_bytes(&mut buf)
.expect("writing to buffer failed");
assert_eq!(u32::from_be_bytes(buf), value);
}
#[test]
fn test_u64_ecss_enum() {
let value = 0xffffffffff;
let u64p = EcssEnumU64::new(value);
test_cloning_works(&u64p);
let praw = ParamsEcssEnum::from(u64p);
assert_eq!(praw.written_len(), 8);
let mut buf = [0; 8];
praw.write_to_be_bytes(&mut buf)
.expect("writing to buffer failed");
assert_eq!(u64::from_be_bytes(buf), value);
}
}

View File

@ -72,7 +72,6 @@
//! }
//! ```
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub use alloc_mod::*;
use core::fmt::{Display, Formatter};
use delegate::delegate;
@ -83,7 +82,7 @@ use spacepackets::ByteConversionError;
use std::error::Error;
type NumBlocks = u16;
pub type StoreAddr = u64;
pub type PoolAddr = u64;
/// Simple address type used for transactions with the local pool.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -101,14 +100,14 @@ impl StaticPoolAddr {
}
}
impl From<StaticPoolAddr> for StoreAddr {
impl From<StaticPoolAddr> for PoolAddr {
fn from(value: StaticPoolAddr) -> Self {
((value.pool_idx as u64) << 16) | value.packet_idx as u64
}
}
impl From<StoreAddr> for StaticPoolAddr {
fn from(value: StoreAddr) -> Self {
impl From<PoolAddr> for StaticPoolAddr {
fn from(value: PoolAddr) -> Self {
Self {
pool_idx: ((value >> 16) & 0xff) as u16,
packet_idx: (value & 0xff) as u16,
@ -151,59 +150,59 @@ impl Error for StoreIdError {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum StoreError {
pub enum PoolError {
/// Requested data block is too large
DataTooLarge(usize),
/// The store is full. Contains the index of the full subpool
StoreFull(u16),
/// Store ID is invalid. This also includes partial errors where only the subpool is invalid
InvalidStoreId(StoreIdError, Option<StoreAddr>),
InvalidStoreId(StoreIdError, Option<PoolAddr>),
/// Valid subpool and packet index, but no data is stored at the given address
DataDoesNotExist(StoreAddr),
DataDoesNotExist(PoolAddr),
ByteConversionError(spacepackets::ByteConversionError),
LockError,
/// Internal or configuration errors
InternalError(u32),
}
impl Display for StoreError {
impl Display for PoolError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
StoreError::DataTooLarge(size) => {
PoolError::DataTooLarge(size) => {
write!(f, "data to store with size {size} is too large")
}
StoreError::StoreFull(u16) => {
PoolError::StoreFull(u16) => {
write!(f, "store is too full. index for full subpool: {u16}")
}
StoreError::InvalidStoreId(id_e, addr) => {
PoolError::InvalidStoreId(id_e, addr) => {
write!(f, "invalid store ID: {id_e}, address: {addr:?}")
}
StoreError::DataDoesNotExist(addr) => {
PoolError::DataDoesNotExist(addr) => {
write!(f, "no data exists at address {addr:?}")
}
StoreError::InternalError(e) => {
PoolError::InternalError(e) => {
write!(f, "internal error: {e}")
}
StoreError::ByteConversionError(e) => {
PoolError::ByteConversionError(e) => {
write!(f, "store error: {e}")
}
StoreError::LockError => {
PoolError::LockError => {
write!(f, "lock error")
}
}
}
}
impl From<ByteConversionError> for StoreError {
impl From<ByteConversionError> for PoolError {
fn from(value: ByteConversionError) -> Self {
Self::ByteConversionError(value)
}
}
#[cfg(feature = "std")]
impl Error for StoreError {
impl Error for PoolError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let StoreError::InvalidStoreId(e, _) = self {
if let PoolError::InvalidStoreId(e, _) = self {
return Some(e);
}
None
@ -218,44 +217,41 @@ impl Error for StoreError {
/// pool structure being wrapped inside a lock.
pub trait PoolProvider {
/// Add new data to the pool. The provider should attempt to reserve a memory block with the
/// appropriate size and then copy the given data to the block. Yields a [StoreAddr] which can
/// appropriate size and then copy the given data to the block. Yields a [PoolAddr] which can
/// be used to access the data stored in the pool
fn add(&mut self, data: &[u8]) -> Result<StoreAddr, StoreError>;
fn add(&mut self, data: &[u8]) -> Result<PoolAddr, PoolError>;
/// The provider should attempt to reserve a free memory block with the appropriate size first.
/// It then executes a user-provided closure and passes a mutable reference to that memory
/// block to the closure. This allows the user to write data to the memory block.
/// The function should yield a [StoreAddr] which can be used to access the data stored in the
/// The function should yield a [PoolAddr] which can be used to access the data stored in the
/// pool.
fn free_element<W: FnMut(&mut [u8])>(
&mut self,
len: usize,
writer: W,
) -> Result<StoreAddr, StoreError>;
) -> Result<PoolAddr, PoolError>;
/// Modify data added previously using a given [StoreAddr]. The provider should use the store
/// Modify data added previously using a given [PoolAddr]. The provider should use the store
/// address to determine if a memory block exists for that address. If it does, it should
/// call the user-provided closure and pass a mutable reference to the memory block
/// to the closure. This allows the user to modify the memory block.
fn modify<U: FnMut(&mut [u8])>(
&mut self,
addr: &StoreAddr,
updater: U,
) -> Result<(), StoreError>;
fn modify<U: FnMut(&mut [u8])>(&mut self, addr: &PoolAddr, updater: U)
-> Result<(), PoolError>;
/// The provider should copy the data from the memory block to the user-provided buffer if
/// it exists.
fn read(&self, addr: &StoreAddr, buf: &mut [u8]) -> Result<usize, StoreError>;
fn read(&self, addr: &PoolAddr, buf: &mut [u8]) -> Result<usize, PoolError>;
/// Delete data inside the pool given a [StoreAddr].
fn delete(&mut self, addr: StoreAddr) -> Result<(), StoreError>;
fn has_element_at(&self, addr: &StoreAddr) -> Result<bool, StoreError>;
/// Delete data inside the pool given a [PoolAddr].
fn delete(&mut self, addr: PoolAddr) -> Result<(), PoolError>;
fn has_element_at(&self, addr: &PoolAddr) -> Result<bool, PoolError>;
/// Retrieve the length of the data at the given store address.
fn len_of_data(&self, addr: &StoreAddr) -> Result<usize, StoreError>;
fn len_of_data(&self, addr: &PoolAddr) -> Result<usize, PoolError>;
#[cfg(feature = "alloc")]
fn read_as_vec(&self, addr: &StoreAddr) -> Result<alloc::vec::Vec<u8>, StoreError> {
fn read_as_vec(&self, addr: &PoolAddr) -> Result<alloc::vec::Vec<u8>, PoolError> {
let mut vec = alloc::vec![0; self.len_of_data(addr)?];
self.read(addr, &mut vec)?;
Ok(vec)
@ -272,7 +268,7 @@ pub trait PoolProviderWithGuards: PoolProvider {
/// This can prevent memory leaks. Users can read the data and release the guard
/// if the data in the store is valid for further processing. If the data is faulty, no
/// manual deletion is necessary when returning from a processing function prematurely.
fn read_with_guard(&mut self, addr: StoreAddr) -> PoolGuard<Self>;
fn read_with_guard(&mut self, addr: PoolAddr) -> PoolGuard<Self>;
/// This function behaves like [PoolProvider::modify], but consumes the provided
/// address and returns a RAII conformant guard object.
@ -282,20 +278,20 @@ pub trait PoolProviderWithGuards: PoolProvider {
/// This can prevent memory leaks. Users can read (and modify) the data and release the guard
/// if the data in the store is valid for further processing. If the data is faulty, no
/// manual deletion is necessary when returning from a processing function prematurely.
fn modify_with_guard(&mut self, addr: StoreAddr) -> PoolRwGuard<Self>;
fn modify_with_guard(&mut self, addr: PoolAddr) -> PoolRwGuard<Self>;
}
pub struct PoolGuard<'a, MemProvider: PoolProvider + ?Sized> {
pool: &'a mut MemProvider,
pub addr: StoreAddr,
pub addr: PoolAddr,
no_deletion: bool,
deletion_failed_error: Option<StoreError>,
deletion_failed_error: Option<PoolError>,
}
/// This helper object can be used to safely access pool data without worrying about memory
/// leaks.
impl<'a, MemProvider: PoolProvider> PoolGuard<'a, MemProvider> {
pub fn new(pool: &'a mut MemProvider, addr: StoreAddr) -> Self {
pub fn new(pool: &'a mut MemProvider, addr: PoolAddr) -> Self {
Self {
pool,
addr,
@ -304,12 +300,12 @@ impl<'a, MemProvider: PoolProvider> PoolGuard<'a, MemProvider> {
}
}
pub fn read(&self, buf: &mut [u8]) -> Result<usize, StoreError> {
pub fn read(&self, buf: &mut [u8]) -> Result<usize, PoolError> {
self.pool.read(&self.addr, buf)
}
#[cfg(feature = "alloc")]
pub fn read_as_vec(&self) -> Result<alloc::vec::Vec<u8>, StoreError> {
pub fn read_as_vec(&self) -> Result<alloc::vec::Vec<u8>, PoolError> {
self.pool.read_as_vec(&self.addr)
}
@ -335,19 +331,19 @@ pub struct PoolRwGuard<'a, MemProvider: PoolProvider + ?Sized> {
}
impl<'a, MemProvider: PoolProvider> PoolRwGuard<'a, MemProvider> {
pub fn new(pool: &'a mut MemProvider, addr: StoreAddr) -> Self {
pub fn new(pool: &'a mut MemProvider, addr: PoolAddr) -> Self {
Self {
guard: PoolGuard::new(pool, addr),
}
}
pub fn update<U: FnMut(&mut [u8])>(&mut self, updater: &mut U) -> Result<(), StoreError> {
pub fn update<U: FnMut(&mut [u8])>(&mut self, updater: &mut U) -> Result<(), PoolError> {
self.guard.pool.modify(&self.guard.addr, updater)
}
delegate!(
to self.guard {
pub fn read(&self, buf: &mut [u8]) -> Result<usize, StoreError>;
pub fn read(&self, buf: &mut [u8]) -> Result<usize, PoolError>;
/// Releasing the pool guard will disable the automatic deletion of the data when the guard
/// is dropped.
pub fn release(&mut self);
@ -358,7 +354,7 @@ impl<'a, MemProvider: PoolProvider> PoolRwGuard<'a, MemProvider> {
#[cfg(feature = "alloc")]
mod alloc_mod {
use super::{PoolGuard, PoolProvider, PoolProviderWithGuards, PoolRwGuard, StaticPoolAddr};
use crate::pool::{NumBlocks, StoreAddr, StoreError, StoreIdError};
use crate::pool::{NumBlocks, PoolAddr, PoolError, StoreIdError};
use alloc::vec;
use alloc::vec::Vec;
use spacepackets::ByteConversionError;
@ -423,7 +419,7 @@ mod alloc_mod {
/// fitting subpool is full. This might be added in the future.
///
/// Transactions with the [pool][StaticMemoryPool] are done using a generic
/// [address][StoreAddr] type. Adding any data to the pool will yield a store address.
/// [address][PoolAddr] type. Adding any data to the pool will yield a store address.
/// Modification and read operations are done using a reference to a store address. Deletion
/// will consume the store address.
pub struct StaticMemoryPool {
@ -453,41 +449,41 @@ mod alloc_mod {
local_pool
}
fn addr_check(&self, addr: &StaticPoolAddr) -> Result<usize, StoreError> {
fn addr_check(&self, addr: &StaticPoolAddr) -> Result<usize, PoolError> {
self.validate_addr(addr)?;
let pool_idx = addr.pool_idx as usize;
let size_list = self.sizes_lists.get(pool_idx).unwrap();
let curr_size = size_list[addr.packet_idx as usize];
if curr_size == STORE_FREE {
return Err(StoreError::DataDoesNotExist(StoreAddr::from(*addr)));
return Err(PoolError::DataDoesNotExist(PoolAddr::from(*addr)));
}
Ok(curr_size)
}
fn validate_addr(&self, addr: &StaticPoolAddr) -> Result<(), StoreError> {
fn validate_addr(&self, addr: &StaticPoolAddr) -> Result<(), PoolError> {
let pool_idx = addr.pool_idx as usize;
if pool_idx >= self.pool_cfg.cfg.len() {
return Err(StoreError::InvalidStoreId(
return Err(PoolError::InvalidStoreId(
StoreIdError::InvalidSubpool(addr.pool_idx),
Some(StoreAddr::from(*addr)),
Some(PoolAddr::from(*addr)),
));
}
if addr.packet_idx >= self.pool_cfg.cfg[addr.pool_idx as usize].0 {
return Err(StoreError::InvalidStoreId(
return Err(PoolError::InvalidStoreId(
StoreIdError::InvalidPacketIdx(addr.packet_idx),
Some(StoreAddr::from(*addr)),
Some(PoolAddr::from(*addr)),
));
}
Ok(())
}
fn reserve(&mut self, data_len: usize) -> Result<StaticPoolAddr, StoreError> {
fn reserve(&mut self, data_len: usize) -> Result<StaticPoolAddr, PoolError> {
let mut subpool_idx = self.find_subpool(data_len, 0)?;
if self.pool_cfg.spill_to_higher_subpools {
while let Err(StoreError::StoreFull(_)) = self.find_empty(subpool_idx) {
while let Err(PoolError::StoreFull(_)) = self.find_empty(subpool_idx) {
if (subpool_idx + 1) as usize == self.sizes_lists.len() {
return Err(StoreError::StoreFull(subpool_idx));
return Err(PoolError::StoreFull(subpool_idx));
}
subpool_idx += 1;
}
@ -501,7 +497,7 @@ mod alloc_mod {
})
}
fn find_subpool(&self, req_size: usize, start_at_subpool: u16) -> Result<u16, StoreError> {
fn find_subpool(&self, req_size: usize, start_at_subpool: u16) -> Result<u16, PoolError> {
for (i, &(_, elem_size)) in self.pool_cfg.cfg.iter().enumerate() {
if i < start_at_subpool as usize {
continue;
@ -510,21 +506,21 @@ mod alloc_mod {
return Ok(i as u16);
}
}
Err(StoreError::DataTooLarge(req_size))
Err(PoolError::DataTooLarge(req_size))
}
fn write(&mut self, addr: &StaticPoolAddr, data: &[u8]) -> Result<(), StoreError> {
let packet_pos = self.raw_pos(addr).ok_or(StoreError::InternalError(0))?;
fn write(&mut self, addr: &StaticPoolAddr, data: &[u8]) -> Result<(), PoolError> {
let packet_pos = self.raw_pos(addr).ok_or(PoolError::InternalError(0))?;
let subpool = self
.pool
.get_mut(addr.pool_idx as usize)
.ok_or(StoreError::InternalError(1))?;
.ok_or(PoolError::InternalError(1))?;
let pool_slice = &mut subpool[packet_pos..packet_pos + data.len()];
pool_slice.copy_from_slice(data);
Ok(())
}
fn find_empty(&mut self, subpool: u16) -> Result<(u16, &mut usize), StoreError> {
fn find_empty(&mut self, subpool: u16) -> Result<(u16, &mut usize), PoolError> {
if let Some(size_list) = self.sizes_lists.get_mut(subpool as usize) {
for (i, elem_size) in size_list.iter_mut().enumerate() {
if *elem_size == STORE_FREE {
@ -532,12 +528,12 @@ mod alloc_mod {
}
}
} else {
return Err(StoreError::InvalidStoreId(
return Err(PoolError::InvalidStoreId(
StoreIdError::InvalidSubpool(subpool),
None,
));
}
Err(StoreError::StoreFull(subpool))
Err(PoolError::StoreFull(subpool))
}
fn raw_pos(&self, addr: &StaticPoolAddr) -> Option<usize> {
@ -547,10 +543,10 @@ mod alloc_mod {
}
impl PoolProvider for StaticMemoryPool {
fn add(&mut self, data: &[u8]) -> Result<StoreAddr, StoreError> {
fn add(&mut self, data: &[u8]) -> Result<PoolAddr, PoolError> {
let data_len = data.len();
if data_len > POOL_MAX_SIZE {
return Err(StoreError::DataTooLarge(data_len));
return Err(PoolError::DataTooLarge(data_len));
}
let addr = self.reserve(data_len)?;
self.write(&addr, data)?;
@ -561,9 +557,9 @@ mod alloc_mod {
&mut self,
len: usize,
mut writer: W,
) -> Result<StoreAddr, StoreError> {
) -> Result<PoolAddr, PoolError> {
if len > POOL_MAX_SIZE {
return Err(StoreError::DataTooLarge(len));
return Err(PoolError::DataTooLarge(len));
}
let addr = self.reserve(len)?;
let raw_pos = self.raw_pos(&addr).unwrap();
@ -575,9 +571,9 @@ mod alloc_mod {
fn modify<U: FnMut(&mut [u8])>(
&mut self,
addr: &StoreAddr,
addr: &PoolAddr,
mut updater: U,
) -> Result<(), StoreError> {
) -> Result<(), PoolError> {
let addr = StaticPoolAddr::from(*addr);
let curr_size = self.addr_check(&addr)?;
let raw_pos = self.raw_pos(&addr).unwrap();
@ -587,7 +583,7 @@ mod alloc_mod {
Ok(())
}
fn read(&self, addr: &StoreAddr, buf: &mut [u8]) -> Result<usize, StoreError> {
fn read(&self, addr: &PoolAddr, buf: &mut [u8]) -> Result<usize, PoolError> {
let addr = StaticPoolAddr::from(*addr);
let curr_size = self.addr_check(&addr)?;
if buf.len() < curr_size {
@ -605,7 +601,7 @@ mod alloc_mod {
Ok(curr_size)
}
fn delete(&mut self, addr: StoreAddr) -> Result<(), StoreError> {
fn delete(&mut self, addr: PoolAddr) -> Result<(), PoolError> {
let addr = StaticPoolAddr::from(addr);
self.addr_check(&addr)?;
let block_size = self.pool_cfg.cfg.get(addr.pool_idx as usize).unwrap().1;
@ -618,7 +614,7 @@ mod alloc_mod {
Ok(())
}
fn has_element_at(&self, addr: &StoreAddr) -> Result<bool, StoreError> {
fn has_element_at(&self, addr: &PoolAddr) -> Result<bool, PoolError> {
let addr = StaticPoolAddr::from(*addr);
self.validate_addr(&addr)?;
let pool_idx = addr.pool_idx as usize;
@ -630,7 +626,7 @@ mod alloc_mod {
Ok(true)
}
fn len_of_data(&self, addr: &StoreAddr) -> Result<usize, StoreError> {
fn len_of_data(&self, addr: &PoolAddr) -> Result<usize, PoolError> {
let addr = StaticPoolAddr::from(*addr);
self.validate_addr(&addr)?;
let pool_idx = addr.pool_idx as usize;
@ -644,11 +640,11 @@ mod alloc_mod {
}
impl PoolProviderWithGuards for StaticMemoryPool {
fn modify_with_guard(&mut self, addr: StoreAddr) -> PoolRwGuard<Self> {
fn modify_with_guard(&mut self, addr: PoolAddr) -> PoolRwGuard<Self> {
PoolRwGuard::new(self, addr)
}
fn read_with_guard(&mut self, addr: StoreAddr) -> PoolGuard<Self> {
fn read_with_guard(&mut self, addr: PoolAddr) -> PoolGuard<Self> {
PoolGuard::new(self, addr)
}
}
@ -657,8 +653,8 @@ mod alloc_mod {
#[cfg(test)]
mod tests {
use crate::pool::{
PoolGuard, PoolProvider, PoolProviderWithGuards, PoolRwGuard, StaticMemoryPool,
StaticPoolAddr, StaticPoolConfig, StoreError, StoreIdError, POOL_MAX_SIZE,
PoolError, PoolGuard, PoolProvider, PoolProviderWithGuards, PoolRwGuard, StaticMemoryPool,
StaticPoolAddr, StaticPoolConfig, StoreIdError, POOL_MAX_SIZE,
};
use std::vec;
@ -782,7 +778,7 @@ mod tests {
let res = local_pool.free_element(8, |_| {});
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(err, StoreError::StoreFull(1));
assert_eq!(err, PoolError::StoreFull(1));
// Verify that the two deletions are successful
assert!(local_pool.delete(addr0).is_ok());
@ -804,7 +800,7 @@ mod tests {
assert!(res.is_err());
assert!(matches!(
res.unwrap_err(),
StoreError::DataDoesNotExist { .. }
PoolError::DataDoesNotExist { .. }
));
}
@ -817,8 +813,8 @@ mod tests {
let res = local_pool.add(&test_buf);
assert!(res.is_err());
let err = res.unwrap_err();
assert!(matches!(err, StoreError::StoreFull { .. }));
if let StoreError::StoreFull(subpool) = err {
assert!(matches!(err, PoolError::StoreFull { .. }));
if let PoolError::StoreFull(subpool) = err {
assert_eq!(subpool, 2);
}
}
@ -836,7 +832,7 @@ mod tests {
let err = res.unwrap_err();
assert!(matches!(
err,
StoreError::InvalidStoreId(StoreIdError::InvalidSubpool(3), Some(_))
PoolError::InvalidStoreId(StoreIdError::InvalidSubpool(3), Some(_))
));
}
@ -853,7 +849,7 @@ mod tests {
let err = res.unwrap_err();
assert!(matches!(
err,
StoreError::InvalidStoreId(StoreIdError::InvalidPacketIdx(1), Some(_))
PoolError::InvalidStoreId(StoreIdError::InvalidPacketIdx(1), Some(_))
));
}
@ -864,7 +860,7 @@ mod tests {
let res = local_pool.add(&data_too_large);
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(err, StoreError::DataTooLarge(20));
assert_eq!(err, PoolError::DataTooLarge(20));
}
#[test]
@ -872,10 +868,7 @@ mod tests {
let mut local_pool = basic_small_pool();
let res = local_pool.free_element(POOL_MAX_SIZE + 1, |_| {});
assert!(res.is_err());
assert_eq!(
res.unwrap_err(),
StoreError::DataTooLarge(POOL_MAX_SIZE + 1)
);
assert_eq!(res.unwrap_err(), PoolError::DataTooLarge(POOL_MAX_SIZE + 1));
}
#[test]
@ -884,7 +877,7 @@ mod tests {
// Try to request a slot which is too large
let res = local_pool.free_element(20, |_| {});
assert!(res.is_err());
assert_eq!(res.unwrap_err(), StoreError::DataTooLarge(20));
assert_eq!(res.unwrap_err(), PoolError::DataTooLarge(20));
}
#[test]
@ -1004,7 +997,7 @@ mod tests {
let should_fail = local_pool.free_element(8, |_| {});
assert!(should_fail.is_err());
if let Err(err) = should_fail {
assert_eq!(err, StoreError::StoreFull(1));
assert_eq!(err, PoolError::StoreFull(1));
} else {
panic!("unexpected store address");
}
@ -1035,7 +1028,7 @@ mod tests {
let should_fail = local_pool.free_element(8, |_| {});
assert!(should_fail.is_err());
if let Err(err) = should_fail {
assert_eq!(err, StoreError::StoreFull(2));
assert_eq!(err, PoolError::StoreFull(2));
} else {
panic!("unexpected store address");
}

View File

@ -1,397 +1,197 @@
use crate::{action::ActionRequest, TargetId};
use crate::{
action::{ActionId, ActionRequest},
params::Params,
request::{GenericMessage, MessageMetadata, RequestId},
};
use super::verification::{TcStateAccepted, VerificationToken};
use satrs_shared::res_code::ResultU16;
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub use std_mod::*;
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
#[allow(unused_imports)]
pub use alloc_mod::*;
/// This trait is an abstraction for the routing of PUS service 8 action requests to a dedicated
/// recipient using the generic [TargetId].
pub trait PusActionRequestRouter {
type Error;
fn route(
&self,
target_id: TargetId,
hk_request: ActionRequest,
token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error>;
#[derive(Clone, Debug)]
pub struct ActionRequestWithId {
pub request_id: RequestId,
pub request: ActionRequest,
}
/// A reply to an action request, but tailored to the PUS standard verification process.
#[non_exhaustive]
#[derive(Clone, PartialEq, Debug)]
pub enum ActionReplyVariant {
Completed,
StepSuccess {
step: u16,
},
CompletionFailed {
error_code: ResultU16,
params: Option<Params>,
},
StepFailed {
error_code: ResultU16,
step: u16,
params: Option<Params>,
},
}
#[derive(Debug, PartialEq, Clone)]
pub struct ActionReplyPus {
pub action_id: ActionId,
pub variant: ActionReplyVariant,
}
impl ActionReplyPus {
pub fn new(action_id: ActionId, variant: ActionReplyVariant) -> Self {
Self { action_id, variant }
}
}
pub type GenericActionReplyPus = GenericMessage<ActionReplyPus>;
impl GenericActionReplyPus {
pub fn new_action_reply(
replier_info: MessageMetadata,
action_id: ActionId,
reply: ActionReplyVariant,
) -> Self {
Self::new(replier_info, ActionReplyPus::new(action_id, reply))
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub mod alloc_mod {
use spacepackets::ecss::tc::PusTcReader;
use crate::{
action::ActionRequest,
queue::GenericTargetedMessagingError,
request::{
GenericMessage, MessageReceiver, MessageSender, MessageSenderAndReceiver, RequestId,
},
ComponentId,
};
use crate::pus::verification::VerificationReportingProvider;
use super::ActionReplyPus;
use super::*;
/// Helper type definition for a mode handler which can handle mode requests.
pub type ActionRequestHandlerInterface<S, R> =
MessageSenderAndReceiver<ActionReplyPus, ActionRequest, S, R>;
/// This trait is an abstraction for the conversion of a PUS service 8 action telecommand into
/// an [ActionRequest].
///
/// Having a dedicated trait for this allows maximum flexiblity and tailoring of the standard.
/// The only requirement is that a valid [TargetId] and an [ActionRequest] are returned by the
/// core conversion function.
///
/// The user should take care of performing the error handling as well. Some of the following
/// aspects might be relevant:
///
/// - Checking the validity of the APID, service ID, subservice ID.
/// - Checking the validity of the user data.
///
/// A [VerificationReportingProvider] instance is passed to the user to also allow handling
/// of the verification process as part of the PUS standard requirements.
pub trait PusActionToRequestConverter {
type Error;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, ActionRequest), Self::Error>;
impl<S: MessageSender<ActionReplyPus>, R: MessageReceiver<ActionRequest>>
ActionRequestHandlerInterface<S, R>
{
pub fn try_recv_action_request(
&self,
) -> Result<Option<GenericMessage<ActionRequest>>, GenericTargetedMessagingError> {
self.try_recv_message()
}
pub fn send_action_reply(
&self,
request_id: RequestId,
target_id: ComponentId,
reply: ActionReplyPus,
) -> Result<(), GenericTargetedMessagingError> {
self.send_message(request_id, target_id, reply)
}
}
/// Helper type defintion for a mode handler object which can send mode requests and receive
/// mode replies.
pub type ActionRequestorInterface<S, R> =
MessageSenderAndReceiver<ActionRequest, ActionReplyPus, S, R>;
impl<S: MessageSender<ActionRequest>, R: MessageReceiver<ActionReplyPus>>
ActionRequestorInterface<S, R>
{
pub fn try_recv_action_reply(
&self,
) -> Result<Option<GenericMessage<ActionReplyPus>>, GenericTargetedMessagingError> {
self.try_recv_message()
}
pub fn send_action_request(
&self,
request_id: RequestId,
target_id: ComponentId,
request: ActionRequest,
) -> Result<(), GenericTargetedMessagingError> {
self.send_message(request_id, target_id, request)
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std_mod {
use crate::pus::{
get_current_cds_short_timestamp, verification::VerificationReportingProvider,
EcssTcInMemConverter, EcssTcReceiverCore, EcssTmSenderCore, GenericRoutingError,
PusPacketHandlerResult, PusPacketHandlingError, PusRoutingErrorHandler, PusServiceHelper,
use std::sync::mpsc;
use crate::{
pus::{
verification::{self, TcStateToken},
ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap,
},
ComponentId,
};
use super::*;
/// This is a high-level handler for the PUS service 8 action service.
///
/// It performs the following handling steps:
///
/// 1. Retrieve the next TC packet from the [PusServiceHelper]. The [EcssTcInMemConverter]
/// allows to configure the used telecommand memory backend.
/// 2. Convert the TC to a targeted action request using the provided
/// [PusActionToRequestConverter]. The generic error type is constrained to the
/// [PusPacketHandlingError] for the concrete implementation which offers a packet handler.
/// 3. Route the action request using the provided [PusActionRequestRouter].
/// 4. Handle all routing errors using the provided [PusRoutingErrorHandler].
pub struct PusService8ActionHandler<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusActionToRequestConverter,
RequestRouter: PusActionRequestRouter<Error = RoutingError>,
RoutingErrorHandler: PusRoutingErrorHandler<Error = RoutingError>,
RoutingError = GenericRoutingError,
> {
service_helper:
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
pub request_converter: RequestConverter,
pub request_router: RequestRouter,
pub routing_error_handler: RoutingErrorHandler,
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActivePusActionRequestStd {
pub action_id: ActionId,
common: ActivePusRequestStd,
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusActionToRequestConverter<Error = PusPacketHandlingError>,
RequestRouter: PusActionRequestRouter<Error = RoutingError>,
RoutingErrorHandler: PusRoutingErrorHandler<Error = RoutingError>,
RoutingError: Clone,
>
PusService8ActionHandler<
TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
RequestConverter,
RequestRouter,
RoutingErrorHandler,
RoutingError,
>
where
PusPacketHandlingError: From<RoutingError>,
{
impl ActiveRequestProvider for ActivePusActionRequestStd {
delegate::delegate! {
to self.common {
fn target_id(&self) -> ComponentId;
fn token(&self) -> verification::TcStateToken;
fn set_token(&mut self, token: verification::TcStateToken);
fn has_timed_out(&self) -> bool;
fn timeout(&self) -> core::time::Duration;
}
}
}
impl ActivePusActionRequestStd {
pub fn new_from_common_req(action_id: ActionId, common: ActivePusRequestStd) -> Self {
Self { action_id, common }
}
pub fn new(
service_helper: PusServiceHelper<
TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
>,
request_converter: RequestConverter,
request_router: RequestRouter,
routing_error_handler: RoutingErrorHandler,
action_id: ActionId,
target_id: ComponentId,
token: TcStateToken,
timeout: core::time::Duration,
) -> Self {
Self {
service_helper,
request_converter,
request_router,
routing_error_handler,
action_id,
common: ActivePusRequestStd::new(target_id, token, timeout),
}
}
/// Core function to poll the next TC packet and try to handle it.
pub fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
}
let ecss_tc_and_token = possible_packet.unwrap();
let tc = self
.service_helper
.tc_in_mem_converter
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
let mut partial_error = None;
let time_stamp = get_current_cds_short_timestamp(&mut partial_error);
let (target_id, action_request) = self.request_converter.convert(
ecss_tc_and_token.token,
&tc,
&time_stamp,
&self.service_helper.common.verification_handler,
)?;
if let Err(e) =
self.request_router
.route(target_id, action_request, ecss_tc_and_token.token)
{
self.routing_error_handler.handle_error(
target_id,
ecss_tc_and_token.token,
&tc,
e.clone(),
&time_stamp,
&self.service_helper.common.verification_handler,
);
return Err(e.into());
}
Ok(PusPacketHandlerResult::RequestHandled)
}
}
pub type DefaultActiveActionRequestMap = DefaultActiveRequestMap<ActivePusActionRequestStd>;
pub type ActionRequestHandlerMpsc = ActionRequestHandlerInterface<
mpsc::Sender<GenericMessage<ActionReplyPus>>,
mpsc::Receiver<GenericMessage<ActionRequest>>,
>;
pub type ActionRequestHandlerMpscBounded = ActionRequestHandlerInterface<
mpsc::SyncSender<GenericMessage<ActionReplyPus>>,
mpsc::Receiver<GenericMessage<ActionRequest>>,
>;
pub type ActionRequestorMpsc = ActionRequestorInterface<
mpsc::Sender<GenericMessage<ActionRequest>>,
mpsc::Receiver<GenericMessage<ActionReplyPus>>,
>;
pub type ActionRequestorBoundedMpsc = ActionRequestorInterface<
mpsc::SyncSender<GenericMessage<ActionRequest>>,
mpsc::Receiver<GenericMessage<ActionReplyPus>>,
>;
}
#[cfg(test)]
mod tests {
use delegate::delegate;
use spacepackets::{
ecss::{
tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader},
tm::PusTmReader,
PusPacket,
},
CcsdsPacket, SequenceFlags, SpHeader,
};
use crate::pus::{
tests::{
PusServiceHandlerWithVecCommon, PusTestHarness, SimplePusPacketHandler, TestConverter,
TestRouter, TestRoutingErrorHandler, APP_DATA_TOO_SHORT, TEST_APID,
},
verification::{
tests::TestVerificationReporter, FailParams, RequestId, VerificationReportingProvider,
},
EcssTcInVecConverter, GenericRoutingError, MpscTcReceiver, PusPacketHandlerResult,
PusPacketHandlingError, TmAsVecSenderWithMpsc,
};
use super::*;
impl PusActionRequestRouter for TestRouter<ActionRequest> {
type Error = GenericRoutingError;
fn route(
&self,
target_id: TargetId,
hk_request: ActionRequest,
_token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error> {
self.routing_requests
.borrow_mut()
.push_back((target_id, hk_request));
self.check_for_injected_error()
}
}
impl PusActionToRequestConverter for TestConverter<8> {
type Error = PusPacketHandlingError;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, ActionRequest), Self::Error> {
self.conversion_request.push_back(tc.raw_data().to_vec());
self.check_service(tc)?;
let target_id = tc.apid();
if tc.user_data().len() < 4 {
verif_reporter
.start_failure(
token,
FailParams::new(
time_stamp,
&APP_DATA_TOO_SHORT,
(tc.user_data().len() as u32).to_be_bytes().as_ref(),
),
)
.expect("start success failure");
return Err(PusPacketHandlingError::NotEnoughAppData {
expected: 4,
found: tc.user_data().len(),
});
}
if tc.subservice() == 1 {
verif_reporter
.start_success(token, time_stamp)
.expect("start success failure");
return Ok((
target_id.into(),
ActionRequest::UnsignedIdAndVecData {
action_id: u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap()),
data: tc.user_data()[4..].to_vec(),
},
));
}
Err(PusPacketHandlingError::InvalidAppData(
"unexpected app data".into(),
))
}
}
struct Pus8HandlerWithVecTester {
common: PusServiceHandlerWithVecCommon<TestVerificationReporter>,
handler: PusService8ActionHandler<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
EcssTcInVecConverter,
TestVerificationReporter,
TestConverter<8>,
TestRouter<ActionRequest>,
TestRoutingErrorHandler,
>,
}
impl Pus8HandlerWithVecTester {
pub fn new() -> Self {
let (common, srv_handler) =
PusServiceHandlerWithVecCommon::new_with_test_verif_sender();
Self {
common,
handler: PusService8ActionHandler::new(
srv_handler,
TestConverter::default(),
TestRouter::default(),
TestRoutingErrorHandler::default(),
),
}
}
delegate! {
to self.handler.request_converter {
pub fn check_next_conversion(&mut self, tc: &PusTcCreator);
}
}
delegate! {
to self.handler.request_router {
pub fn retrieve_next_request(&mut self) -> (TargetId, ActionRequest);
}
}
delegate! {
to self.handler.routing_error_handler {
pub fn retrieve_next_error(&mut self) -> (TargetId, GenericRoutingError);
}
}
}
impl PusTestHarness for Pus8HandlerWithVecTester {
delegate! {
to self.common {
fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted>;
fn read_next_tm(&mut self) -> PusTmReader<'_>;
fn check_no_tm_available(&self) -> bool;
fn check_next_verification_tm(
&self,
subservice: u8,
expected_request_id: RequestId,
);
}
}
}
impl SimplePusPacketHandler for Pus8HandlerWithVecTester {
delegate! {
to self.handler {
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
}
}
}
#[test]
fn basic_test() {
let mut action_handler = Pus8HandlerWithVecTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sec_header = PusTcSecondaryHeader::new_simple(8, 1);
let action_id: u32 = 1;
let action_id_raw = action_id.to_be_bytes();
let tc = PusTcCreator::new(&mut sp_header, sec_header, action_id_raw.as_ref(), true);
action_handler.send_tc(&tc);
let result = action_handler.handle_one_tc();
assert!(result.is_ok());
action_handler.check_next_conversion(&tc);
let (target_id, action_req) = action_handler.retrieve_next_request();
assert_eq!(target_id, TEST_APID.into());
if let ActionRequest::UnsignedIdAndVecData { action_id, data } = action_req {
assert_eq!(action_id, 1);
assert_eq!(data, &[]);
}
}
#[test]
fn test_routing_error() {
let mut action_handler = Pus8HandlerWithVecTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sec_header = PusTcSecondaryHeader::new_simple(8, 1);
let action_id: u32 = 1;
let action_id_raw = action_id.to_be_bytes();
let tc = PusTcCreator::new(&mut sp_header, sec_header, action_id_raw.as_ref(), true);
let error = GenericRoutingError::UnknownTargetId(25);
action_handler
.handler
.request_router
.inject_routing_error(error);
action_handler.send_tc(&tc);
let result = action_handler.handle_one_tc();
assert!(result.is_err());
let check_error = |routing_error: GenericRoutingError| {
if let GenericRoutingError::UnknownTargetId(id) = routing_error {
assert_eq!(id, 25);
} else {
panic!("unexpected error type");
}
};
if let PusPacketHandlingError::RequestRoutingError(routing_error) = result.unwrap_err() {
check_error(routing_error);
} else {
panic!("unexpected error type");
}
action_handler.check_next_conversion(&tc);
let (target_id, action_req) = action_handler.retrieve_next_request();
assert_eq!(target_id, TEST_APID.into());
if let ActionRequest::UnsignedIdAndVecData { action_id, data } = action_req {
assert_eq!(action_id, 1);
assert_eq!(data, &[]);
}
let (target_id, found_error) = action_handler.retrieve_next_error();
assert_eq!(target_id, TEST_APID.into());
check_error(found_error);
}
}
mod tests {}

View File

@ -1,153 +1,129 @@
use crate::pus::{source_buffer_large_enough, EcssTmtcError};
use crate::pus::source_buffer_large_enough;
use spacepackets::ecss::tm::PusTmCreator;
use spacepackets::ecss::tm::PusTmSecondaryHeader;
use spacepackets::ecss::{EcssEnumeration, PusError};
use spacepackets::ecss::EcssEnumeration;
use spacepackets::ByteConversionError;
use spacepackets::{SpHeader, MAX_APID};
use crate::pus::EcssTmSenderCore;
#[cfg(feature = "alloc")]
pub use alloc_mod::EventReporter;
pub use alloc_mod::*;
pub use spacepackets::ecss::event::*;
pub struct EventReporterBase {
msg_count: u16,
pub struct EventReportCreator {
apid: u16,
pub dest_id: u16,
}
impl EventReporterBase {
pub fn new(apid: u16) -> Option<Self> {
impl EventReportCreator {
pub fn new(apid: u16, dest_id: u16) -> Option<Self> {
if apid > MAX_APID {
return None;
}
Some(Self {
msg_count: 0,
dest_id: 0,
apid,
})
Some(Self { dest_id, apid })
}
pub fn event_info(
&mut self,
buf: &mut [u8],
sender: &mut (impl EcssTmSenderCore + ?Sized),
time_stamp: &[u8],
pub fn event_info<'time, 'src_data>(
&self,
time_stamp: &'time [u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
) -> Result<(), EcssTmtcError> {
params: Option<&'src_data [u8]>,
src_data_buf: &'src_data mut [u8],
) -> Result<PusTmCreator<'time, 'src_data>, ByteConversionError> {
self.generate_and_send_generic_tm(
buf,
Subservice::TmInfoReport,
sender,
time_stamp,
event_id,
aux_data,
params,
src_data_buf,
)
}
pub fn event_low_severity(
&mut self,
buf: &mut [u8],
sender: &mut (impl EcssTmSenderCore + ?Sized),
time_stamp: &[u8],
pub fn event_low_severity<'time, 'src_data>(
&self,
time_stamp: &'time [u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
) -> Result<(), EcssTmtcError> {
params: Option<&'src_data [u8]>,
src_data_buf: &'src_data mut [u8],
) -> Result<PusTmCreator<'time, 'src_data>, ByteConversionError> {
self.generate_and_send_generic_tm(
buf,
Subservice::TmLowSeverityReport,
sender,
time_stamp,
event_id,
aux_data,
params,
src_data_buf,
)
}
pub fn event_medium_severity(
&mut self,
buf: &mut [u8],
sender: &mut (impl EcssTmSenderCore + ?Sized),
time_stamp: &[u8],
pub fn event_medium_severity<'time, 'src_data>(
&self,
time_stamp: &'time [u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
) -> Result<(), EcssTmtcError> {
params: Option<&'src_data [u8]>,
buf: &'src_data mut [u8],
) -> Result<PusTmCreator<'time, 'src_data>, ByteConversionError> {
self.generate_and_send_generic_tm(
buf,
Subservice::TmMediumSeverityReport,
sender,
time_stamp,
event_id,
aux_data,
)
}
pub fn event_high_severity(
&mut self,
buf: &mut [u8],
sender: &mut (impl EcssTmSenderCore + ?Sized),
time_stamp: &[u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
) -> Result<(), EcssTmtcError> {
self.generate_and_send_generic_tm(
params,
buf,
Subservice::TmHighSeverityReport,
sender,
time_stamp,
event_id,
aux_data,
)
}
fn generate_and_send_generic_tm(
&mut self,
buf: &mut [u8],
subservice: Subservice,
sender: &mut (impl EcssTmSenderCore + ?Sized),
time_stamp: &[u8],
pub fn event_high_severity<'time, 'src_data>(
&self,
time_stamp: &'time [u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
) -> Result<(), EcssTmtcError> {
let tm = self.generate_generic_event_tm(buf, subservice, time_stamp, event_id, aux_data)?;
sender.send_tm(tm.into())?;
self.msg_count += 1;
Ok(())
params: Option<&'src_data [u8]>,
src_data_buf: &'src_data mut [u8],
) -> Result<PusTmCreator<'time, 'src_data>, ByteConversionError> {
self.generate_and_send_generic_tm(
Subservice::TmHighSeverityReport,
time_stamp,
event_id,
params,
src_data_buf,
)
}
fn generate_generic_event_tm<'a>(
&'a self,
buf: &'a mut [u8],
fn generate_and_send_generic_tm<'time, 'src_data>(
&self,
subservice: Subservice,
time_stamp: &'a [u8],
time_stamp: &'time [u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
) -> Result<PusTmCreator, EcssTmtcError> {
params: Option<&'src_data [u8]>,
src_data_buf: &'src_data mut [u8],
) -> Result<PusTmCreator<'time, 'src_data>, ByteConversionError> {
self.generate_generic_event_tm(subservice, time_stamp, event_id, params, src_data_buf)
}
fn generate_generic_event_tm<'time, 'src_data>(
&self,
subservice: Subservice,
time_stamp: &'time [u8],
event_id: impl EcssEnumeration,
params: Option<&'src_data [u8]>,
src_data_buf: &'src_data mut [u8],
) -> Result<PusTmCreator<'time, 'src_data>, ByteConversionError> {
let mut src_data_len = event_id.size();
if let Some(aux_data) = aux_data {
if let Some(aux_data) = params {
src_data_len += aux_data.len();
}
source_buffer_large_enough(buf.len(), src_data_len)?;
let mut sp_header = SpHeader::tm_unseg(self.apid, 0, 0).unwrap();
let sec_header = PusTmSecondaryHeader::new(
5,
subservice.into(),
self.msg_count,
self.dest_id,
Some(time_stamp),
);
source_buffer_large_enough(src_data_buf.len(), src_data_len)?;
let sec_header =
PusTmSecondaryHeader::new(5, subservice.into(), 0, self.dest_id, time_stamp);
let mut current_idx = 0;
event_id
.write_to_be_bytes(&mut buf[0..event_id.size()])
.map_err(PusError::ByteConversion)?;
event_id.write_to_be_bytes(&mut src_data_buf[0..event_id.size()])?;
current_idx += event_id.size();
if let Some(aux_data) = aux_data {
buf[current_idx..current_idx + aux_data.len()].copy_from_slice(aux_data);
if let Some(aux_data) = params {
src_data_buf[current_idx..current_idx + aux_data.len()].copy_from_slice(aux_data);
current_idx += aux_data.len();
}
Ok(PusTmCreator::new(
&mut sp_header,
SpHeader::new_from_apid(self.apid),
sec_header,
&buf[0..current_idx],
&src_data_buf[0..current_idx],
true,
))
}
@ -156,84 +132,132 @@ impl EventReporterBase {
#[cfg(feature = "alloc")]
mod alloc_mod {
use super::*;
use crate::pus::{EcssTmSender, EcssTmtcError};
use crate::ComponentId;
use alloc::vec;
use alloc::vec::Vec;
use core::cell::RefCell;
use spacepackets::ecss::PusError;
pub struct EventReporter {
source_data_buf: Vec<u8>,
pub reporter: EventReporterBase,
pub trait EventTmHookProvider {
fn modify_tm(&self, tm: &mut PusTmCreator);
}
impl EventReporter {
pub fn new(apid: u16, max_event_id_and_aux_data_size: usize) -> Option<Self> {
let reporter = EventReporterBase::new(apid)?;
#[derive(Default)]
pub struct DummyEventHook {}
impl EventTmHookProvider for DummyEventHook {
fn modify_tm(&self, _tm: &mut PusTmCreator) {}
}
pub struct EventReporter<EventTmHook: EventTmHookProvider = DummyEventHook> {
id: ComponentId,
// Use interior mutability pattern here. This is just an intermediate buffer to the PUS event packet
// generation.
source_data_buf: RefCell<Vec<u8>>,
pub report_creator: EventReportCreator,
pub tm_hook: EventTmHook,
}
impl EventReporter<DummyEventHook> {
pub fn new(
id: ComponentId,
default_apid: u16,
default_dest_id: u16,
max_event_id_and_aux_data_size: usize,
) -> Option<Self> {
let reporter = EventReportCreator::new(default_apid, default_dest_id)?;
Some(Self {
source_data_buf: vec![0; max_event_id_and_aux_data_size],
reporter,
id,
source_data_buf: RefCell::new(vec![0; max_event_id_and_aux_data_size]),
report_creator: reporter,
tm_hook: DummyEventHook::default(),
})
}
}
impl<EventTmHook: EventTmHookProvider> EventReporter<EventTmHook> {
pub fn new_with_hook(
id: ComponentId,
default_apid: u16,
default_dest_id: u16,
max_event_id_and_aux_data_size: usize,
tm_hook: EventTmHook,
) -> Option<Self> {
let reporter = EventReportCreator::new(default_apid, default_dest_id)?;
Some(Self {
id,
source_data_buf: RefCell::new(vec![0; max_event_id_and_aux_data_size]),
report_creator: reporter,
tm_hook,
})
}
pub fn event_info(
&mut self,
sender: &mut (impl EcssTmSenderCore + ?Sized),
&self,
sender: &(impl EcssTmSender + ?Sized),
time_stamp: &[u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
params: Option<&[u8]>,
) -> Result<(), EcssTmtcError> {
self.reporter.event_info(
self.source_data_buf.as_mut_slice(),
sender,
time_stamp,
event_id,
aux_data,
)
let mut mut_buf = self.source_data_buf.borrow_mut();
let mut tm_creator = self
.report_creator
.event_info(time_stamp, event_id, params, mut_buf.as_mut_slice())
.map_err(PusError::ByteConversion)?;
self.tm_hook.modify_tm(&mut tm_creator);
sender.send_tm(self.id, tm_creator.into())?;
Ok(())
}
pub fn event_low_severity(
&mut self,
sender: &mut (impl EcssTmSenderCore + ?Sized),
&self,
sender: &(impl EcssTmSender + ?Sized),
time_stamp: &[u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
params: Option<&[u8]>,
) -> Result<(), EcssTmtcError> {
self.reporter.event_low_severity(
self.source_data_buf.as_mut_slice(),
sender,
time_stamp,
event_id,
aux_data,
)
let mut mut_buf = self.source_data_buf.borrow_mut();
let mut tm_creator = self
.report_creator
.event_low_severity(time_stamp, event_id, params, mut_buf.as_mut_slice())
.map_err(PusError::ByteConversion)?;
self.tm_hook.modify_tm(&mut tm_creator);
sender.send_tm(self.id, tm_creator.into())?;
Ok(())
}
pub fn event_medium_severity(
&mut self,
sender: &mut (impl EcssTmSenderCore + ?Sized),
&self,
sender: &(impl EcssTmSender + ?Sized),
time_stamp: &[u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
params: Option<&[u8]>,
) -> Result<(), EcssTmtcError> {
self.reporter.event_medium_severity(
self.source_data_buf.as_mut_slice(),
sender,
time_stamp,
event_id,
aux_data,
)
let mut mut_buf = self.source_data_buf.borrow_mut();
let mut tm_creator = self
.report_creator
.event_medium_severity(time_stamp, event_id, params, mut_buf.as_mut_slice())
.map_err(PusError::ByteConversion)?;
self.tm_hook.modify_tm(&mut tm_creator);
sender.send_tm(self.id, tm_creator.into())?;
Ok(())
}
pub fn event_high_severity(
&mut self,
sender: &mut (impl EcssTmSenderCore + ?Sized),
&self,
sender: &(impl EcssTmSender + ?Sized),
time_stamp: &[u8],
event_id: impl EcssEnumeration,
aux_data: Option<&[u8]>,
params: Option<&[u8]>,
) -> Result<(), EcssTmtcError> {
self.reporter.event_high_severity(
self.source_data_buf.as_mut_slice(),
sender,
time_stamp,
event_id,
aux_data,
)
let mut mut_buf = self.source_data_buf.borrow_mut();
let mut tm_creator = self
.report_creator
.event_high_severity(time_stamp, event_id, params, mut_buf.as_mut_slice())
.map_err(PusError::ByteConversion)?;
self.tm_hook.modify_tm(&mut tm_creator);
sender.send_tm(self.id, tm_creator.into())?;
Ok(())
}
}
}
@ -242,9 +266,11 @@ mod alloc_mod {
mod tests {
use super::*;
use crate::events::{EventU32, Severity};
use crate::pus::test_util::TEST_COMPONENT_ID_0;
use crate::pus::tests::CommonTmInfo;
use crate::pus::{EcssChannel, PusTmWrapper};
use crate::ChannelId;
use crate::pus::{ChannelWithId, EcssTmSender, EcssTmtcError, PusTmVariant};
use crate::ComponentId;
use spacepackets::ecss::PusError;
use spacepackets::ByteConversionError;
use std::cell::RefCell;
use std::collections::VecDeque;
@ -258,6 +284,7 @@ mod tests {
#[derive(Debug, Eq, PartialEq, Clone)]
struct TmInfo {
pub sender_id: ComponentId,
pub common: CommonTmInfo,
pub event: EventU32,
pub aux_data: Vec<u8>,
@ -268,19 +295,19 @@ mod tests {
pub service_queue: RefCell<VecDeque<TmInfo>>,
}
impl EcssChannel for TestSender {
fn channel_id(&self) -> ChannelId {
impl ChannelWithId for TestSender {
fn id(&self) -> ComponentId {
0
}
}
impl EcssTmSenderCore for TestSender {
fn send_tm(&self, tm: PusTmWrapper) -> Result<(), EcssTmtcError> {
impl EcssTmSender for TestSender {
fn send_tm(&self, sender_id: ComponentId, tm: PusTmVariant) -> Result<(), EcssTmtcError> {
match tm {
PusTmWrapper::InStore(_) => {
PusTmVariant::InStore(_) => {
panic!("TestSender: unexpected call with address");
}
PusTmWrapper::Direct(tm) => {
PusTmVariant::Direct(tm) => {
assert!(!tm.source_data().is_empty());
let src_data = tm.source_data();
assert!(src_data.len() >= 4);
@ -291,6 +318,7 @@ mod tests {
aux_data.extend_from_slice(&src_data[4..]);
}
self.service_queue.borrow_mut().push_back(TmInfo {
sender_id,
common: CommonTmInfo::new_from_tm(&tm),
event,
aux_data,
@ -303,10 +331,10 @@ mod tests {
fn severity_to_subservice(severity: Severity) -> Subservice {
match severity {
Severity::INFO => Subservice::TmInfoReport,
Severity::LOW => Subservice::TmLowSeverityReport,
Severity::MEDIUM => Subservice::TmMediumSeverityReport,
Severity::HIGH => Subservice::TmHighSeverityReport,
Severity::Info => Subservice::TmInfoReport,
Severity::Low => Subservice::TmLowSeverityReport,
Severity::Medium => Subservice::TmMediumSeverityReport,
Severity::High => Subservice::TmHighSeverityReport,
}
}
@ -319,22 +347,22 @@ mod tests {
aux_data: Option<&[u8]>,
) {
match severity {
Severity::INFO => {
Severity::Info => {
reporter
.event_info(sender, time_stamp, event, aux_data)
.expect("Error reporting info event");
}
Severity::LOW => {
Severity::Low => {
reporter
.event_low_severity(sender, time_stamp, event, aux_data)
.expect("Error reporting low event");
}
Severity::MEDIUM => {
Severity::Medium => {
reporter
.event_medium_severity(sender, time_stamp, event, aux_data)
.expect("Error reporting medium event");
}
Severity::HIGH => {
Severity::High => {
reporter
.event_high_severity(sender, time_stamp, event, aux_data)
.expect("Error reporting high event");
@ -348,7 +376,12 @@ mod tests {
error_data: Option<&[u8]>,
) {
let mut sender = TestSender::default();
let reporter = EventReporter::new(EXAMPLE_APID, max_event_aux_data_buf);
let reporter = EventReporter::new(
TEST_COMPONENT_ID_0.id(),
EXAMPLE_APID,
0,
max_event_aux_data_buf,
);
assert!(reporter.is_some());
let mut reporter = reporter.unwrap();
let time_stamp_empty: [u8; 7] = [0; 7];
@ -356,7 +389,7 @@ mod tests {
if let Some(err_data) = error_data {
error_copy.extend_from_slice(err_data);
}
let event = EventU32::new(severity, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0)
let event = EventU32::new_checked(severity, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0)
.expect("Error creating example event");
report_basic_event(
&mut reporter,
@ -374,44 +407,45 @@ mod tests {
severity_to_subservice(severity) as u8
);
assert_eq!(tm_info.common.dest_id, 0);
assert_eq!(tm_info.common.time_stamp, time_stamp_empty);
assert_eq!(tm_info.common.timestamp, time_stamp_empty);
assert_eq!(tm_info.common.msg_counter, 0);
assert_eq!(tm_info.common.apid, EXAMPLE_APID);
assert_eq!(tm_info.event, event);
assert_eq!(tm_info.sender_id, TEST_COMPONENT_ID_0.id());
assert_eq!(tm_info.aux_data, error_copy);
}
#[test]
fn basic_info_event_generation() {
basic_event_test(4, Severity::INFO, None);
basic_event_test(4, Severity::Info, None);
}
#[test]
fn basic_low_severity_event() {
basic_event_test(4, Severity::LOW, None);
basic_event_test(4, Severity::Low, None);
}
#[test]
fn basic_medium_severity_event() {
basic_event_test(4, Severity::MEDIUM, None);
basic_event_test(4, Severity::Medium, None);
}
#[test]
fn basic_high_severity_event() {
basic_event_test(4, Severity::HIGH, None);
basic_event_test(4, Severity::High, None);
}
#[test]
fn event_with_info_string() {
let info_string = "Test Information";
basic_event_test(32, Severity::INFO, Some(info_string.as_bytes()));
basic_event_test(32, Severity::Info, Some(info_string.as_bytes()));
}
#[test]
fn low_severity_with_raw_err_data() {
let raw_err_param: i32 = -1;
let raw_err = raw_err_param.to_be_bytes();
basic_event_test(8, Severity::LOW, Some(&raw_err))
basic_event_test(8, Severity::Low, Some(&raw_err))
}
fn check_buf_too_small(
@ -420,7 +454,7 @@ mod tests {
expected_found_len: usize,
) {
let time_stamp_empty: [u8; 7] = [0; 7];
let event = EventU32::new(Severity::INFO, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0)
let event = EventU32::new_checked(Severity::Info, EXAMPLE_GROUP_ID, EXAMPLE_EVENT_ID_0)
.expect("Error creating example event");
let err = reporter.event_info(sender, &time_stamp_empty, event, None);
assert!(err.is_err());
@ -440,7 +474,7 @@ mod tests {
fn insufficient_buffer() {
let mut sender = TestSender::default();
for i in 0..3 {
let reporter = EventReporter::new(EXAMPLE_APID, i);
let reporter = EventReporter::new(0, EXAMPLE_APID, 0, i);
assert!(reporter.is_some());
let mut reporter = reporter.unwrap();
check_buf_too_small(&mut reporter, &mut sender, i);

View File

@ -10,13 +10,11 @@ use hashbrown::HashSet;
pub use crate::pus::event::EventReporter;
use crate::pus::verification::TcStateToken;
#[cfg(feature = "alloc")]
use crate::pus::EcssTmSenderCore;
use crate::pus::EcssTmSender;
use crate::pus::EcssTmtcError;
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub use alloc_mod::*;
#[cfg(feature = "heapless")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "heapless")))]
pub use heapless_mod::*;
/// This trait allows the PUS event manager implementation to stay generic over various types
@ -30,7 +28,7 @@ pub use heapless_mod::*;
/// structure to track disabled events. A more primitive and embedded friendly
/// solution could track this information in a static or pre-allocated list which contains
/// the disabled events.
pub trait PusEventMgmtBackendProvider<Event: GenericEvent> {
pub trait PusEventReportingMapProvider<Event: GenericEvent> {
type Error;
fn event_enabled(&self, event: &Event) -> bool;
@ -44,7 +42,6 @@ pub mod heapless_mod {
use crate::events::LargestEventRaw;
use core::marker::PhantomData;
#[cfg_attr(doc_cfg, doc(cfg(feature = "heapless")))]
// TODO: After a new version of heapless is released which uses hash32 version 0.3, try using
// regular Event type again.
#[derive(Default)]
@ -59,7 +56,7 @@ pub mod heapless_mod {
{
}
impl<const N: usize, Provider: GenericEvent> PusEventMgmtBackendProvider<Provider>
impl<const N: usize, Provider: GenericEvent> PusEventReportingMapProvider<Provider>
for HeaplessPusMgmtBackendProvider<N, Provider>
{
type Error = ();
@ -108,20 +105,24 @@ impl From<EcssTmtcError> for EventManError {
pub mod alloc_mod {
use core::marker::PhantomData;
use crate::events::EventU16;
use crate::{
events::EventU16,
params::{Params, WritableToBeBytes},
pus::event::{DummyEventHook, EventTmHookProvider},
};
use super::*;
/// Default backend provider which uses a hash set as the event reporting status container
/// like mentioned in the example of the [PusEventMgmtBackendProvider] documentation.
/// like mentioned in the example of the [PusEventReportingMapProvider] documentation.
///
/// This provider is a good option for host systems or larger embedded systems where
/// the expected occasional memory allocation performed by the [HashSet] is not an issue.
pub struct DefaultPusEventMgmtBackend<Event: GenericEvent = EventU32> {
pub struct DefaultPusEventReportingMap<Event: GenericEvent = EventU32> {
disabled: HashSet<Event>,
}
impl<Event: GenericEvent> Default for DefaultPusEventMgmtBackend<Event> {
impl<Event: GenericEvent> Default for DefaultPusEventReportingMap<Event> {
fn default() -> Self {
Self {
disabled: HashSet::default(),
@ -129,118 +130,176 @@ pub mod alloc_mod {
}
}
impl<EV: GenericEvent + PartialEq + Eq + Hash + Copy + Clone> PusEventMgmtBackendProvider<EV>
for DefaultPusEventMgmtBackend<EV>
impl<Event: GenericEvent + PartialEq + Eq + Hash + Copy + Clone>
PusEventReportingMapProvider<Event> for DefaultPusEventReportingMap<Event>
{
type Error = ();
fn event_enabled(&self, event: &EV) -> bool {
fn event_enabled(&self, event: &Event) -> bool {
!self.disabled.contains(event)
}
fn enable_event_reporting(&mut self, event: &EV) -> Result<bool, Self::Error> {
fn enable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error> {
Ok(self.disabled.remove(event))
}
fn disable_event_reporting(&mut self, event: &EV) -> Result<bool, Self::Error> {
fn disable_event_reporting(&mut self, event: &Event) -> Result<bool, Self::Error> {
Ok(self.disabled.insert(*event))
}
}
pub struct PusEventDispatcher<
B: PusEventMgmtBackendProvider<EV, Error = E>,
EV: GenericEvent,
E,
> {
reporter: EventReporter,
backend: B,
phantom: PhantomData<(E, EV)>,
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct EventGenerationResult {
pub event_was_enabled: bool,
pub params_were_propagated: bool,
}
impl<B: PusEventMgmtBackendProvider<EV, Error = E>, EV: GenericEvent, E>
PusEventDispatcher<B, EV, E>
pub struct PusEventTmCreatorWithMap<
ReportingMap: PusEventReportingMapProvider<Event>,
Event: GenericEvent,
EventTmHook: EventTmHookProvider = DummyEventHook,
> {
pub reporter: EventReporter<EventTmHook>,
reporting_map: ReportingMap,
phantom: PhantomData<Event>,
}
impl<
ReportingMap: PusEventReportingMapProvider<Event>,
Event: GenericEvent,
EventTmHook: EventTmHookProvider,
> PusEventTmCreatorWithMap<ReportingMap, Event, EventTmHook>
{
pub fn new(reporter: EventReporter, backend: B) -> Self {
pub fn new(reporter: EventReporter<EventTmHook>, backend: ReportingMap) -> Self {
Self {
reporter,
backend,
reporting_map: backend,
phantom: PhantomData,
}
}
pub fn enable_tm_for_event(&mut self, event: &EV) -> Result<bool, E> {
self.backend.enable_event_reporting(event)
pub fn enable_tm_for_event(&mut self, event: &Event) -> Result<bool, ReportingMap::Error> {
self.reporting_map.enable_event_reporting(event)
}
pub fn disable_tm_for_event(&mut self, event: &EV) -> Result<bool, E> {
self.backend.disable_event_reporting(event)
pub fn disable_tm_for_event(&mut self, event: &Event) -> Result<bool, ReportingMap::Error> {
self.reporting_map.disable_event_reporting(event)
}
pub fn generate_pus_event_tm_generic(
&mut self,
sender: &mut (impl EcssTmSenderCore + ?Sized),
&self,
sender: &(impl EcssTmSender + ?Sized),
time_stamp: &[u8],
event: EV,
aux_data: Option<&[u8]>,
event: Event,
params: Option<&[u8]>,
) -> Result<bool, EventManError> {
if !self.backend.event_enabled(&event) {
if !self.reporting_map.event_enabled(&event) {
return Ok(false);
}
match event.severity() {
Severity::INFO => self
Severity::Info => self
.reporter
.event_info(sender, time_stamp, event, aux_data)
.event_info(sender, time_stamp, event, params)
.map(|_| true)
.map_err(|e| e.into()),
Severity::LOW => self
Severity::Low => self
.reporter
.event_low_severity(sender, time_stamp, event, aux_data)
.event_low_severity(sender, time_stamp, event, params)
.map(|_| true)
.map_err(|e| e.into()),
Severity::MEDIUM => self
Severity::Medium => self
.reporter
.event_medium_severity(sender, time_stamp, event, aux_data)
.event_medium_severity(sender, time_stamp, event, params)
.map(|_| true)
.map_err(|e| e.into()),
Severity::HIGH => self
Severity::High => self
.reporter
.event_high_severity(sender, time_stamp, event, aux_data)
.event_high_severity(sender, time_stamp, event, params)
.map(|_| true)
.map_err(|e| e.into()),
}
}
pub fn generate_pus_event_tm_generic_with_generic_params(
&self,
sender: &(impl EcssTmSender + ?Sized),
time_stamp: &[u8],
event: Event,
small_data_buf: &mut [u8],
params: Option<&Params>,
) -> Result<EventGenerationResult, EventManError> {
let mut result = EventGenerationResult {
event_was_enabled: false,
params_were_propagated: true,
};
if params.is_none() {
result.event_was_enabled =
self.generate_pus_event_tm_generic(sender, time_stamp, event, None)?;
return Ok(result);
}
let params = params.unwrap();
result.event_was_enabled = match params {
Params::Heapless(heapless_param) => {
heapless_param
.write_to_be_bytes(&mut small_data_buf[..heapless_param.written_len()])
.map_err(EcssTmtcError::ByteConversion)?;
self.generate_pus_event_tm_generic(
sender,
time_stamp,
event,
Some(small_data_buf),
)?
}
Params::Vec(vec) => {
self.generate_pus_event_tm_generic(sender, time_stamp, event, Some(vec))?
}
Params::String(string) => self.generate_pus_event_tm_generic(
sender,
time_stamp,
event,
Some(string.as_bytes()),
)?,
_ => {
result.params_were_propagated = false;
self.generate_pus_event_tm_generic(sender, time_stamp, event, None)?
}
};
Ok(result)
}
}
impl<EV: GenericEvent + Copy + PartialEq + Eq + Hash>
PusEventDispatcher<DefaultPusEventMgmtBackend<EV>, EV, ()>
impl<Event: GenericEvent + Copy + PartialEq + Eq + Hash, EventTmHook: EventTmHookProvider>
PusEventTmCreatorWithMap<DefaultPusEventReportingMap<Event>, Event, EventTmHook>
{
pub fn new_with_default_backend(reporter: EventReporter) -> Self {
pub fn new_with_default_backend(reporter: EventReporter<EventTmHook>) -> Self {
Self {
reporter,
backend: DefaultPusEventMgmtBackend::default(),
reporting_map: DefaultPusEventReportingMap::default(),
phantom: PhantomData,
}
}
}
impl<B: PusEventMgmtBackendProvider<EventU32, Error = E>, E> PusEventDispatcher<B, EventU32, E> {
impl<ReportingMap: PusEventReportingMapProvider<EventU32>>
PusEventTmCreatorWithMap<ReportingMap, EventU32>
{
pub fn enable_tm_for_event_with_sev<Severity: HasSeverity>(
&mut self,
event: &EventU32TypedSev<Severity>,
) -> Result<bool, E> {
self.backend.enable_event_reporting(event.as_ref())
) -> Result<bool, ReportingMap::Error> {
self.reporting_map.enable_event_reporting(event.as_ref())
}
pub fn disable_tm_for_event_with_sev<Severity: HasSeverity>(
&mut self,
event: &EventU32TypedSev<Severity>,
) -> Result<bool, E> {
self.backend.disable_event_reporting(event.as_ref())
) -> Result<bool, ReportingMap::Error> {
self.reporting_map.disable_event_reporting(event.as_ref())
}
pub fn generate_pus_event_tm<Severity: HasSeverity>(
&mut self,
sender: &mut (impl EcssTmSenderCore + ?Sized),
&self,
sender: &(impl EcssTmSender + ?Sized),
time_stamp: &[u8],
event: EventU32TypedSev<Severity>,
aux_data: Option<&[u8]>,
@ -249,39 +308,48 @@ pub mod alloc_mod {
}
}
pub type DefaultPusEventU16Dispatcher<E> =
PusEventDispatcher<DefaultPusEventMgmtBackend<EventU16>, EventU16, E>;
pub type DefaultPusEventU32Dispatcher<E> =
PusEventDispatcher<DefaultPusEventMgmtBackend<EventU32>, EventU32, E>;
pub type DefaultPusEventU16TmCreator<EventTmHook = DummyEventHook> =
PusEventTmCreatorWithMap<DefaultPusEventReportingMap<EventU16>, EventU16, EventTmHook>;
pub type DefaultPusEventU32TmCreator<EventTmHook = DummyEventHook> =
PusEventTmCreatorWithMap<DefaultPusEventReportingMap<EventU32>, EventU32, EventTmHook>;
}
#[cfg(test)]
mod tests {
use alloc::string::{String, ToString};
use alloc::vec;
use spacepackets::ecss::event::Subservice;
use spacepackets::ecss::tm::PusTmReader;
use spacepackets::ecss::PusPacket;
use super::*;
use crate::{events::SeverityInfo, pus::TmAsVecSenderWithMpsc};
use crate::request::UniqueApidTargetId;
use crate::{events::SeverityInfo, tmtc::PacketAsVec};
use std::sync::mpsc::{self, TryRecvError};
const INFO_EVENT: EventU32TypedSev<SeverityInfo> =
EventU32TypedSev::<SeverityInfo>::const_new(1, 0);
const LOW_SEV_EVENT: EventU32 = EventU32::const_new(Severity::LOW, 1, 5);
const INFO_EVENT: EventU32TypedSev<SeverityInfo> = EventU32TypedSev::<SeverityInfo>::new(1, 0);
const LOW_SEV_EVENT: EventU32 = EventU32::new(Severity::Low, 1, 5);
const EMPTY_STAMP: [u8; 7] = [0; 7];
const TEST_APID: u16 = 0x02;
const TEST_ID: UniqueApidTargetId = UniqueApidTargetId::new(TEST_APID, 0x05);
fn create_basic_man_1() -> DefaultPusEventU32Dispatcher<()> {
let reporter = EventReporter::new(0x02, 128).expect("Creating event repoter failed");
PusEventDispatcher::new_with_default_backend(reporter)
fn create_basic_man_1() -> DefaultPusEventU32TmCreator {
let reporter = EventReporter::new(TEST_ID.raw(), TEST_APID, 0, 128)
.expect("Creating event repoter failed");
PusEventTmCreatorWithMap::new_with_default_backend(reporter)
}
fn create_basic_man_2() -> DefaultPusEventU32Dispatcher<()> {
let reporter = EventReporter::new(0x02, 128).expect("Creating event repoter failed");
let backend = DefaultPusEventMgmtBackend::default();
PusEventDispatcher::new(reporter, backend)
fn create_basic_man_2() -> DefaultPusEventU32TmCreator {
let reporter = EventReporter::new(TEST_ID.raw(), TEST_APID, 0, 128)
.expect("Creating event repoter failed");
let backend = DefaultPusEventReportingMap::default();
PusEventTmCreatorWithMap::new(reporter, backend)
}
#[test]
fn test_basic() {
let mut event_man = create_basic_man_1();
let (event_tx, event_rx) = mpsc::channel();
let mut sender = TmAsVecSenderWithMpsc::new(0, "test_sender", event_tx);
let event_man = create_basic_man_1();
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
let event_sent = event_man
.generate_pus_event_tm(&mut sender, &EMPTY_STAMP, INFO_EVENT, None)
.generate_pus_event_tm(&event_tx, &EMPTY_STAMP, INFO_EVENT, None)
.expect("Sending info event failed");
assert!(event_sent);
@ -292,13 +360,13 @@ mod tests {
#[test]
fn test_disable_event() {
let mut event_man = create_basic_man_2();
let (event_tx, event_rx) = mpsc::channel();
let mut sender = TmAsVecSenderWithMpsc::new(0, "test", event_tx);
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
// let mut sender = TmAsVecSenderWithMpsc::new(0, "test", event_tx);
let res = event_man.disable_tm_for_event(&LOW_SEV_EVENT);
assert!(res.is_ok());
assert!(res.unwrap());
let mut event_sent = event_man
.generate_pus_event_tm_generic(&mut sender, &EMPTY_STAMP, LOW_SEV_EVENT, None)
.generate_pus_event_tm_generic(&event_tx, &EMPTY_STAMP, LOW_SEV_EVENT, None)
.expect("Sending low severity event failed");
assert!(!event_sent);
let res = event_rx.try_recv();
@ -306,7 +374,7 @@ mod tests {
assert!(matches!(res.unwrap_err(), TryRecvError::Empty));
// Check that only the low severity event was disabled
event_sent = event_man
.generate_pus_event_tm(&mut sender, &EMPTY_STAMP, INFO_EVENT, None)
.generate_pus_event_tm(&event_tx, &EMPTY_STAMP, INFO_EVENT, None)
.expect("Sending info event failed");
assert!(event_sent);
event_rx.try_recv().expect("No info event received");
@ -315,8 +383,7 @@ mod tests {
#[test]
fn test_reenable_event() {
let mut event_man = create_basic_man_1();
let (event_tx, event_rx) = mpsc::channel();
let mut sender = TmAsVecSenderWithMpsc::new(0, "test", event_tx);
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
let mut res = event_man.disable_tm_for_event_with_sev(&INFO_EVENT);
assert!(res.is_ok());
assert!(res.unwrap());
@ -324,9 +391,75 @@ mod tests {
assert!(res.is_ok());
assert!(res.unwrap());
let event_sent = event_man
.generate_pus_event_tm(&mut sender, &EMPTY_STAMP, INFO_EVENT, None)
.generate_pus_event_tm(&event_tx, &EMPTY_STAMP, INFO_EVENT, None)
.expect("Sending info event failed");
assert!(event_sent);
event_rx.try_recv().expect("No info event received");
}
#[test]
fn test_event_with_generic_string_param() {
let event_man = create_basic_man_1();
let mut small_data_buf = [0; 128];
let param_data = "hello world";
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
let res = event_man.generate_pus_event_tm_generic_with_generic_params(
&event_tx,
&EMPTY_STAMP,
INFO_EVENT.into(),
&mut small_data_buf,
Some(&param_data.to_string().into()),
);
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.event_was_enabled);
assert!(res.params_were_propagated);
let event_tm = event_rx.try_recv().expect("no event received");
let (tm, _) = PusTmReader::new(&event_tm.packet, 7).expect("reading TM failed");
assert_eq!(tm.service(), 5);
assert_eq!(tm.subservice(), Subservice::TmInfoReport as u8);
assert_eq!(tm.user_data().len(), 4 + param_data.len());
let u32_event = u32::from_be_bytes(tm.user_data()[0..4].try_into().unwrap());
assert_eq!(u32_event, INFO_EVENT.raw());
let string_data = String::from_utf8_lossy(&tm.user_data()[4..]);
assert_eq!(string_data, param_data);
}
#[test]
fn test_event_with_generic_vec_param() {
let event_man = create_basic_man_1();
let mut small_data_buf = [0; 128];
let param_data = vec![1, 2, 3, 4];
let (event_tx, event_rx) = mpsc::channel::<PacketAsVec>();
let res = event_man.generate_pus_event_tm_generic_with_generic_params(
&event_tx,
&EMPTY_STAMP,
INFO_EVENT.into(),
&mut small_data_buf,
Some(&param_data.clone().into()),
);
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.event_was_enabled);
assert!(res.params_were_propagated);
let event_tm = event_rx.try_recv().expect("no event received");
let (tm, _) = PusTmReader::new(&event_tm.packet, 7).expect("reading TM failed");
assert_eq!(tm.service(), 5);
assert_eq!(tm.subservice(), Subservice::TmInfoReport as u8);
assert_eq!(tm.user_data().len(), 4 + param_data.len());
let u32_event = u32::from_be_bytes(tm.user_data()[0..4].try_into().unwrap());
assert_eq!(u32_event, INFO_EVENT.raw());
let vec_data = tm.user_data()[4..].to_vec();
assert_eq!(vec_data, param_data);
}
#[test]
fn test_event_with_generic_store_param_not_propagated() {
// TODO: Test this.
}
#[test]
fn test_event_with_generic_heapless_param() {
// TODO: Test this.
}
}

View File

@ -1,20 +1,21 @@
use crate::events::EventU32;
use crate::pus::event_man::{EventRequest, EventRequestWithToken};
use crate::pus::verification::TcStateToken;
use crate::pus::{PartialPusHandlingError, PusPacketHandlerResult, PusPacketHandlingError};
use crate::pus::{DirectPusPacketHandlerResult, PartialPusHandlingError, PusPacketHandlingError};
use crate::queue::GenericSendError;
use spacepackets::ecss::event::Subservice;
use spacepackets::ecss::PusPacket;
use std::sync::mpsc::Sender;
use super::verification::VerificationReportingProvider;
use super::{
get_current_cds_short_timestamp, EcssTcInMemConverter, EcssTcReceiverCore, EcssTmSenderCore,
PusServiceHelper,
EcssTcInMemConverter, EcssTcReceiver, EcssTmSender, GenericConversionError,
GenericRoutingError, HandlingStatus, PusServiceHelper,
};
pub struct PusService5EventHandler<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
pub struct PusEventServiceHandler<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
@ -24,11 +25,11 @@ pub struct PusService5EventHandler<
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> PusService5EventHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
> PusEventServiceHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
{
pub fn new(
service_helper: PusServiceHelper<
@ -45,91 +46,99 @@ impl<
}
}
pub fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
pub fn poll_and_handle_next_tc<ErrorCb: FnMut(&PartialPusHandlingError)>(
&mut self,
mut error_callback: ErrorCb,
time_stamp: &[u8],
) -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
return Ok(HandlingStatus::Empty.into());
}
let ecss_tc_and_token = possible_packet.unwrap();
let tc = self
.service_helper
.tc_in_mem_converter
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
self.service_helper
.tc_in_mem_converter_mut()
.cache(&ecss_tc_and_token.tc_in_memory)?;
let tc = self.service_helper.tc_in_mem_converter().convert()?;
let subservice = tc.subservice();
let srv = Subservice::try_from(subservice);
if srv.is_err() {
return Ok(PusPacketHandlerResult::CustomSubservice(
return Ok(DirectPusPacketHandlerResult::CustomSubservice(
tc.subservice(),
ecss_tc_and_token.token,
));
}
let handle_enable_disable_request = |enable: bool, stamp: [u8; 7]| {
if tc.user_data().len() < 4 {
return Err(PusPacketHandlingError::NotEnoughAppData {
expected: 4,
found: tc.user_data().len(),
});
}
let user_data = tc.user_data();
let event_u32 = EventU32::from(u32::from_be_bytes(user_data[0..4].try_into().unwrap()));
let start_token = self
.service_helper
.common
.verification_handler
.start_success(ecss_tc_and_token.token, &stamp)
.map_err(|_| PartialPusHandlingError::Verification);
let partial_error = start_token.clone().err();
let mut token: TcStateToken = ecss_tc_and_token.token.into();
if let Ok(start_token) = start_token {
token = start_token.into();
}
let event_req_with_token = if enable {
EventRequestWithToken {
request: EventRequest::Enable(event_u32),
token,
let mut handle_enable_disable_request =
|enable: bool| -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
if tc.user_data().len() < 4 {
return Err(GenericConversionError::NotEnoughAppData {
expected: 4,
found: tc.user_data().len(),
}
.into());
}
} else {
EventRequestWithToken {
request: EventRequest::Disable(event_u32),
token,
let user_data = tc.user_data();
let event_u32 =
EventU32::from(u32::from_be_bytes(user_data[0..4].try_into().unwrap()));
let mut token: TcStateToken = ecss_tc_and_token.token.into();
match self.service_helper.common.verif_reporter.start_success(
&self.service_helper.common.tm_sender,
ecss_tc_and_token.token,
time_stamp,
) {
Ok(start_token) => {
token = start_token.into();
}
Err(e) => {
error_callback(&PartialPusHandlingError::Verification(e));
}
}
let event_req_with_token = if enable {
EventRequestWithToken {
request: EventRequest::Enable(event_u32),
token,
}
} else {
EventRequestWithToken {
request: EventRequest::Disable(event_u32),
token,
}
};
self.event_request_tx
.send(event_req_with_token)
.map_err(|_| {
PusPacketHandlingError::RequestRouting(GenericRoutingError::Send(
GenericSendError::RxDisconnected,
))
})?;
Ok(HandlingStatus::HandledOne.into())
};
self.event_request_tx
.send(event_req_with_token)
.map_err(|_| {
PusPacketHandlingError::Other("Forwarding event request failed".into())
})?;
if let Some(partial_error) = partial_error {
return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess(
partial_error,
));
}
Ok(PusPacketHandlerResult::RequestHandled)
};
let mut partial_error = None;
let time_stamp = get_current_cds_short_timestamp(&mut partial_error);
match srv.unwrap() {
Subservice::TmInfoReport
| Subservice::TmLowSeverityReport
| Subservice::TmMediumSeverityReport
| Subservice::TmHighSeverityReport => {
return Err(PusPacketHandlingError::InvalidSubservice(tc.subservice()))
return Err(PusPacketHandlingError::RequestConversion(
GenericConversionError::WrongService(tc.subservice()),
))
}
Subservice::TcEnableEventGeneration => {
handle_enable_disable_request(true, time_stamp)?;
handle_enable_disable_request(true)?;
}
Subservice::TcDisableEventGeneration => {
handle_enable_disable_request(false, time_stamp)?;
handle_enable_disable_request(false)?;
}
Subservice::TcReportDisabledList | Subservice::TmDisabledEventsReport => {
return Ok(PusPacketHandlerResult::SubserviceNotImplemented(
return Ok(DirectPusPacketHandlerResult::SubserviceNotImplemented(
subservice,
ecss_tc_and_token.token,
));
}
}
Ok(PusPacketHandlerResult::RequestHandled)
Ok(HandlingStatus::HandledOne.into())
}
}
@ -137,60 +146,75 @@ impl<
mod tests {
use delegate::delegate;
use spacepackets::ecss::event::Subservice;
use spacepackets::time::{cds, TimeWriter};
use spacepackets::util::UnsignedEnum;
use spacepackets::{
ecss::{
tc::{PusTcCreator, PusTcSecondaryHeader},
tm::PusTmReader,
},
SequenceFlags, SpHeader,
SpHeader,
};
use std::sync::mpsc::{self, Sender};
use crate::pus::event_man::EventRequest;
use crate::pus::tests::SimplePusPacketHandler;
use crate::pus::test_util::{PusTestHarness, SimplePusPacketHandler, TEST_APID};
use crate::pus::verification::{
RequestId, VerificationReporterWithSharedPoolMpscBoundedSender,
RequestId, VerificationReporter, VerificationReportingProvider,
};
use crate::pus::{MpscTcReceiver, TmInSharedPoolSenderWithBoundedMpsc};
use crate::pus::{GenericConversionError, HandlingStatus, MpscTcReceiver};
use crate::tmtc::PacketSenderWithSharedPool;
use crate::{
events::EventU32,
pus::{
event_man::EventRequestWithToken,
tests::{PusServiceHandlerWithSharedStoreCommon, PusTestHarness, TEST_APID},
tests::PusServiceHandlerWithSharedStoreCommon,
verification::{TcStateAccepted, VerificationToken},
EcssTcInSharedStoreConverter, PusPacketHandlerResult, PusPacketHandlingError,
DirectPusPacketHandlerResult, EcssTcInSharedStoreConverter, PusPacketHandlingError,
},
};
use super::PusService5EventHandler;
use super::PusEventServiceHandler;
const TEST_EVENT_0: EventU32 = EventU32::const_new(crate::events::Severity::INFO, 5, 25);
const TEST_EVENT_0: EventU32 = EventU32::new(crate::events::Severity::Info, 5, 25);
struct Pus5HandlerWithStoreTester {
common: PusServiceHandlerWithSharedStoreCommon,
handler: PusService5EventHandler<
handler: PusEventServiceHandler<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
VerificationReporter,
>,
}
impl Pus5HandlerWithStoreTester {
pub fn new(event_request_tx: Sender<EventRequestWithToken>) -> Self {
let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new();
let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new(0);
Self {
common,
handler: PusService5EventHandler::new(srv_handler, event_request_tx),
handler: PusEventServiceHandler::new(srv_handler, event_request_tx),
}
}
}
impl PusTestHarness for Pus5HandlerWithStoreTester {
fn init_verification(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
let init_token = self.handler.service_helper.verif_reporter_mut().add_tc(tc);
self.handler
.service_helper
.verif_reporter()
.acceptance_success(self.handler.service_helper.tm_sender(), init_token, &[0; 7])
.expect("acceptance success failure")
}
fn send_tc(&self, token: &VerificationToken<TcStateAccepted>, tc: &PusTcCreator) {
self.common
.send_tc(self.handler.service_helper.id(), token, tc);
}
delegate! {
to self.common {
fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted>;
fn read_next_tm(&mut self) -> PusTmReader<'_>;
fn check_no_tm_available(&self) -> bool;
fn check_next_verification_tm(&self, subservice: u8, expected_request_id: RequestId);
@ -200,10 +224,11 @@ mod tests {
}
impl SimplePusPacketHandler for Pus5HandlerWithStoreTester {
delegate! {
to self.handler {
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
}
fn handle_one_tc(
&mut self,
) -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap();
self.handler.poll_and_handle_next_tc(|_| {}, &time_stamp)
}
}
@ -213,15 +238,16 @@ mod tests {
expected_event_req: EventRequest,
event_req_receiver: mpsc::Receiver<EventRequestWithToken>,
) {
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(5, subservice as u8);
let mut app_data = [0; 4];
TEST_EVENT_0
.write_to_be_bytes(&mut app_data)
.expect("writing test event failed");
let ping_tc = PusTcCreator::new(&mut sp_header, sec_header, &app_data, true);
let token = test_harness.send_tc(&ping_tc);
let request_id = token.req_id();
let ping_tc = PusTcCreator::new(sp_header, sec_header, &app_data, true);
let token = test_harness.init_verification(&ping_tc);
test_harness.send_tc(&token, &ping_tc);
let request_id = token.request_id();
test_harness.handle_one_tc().unwrap();
test_harness.check_next_verification_tm(1, request_id);
test_harness.check_next_verification_tm(3, request_id);
@ -264,24 +290,28 @@ mod tests {
let result = test_harness.handle_one_tc();
assert!(result.is_ok());
let result = result.unwrap();
if let PusPacketHandlerResult::Empty = result {
} else {
panic!("unexpected result type {result:?}")
}
assert!(
matches!(
result,
DirectPusPacketHandlerResult::Handled(HandlingStatus::Empty)
),
"unexpected result type {result:?}"
)
}
#[test]
fn test_sending_custom_subservice() {
let (event_request_tx, _) = mpsc::channel();
let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx);
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(5, 200);
let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true);
test_harness.send_tc(&ping_tc);
let ping_tc = PusTcCreator::new_no_app_data(sp_header, sec_header, true);
let token = test_harness.init_verification(&ping_tc);
test_harness.send_tc(&token, &ping_tc);
let result = test_harness.handle_one_tc();
assert!(result.is_ok());
let result = result.unwrap();
if let PusPacketHandlerResult::CustomSubservice(subservice, _) = result {
if let DirectPusPacketHandlerResult::CustomSubservice(subservice, _) = result {
assert_eq!(subservice, 200);
} else {
panic!("unexpected result type {result:?}")
@ -292,15 +322,19 @@ mod tests {
fn test_sending_invalid_app_data() {
let (event_request_tx, _) = mpsc::channel();
let mut test_harness = Pus5HandlerWithStoreTester::new(event_request_tx);
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header =
PusTcSecondaryHeader::new_simple(5, Subservice::TcEnableEventGeneration as u8);
let ping_tc = PusTcCreator::new(&mut sp_header, sec_header, &[0, 1, 2], true);
test_harness.send_tc(&ping_tc);
let ping_tc = PusTcCreator::new(sp_header, sec_header, &[0, 1, 2], true);
let token = test_harness.init_verification(&ping_tc);
test_harness.send_tc(&token, &ping_tc);
let result = test_harness.handle_one_tc();
assert!(result.is_err());
let result = result.unwrap_err();
if let PusPacketHandlingError::NotEnoughAppData { expected, found } = result {
if let PusPacketHandlingError::RequestConversion(
GenericConversionError::NotEnoughAppData { expected, found },
) = result
{
assert_eq!(expected, 4);
assert_eq!(found, 3);
} else {

View File

@ -1,406 +0,0 @@
pub use spacepackets::ecss::hk::*;
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub use std_mod::*;
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub use alloc_mod::*;
use crate::{hk::HkRequest, TargetId};
use super::verification::{TcStateAccepted, VerificationToken};
/// This trait is an abstraction for the routing of PUS service 3 housekeeping requests to a
/// dedicated recipient using the generic [TargetId].
pub trait PusHkRequestRouter {
type Error;
fn route(
&self,
target_id: TargetId,
hk_request: HkRequest,
token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error>;
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub mod alloc_mod {
use spacepackets::ecss::tc::PusTcReader;
use crate::pus::verification::VerificationReportingProvider;
use super::*;
/// This trait is an abstraction for the conversion of a PUS service 8 action telecommand into
/// a [HkRequest].
///
/// Having a dedicated trait for this allows maximum flexiblity and tailoring of the standard.
/// The only requirement is that a valid [TargetId] and a [HkRequest] are returned by the
/// core conversion function.
///
/// The user should take care of performing the error handling as well. Some of the following
/// aspects might be relevant:
///
/// - Checking the validity of the APID, service ID, subservice ID.
/// - Checking the validity of the user data.
///
/// A [VerificationReportingProvider] is passed to the user to also allow handling
/// of the verification process as part of the PUS standard requirements.
pub trait PusHkToRequestConverter {
type Error;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, HkRequest), Self::Error>;
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std_mod {
use crate::pus::{
get_current_cds_short_timestamp, verification::VerificationReportingProvider,
EcssTcInMemConverter, EcssTcReceiverCore, EcssTmSenderCore, GenericRoutingError,
PusPacketHandlerResult, PusPacketHandlingError, PusRoutingErrorHandler, PusServiceHelper,
};
use super::*;
/// This is a generic high-level handler for the PUS service 3 housekeeping service.
///
/// It performs the following handling steps:
///
/// 1. Retrieve the next TC packet from the [PusServiceHelper]. The [EcssTcInMemConverter]
/// allows to configure the used telecommand memory backend.
/// 2. Convert the TC to a targeted action request using the provided
/// [PusHkToRequestConverter]. The generic error type is constrained to the
/// [PusPacketHandlerResult] for the concrete implementation which offers a packet handler.
/// 3. Route the action request using the provided [PusHkRequestRouter]. The generic error
/// type is constrained to the [GenericRoutingError] for the concrete implementation.
/// 4. Handle all routing errors using the provided [PusRoutingErrorHandler]. The generic error
/// type is constrained to the [GenericRoutingError] for the concrete implementation.
pub struct PusService3HkHandler<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusHkToRequestConverter,
RequestRouter: PusHkRequestRouter<Error = RoutingError>,
RoutingErrorHandler: PusRoutingErrorHandler<Error = RoutingError>,
RoutingError = GenericRoutingError,
> {
service_helper:
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
pub request_converter: RequestConverter,
pub request_router: RequestRouter,
pub routing_error_handler: RoutingErrorHandler,
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
RequestConverter: PusHkToRequestConverter<Error = PusPacketHandlingError>,
RequestRouter: PusHkRequestRouter<Error = RoutingError>,
RoutingErrorHandler: PusRoutingErrorHandler<Error = RoutingError>,
RoutingError: Clone,
>
PusService3HkHandler<
TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
RequestConverter,
RequestRouter,
RoutingErrorHandler,
RoutingError,
>
where
PusPacketHandlingError: From<RoutingError>,
{
pub fn new(
service_helper: PusServiceHelper<
TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
>,
request_converter: RequestConverter,
request_router: RequestRouter,
routing_error_handler: RoutingErrorHandler,
) -> Self {
Self {
service_helper,
request_converter,
request_router,
routing_error_handler,
}
}
pub fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
}
let ecss_tc_and_token = possible_packet.unwrap();
let tc = self
.service_helper
.tc_in_mem_converter
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
let mut partial_error = None;
let time_stamp = get_current_cds_short_timestamp(&mut partial_error);
let (target_id, hk_request) = self.request_converter.convert(
ecss_tc_and_token.token,
&tc,
&time_stamp,
&self.service_helper.common.verification_handler,
)?;
if let Err(e) =
self.request_router
.route(target_id, hk_request, ecss_tc_and_token.token)
{
self.routing_error_handler.handle_error(
target_id,
ecss_tc_and_token.token,
&tc,
e.clone(),
&time_stamp,
&self.service_helper.common.verification_handler,
);
return Err(e.into());
}
Ok(PusPacketHandlerResult::RequestHandled)
}
}
}
#[cfg(test)]
mod tests {
use delegate::delegate;
use spacepackets::ecss::hk::Subservice;
use spacepackets::{
ecss::{
tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader},
tm::PusTmReader,
PusPacket,
},
CcsdsPacket, SequenceFlags, SpHeader,
};
use crate::pus::{MpscTcReceiver, TmAsVecSenderWithMpsc};
use crate::{
hk::HkRequest,
pus::{
tests::{
PusServiceHandlerWithVecCommon, PusTestHarness, SimplePusPacketHandler,
TestConverter, TestRouter, TestRoutingErrorHandler, APP_DATA_TOO_SHORT, TEST_APID,
},
verification::{
tests::TestVerificationReporter, FailParams, RequestId, TcStateAccepted,
VerificationReportingProvider, VerificationToken,
},
EcssTcInVecConverter, GenericRoutingError, PusPacketHandlerResult,
PusPacketHandlingError,
},
TargetId,
};
use super::{PusHkRequestRouter, PusHkToRequestConverter, PusService3HkHandler};
impl PusHkRequestRouter for TestRouter<HkRequest> {
type Error = GenericRoutingError;
fn route(
&self,
target_id: TargetId,
hk_request: HkRequest,
_token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error> {
self.routing_requests
.borrow_mut()
.push_back((target_id, hk_request));
self.check_for_injected_error()
}
}
impl PusHkToRequestConverter for TestConverter<3> {
type Error = PusPacketHandlingError;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &impl VerificationReportingProvider,
) -> Result<(TargetId, HkRequest), Self::Error> {
self.conversion_request.push_back(tc.raw_data().to_vec());
self.check_service(tc)?;
let target_id = tc.apid();
if tc.user_data().len() < 4 {
verif_reporter
.start_failure(
token,
FailParams::new(
time_stamp,
&APP_DATA_TOO_SHORT,
(tc.user_data().len() as u32).to_be_bytes().as_ref(),
),
)
.expect("start success failure");
return Err(PusPacketHandlingError::NotEnoughAppData {
expected: 4,
found: tc.user_data().len(),
});
}
if tc.subservice() == Subservice::TcGenerateOneShotHk as u8 {
verif_reporter
.start_success(token, time_stamp)
.expect("start success failure");
return Ok((
target_id.into(),
HkRequest::OneShot(u32::from_be_bytes(
tc.user_data()[0..4].try_into().unwrap(),
)),
));
}
Err(PusPacketHandlingError::InvalidAppData(
"unexpected app data".into(),
))
}
}
struct Pus3HandlerWithVecTester {
common: PusServiceHandlerWithVecCommon<TestVerificationReporter>,
handler: PusService3HkHandler<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
EcssTcInVecConverter,
TestVerificationReporter,
TestConverter<3>,
TestRouter<HkRequest>,
TestRoutingErrorHandler,
>,
}
impl Pus3HandlerWithVecTester {
pub fn new() -> Self {
let (common, srv_handler) =
PusServiceHandlerWithVecCommon::new_with_test_verif_sender();
Self {
common,
handler: PusService3HkHandler::new(
srv_handler,
TestConverter::default(),
TestRouter::default(),
TestRoutingErrorHandler::default(),
),
}
}
delegate! {
to self.handler.request_converter {
pub fn check_next_conversion(&mut self, tc: &PusTcCreator);
}
}
delegate! {
to self.handler.request_router {
pub fn retrieve_next_request(&mut self) -> (TargetId, HkRequest);
}
}
delegate! {
to self.handler.routing_error_handler {
pub fn retrieve_next_error(&mut self) -> (TargetId, GenericRoutingError);
}
}
}
impl PusTestHarness for Pus3HandlerWithVecTester {
delegate! {
to self.common {
fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted>;
fn read_next_tm(&mut self) -> PusTmReader<'_>;
fn check_no_tm_available(&self) -> bool;
fn check_next_verification_tm(
&self,
subservice: u8,
expected_request_id: RequestId,
);
}
}
}
impl SimplePusPacketHandler for Pus3HandlerWithVecTester {
delegate! {
to self.handler {
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
}
}
}
#[test]
fn basic_test() {
let mut hk_handler = Pus3HandlerWithVecTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sec_header = PusTcSecondaryHeader::new_simple(3, Subservice::TcGenerateOneShotHk as u8);
let unique_id: u32 = 1;
let unique_id_raw = unique_id.to_be_bytes();
let tc = PusTcCreator::new(&mut sp_header, sec_header, unique_id_raw.as_ref(), true);
hk_handler.send_tc(&tc);
let result = hk_handler.handle_one_tc();
assert!(result.is_ok());
hk_handler.check_next_conversion(&tc);
let (target_id, hk_request) = hk_handler.retrieve_next_request();
assert_eq!(target_id, TEST_APID.into());
if let HkRequest::OneShot(id) = hk_request {
assert_eq!(id, unique_id);
} else {
panic!("unexpected request");
}
}
#[test]
fn test_routing_error() {
let mut hk_handler = Pus3HandlerWithVecTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sec_header = PusTcSecondaryHeader::new_simple(3, Subservice::TcGenerateOneShotHk as u8);
let unique_id: u32 = 1;
let unique_id_raw = unique_id.to_be_bytes();
let tc = PusTcCreator::new(&mut sp_header, sec_header, unique_id_raw.as_ref(), true);
let error = GenericRoutingError::UnknownTargetId(25);
hk_handler
.handler
.request_router
.inject_routing_error(error);
hk_handler.send_tc(&tc);
let result = hk_handler.handle_one_tc();
assert!(result.is_err());
let check_error = |routing_error: GenericRoutingError| {
if let GenericRoutingError::UnknownTargetId(id) = routing_error {
assert_eq!(id, 25);
} else {
panic!("unexpected error type");
}
};
if let PusPacketHandlingError::RequestRoutingError(routing_error) = result.unwrap_err() {
check_error(routing_error);
} else {
panic!("unexpected error type");
}
hk_handler.check_next_conversion(&tc);
let (target_id, hk_req) = hk_handler.retrieve_next_request();
assert_eq!(target_id, TEST_APID.into());
if let HkRequest::OneShot(unique_id) = hk_req {
assert_eq!(unique_id, 1);
}
let (target_id, found_error) = hk_handler.retrieve_next_error();
assert_eq!(target_id, TEST_APID.into());
check_error(found_error);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,16 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "alloc")]
#[allow(unused_imports)]
pub use alloc_mod::*;
#[cfg(feature = "std")]
#[allow(unused_imports)]
pub use std_mod::*;
pub const MODE_SERVICE_ID: u8 = 200;
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
@ -14,3 +24,132 @@ pub enum Subservice {
TmCantReachMode = 7,
TmWrongModeReply = 8,
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {}
#[cfg(feature = "alloc")]
pub mod std_mod {}
#[cfg(test)]
mod tests {
use std::sync::mpsc;
use crate::{
mode::{
ModeAndSubmode, ModeReply, ModeReplySender, ModeRequest, ModeRequestSender,
ModeRequestorAndHandlerMpsc, ModeRequestorMpsc,
},
request::{GenericMessage, MessageMetadata},
};
const TEST_COMPONENT_ID_0: u64 = 5;
const TEST_COMPONENT_ID_1: u64 = 6;
const TEST_COMPONENT_ID_2: u64 = 7;
#[test]
fn test_simple_mode_requestor() {
let (reply_sender, reply_receiver) = mpsc::channel();
let (request_sender, request_receiver) = mpsc::channel();
let mut mode_requestor = ModeRequestorMpsc::new(TEST_COMPONENT_ID_0, reply_receiver);
mode_requestor.add_message_target(TEST_COMPONENT_ID_1, request_sender);
// Send a request and verify it arrives at the receiver.
let request_id = 2;
let sent_request = ModeRequest::ReadMode;
mode_requestor
.send_mode_request(request_id, TEST_COMPONENT_ID_1, sent_request)
.expect("send failed");
let request = request_receiver.recv().expect("recv failed");
assert_eq!(request.request_id(), 2);
assert_eq!(request.sender_id(), TEST_COMPONENT_ID_0);
assert_eq!(request.message, sent_request);
// Send a reply and verify it arrives at the requestor.
let mode_reply = ModeReply::ModeReply(ModeAndSubmode::new(1, 5));
reply_sender
.send(GenericMessage::new(
MessageMetadata::new(request_id, TEST_COMPONENT_ID_1),
mode_reply,
))
.expect("send failed");
let reply = mode_requestor.try_recv_mode_reply().expect("recv failed");
assert!(reply.is_some());
let reply = reply.unwrap();
assert_eq!(reply.sender_id(), TEST_COMPONENT_ID_1);
assert_eq!(reply.request_id(), 2);
assert_eq!(reply.message, mode_reply);
}
#[test]
fn test_mode_requestor_and_request_handler_request_sending() {
let (_reply_sender_to_connector, reply_receiver_of_connector) = mpsc::channel();
let (_request_sender_to_connector, request_receiver_of_connector) = mpsc::channel();
let (request_sender_to_channel_1, request_receiver_channel_1) = mpsc::channel();
//let (reply_sender_to_channel_2, reply_receiver_channel_2) = mpsc::channel();
let mut mode_connector = ModeRequestorAndHandlerMpsc::new(
TEST_COMPONENT_ID_0,
request_receiver_of_connector,
reply_receiver_of_connector,
);
assert_eq!(
ModeRequestSender::local_channel_id(&mode_connector),
TEST_COMPONENT_ID_0
);
assert_eq!(
ModeReplySender::local_channel_id(&mode_connector),
TEST_COMPONENT_ID_0
);
assert_eq!(
mode_connector.local_channel_id_generic(),
TEST_COMPONENT_ID_0
);
mode_connector.add_request_target(TEST_COMPONENT_ID_1, request_sender_to_channel_1);
// Send a request and verify it arrives at the receiver.
let request_id = 2;
let sent_request = ModeRequest::ReadMode;
mode_connector
.send_mode_request(request_id, TEST_COMPONENT_ID_1, sent_request)
.expect("send failed");
let request = request_receiver_channel_1.recv().expect("recv failed");
assert_eq!(request.request_id(), 2);
assert_eq!(request.sender_id(), TEST_COMPONENT_ID_0);
assert_eq!(request.message, ModeRequest::ReadMode);
}
#[test]
fn test_mode_requestor_and_request_handler_reply_sending() {
let (_reply_sender_to_connector, reply_receiver_of_connector) = mpsc::channel();
let (_request_sender_to_connector, request_receiver_of_connector) = mpsc::channel();
let (reply_sender_to_channel_2, reply_receiver_channel_2) = mpsc::channel();
let mut mode_connector = ModeRequestorAndHandlerMpsc::new(
TEST_COMPONENT_ID_0,
request_receiver_of_connector,
reply_receiver_of_connector,
);
mode_connector.add_reply_target(TEST_COMPONENT_ID_2, reply_sender_to_channel_2);
// Send a reply and verify it arrives at the receiver.
let request_id = 2;
let sent_reply = ModeReply::ModeReply(ModeAndSubmode::new(3, 5));
mode_connector
.send_mode_reply(
MessageMetadata::new(request_id, TEST_COMPONENT_ID_2),
sent_reply,
)
.expect("send failed");
let reply = reply_receiver_channel_2.recv().expect("recv failed");
assert_eq!(reply.request_id(), 2);
assert_eq!(reply.sender_id(), TEST_COMPONENT_ID_0);
assert_eq!(reply.message, sent_reply);
}
#[test]
fn test_mode_reply_handler() {}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,17 @@
use super::scheduler::PusSchedulerProvider;
use super::verification::{
VerificationReporterWithSharedPoolMpscBoundedSender,
VerificationReporterWithSharedPoolMpscSender, VerificationReporterWithVecMpscBoundedSender,
VerificationReporterWithVecMpscSender, VerificationReportingProvider,
};
use super::verification::{VerificationReporter, VerificationReportingProvider};
use super::{
get_current_cds_short_timestamp, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
EcssTcInVecConverter, EcssTcReceiverCore, EcssTmSenderCore, MpscTcReceiver, PusServiceHelper,
TmAsVecSenderWithBoundedMpsc, TmAsVecSenderWithMpsc, TmInSharedPoolSenderWithBoundedMpsc,
TmInSharedPoolSenderWithMpsc,
DirectPusPacketHandlerResult, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
EcssTcInVecConverter, EcssTcReceiver, EcssTmSender, HandlingStatus, MpscTcReceiver,
PartialPusHandlingError, PusServiceHelper,
};
use crate::pool::PoolProvider;
use crate::pus::{PusPacketHandlerResult, PusPacketHandlingError};
use crate::pus::PusPacketHandlingError;
use crate::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use alloc::string::ToString;
use spacepackets::ecss::{scheduling, PusPacket};
use spacepackets::time::cds::TimeProvider;
use spacepackets::time::cds::CdsTime;
use std::sync::mpsc;
/// This is a helper class for [std] environments to handle generic PUS 11 (scheduling service)
/// packets. This handler is able to handle the most important PUS requests for a scheduling
@ -24,9 +21,9 @@ use spacepackets::time::cds::TimeProvider;
/// telecommands inside the scheduler. The user can retrieve the wrapped scheduler via the
/// [Self::scheduler] and [Self::scheduler_mut] function and then use the scheduler API to release
/// telecommands when applicable.
pub struct PusService11SchedHandler<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
pub struct PusSchedServiceHandler<
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
PusScheduler: PusSchedulerProvider,
@ -37,19 +34,13 @@ pub struct PusService11SchedHandler<
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
Scheduler: PusSchedulerProvider,
>
PusService11SchedHandler<
TcReceiver,
TmSender,
TcInMemConverter,
VerificationReporter,
Scheduler,
>
PusSchedServiceHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter, Scheduler>
{
pub fn new(
service_helper: PusServiceHelper<
@ -74,45 +65,52 @@ impl<
&self.scheduler
}
pub fn handle_one_tc(
pub fn poll_and_handle_next_tc<ErrorCb: FnMut(&PartialPusHandlingError)>(
&mut self,
mut error_callback: ErrorCb,
time_stamp: &[u8],
sched_tc_pool: &mut (impl PoolProvider + ?Sized),
) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
) -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
return Ok(HandlingStatus::Empty.into());
}
let ecss_tc_and_token = possible_packet.unwrap();
let tc = self
.service_helper
.tc_in_mem_converter
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
self.service_helper
.tc_in_mem_converter_mut()
.cache(&ecss_tc_and_token.tc_in_memory)?;
let tc = self.service_helper.tc_in_mem_converter().convert()?;
let subservice = PusPacket::subservice(&tc);
let standard_subservice = scheduling::Subservice::try_from(subservice);
if standard_subservice.is_err() {
return Ok(PusPacketHandlerResult::CustomSubservice(
return Ok(DirectPusPacketHandlerResult::CustomSubservice(
subservice,
ecss_tc_and_token.token,
));
}
let mut partial_error = None;
let time_stamp = get_current_cds_short_timestamp(&mut partial_error);
match standard_subservice.unwrap() {
scheduling::Subservice::TcEnableScheduling => {
let start_token = self
.service_helper
.common
.verification_handler
.start_success(ecss_tc_and_token.token, &time_stamp)
.expect("Error sending start success");
let opt_started_token = match self.service_helper.verif_reporter().start_success(
&self.service_helper.common.tm_sender,
ecss_tc_and_token.token,
time_stamp,
) {
Ok(started_token) => Some(started_token),
Err(e) => {
error_callback(&PartialPusHandlingError::Verification(e));
None
}
};
self.scheduler.enable();
if self.scheduler.is_enabled() {
self.service_helper
.common
.verification_handler
.completion_success(start_token, &time_stamp)
.expect("Error sending completion success");
if self.scheduler.is_enabled() && opt_started_token.is_some() {
if let Err(e) = self.service_helper.verif_reporter().completion_success(
&self.service_helper.common.tm_sender,
opt_started_token.unwrap(),
time_stamp,
) {
error_callback(&PartialPusHandlingError::Verification(e));
}
} else {
return Err(PusPacketHandlingError::Other(
"failed to enabled scheduler".to_string(),
@ -120,20 +118,27 @@ impl<
}
}
scheduling::Subservice::TcDisableScheduling => {
let start_token = self
.service_helper
.common
.verification_handler
.start_success(ecss_tc_and_token.token, &time_stamp)
.expect("Error sending start success");
let opt_started_token = match self.service_helper.verif_reporter().start_success(
&self.service_helper.common.tm_sender,
ecss_tc_and_token.token,
time_stamp,
) {
Ok(started_token) => Some(started_token),
Err(e) => {
error_callback(&PartialPusHandlingError::Verification(e));
None
}
};
self.scheduler.disable();
if !self.scheduler.is_enabled() {
self.service_helper
.common
.verification_handler
.completion_success(start_token, &time_stamp)
.expect("Error sending completion success");
if !self.scheduler.is_enabled() && opt_started_token.is_some() {
if let Err(e) = self.service_helper.verif_reporter().completion_success(
&self.service_helper.common.tm_sender,
opt_started_token.unwrap(),
time_stamp,
) {
error_callback(&PartialPusHandlingError::Verification(e));
}
} else {
return Err(PusPacketHandlingError::Other(
"failed to disable scheduler".to_string(),
@ -143,9 +148,12 @@ impl<
scheduling::Subservice::TcResetScheduling => {
let start_token = self
.service_helper
.common
.verification_handler
.start_success(ecss_tc_and_token.token, &time_stamp)
.verif_reporter()
.start_success(
&self.service_helper.common.tm_sender,
ecss_tc_and_token.token,
time_stamp,
)
.expect("Error sending start success");
self.scheduler
@ -153,95 +161,102 @@ impl<
.expect("Error resetting TC Pool");
self.service_helper
.common
.verification_handler
.completion_success(start_token, &time_stamp)
.verif_reporter()
.completion_success(
&self.service_helper.common.tm_sender,
start_token,
time_stamp,
)
.expect("Error sending completion success");
}
scheduling::Subservice::TcInsertActivity => {
let start_token = self
.service_helper
.common
.verification_handler
.start_success(ecss_tc_and_token.token, &time_stamp)
.verif_reporter
.start_success(
&self.service_helper.common.tm_sender,
ecss_tc_and_token.token,
time_stamp,
)
.expect("error sending start success");
// let mut pool = self.sched_tc_pool.write().expect("locking pool failed");
self.scheduler
.insert_wrapped_tc::<TimeProvider>(&tc, sched_tc_pool)
.insert_wrapped_tc::<CdsTime>(&tc, sched_tc_pool)
.expect("insertion of activity into pool failed");
self.service_helper
.common
.verification_handler
.completion_success(start_token, &time_stamp)
.verif_reporter()
.completion_success(
&self.service_helper.common.tm_sender,
start_token,
time_stamp,
)
.expect("sending completion success failed");
}
_ => {
// Treat unhandled standard subservices as custom subservices for now.
return Ok(PusPacketHandlerResult::CustomSubservice(
return Ok(DirectPusPacketHandlerResult::CustomSubservice(
subservice,
ecss_tc_and_token.token,
));
}
}
if let Some(partial_error) = partial_error {
return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess(
partial_error,
));
}
Ok(PusPacketHandlerResult::RequestHandled)
Ok(HandlingStatus::HandledOne.into())
}
}
/// Helper type definition for a PUS 11 handler with a dynamic TMTC memory backend and regular
/// mpsc queues.
pub type PusService11SchedHandlerDynWithMpsc<PusScheduler> = PusService11SchedHandler<
pub type PusService11SchedHandlerDynWithMpsc<PusScheduler> = PusSchedServiceHandler<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
mpsc::Sender<PacketAsVec>,
EcssTcInVecConverter,
VerificationReporterWithVecMpscSender,
VerificationReporter,
PusScheduler,
>;
/// Helper type definition for a PUS 11 handler with a dynamic TMTC memory backend and bounded MPSC
/// queues.
pub type PusService11SchedHandlerDynWithBoundedMpsc<PusScheduler> = PusService11SchedHandler<
pub type PusService11SchedHandlerDynWithBoundedMpsc<PusScheduler> = PusSchedServiceHandler<
MpscTcReceiver,
TmAsVecSenderWithBoundedMpsc,
mpsc::SyncSender<PacketAsVec>,
EcssTcInVecConverter,
VerificationReporterWithVecMpscBoundedSender,
VerificationReporter,
PusScheduler,
>;
/// Helper type definition for a PUS 11 handler with a shared store TMTC memory backend and regular
/// mpsc queues.
pub type PusService11SchedHandlerStaticWithMpsc<PusScheduler> = PusService11SchedHandler<
pub type PusService11SchedHandlerStaticWithMpsc<PusScheduler> = PusSchedServiceHandler<
MpscTcReceiver,
TmInSharedPoolSenderWithMpsc,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscSender,
VerificationReporter,
PusScheduler,
>;
/// Helper type definition for a PUS 11 handler with a shared store TMTC memory backend and bounded
/// mpsc queues.
pub type PusService11SchedHandlerStaticWithBoundedMpsc<PusScheduler> = PusService11SchedHandler<
pub type PusService11SchedHandlerStaticWithBoundedMpsc<PusScheduler> = PusSchedServiceHandler<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
VerificationReporter,
PusScheduler,
>;
#[cfg(test)]
mod tests {
use crate::pool::{StaticMemoryPool, StaticPoolConfig};
use crate::pus::tests::TEST_APID;
use crate::pus::verification::VerificationReporterWithSharedPoolMpscBoundedSender;
use crate::pus::test_util::{PusTestHarness, TEST_APID};
use crate::pus::verification::{VerificationReporter, VerificationReportingProvider};
use crate::pus::{
scheduler::{self, PusSchedulerProvider, TcInfo},
tests::{PusServiceHandlerWithSharedStoreCommon, PusTestHarness},
tests::PusServiceHandlerWithSharedStoreCommon,
verification::{RequestId, TcStateAccepted, VerificationToken},
EcssTcInSharedStoreConverter,
};
use crate::pus::{MpscTcReceiver, TmInSharedPoolSenderWithBoundedMpsc};
use crate::pus::{DirectPusPacketHandlerResult, MpscTcReceiver, PusPacketHandlingError};
use crate::tmtc::PacketSenderWithSharedPool;
use alloc::collections::VecDeque;
use delegate::delegate;
use spacepackets::ecss::scheduling::Subservice;
@ -254,15 +269,15 @@ mod tests {
time::cds,
};
use super::PusService11SchedHandler;
use super::PusSchedServiceHandler;
struct Pus11HandlerWithStoreTester {
common: PusServiceHandlerWithSharedStoreCommon,
handler: PusService11SchedHandler<
handler: PusSchedServiceHandler<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
VerificationReporter,
TestScheduler,
>,
sched_tc_pool: StaticMemoryPool,
@ -273,19 +288,40 @@ mod tests {
let test_scheduler = TestScheduler::default();
let pool_cfg = StaticPoolConfig::new(alloc::vec![(16, 16), (8, 32), (4, 64)], false);
let sched_tc_pool = StaticMemoryPool::new(pool_cfg.clone());
let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new();
let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new(0);
Self {
common,
handler: PusService11SchedHandler::new(srv_handler, test_scheduler),
handler: PusSchedServiceHandler::new(srv_handler, test_scheduler),
sched_tc_pool,
}
}
pub fn handle_one_tc(
&mut self,
) -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap();
self.handler
.poll_and_handle_next_tc(|_| {}, &time_stamp, &mut self.sched_tc_pool)
}
}
impl PusTestHarness for Pus11HandlerWithStoreTester {
fn init_verification(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
let init_token = self.handler.service_helper.verif_reporter_mut().add_tc(tc);
self.handler
.service_helper
.verif_reporter()
.acceptance_success(self.handler.service_helper.tm_sender(), init_token, &[0; 7])
.expect("acceptance success failure")
}
fn send_tc(&self, token: &VerificationToken<TcStateAccepted>, tc: &PusTcCreator) {
self.common
.send_tc(self.handler.service_helper.id(), token, tc);
}
delegate! {
to self.common {
fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted>;
fn read_next_tm(&mut self) -> PusTmReader<'_>;
fn check_no_tm_available(&self) -> bool;
fn check_next_verification_tm(&self, subservice: u8, expected_request_id: RequestId);
@ -303,12 +339,12 @@ mod tests {
}
impl PusSchedulerProvider for TestScheduler {
type TimeProvider = cds::TimeProvider;
type TimeProvider = cds::CdsTime;
fn reset(
&mut self,
_store: &mut (impl crate::pool::PoolProvider + ?Sized),
) -> Result<(), crate::pool::StoreError> {
) -> Result<(), crate::pool::PoolError> {
self.reset_count += 1;
Ok(())
}
@ -329,7 +365,7 @@ mod tests {
fn insert_unwrapped_and_stored_tc(
&mut self,
_time_stamp: spacepackets::time::UnixTimestamp,
_time_stamp: spacepackets::time::UnixTime,
info: crate::pus::scheduler::TcInfo,
) -> Result<(), crate::pus::scheduler::ScheduleError> {
self.inserted_tcs.push_back(info);
@ -341,15 +377,17 @@ mod tests {
test_harness: &mut Pus11HandlerWithStoreTester,
subservice: Subservice,
) {
let mut reply_header = SpHeader::tm_unseg(TEST_APID, 0, 0).unwrap();
let reply_header = SpHeader::new_for_unseg_tm(TEST_APID, 0, 0);
let tc_header = PusTcSecondaryHeader::new_simple(11, subservice as u8);
let enable_scheduling = PusTcCreator::new(&mut reply_header, tc_header, &[0; 7], true);
let token = test_harness.send_tc(&enable_scheduling);
let enable_scheduling = PusTcCreator::new(reply_header, tc_header, &[0; 7], true);
let token = test_harness.init_verification(&enable_scheduling);
test_harness.send_tc(&token, &enable_scheduling);
let request_id = token.req_id();
let request_id = token.request_id();
let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap();
test_harness
.handler
.handle_one_tc(&mut test_harness.sched_tc_pool)
.poll_and_handle_next_tc(|_| {}, &time_stamp, &mut test_harness.sched_tc_pool)
.unwrap();
test_harness.check_next_verification_tm(1, request_id);
test_harness.check_next_verification_tm(3, request_id);
@ -386,31 +424,29 @@ mod tests {
#[test]
fn test_insert_activity_tc() {
let mut test_harness = Pus11HandlerWithStoreTester::new();
let mut reply_header = SpHeader::tm_unseg(TEST_APID, 0, 0).unwrap();
let mut reply_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let mut sec_header = PusTcSecondaryHeader::new_simple(17, 1);
let ping_tc = PusTcCreator::new(&mut reply_header, sec_header, &[], true);
let ping_tc = PusTcCreator::new(reply_header, sec_header, &[], true);
let req_id_ping_tc = scheduler::RequestId::from_tc(&ping_tc);
let stamper = cds::TimeProvider::from_now_with_u16_days().expect("time provider failed");
let stamper = cds::CdsTime::now_with_u16_days().expect("time provider failed");
let mut sched_app_data: [u8; 64] = [0; 64];
let mut written_len = stamper.write_to_bytes(&mut sched_app_data).unwrap();
let ping_raw = ping_tc.to_vec().expect("generating raw tc failed");
sched_app_data[written_len..written_len + ping_raw.len()].copy_from_slice(&ping_raw);
written_len += ping_raw.len();
reply_header = SpHeader::tm_unseg(TEST_APID, 1, 0).unwrap();
reply_header = SpHeader::new_for_unseg_tc(TEST_APID, 1, 0);
sec_header = PusTcSecondaryHeader::new_simple(11, Subservice::TcInsertActivity as u8);
let enable_scheduling = PusTcCreator::new(
&mut reply_header,
reply_header,
sec_header,
&sched_app_data[..written_len],
true,
);
let token = test_harness.send_tc(&enable_scheduling);
let token = test_harness.init_verification(&enable_scheduling);
test_harness.send_tc(&token, &enable_scheduling);
let request_id = token.req_id();
test_harness
.handler
.handle_one_tc(&mut test_harness.sched_tc_pool)
.unwrap();
let request_id = token.request_id();
test_harness.handle_one_tc().unwrap();
test_harness.check_next_verification_tm(1, request_id);
test_harness.check_next_verification_tm(3, request_id);
test_harness.check_next_verification_tm(7, request_id);

View File

@ -1,27 +1,23 @@
use crate::pus::{
PartialPusHandlingError, PusPacketHandlerResult, PusPacketHandlingError, PusTmWrapper,
DirectPusPacketHandlerResult, PartialPusHandlingError, PusPacketHandlingError, PusTmVariant,
};
use crate::tmtc::{PacketAsVec, PacketSenderWithSharedPool};
use spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use spacepackets::ecss::PusPacket;
use spacepackets::SpHeader;
use std::sync::mpsc;
use super::verification::{
VerificationReporterWithSharedPoolMpscBoundedSender,
VerificationReporterWithSharedPoolMpscSender, VerificationReporterWithVecMpscBoundedSender,
VerificationReporterWithVecMpscSender, VerificationReportingProvider,
};
use super::verification::{VerificationReporter, VerificationReportingProvider};
use super::{
get_current_cds_short_timestamp, EcssTcInMemConverter, EcssTcInSharedStoreConverter,
EcssTcInVecConverter, EcssTcReceiverCore, EcssTmSenderCore, MpscTcReceiver, PusServiceHelper,
TmAsVecSenderWithBoundedMpsc, TmAsVecSenderWithMpsc, TmInSharedPoolSenderWithBoundedMpsc,
TmInSharedPoolSenderWithMpsc,
EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcReceiver,
EcssTmSender, GenericConversionError, HandlingStatus, MpscTcReceiver, PusServiceHelper,
};
/// This is a helper class for [std] environments to handle generic PUS 17 (test service) packets.
/// This handler only processes ping requests and generates a ping reply for them accordingly.
pub struct PusService17TestHandler<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> {
@ -30,8 +26,8 @@ pub struct PusService17TestHandler<
}
impl<
TcReceiver: EcssTcReceiverCore,
TmSender: EcssTmSenderCore,
TcReceiver: EcssTcReceiver,
TmSender: EcssTmSender,
TcInMemConverter: EcssTcInMemConverter,
VerificationReporter: VerificationReportingProvider,
> PusService17TestHandler<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>
@ -47,72 +43,66 @@ impl<
Self { service_helper }
}
pub fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
pub fn poll_and_handle_next_tc<ErrorCb: FnMut(&PartialPusHandlingError)>(
&mut self,
mut error_callback: ErrorCb,
time_stamp: &[u8],
) -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
return Ok(HandlingStatus::Empty.into());
}
let ecss_tc_and_token = possible_packet.unwrap();
let tc = self
.service_helper
.tc_in_mem_converter
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
self.service_helper
.tc_in_mem_converter_mut()
.cache(&ecss_tc_and_token.tc_in_memory)?;
let tc = self.service_helper.tc_in_mem_converter().convert()?;
if tc.service() != 17 {
return Err(PusPacketHandlingError::WrongService(tc.service()));
return Err(GenericConversionError::WrongService(tc.service()).into());
}
if tc.subservice() == 1 {
let mut partial_error = None;
let time_stamp = get_current_cds_short_timestamp(&mut partial_error);
let result = self
.service_helper
.common
.verification_handler
.start_success(ecss_tc_and_token.token, &time_stamp)
.map_err(|_| PartialPusHandlingError::Verification);
let start_token = if let Ok(result) = result {
Some(result)
} else {
partial_error = Some(result.unwrap_err());
None
let opt_started_token = match self.service_helper.verif_reporter().start_success(
&self.service_helper.common.tm_sender,
ecss_tc_and_token.token,
time_stamp,
) {
Ok(token) => Some(token),
Err(e) => {
error_callback(&PartialPusHandlingError::Verification(e));
None
}
};
// Sequence count will be handled centrally in TM funnel.
let mut reply_header =
SpHeader::tm_unseg(self.service_helper.common.tm_apid, 0, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &time_stamp);
let ping_reply = PusTmCreator::new(&mut reply_header, tc_header, &[], true);
let result = self
// It is assumed that the verification reporter was built with a valid APID, so we use
// the unchecked API here.
let reply_header =
SpHeader::new_for_unseg_tm(self.service_helper.verif_reporter().apid(), 0, 0);
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, time_stamp);
let ping_reply = PusTmCreator::new(reply_header, tc_header, &[], true);
if let Err(e) = self
.service_helper
.common
.tm_sender
.send_tm(PusTmWrapper::Direct(ping_reply))
.map_err(PartialPusHandlingError::TmSend);
if let Err(err) = result {
partial_error = Some(err);
.send_tm(self.service_helper.id(), PusTmVariant::Direct(ping_reply))
{
error_callback(&PartialPusHandlingError::TmSend(e));
}
if let Some(start_token) = start_token {
if self
.service_helper
.common
.verification_handler
.completion_success(start_token, &time_stamp)
.is_err()
{
partial_error = Some(PartialPusHandlingError::Verification)
if let Some(start_token) = opt_started_token {
if let Err(e) = self.service_helper.verif_reporter().completion_success(
&self.service_helper.common.tm_sender,
start_token,
time_stamp,
) {
error_callback(&PartialPusHandlingError::Verification(e));
}
}
if let Some(partial_error) = partial_error {
return Ok(PusPacketHandlerResult::RequestHandledPartialSuccess(
partial_error,
));
};
} else {
return Ok(PusPacketHandlerResult::CustomSubservice(
return Ok(DirectPusPacketHandlerResult::CustomSubservice(
tc.subservice(),
ecss_tc_and_token.token,
));
}
Ok(PusPacketHandlerResult::RequestHandled)
Ok(HandlingStatus::HandledOne.into())
}
}
@ -120,55 +110,50 @@ impl<
/// mpsc queues.
pub type PusService17TestHandlerDynWithMpsc = PusService17TestHandler<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
mpsc::Sender<PacketAsVec>,
EcssTcInVecConverter,
VerificationReporterWithVecMpscSender,
VerificationReporter,
>;
/// Helper type definition for a PUS 17 handler with a dynamic TMTC memory backend and bounded MPSC
/// queues.
pub type PusService17TestHandlerDynWithBoundedMpsc = PusService17TestHandler<
MpscTcReceiver,
TmAsVecSenderWithBoundedMpsc,
mpsc::SyncSender<PacketAsVec>,
EcssTcInVecConverter,
VerificationReporterWithVecMpscBoundedSender,
>;
/// Helper type definition for a PUS 17 handler with a shared store TMTC memory backend and regular
/// mpsc queues.
pub type PusService17TestHandlerStaticWithMpsc = PusService17TestHandler<
MpscTcReceiver,
TmInSharedPoolSenderWithMpsc,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscSender,
VerificationReporter,
>;
/// Helper type definition for a PUS 17 handler with a shared store TMTC memory backend and bounded
/// mpsc queues.
pub type PusService17TestHandlerStaticWithBoundedMpsc = PusService17TestHandler<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
VerificationReporter,
>;
#[cfg(test)]
mod tests {
use crate::pus::test_util::{PusTestHarness, SimplePusPacketHandler, TEST_APID};
use crate::pus::tests::{
PusServiceHandlerWithSharedStoreCommon, PusServiceHandlerWithVecCommon, PusTestHarness,
SimplePusPacketHandler, TEST_APID,
PusServiceHandlerWithSharedStoreCommon, PusServiceHandlerWithVecCommon,
};
use crate::pus::verification::std_mod::{
VerificationReporterWithSharedPoolMpscBoundedSender, VerificationReporterWithVecMpscSender,
use crate::pus::verification::{
RequestId, VerificationReporter, VerificationReportingProvider,
};
use crate::pus::verification::RequestId;
use crate::pus::verification::{TcStateAccepted, VerificationToken};
use crate::pus::{
EcssTcInSharedStoreConverter, EcssTcInVecConverter, MpscTcReceiver, PusPacketHandlerResult,
PusPacketHandlingError, TmAsVecSenderWithMpsc, TmInSharedPoolSenderWithBoundedMpsc,
DirectPusPacketHandlerResult, EcssTcInSharedStoreConverter, EcssTcInVecConverter,
GenericConversionError, HandlingStatus, MpscTcReceiver, MpscTmAsVecSender,
PartialPusHandlingError, PusPacketHandlingError,
};
use crate::tmtc::PacketSenderWithSharedPool;
use crate::ComponentId;
use delegate::delegate;
use spacepackets::ecss::tc::{PusTcCreator, PusTcSecondaryHeader};
use spacepackets::ecss::tm::PusTmReader;
use spacepackets::ecss::PusPacket;
use spacepackets::{SequenceFlags, SpHeader};
use spacepackets::time::{cds, TimeWriter};
use spacepackets::SpHeader;
use super::PusService17TestHandler;
@ -176,15 +161,15 @@ mod tests {
common: PusServiceHandlerWithSharedStoreCommon,
handler: PusService17TestHandler<
MpscTcReceiver,
TmInSharedPoolSenderWithBoundedMpsc,
PacketSenderWithSharedPool,
EcssTcInSharedStoreConverter,
VerificationReporterWithSharedPoolMpscBoundedSender,
VerificationReporter,
>,
}
impl Pus17HandlerWithStoreTester {
pub fn new() -> Self {
let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new();
pub fn new(id: ComponentId) -> Self {
let (common, srv_handler) = PusServiceHandlerWithSharedStoreCommon::new(id);
let pus_17_handler = PusService17TestHandler::new(srv_handler);
Self {
common,
@ -194,9 +179,22 @@ mod tests {
}
impl PusTestHarness for Pus17HandlerWithStoreTester {
fn init_verification(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
let init_token = self.handler.service_helper.verif_reporter_mut().add_tc(tc);
self.handler
.service_helper
.verif_reporter()
.acceptance_success(self.handler.service_helper.tm_sender(), init_token, &[0; 7])
.expect("acceptance success failure")
}
fn send_tc(&self, token: &VerificationToken<TcStateAccepted>, tc: &PusTcCreator) {
self.common
.send_tc(self.handler.service_helper.id(), token, tc);
}
delegate! {
to self.common {
fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted>;
fn read_next_tm(&mut self) -> PusTmReader<'_>;
fn check_no_tm_available(&self) -> bool;
fn check_next_verification_tm(
@ -208,27 +206,29 @@ mod tests {
}
}
impl SimplePusPacketHandler for Pus17HandlerWithStoreTester {
delegate! {
to self.handler {
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
}
fn handle_one_tc(
&mut self,
) -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap();
self.handler
.poll_and_handle_next_tc(|_partial_error: &PartialPusHandlingError| {}, &time_stamp)
}
}
struct Pus17HandlerWithVecTester {
common: PusServiceHandlerWithVecCommon<VerificationReporterWithVecMpscSender>,
common: PusServiceHandlerWithVecCommon,
handler: PusService17TestHandler<
MpscTcReceiver,
TmAsVecSenderWithMpsc,
MpscTmAsVecSender,
EcssTcInVecConverter,
VerificationReporterWithVecMpscSender,
VerificationReporter,
>,
}
impl Pus17HandlerWithVecTester {
pub fn new() -> Self {
pub fn new(id: ComponentId) -> Self {
let (common, srv_handler) =
PusServiceHandlerWithVecCommon::new_with_standard_verif_reporter();
PusServiceHandlerWithVecCommon::new_with_standard_verif_reporter(id);
Self {
common,
handler: PusService17TestHandler::new(srv_handler),
@ -237,9 +237,22 @@ mod tests {
}
impl PusTestHarness for Pus17HandlerWithVecTester {
fn init_verification(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted> {
let init_token = self.handler.service_helper.verif_reporter_mut().add_tc(tc);
self.handler
.service_helper
.verif_reporter()
.acceptance_success(self.handler.service_helper.tm_sender(), init_token, &[0; 7])
.expect("acceptance success failure")
}
fn send_tc(&self, token: &VerificationToken<TcStateAccepted>, tc: &PusTcCreator) {
self.common
.send_tc(self.handler.service_helper.id(), token, tc);
}
delegate! {
to self.common {
fn send_tc(&mut self, tc: &PusTcCreator) -> VerificationToken<TcStateAccepted>;
fn read_next_tm(&mut self) -> PusTmReader<'_>;
fn check_no_tm_available(&self) -> bool;
fn check_next_verification_tm(
@ -251,20 +264,23 @@ mod tests {
}
}
impl SimplePusPacketHandler for Pus17HandlerWithVecTester {
delegate! {
to self.handler {
fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError>;
}
fn handle_one_tc(
&mut self,
) -> Result<DirectPusPacketHandlerResult, PusPacketHandlingError> {
let time_stamp = cds::CdsTime::new_with_u16_days(0, 0).to_vec().unwrap();
self.handler
.poll_and_handle_next_tc(|_partial_error: &PartialPusHandlingError| {}, &time_stamp)
}
}
fn ping_test(test_harness: &mut (impl PusTestHarness + SimplePusPacketHandler)) {
// Create a ping TC, verify acceptance.
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(17, 1);
let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true);
let token = test_harness.send_tc(&ping_tc);
let request_id = token.req_id();
let ping_tc = PusTcCreator::new_no_app_data(sp_header, sec_header, true);
let token = test_harness.init_verification(&ping_tc);
test_harness.send_tc(&token, &ping_tc);
let request_id = token.request_id();
let result = test_harness.handle_one_tc();
assert!(result.is_ok());
// We should see 4 replies in the TM queue now: Acceptance TM, Start TM, ping reply and
@ -288,39 +304,44 @@ mod tests {
#[test]
fn test_basic_ping_processing_using_store() {
let mut test_harness = Pus17HandlerWithStoreTester::new();
let mut test_harness = Pus17HandlerWithStoreTester::new(0);
ping_test(&mut test_harness);
}
#[test]
fn test_basic_ping_processing_using_vec() {
let mut test_harness = Pus17HandlerWithVecTester::new();
let mut test_harness = Pus17HandlerWithVecTester::new(0);
ping_test(&mut test_harness);
}
#[test]
fn test_empty_tc_queue() {
let mut test_harness = Pus17HandlerWithStoreTester::new();
let mut test_harness = Pus17HandlerWithStoreTester::new(0);
let result = test_harness.handle_one_tc();
assert!(result.is_ok());
let result = result.unwrap();
if let PusPacketHandlerResult::Empty = result {
} else {
panic!("unexpected result type {result:?}")
match result.unwrap() {
DirectPusPacketHandlerResult::Handled(handled) => {
assert_eq!(handled, HandlingStatus::Empty);
}
_ => panic!("unexpected result"),
}
}
#[test]
fn test_sending_unsupported_service() {
let mut test_harness = Pus17HandlerWithStoreTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let mut test_harness = Pus17HandlerWithStoreTester::new(0);
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(3, 1);
let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true);
test_harness.send_tc(&ping_tc);
let ping_tc = PusTcCreator::new_no_app_data(sp_header, sec_header, true);
let token = test_harness.init_verification(&ping_tc);
test_harness.send_tc(&token, &ping_tc);
let result = test_harness.handle_one_tc();
assert!(result.is_err());
let error = result.unwrap_err();
if let PusPacketHandlingError::WrongService(num) = error {
if let PusPacketHandlingError::RequestConversion(GenericConversionError::WrongService(
num,
)) = error
{
assert_eq!(num, 3);
} else {
panic!("unexpected error type {error}")
@ -329,15 +350,16 @@ mod tests {
#[test]
fn test_sending_custom_subservice() {
let mut test_harness = Pus17HandlerWithStoreTester::new();
let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap();
let mut test_harness = Pus17HandlerWithStoreTester::new(0);
let sp_header = SpHeader::new_for_unseg_tc(TEST_APID, 0, 0);
let sec_header = PusTcSecondaryHeader::new_simple(17, 200);
let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true);
test_harness.send_tc(&ping_tc);
let ping_tc = PusTcCreator::new_no_app_data(sp_header, sec_header, true);
let token = test_harness.init_verification(&ping_tc);
test_harness.send_tc(&token, &ping_tc);
let result = test_harness.handle_one_tc();
assert!(result.is_ok());
let result = result.unwrap();
if let PusPacketHandlerResult::CustomSubservice(subservice, _) = result {
if let DirectPusPacketHandlerResult::CustomSubservice(subservice, _) = result {
assert_eq!(subservice, 200);
} else {
panic!("unexpected result type {result:?}")

File diff suppressed because it is too large Load Diff

View File

@ -4,11 +4,17 @@ use std::error::Error;
#[cfg(feature = "std")]
use std::sync::mpsc;
use crate::ComponentId;
/// Generic channel ID type.
pub type ChannelId = u32;
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum GenericSendError {
RxDisconnected,
QueueFull(Option<u32>),
TargetDoesNotExist(ComponentId),
}
impl Display for GenericSendError {
@ -20,6 +26,9 @@ impl Display for GenericSendError {
GenericSendError::QueueFull(max_cap) => {
write!(f, "queue with max capacity of {max_cap:?} is full")
}
GenericSendError::TargetDoesNotExist(target) => {
write!(f, "target queue with ID {target} does not exist")
}
}
}
}
@ -28,17 +37,17 @@ impl Display for GenericSendError {
impl Error for GenericSendError {}
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone)]
pub enum GenericRecvError {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum GenericReceiveError {
Empty,
TxDisconnected,
TxDisconnected(Option<ComponentId>),
}
impl Display for GenericRecvError {
impl Display for GenericReceiveError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::TxDisconnected => {
write!(f, "tx side has disconnected")
Self::TxDisconnected(channel_id) => {
write!(f, "tx side with id {channel_id:?} has disconnected")
}
Self::Empty => {
write!(f, "nothing to receive")
@ -48,7 +57,43 @@ impl Display for GenericRecvError {
}
#[cfg(feature = "std")]
impl Error for GenericRecvError {}
impl Error for GenericReceiveError {}
#[derive(Debug, Clone)]
pub enum GenericTargetedMessagingError {
Send(GenericSendError),
Receive(GenericReceiveError),
}
impl From<GenericSendError> for GenericTargetedMessagingError {
fn from(value: GenericSendError) -> Self {
Self::Send(value)
}
}
impl From<GenericReceiveError> for GenericTargetedMessagingError {
fn from(value: GenericReceiveError) -> Self {
Self::Receive(value)
}
}
impl Display for GenericTargetedMessagingError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::Send(err) => write!(f, "generic targeted messaging error: {}", err),
Self::Receive(err) => write!(f, "generic targeted messaging error: {}", err),
}
}
}
#[cfg(feature = "std")]
impl Error for GenericTargetedMessagingError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
GenericTargetedMessagingError::Send(send) => Some(send),
GenericTargetedMessagingError::Receive(receive) => Some(receive),
}
}
}
#[cfg(feature = "std")]
impl<T> From<mpsc::SendError<T>> for GenericSendError {

View File

@ -1,110 +1,584 @@
use core::fmt;
use core::{fmt, marker::PhantomData};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
#[cfg(feature = "std")]
use std::error::Error;
pub use std_mod::*;
use spacepackets::{
ecss::{tc::IsPusTelecommand, PusPacket},
ByteConversionError, CcsdsPacket,
ByteConversionError,
};
use crate::TargetId;
use crate::{queue::GenericTargetedMessagingError, ComponentId};
/// Generic request ID type. Requests can be associated with an ID to have a unique identifier
/// for them. This can be useful for tasks like tracking their progress.
pub type RequestId = u32;
/// CCSDS APID type definition. Please note that the APID is a 14 bit value.
pub type Apid = u16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TargetIdCreationError {
ByteConversion(ByteConversionError),
NotEnoughAppData(usize),
}
impl From<ByteConversionError> for TargetIdCreationError {
fn from(e: ByteConversionError) -> Self {
Self::ByteConversion(e)
}
}
impl fmt::Display for TargetIdCreationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::ByteConversion(e) => write!(f, "target ID creation: {}", e),
Self::NotEnoughAppData(len) => {
write!(f, "not enough app data to generate target ID: {}", len)
}
}
}
}
#[cfg(feature = "std")]
impl Error for TargetIdCreationError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::ByteConversion(e) = self {
return Some(e);
}
None
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct TargetAndApidId {
pub struct UniqueApidTargetId {
pub apid: Apid,
pub target: u32,
pub unique_id: u32,
}
impl TargetAndApidId {
pub fn new(apid: Apid, target: u32) -> Self {
Self { apid, target }
impl UniqueApidTargetId {
pub const fn new(apid: Apid, target: u32) -> Self {
Self {
apid,
unique_id: target,
}
}
pub fn apid(&self) -> Apid {
self.apid
pub fn raw(&self) -> ComponentId {
((self.apid as u64) << 32) | (self.unique_id as u64)
}
pub fn target(&self) -> u32 {
self.target
}
pub fn raw(&self) -> TargetId {
((self.apid as u64) << 32) | (self.target as u64)
}
pub fn target_id(&self) -> TargetId {
pub fn id(&self) -> ComponentId {
self.raw()
}
/// This function attempts to build the ID from a PUS telecommand by extracting the APID
/// and the first four bytes of the application data field as the target field.
pub fn from_pus_tc(
tc: &(impl CcsdsPacket + PusPacket + IsPusTelecommand),
) -> Result<Self, TargetIdCreationError> {
tc: &(impl PusPacket + IsPusTelecommand),
) -> Result<Self, ByteConversionError> {
if tc.user_data().len() < 4 {
return Err(ByteConversionError::FromSliceTooSmall {
found: tc.user_data().len(),
expected: 8,
}
.into());
expected: 4,
});
}
Ok(Self {
apid: tc.apid(),
target: u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap()),
})
Ok(Self::new(
tc.apid(),
u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap()),
))
}
}
impl From<u64> for TargetAndApidId {
impl From<u64> for UniqueApidTargetId {
fn from(raw: u64) -> Self {
Self {
apid: (raw >> 32) as u16,
target: raw as u32,
unique_id: raw as u32,
}
}
}
impl From<TargetAndApidId> for u64 {
fn from(target_and_apid_id: TargetAndApidId) -> Self {
impl From<UniqueApidTargetId> for u64 {
fn from(target_and_apid_id: UniqueApidTargetId) -> Self {
target_and_apid_id.raw()
}
}
impl fmt::Display for TargetAndApidId {
impl fmt::Display for UniqueApidTargetId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}, {}", self.apid, self.target)
write!(
f,
"Target and APID ID with APID {:#03x} and target {}",
self.apid, self.unique_id
)
}
}
/// This contains metadata information which might be useful when used together with a
/// generic message tpye.
///
/// This could for example be used to build request/reply patterns or state tracking for request.
#[derive(Debug, Copy, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MessageMetadata {
request_id: RequestId,
sender_id: ComponentId,
}
impl MessageMetadata {
pub const fn new(request_id: RequestId, sender_id: ComponentId) -> Self {
Self {
request_id,
sender_id,
}
}
pub fn request_id(&self) -> RequestId {
self.request_id
}
pub fn sender_id(&self) -> ComponentId {
self.sender_id
}
}
/// Generic message type which adds [metadata][MessageMetadata] to a generic message typ.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GenericMessage<Message> {
pub requestor_info: MessageMetadata,
pub message: Message,
}
impl<Message> GenericMessage<Message> {
pub fn new(requestor_info: MessageMetadata, message: Message) -> Self {
Self {
requestor_info,
message,
}
}
delegate::delegate! {
to self.requestor_info {
pub fn request_id(&self) -> RequestId;
pub fn sender_id(&self) -> ComponentId;
}
}
}
/// Generic trait for objects which can send targeted messages.
pub trait MessageSender<MSG>: Send {
fn send(&self, message: GenericMessage<MSG>) -> Result<(), GenericTargetedMessagingError>;
}
// Generic trait for objects which can receive targeted messages.
pub trait MessageReceiver<MSG> {
fn try_recv(&self) -> Result<Option<GenericMessage<MSG>>, GenericTargetedMessagingError>;
}
pub struct MessageWithSenderIdReceiver<MSG, R: MessageReceiver<MSG>>(pub R, PhantomData<MSG>);
impl<MSG, R: MessageReceiver<MSG>> From<R> for MessageWithSenderIdReceiver<MSG, R> {
fn from(receiver: R) -> Self {
MessageWithSenderIdReceiver(receiver, PhantomData)
}
}
impl<MSG, R: MessageReceiver<MSG>> MessageWithSenderIdReceiver<MSG, R> {
pub fn try_recv_message(
&self,
) -> Result<Option<GenericMessage<MSG>>, GenericTargetedMessagingError> {
self.0.try_recv()
}
}
pub struct MessageReceiverWithId<MSG, R: MessageReceiver<MSG>> {
local_channel_id: ComponentId,
reply_receiver: MessageWithSenderIdReceiver<MSG, R>,
}
impl<MSG, R: MessageReceiver<MSG>> MessageReceiverWithId<MSG, R> {
pub fn new(local_channel_id: ComponentId, reply_receiver: R) -> Self {
Self {
local_channel_id,
reply_receiver: MessageWithSenderIdReceiver::from(reply_receiver),
}
}
pub fn local_channel_id(&self) -> ComponentId {
self.local_channel_id
}
}
impl<MSG, R: MessageReceiver<MSG>> MessageReceiverWithId<MSG, R> {
pub fn try_recv_message(
&self,
) -> Result<Option<GenericMessage<MSG>>, GenericTargetedMessagingError> {
self.reply_receiver.0.try_recv()
}
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use crate::queue::GenericSendError;
use super::*;
use hashbrown::HashMap;
pub struct MessageSenderMap<MSG, S: MessageSender<MSG>>(
pub HashMap<ComponentId, S>,
pub(crate) PhantomData<MSG>,
);
impl<MSG, S: MessageSender<MSG>> Default for MessageSenderMap<MSG, S> {
fn default() -> Self {
Self(Default::default(), PhantomData)
}
}
impl<MSG, S: MessageSender<MSG>> MessageSenderMap<MSG, S> {
pub fn add_message_target(&mut self, target_id: ComponentId, message_sender: S) {
self.0.insert(target_id, message_sender);
}
pub fn send_message(
&self,
requestor_info: MessageMetadata,
target_channel_id: ComponentId,
message: MSG,
) -> Result<(), GenericTargetedMessagingError> {
if self.0.contains_key(&target_channel_id) {
return self
.0
.get(&target_channel_id)
.unwrap()
.send(GenericMessage::new(requestor_info, message));
}
Err(GenericSendError::TargetDoesNotExist(target_channel_id).into())
}
}
pub struct MessageSenderAndReceiver<TO, FROM, S: MessageSender<TO>, R: MessageReceiver<FROM>> {
pub local_channel_id: ComponentId,
pub message_sender_map: MessageSenderMap<TO, S>,
pub message_receiver: MessageWithSenderIdReceiver<FROM, R>,
}
impl<TO, FROM, S: MessageSender<TO>, R: MessageReceiver<FROM>>
MessageSenderAndReceiver<TO, FROM, S, R>
{
pub fn new(local_channel_id: ComponentId, message_receiver: R) -> Self {
Self {
local_channel_id,
message_sender_map: Default::default(),
message_receiver: MessageWithSenderIdReceiver::from(message_receiver),
}
}
pub fn add_message_target(&mut self, target_id: ComponentId, message_sender: S) {
self.message_sender_map
.add_message_target(target_id, message_sender)
}
pub fn local_channel_id_generic(&self) -> ComponentId {
self.local_channel_id
}
/// Try to send a message, which can be a reply or a request, depending on the generics.
pub fn send_message(
&self,
request_id: RequestId,
target_id: ComponentId,
message: TO,
) -> Result<(), GenericTargetedMessagingError> {
self.message_sender_map.send_message(
MessageMetadata::new(request_id, self.local_channel_id_generic()),
target_id,
message,
)
}
/// Try to receive a message, which can be a reply or a request, depending on the generics.
pub fn try_recv_message(
&self,
) -> Result<Option<GenericMessage<FROM>>, GenericTargetedMessagingError> {
self.message_receiver.try_recv_message()
}
}
pub struct RequestAndReplySenderAndReceiver<
REQUEST,
REPLY,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<REQUEST>,
> {
pub local_channel_id: ComponentId,
// These 2 are a functional group.
pub request_sender_map: MessageSenderMap<REQUEST, S0>,
pub reply_receiver: MessageWithSenderIdReceiver<REPLY, R0>,
// These 2 are a functional group.
pub request_receiver: MessageWithSenderIdReceiver<REQUEST, R1>,
pub reply_sender_map: MessageSenderMap<REPLY, S1>,
}
impl<
REQUEST,
REPLY,
S0: MessageSender<REQUEST>,
R0: MessageReceiver<REPLY>,
S1: MessageSender<REPLY>,
R1: MessageReceiver<REQUEST>,
> RequestAndReplySenderAndReceiver<REQUEST, REPLY, S0, R0, S1, R1>
{
pub fn new(
local_channel_id: ComponentId,
request_receiver: R1,
reply_receiver: R0,
) -> Self {
Self {
local_channel_id,
request_receiver: request_receiver.into(),
reply_receiver: reply_receiver.into(),
request_sender_map: Default::default(),
reply_sender_map: Default::default(),
}
}
pub fn local_channel_id_generic(&self) -> ComponentId {
self.local_channel_id
}
}
}
#[cfg(feature = "std")]
pub mod std_mod {
use super::*;
use std::sync::mpsc;
use crate::queue::{GenericReceiveError, GenericSendError};
impl<MSG: Send> MessageSender<MSG> for mpsc::Sender<GenericMessage<MSG>> {
fn send(&self, message: GenericMessage<MSG>) -> Result<(), GenericTargetedMessagingError> {
self.send(message)
.map_err(|_| GenericSendError::RxDisconnected)?;
Ok(())
}
}
impl<MSG: Send> MessageSender<MSG> for mpsc::SyncSender<GenericMessage<MSG>> {
fn send(&self, message: GenericMessage<MSG>) -> Result<(), GenericTargetedMessagingError> {
if let Err(e) = self.try_send(message) {
return match e {
mpsc::TrySendError::Full(_) => Err(GenericSendError::QueueFull(None).into()),
mpsc::TrySendError::Disconnected(_) => {
Err(GenericSendError::RxDisconnected.into())
}
};
}
Ok(())
}
}
pub type MessageSenderMapMpsc<MSG> = MessageReceiverWithId<MSG, mpsc::Sender<MSG>>;
pub type MessageSenderMapBoundedMpsc<MSG> = MessageReceiverWithId<MSG, mpsc::SyncSender<MSG>>;
impl<MSG> MessageReceiver<MSG> for mpsc::Receiver<GenericMessage<MSG>> {
fn try_recv(&self) -> Result<Option<GenericMessage<MSG>>, GenericTargetedMessagingError> {
match self.try_recv() {
Ok(msg) => Ok(Some(msg)),
Err(e) => match e {
mpsc::TryRecvError::Empty => Ok(None),
mpsc::TryRecvError::Disconnected => {
Err(GenericReceiveError::TxDisconnected(None).into())
}
},
}
}
}
pub type MessageReceiverWithIdMpsc<MSG> = MessageReceiverWithId<MSG, mpsc::Receiver<MSG>>;
}
#[cfg(test)]
mod tests {
use std::sync::mpsc;
use alloc::string::ToString;
use spacepackets::{
ecss::tc::{PusTcCreator, PusTcSecondaryHeader},
ByteConversionError, SpHeader,
};
use crate::{
queue::{GenericReceiveError, GenericSendError, GenericTargetedMessagingError},
request::{MessageMetadata, MessageSenderMap},
};
use super::{GenericMessage, MessageReceiverWithId, UniqueApidTargetId};
const TEST_CHANNEL_ID_0: u64 = 1;
const TEST_CHANNEL_ID_1: u64 = 2;
const TEST_CHANNEL_ID_2: u64 = 3;
#[test]
fn test_basic_target_id_with_apid() {
let id = UniqueApidTargetId::new(0x111, 0x01);
assert_eq!(id.apid, 0x111);
assert_eq!(id.unique_id, 0x01);
assert_eq!(id.id(), id.raw());
assert_eq!(u64::from(id), id.raw());
let id_raw = id.raw();
let id_from_raw = UniqueApidTargetId::from(id_raw);
assert_eq!(id_from_raw, id);
assert_eq!(id.id(), (0x111 << 32) | 0x01);
let string = id.to_string();
assert_eq!(
string,
"Target and APID ID with APID 0x111 and target 1".to_string()
);
}
#[test]
fn test_basic_target_id_with_apid_from_pus_tc() {
let sp_header = SpHeader::new_for_unseg_tc(0x111, 5, 0);
let app_data = 1_u32.to_be_bytes();
let pus_tc = PusTcCreator::new_simple(sp_header, 17, 1, &app_data, true);
let id = UniqueApidTargetId::from_pus_tc(&pus_tc).unwrap();
assert_eq!(id.apid, 0x111);
assert_eq!(id.unique_id, 1);
}
#[test]
fn test_basic_target_id_with_apid_from_pus_tc_invalid_app_data() {
let sp_header = SpHeader::new_for_unseg_tc(0x111, 5, 0);
let sec_header = PusTcSecondaryHeader::new_simple(17, 1);
let pus_tc = PusTcCreator::new_no_app_data(sp_header, sec_header, true);
let error = UniqueApidTargetId::from_pus_tc(&pus_tc);
assert!(error.is_err());
let error = error.unwrap_err();
if let ByteConversionError::FromSliceTooSmall { found, expected } = error {
assert_eq!(found, 0);
assert_eq!(expected, 4);
} else {
panic!("Unexpected error type");
}
}
#[test]
fn test_receiver_only() {
let (sender, receiver) = mpsc::channel();
// Test structure with only a receiver which has a channel ID.
let receiver = MessageReceiverWithId::new(TEST_CHANNEL_ID_0, receiver);
let request_id = 5;
sender
.send(GenericMessage::new(
MessageMetadata::new(request_id, TEST_CHANNEL_ID_1),
5,
))
.unwrap();
let reply = receiver.try_recv_message().unwrap();
assert!(reply.is_some());
assert_eq!(receiver.local_channel_id(), TEST_CHANNEL_ID_0);
let reply = reply.unwrap();
assert_eq!(reply.requestor_info.request_id, request_id);
assert_eq!(reply.requestor_info.sender_id, TEST_CHANNEL_ID_1);
assert_eq!(reply.message, 5);
}
#[test]
fn test_receiver_empty() {
let (_sender, receiver) = mpsc::sync_channel::<GenericMessage<i32>>(2);
// Test structure with only a receiver which has a channel ID.
let receiver = MessageReceiverWithId::new(TEST_CHANNEL_ID_0, receiver);
let reply = receiver.try_recv_message().unwrap();
assert!(reply.is_none());
}
#[test]
fn test_all_tx_disconnected() {
let (sender, receiver) = mpsc::sync_channel::<GenericMessage<i32>>(2);
// Test structure with only a receiver which has a channel ID.
let receiver = MessageReceiverWithId::new(TEST_CHANNEL_ID_0, receiver);
drop(sender);
let reply = receiver.try_recv_message();
assert!(reply.is_err());
let error = reply.unwrap_err();
if let GenericTargetedMessagingError::Receive(GenericReceiveError::TxDisconnected(None)) =
error
{
} else {
panic!("unexpected error type");
}
}
#[test]
fn test_sender_map() {
let (sender0, receiver0) = mpsc::channel();
let (sender1, receiver1) = mpsc::channel();
let mut sender_map = MessageSenderMap::default();
sender_map.add_message_target(TEST_CHANNEL_ID_1, sender0);
sender_map.add_message_target(TEST_CHANNEL_ID_2, sender1);
sender_map
.send_message(
MessageMetadata::new(1, TEST_CHANNEL_ID_0),
TEST_CHANNEL_ID_1,
5,
)
.expect("sending message failed");
let mut reply = receiver0.recv().expect("receiving message failed");
assert_eq!(reply.request_id(), 1);
assert_eq!(reply.sender_id(), TEST_CHANNEL_ID_0);
assert_eq!(reply.message, 5);
sender_map
.send_message(
MessageMetadata::new(2, TEST_CHANNEL_ID_0),
TEST_CHANNEL_ID_2,
10,
)
.expect("sending message failed");
reply = receiver1.recv().expect("receiving message failed");
assert_eq!(reply.request_id(), 2);
assert_eq!(reply.sender_id(), TEST_CHANNEL_ID_0);
assert_eq!(reply.message, 10);
}
#[test]
fn test_sender_map_target_does_not_exist() {
let (sender0, _) = mpsc::channel();
let mut sender_map_with_id = MessageSenderMap::default();
sender_map_with_id.add_message_target(TEST_CHANNEL_ID_1, sender0);
let result = sender_map_with_id.send_message(
MessageMetadata::new(1, TEST_CHANNEL_ID_0),
TEST_CHANNEL_ID_2,
5,
);
assert!(result.is_err());
let error = result.unwrap_err();
if let GenericTargetedMessagingError::Send(GenericSendError::TargetDoesNotExist(target)) =
error
{
assert_eq!(target, TEST_CHANNEL_ID_2);
} else {
panic!("Unexpected error type");
}
}
#[test]
fn test_sender_map_queue_full() {
let (sender0, _receiver0) = mpsc::sync_channel(1);
let mut sender_map_with_id = MessageSenderMap::default();
sender_map_with_id.add_message_target(TEST_CHANNEL_ID_1, sender0);
sender_map_with_id
.send_message(
MessageMetadata::new(1, TEST_CHANNEL_ID_0),
TEST_CHANNEL_ID_1,
5,
)
.expect("sending message failed");
let result = sender_map_with_id.send_message(
MessageMetadata::new(1, TEST_CHANNEL_ID_0),
TEST_CHANNEL_ID_1,
5,
);
assert!(result.is_err());
let error = result.unwrap_err();
if let GenericTargetedMessagingError::Send(GenericSendError::QueueFull(capacity)) = error {
assert!(capacity.is_none());
} else {
panic!("Unexpected error type {}", error);
}
}
#[test]
fn test_sender_map_queue_receiver_disconnected() {
let (sender0, receiver0) = mpsc::sync_channel(1);
let mut sender_map_with_id = MessageSenderMap::default();
sender_map_with_id.add_message_target(TEST_CHANNEL_ID_1, sender0);
drop(receiver0);
let result = sender_map_with_id.send_message(
MessageMetadata::new(1, TEST_CHANNEL_ID_0),
TEST_CHANNEL_ID_1,
5,
);
assert!(result.is_err());
let error = result.unwrap_err();
if let GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected) = error {
} else {
panic!("Unexpected error type {}", error);
}
}
}

View File

@ -32,7 +32,7 @@ dyn_clone::clone_trait_object!(SequenceCountProvider<u16>);
#[cfg(feature = "alloc")]
impl<T, Raw> SequenceCountProvider<Raw> for T where T: SequenceCountProviderCore<Raw> + Clone {}
#[derive(Default, Clone)]
#[derive(Clone)]
pub struct SeqCountProviderSimple<T: Copy> {
seq_count: Cell<T>,
max_val: T,
@ -43,13 +43,12 @@ macro_rules! impl_for_primitives {
$(
paste! {
impl SeqCountProviderSimple<$ty> {
pub fn [<new_ $ty _max_val>](max_val: $ty) -> Self {
pub fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self {
Self {
seq_count: Cell::new(0),
max_val,
}
}
pub fn [<new_ $ty>]() -> Self {
Self {
seq_count: Cell::new(0),
@ -58,6 +57,12 @@ macro_rules! impl_for_primitives {
}
}
impl Default for SeqCountProviderSimple<$ty> {
fn default() -> Self {
Self::[<new_ $ty>]()
}
}
impl SequenceCountProviderCore<$ty> for SeqCountProviderSimple<$ty> {
fn get(&self) -> $ty {
self.seq_count.get()
@ -86,21 +91,16 @@ macro_rules! impl_for_primitives {
impl_for_primitives!(u8, u16, u32, u64,);
/// This is a sequence count provider which wraps around at [MAX_SEQ_COUNT].
#[derive(Clone)]
pub struct CcsdsSimpleSeqCountProvider {
provider: SeqCountProviderSimple<u16>,
}
impl CcsdsSimpleSeqCountProvider {
pub fn new() -> Self {
Self {
provider: SeqCountProviderSimple::new_u16_max_val(MAX_SEQ_COUNT),
}
}
}
impl Default for CcsdsSimpleSeqCountProvider {
fn default() -> Self {
Self::new()
Self {
provider: SeqCountProviderSimple::new_custom_max_val_u16(MAX_SEQ_COUNT),
}
}
}
@ -187,7 +187,7 @@ mod tests {
#[test]
fn test_u8_counter() {
let u8_counter = SeqCountProviderSimple::new_u8();
let u8_counter = SeqCountProviderSimple::<u8>::default();
assert_eq!(u8_counter.get(), 0);
assert_eq!(u8_counter.get_and_increment(), 0);
assert_eq!(u8_counter.get_and_increment(), 1);

7
satrs/src/time.rs Normal file
View File

@ -0,0 +1,7 @@
use core::fmt::Debug;
/// Generic abstraction for a check/countdown timer.
pub trait CountdownProvider: Debug {
fn has_expired(&self) -> bool;
fn reset(&mut self);
}

View File

@ -1,390 +0,0 @@
//! CCSDS packet routing components.
//!
//! The routing components consist of two core components:
//! 1. [CcsdsDistributor] component which dispatches received packets to a user-provided handler
//! 2. [CcsdsPacketHandler] trait which should be implemented by the user-provided packet handler.
//!
//! The [CcsdsDistributor] implements the [ReceivesCcsdsTc] and [ReceivesTcCore] trait which allows to
//! pass raw or CCSDS packets to it. Upon receiving a packet, it performs the following steps:
//!
//! 1. It tries to identify the target Application Process Identifier (APID) based on the
//! respective CCSDS space packet header field. If that process fails, a [ByteConversionError] is
//! returned to the user
//! 2. If a valid APID is found and matches one of the APIDs provided by
//! [CcsdsPacketHandler::valid_apids], it will pass the packet to the user provided
//! [CcsdsPacketHandler::handle_known_apid] function. If no valid APID is found, the packet
//! will be passed to the [CcsdsPacketHandler::handle_unknown_apid] function.
//!
//! # Example
//!
//! ```rust
//! use satrs::tmtc::ccsds_distrib::{CcsdsPacketHandler, CcsdsDistributor};
//! use satrs::tmtc::{ReceivesTc, ReceivesTcCore};
//! use spacepackets::{CcsdsPacket, SpHeader};
//! use spacepackets::ecss::WritablePusPacket;
//! use spacepackets::ecss::tc::{PusTc, PusTcCreator};
//!
//! #[derive (Default)]
//! struct ConcreteApidHandler {
//! known_call_count: u32,
//! unknown_call_count: u32
//! }
//!
//! impl ConcreteApidHandler {
//! fn mutable_foo(&mut self) {}
//! }
//!
//! impl CcsdsPacketHandler for ConcreteApidHandler {
//! type Error = ();
//! fn valid_apids(&self) -> &'static [u16] { &[0x002] }
//! fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
//! assert_eq!(sp_header.apid(), 0x002);
//! assert_eq!(tc_raw.len(), 13);
//! self.known_call_count += 1;
//! Ok(())
//! }
//! fn handle_unknown_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
//! assert_eq!(sp_header.apid(), 0x003);
//! assert_eq!(tc_raw.len(), 13);
//! self.unknown_call_count += 1;
//! Ok(())
//! }
//! }
//!
//! let apid_handler = ConcreteApidHandler::default();
//! let mut ccsds_distributor = CcsdsDistributor::new(apid_handler);
//!
//! // Create and pass PUS telecommand with a valid APID
//! let mut space_packet_header = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
//! let mut pus_tc = PusTcCreator::new_simple(&mut space_packet_header, 17, 1, None, true);
//! let mut test_buf: [u8; 32] = [0; 32];
//! let mut size = pus_tc
//! .write_to_bytes(test_buf.as_mut_slice())
//! .expect("Error writing TC to buffer");
//! let tc_slice = &test_buf[0..size];
//! ccsds_distributor.pass_tc(&tc_slice).expect("Passing TC slice failed");
//!
//! // Now pass a packet with an unknown APID to the distributor
//! pus_tc.set_apid(0x003);
//! size = pus_tc
//! .write_to_bytes(test_buf.as_mut_slice())
//! .expect("Error writing TC to buffer");
//! let tc_slice = &test_buf[0..size];
//! ccsds_distributor.pass_tc(&tc_slice).expect("Passing TC slice failed");
//!
//! // Retrieve the APID handler.
//! let handler_ref = ccsds_distributor.packet_handler();
//! assert_eq!(handler_ref.known_call_count, 1);
//! assert_eq!(handler_ref.unknown_call_count, 1);
//!
//! // Mutable access to the handler.
//! let mutable_handler_ref = ccsds_distributor.packet_handler_mut();
//! mutable_handler_ref.mutable_foo();
//! ```
use crate::tmtc::{ReceivesCcsdsTc, ReceivesTcCore};
use core::fmt::{Display, Formatter};
use spacepackets::{ByteConversionError, CcsdsPacket, SpHeader};
#[cfg(feature = "std")]
use std::error::Error;
/// Generic trait for a handler or dispatcher object handling CCSDS packets.
///
/// Users should implement this trait on their custom CCSDS packet handler and then pass a boxed
/// instance of this handler to the [CcsdsDistributor]. The distributor will use the trait
/// interface to dispatch received packets to the user based on the Application Process Identifier
/// (APID) field of the CCSDS packet.
pub trait CcsdsPacketHandler {
type Error;
fn valid_apids(&self) -> &'static [u16];
fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8])
-> Result<(), Self::Error>;
fn handle_unknown_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error>;
}
/// The CCSDS distributor dispatches received CCSDS packets to a user provided packet handler.
pub struct CcsdsDistributor<PacketHandler: CcsdsPacketHandler<Error = E>, E> {
/// User provided APID handler stored as a generic trait object.
/// It can be cast back to the original concrete type using [Self::packet_handler] or
/// the [Self::packet_handler_mut] method.
packet_handler: PacketHandler,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CcsdsError<E> {
CustomError(E),
ByteConversionError(ByteConversionError),
}
impl<E: Display> Display for CcsdsError<E> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::CustomError(e) => write!(f, "{e}"),
Self::ByteConversionError(e) => write!(f, "{e}"),
}
}
}
#[cfg(feature = "std")]
impl<E: Error> Error for CcsdsError<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::CustomError(e) => e.source(),
Self::ByteConversionError(e) => e.source(),
}
}
}
impl<PacketHandler: CcsdsPacketHandler<Error = E>, E: 'static> ReceivesCcsdsTc
for CcsdsDistributor<PacketHandler, E>
{
type Error = CcsdsError<E>;
fn pass_ccsds(&mut self, header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
self.dispatch_ccsds(header, tc_raw)
}
}
impl<PacketHandler: CcsdsPacketHandler<Error = E>, E: 'static> ReceivesTcCore
for CcsdsDistributor<PacketHandler, E>
{
type Error = CcsdsError<E>;
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
if tc_raw.len() < 7 {
return Err(CcsdsError::ByteConversionError(
ByteConversionError::FromSliceTooSmall {
found: tc_raw.len(),
expected: 7,
},
));
}
let (sp_header, _) =
SpHeader::from_be_bytes(tc_raw).map_err(|e| CcsdsError::ByteConversionError(e))?;
self.dispatch_ccsds(&sp_header, tc_raw)
}
}
impl<PacketHandler: CcsdsPacketHandler<Error = E>, E: 'static> CcsdsDistributor<PacketHandler, E> {
pub fn new(packet_handler: PacketHandler) -> Self {
CcsdsDistributor { packet_handler }
}
pub fn packet_handler(&self) -> &PacketHandler {
&self.packet_handler
}
pub fn packet_handler_mut(&mut self) -> &mut PacketHandler {
&mut self.packet_handler
}
fn dispatch_ccsds(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), CcsdsError<E>> {
let apid = sp_header.apid();
let valid_apids = self.packet_handler.valid_apids();
for &valid_apid in valid_apids {
if valid_apid == apid {
return self
.packet_handler
.handle_known_apid(sp_header, tc_raw)
.map_err(|e| CcsdsError::CustomError(e));
}
}
self.packet_handler
.handle_unknown_apid(sp_header, tc_raw)
.map_err(|e| CcsdsError::CustomError(e))
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::tmtc::ccsds_distrib::{CcsdsDistributor, CcsdsPacketHandler};
use spacepackets::ecss::tc::PusTcCreator;
use spacepackets::ecss::WritablePusPacket;
use spacepackets::CcsdsPacket;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::vec::Vec;
fn is_send<T: Send>(_: &T) {}
pub fn generate_ping_tc(buf: &mut [u8]) -> &[u8] {
let mut sph = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let size = pus_tc
.write_to_bytes(buf)
.expect("Error writing TC to buffer");
assert_eq!(size, 13);
&buf[0..size]
}
pub fn generate_ping_tc_as_vec() -> Vec<u8> {
let mut sph = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
PusTcCreator::new_simple(&mut sph, 17, 1, None, true)
.to_vec()
.unwrap()
}
type SharedPacketQueue = Arc<Mutex<VecDeque<(u16, Vec<u8>)>>>;
pub struct BasicApidHandlerSharedQueue {
pub known_packet_queue: SharedPacketQueue,
pub unknown_packet_queue: SharedPacketQueue,
}
#[derive(Default)]
pub struct BasicApidHandlerOwnedQueue {
pub known_packet_queue: VecDeque<(u16, Vec<u8>)>,
pub unknown_packet_queue: VecDeque<(u16, Vec<u8>)>,
}
impl CcsdsPacketHandler for BasicApidHandlerSharedQueue {
type Error = ();
fn valid_apids(&self) -> &'static [u16] {
&[0x000, 0x002]
}
fn handle_known_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
let mut vec = Vec::new();
vec.extend_from_slice(tc_raw);
self.known_packet_queue
.lock()
.unwrap()
.push_back((sp_header.apid(), vec));
Ok(())
}
fn handle_unknown_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
let mut vec = Vec::new();
vec.extend_from_slice(tc_raw);
self.unknown_packet_queue
.lock()
.unwrap()
.push_back((sp_header.apid(), vec));
Ok(())
}
}
impl CcsdsPacketHandler for BasicApidHandlerOwnedQueue {
type Error = ();
fn valid_apids(&self) -> &'static [u16] {
&[0x000, 0x002]
}
fn handle_known_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
let mut vec = Vec::new();
vec.extend_from_slice(tc_raw);
self.known_packet_queue.push_back((sp_header.apid(), vec));
Ok(())
}
fn handle_unknown_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
let mut vec = Vec::new();
vec.extend_from_slice(tc_raw);
self.unknown_packet_queue.push_back((sp_header.apid(), vec));
Ok(())
}
}
#[test]
fn test_distribs_known_apid() {
let known_packet_queue = Arc::new(Mutex::default());
let unknown_packet_queue = Arc::new(Mutex::default());
let apid_handler = BasicApidHandlerSharedQueue {
known_packet_queue: known_packet_queue.clone(),
unknown_packet_queue: unknown_packet_queue.clone(),
};
let mut ccsds_distrib = CcsdsDistributor::new(apid_handler);
is_send(&ccsds_distrib);
let mut test_buf: [u8; 32] = [0; 32];
let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
ccsds_distrib.pass_tc(tc_slice).expect("Passing TC failed");
let recvd = known_packet_queue.lock().unwrap().pop_front();
assert!(unknown_packet_queue.lock().unwrap().is_empty());
assert!(recvd.is_some());
let (apid, packet) = recvd.unwrap();
assert_eq!(apid, 0x002);
assert_eq!(packet, tc_slice);
}
#[test]
fn test_unknown_apid_handling() {
let apid_handler = BasicApidHandlerOwnedQueue::default();
let mut ccsds_distrib = CcsdsDistributor::new(apid_handler);
let mut sph = SpHeader::tc_unseg(0x004, 0x34, 0).unwrap();
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let mut test_buf: [u8; 32] = [0; 32];
pus_tc
.write_to_bytes(test_buf.as_mut_slice())
.expect("Error writing TC to buffer");
ccsds_distrib.pass_tc(&test_buf).expect("Passing TC failed");
assert!(ccsds_distrib.packet_handler().known_packet_queue.is_empty());
let apid_handler = ccsds_distrib.packet_handler_mut();
let recvd = apid_handler.unknown_packet_queue.pop_front();
assert!(recvd.is_some());
let (apid, packet) = recvd.unwrap();
assert_eq!(apid, 0x004);
assert_eq!(packet.as_slice(), test_buf);
}
#[test]
fn test_ccsds_distribution() {
let mut ccsds_distrib = CcsdsDistributor::new(BasicApidHandlerOwnedQueue::default());
let mut sph = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let tc_vec = pus_tc.to_vec().unwrap();
ccsds_distrib
.pass_ccsds(&sph, &tc_vec)
.expect("passing CCSDS TC failed");
let recvd = ccsds_distrib
.packet_handler_mut()
.known_packet_queue
.pop_front();
assert!(recvd.is_some());
let recvd = recvd.unwrap();
assert_eq!(recvd.0, 0x002);
assert_eq!(recvd.1, tc_vec);
}
#[test]
fn test_distribution_short_packet_fails() {
let mut ccsds_distrib = CcsdsDistributor::new(BasicApidHandlerOwnedQueue::default());
let mut sph = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
let pus_tc = PusTcCreator::new_simple(&mut sph, 17, 1, None, true);
let tc_vec = pus_tc.to_vec().unwrap();
let result = ccsds_distrib.pass_tc(&tc_vec[0..6]);
assert!(result.is_err());
let error = result.unwrap_err();
if let CcsdsError::ByteConversionError(ByteConversionError::FromSliceTooSmall {
found,
expected,
}) = error
{
assert_eq!(found, 6);
assert_eq!(expected, 7);
} else {
panic!("Unexpected error variant");
}
}
}

View File

@ -1,115 +1,651 @@
//! Telemetry and Telecommanding (TMTC) module. Contains packet routing components with special
//! support for CCSDS and ECSS packets.
//!
//! The distributor modules provided by this module use trait objects provided by the user to
//! directly dispatch received packets to packet listeners based on packet fields like the CCSDS
//! Application Process ID (APID) or the ECSS PUS service type. This allows for fast packet
//! routing without the overhead and complication of using message queues. However, it also requires
//! It is recommended to read the [sat-rs book chapter](https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/book/communication.html)
//! about communication first. The TMTC abstractions provided by this framework are based on the
//! assumption that all telemetry is sent to a special handler object called the TM sink while
//! all received telecommands are sent to a special handler object called TC source. Using
//! a design like this makes it simpler to add new TC packet sources or new telemetry generators:
//! They only need to send the received and generated data to these objects.
use crate::queue::GenericSendError;
use crate::{
pool::{PoolAddr, PoolError},
ComponentId,
};
#[cfg(feature = "std")]
pub use alloc_mod::*;
#[cfg(feature = "alloc")]
use downcast_rs::{impl_downcast, Downcast};
use spacepackets::SpHeader;
use spacepackets::{
ecss::{
tc::PusTcReader,
tm::{PusTmCreator, PusTmReader},
},
SpHeader,
};
#[cfg(feature = "std")]
use std::sync::mpsc;
#[cfg(feature = "std")]
pub use std_mod::*;
#[cfg(feature = "alloc")]
pub mod ccsds_distrib;
#[cfg(feature = "alloc")]
pub mod pus_distrib;
pub mod tm_helper;
#[cfg(feature = "alloc")]
pub use ccsds_distrib::{CcsdsDistributor, CcsdsError, CcsdsPacketHandler};
#[cfg(feature = "alloc")]
pub use pus_distrib::{PusDistributor, PusServiceDistributor};
/// Simple type modelling packet stored inside a pool structure. This structure is intended to
/// be used when sending a packet via a message queue, so it also contains the sender ID.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PacketInPool {
pub sender_id: ComponentId,
pub store_addr: PoolAddr,
}
/// Generic trait for object which can receive any telecommands in form of a raw bytestream, with
impl PacketInPool {
pub fn new(sender_id: ComponentId, store_addr: PoolAddr) -> Self {
Self {
sender_id,
store_addr,
}
}
}
/// Generic trait for object which can send any packets in form of a raw bytestream, with
/// no assumptions about the received protocol.
///
/// This trait is implemented by both the [crate::tmtc::pus_distrib::PusDistributor] and the
/// [crate::tmtc::ccsds_distrib::CcsdsDistributor] which allows to pass the respective packets in
/// raw byte format into them.
pub trait ReceivesTcCore {
pub trait PacketSenderRaw: Send {
type Error;
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error>;
fn send_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error>;
}
/// Extension trait of [ReceivesTcCore] which allows downcasting by implementing [Downcast] and
/// is also sendable.
/// Extension trait of [PacketSenderRaw] which allows downcasting by implementing [Downcast].
#[cfg(feature = "alloc")]
pub trait ReceivesTc: ReceivesTcCore + Downcast + Send {
pub trait PacketSenderRawExt: PacketSenderRaw + Downcast {
// Remove this once trait upcasting coercion has been implemented.
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
fn upcast(&self) -> &dyn ReceivesTcCore<Error = Self::Error>;
fn upcast(&self) -> &dyn PacketSenderRaw<Error = Self::Error>;
// 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 ReceivesTcCore<Error = Self::Error>;
fn upcast_mut(&mut self) -> &mut dyn PacketSenderRaw<Error = Self::Error>;
}
/// Blanket implementation to automatically implement [ReceivesTc] when the [alloc] feature
/// is enabled.
/// Blanket implementation to automatically implement [PacketSenderRawExt] when the [alloc]
/// feature is enabled.
#[cfg(feature = "alloc")]
impl<T> ReceivesTc for T
impl<T> PacketSenderRawExt for T
where
T: ReceivesTcCore + Send + 'static,
T: PacketSenderRaw + Send + 'static,
{
// Remove this once trait upcasting coercion has been implemented.
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
fn upcast(&self) -> &dyn ReceivesTcCore<Error = Self::Error> {
fn upcast(&self) -> &dyn PacketSenderRaw<Error = Self::Error> {
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 ReceivesTcCore<Error = Self::Error> {
fn upcast_mut(&mut self) -> &mut dyn PacketSenderRaw<Error = Self::Error> {
self
}
}
#[cfg(feature = "alloc")]
impl_downcast!(ReceivesTc assoc Error);
impl_downcast!(PacketSenderRawExt assoc Error);
/// Generic trait for object which can receive CCSDS space packets, for example ECSS PUS packets
/// for CCSDS File Delivery Protocol (CFDP) packets.
///
/// This trait is implemented by both the [crate::tmtc::pus_distrib::PusDistributor] and the
/// [crate::tmtc::ccsds_distrib::CcsdsDistributor] which allows
/// to pass the respective packets in raw byte format or in CCSDS format into them.
pub trait ReceivesCcsdsTc {
/// Generic trait for object which can send CCSDS space packets, for example ECSS PUS packets
/// or CCSDS File Delivery Protocol (CFDP) packets wrapped in space packets.
pub trait PacketSenderCcsds: Send {
type Error;
fn pass_ccsds(&mut self, header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error>;
fn send_ccsds(
&self,
sender_id: ComponentId,
header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error>;
}
/// Generic trait for a TM packet source, with no restrictions on the type of TM.
#[cfg(feature = "std")]
impl PacketSenderCcsds for mpsc::Sender<PacketAsVec> {
type Error = GenericSendError;
fn send_ccsds(
&self,
sender_id: ComponentId,
_: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
self.send(PacketAsVec::new(sender_id, tc_raw.to_vec()))
.map_err(|_| GenericSendError::RxDisconnected)
}
}
#[cfg(feature = "std")]
impl PacketSenderCcsds for mpsc::SyncSender<PacketAsVec> {
type Error = GenericSendError;
fn send_ccsds(
&self,
sender_id: ComponentId,
_: &SpHeader,
packet_raw: &[u8],
) -> Result<(), Self::Error> {
self.try_send(PacketAsVec::new(sender_id, packet_raw.to_vec()))
.map_err(|e| match e {
mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None),
mpsc::TrySendError::Disconnected(_) => GenericSendError::RxDisconnected,
})
}
}
/// Generic trait for a packet receiver, with no restrictions on the type of packet.
/// Implementors write the telemetry into the provided buffer and return the size of the telemetry.
pub trait TmPacketSourceCore {
pub trait PacketSource: Send {
type Error;
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error>;
}
/// Extension trait of [TmPacketSourceCore] which allows downcasting by implementing [Downcast] and
/// is also sendable.
/// Extension trait of [PacketSource] which allows downcasting by implementing [Downcast].
#[cfg(feature = "alloc")]
pub trait TmPacketSource: TmPacketSourceCore + Downcast + Send {
pub trait PacketSourceExt: PacketSource + Downcast {
// Remove this once trait upcasting coercion has been implemented.
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
fn upcast(&self) -> &dyn TmPacketSourceCore<Error = Self::Error>;
fn upcast(&self) -> &dyn PacketSource<Error = Self::Error>;
// 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 TmPacketSourceCore<Error = Self::Error>;
fn upcast_mut(&mut self) -> &mut dyn PacketSource<Error = Self::Error>;
}
/// Blanket implementation to automatically implement [ReceivesTc] when the [alloc] feature
/// Blanket implementation to automatically implement [PacketSourceExt] when the [alloc] feature
/// is enabled.
#[cfg(feature = "alloc")]
impl<T> TmPacketSource for T
impl<T> PacketSourceExt for T
where
T: TmPacketSourceCore + Send + 'static,
T: PacketSource + 'static,
{
// Remove this once trait upcasting coercion has been implemented.
// Tracking issue: https://github.com/rust-lang/rust/issues/65991
fn upcast(&self) -> &dyn TmPacketSourceCore<Error = Self::Error> {
fn upcast(&self) -> &dyn PacketSource<Error = Self::Error> {
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 TmPacketSourceCore<Error = Self::Error> {
fn upcast_mut(&mut self) -> &mut dyn PacketSource<Error = Self::Error> {
self
}
}
/// Helper trait for any generic (static) store which allows storing raw or CCSDS packets.
pub trait CcsdsPacketPool {
fn add_ccsds_tc(&mut self, _: &SpHeader, tc_raw: &[u8]) -> Result<PoolAddr, PoolError> {
self.add_raw_tc(tc_raw)
}
fn add_raw_tc(&mut self, tc_raw: &[u8]) -> Result<PoolAddr, PoolError>;
}
/// Helper trait for any generic (static) store which allows storing ECSS PUS Telecommand packets.
pub trait PusTcPool {
fn add_pus_tc(&mut self, pus_tc: &PusTcReader) -> Result<PoolAddr, PoolError>;
}
/// Helper trait for any generic (static) store which allows storing ECSS PUS Telemetry packets.
pub trait PusTmPool {
fn add_pus_tm_from_reader(&mut self, pus_tm: &PusTmReader) -> Result<PoolAddr, PoolError>;
fn add_pus_tm_from_creator(&mut self, pus_tm: &PusTmCreator) -> Result<PoolAddr, PoolError>;
}
/// Generic trait for any sender component able to send packets stored inside a pool structure.
pub trait PacketInPoolSender: Send {
fn send_packet(
&self,
sender_id: ComponentId,
store_addr: PoolAddr,
) -> Result<(), GenericSendError>;
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use alloc::vec::Vec;
use super::*;
/// Simple type modelling packet stored in the heap. This structure is intended to
/// be used when sending a packet via a message queue, so it also contains the sender ID.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PacketAsVec {
pub sender_id: ComponentId,
pub packet: Vec<u8>,
}
impl PacketAsVec {
pub fn new(sender_id: ComponentId, packet: Vec<u8>) -> Self {
Self { sender_id, packet }
}
}
}
#[cfg(feature = "std")]
pub mod std_mod {
use core::cell::RefCell;
#[cfg(feature = "crossbeam")]
use crossbeam_channel as cb;
use spacepackets::ecss::WritablePusPacket;
use thiserror::Error;
use crate::pool::PoolProvider;
use crate::pus::{EcssTmSender, EcssTmtcError, PacketSenderPusTc};
use super::*;
/// Newtype wrapper around the [SharedStaticMemoryPool] to enable extension helper traits on
/// top of the regular shared memory pool API.
#[derive(Clone)]
pub struct SharedPacketPool(pub SharedStaticMemoryPool);
impl SharedPacketPool {
pub fn new(pool: &SharedStaticMemoryPool) -> Self {
Self(pool.clone())
}
}
impl PusTcPool for SharedPacketPool {
fn add_pus_tc(&mut self, pus_tc: &PusTcReader) -> Result<PoolAddr, PoolError> {
let mut pg = self.0.write().map_err(|_| PoolError::LockError)?;
let addr = pg.free_element(pus_tc.len_packed(), |buf| {
buf[0..pus_tc.len_packed()].copy_from_slice(pus_tc.raw_data());
})?;
Ok(addr)
}
}
impl PusTmPool for SharedPacketPool {
fn add_pus_tm_from_reader(&mut self, pus_tm: &PusTmReader) -> Result<PoolAddr, PoolError> {
let mut pg = self.0.write().map_err(|_| PoolError::LockError)?;
let addr = pg.free_element(pus_tm.len_packed(), |buf| {
buf[0..pus_tm.len_packed()].copy_from_slice(pus_tm.raw_data());
})?;
Ok(addr)
}
fn add_pus_tm_from_creator(
&mut self,
pus_tm: &PusTmCreator,
) -> Result<PoolAddr, PoolError> {
let mut pg = self.0.write().map_err(|_| PoolError::LockError)?;
let mut result = Ok(0);
let addr = pg.free_element(pus_tm.len_written(), |buf| {
result = pus_tm.write_to_bytes(buf);
})?;
result?;
Ok(addr)
}
}
impl CcsdsPacketPool for SharedPacketPool {
fn add_raw_tc(&mut self, tc_raw: &[u8]) -> Result<PoolAddr, PoolError> {
let mut pg = self.0.write().map_err(|_| PoolError::LockError)?;
let addr = pg.free_element(tc_raw.len(), |buf| {
buf[0..tc_raw.len()].copy_from_slice(tc_raw);
})?;
Ok(addr)
}
}
#[cfg(feature = "std")]
impl PacketSenderRaw for mpsc::Sender<PacketAsVec> {
type Error = GenericSendError;
fn send_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> {
self.send(PacketAsVec::new(sender_id, packet.to_vec()))
.map_err(|_| GenericSendError::RxDisconnected)
}
}
#[cfg(feature = "std")]
impl PacketSenderRaw for mpsc::SyncSender<PacketAsVec> {
type Error = GenericSendError;
fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> {
self.try_send(PacketAsVec::new(sender_id, tc_raw.to_vec()))
.map_err(|e| match e {
mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None),
mpsc::TrySendError::Disconnected(_) => GenericSendError::RxDisconnected,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum StoreAndSendError {
#[error("Store error: {0}")]
Store(#[from] PoolError),
#[error("Genreric send error: {0}")]
Send(#[from] GenericSendError),
}
pub use crate::pool::SharedStaticMemoryPool;
impl PacketInPoolSender for mpsc::Sender<PacketInPool> {
fn send_packet(
&self,
sender_id: ComponentId,
store_addr: PoolAddr,
) -> Result<(), GenericSendError> {
self.send(PacketInPool::new(sender_id, store_addr))
.map_err(|_| GenericSendError::RxDisconnected)
}
}
impl PacketInPoolSender for mpsc::SyncSender<PacketInPool> {
fn send_packet(
&self,
sender_id: ComponentId,
store_addr: PoolAddr,
) -> Result<(), GenericSendError> {
self.try_send(PacketInPool::new(sender_id, store_addr))
.map_err(|e| match e {
mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None),
mpsc::TrySendError::Disconnected(_) => GenericSendError::RxDisconnected,
})
}
}
#[cfg(feature = "crossbeam")]
impl PacketInPoolSender for cb::Sender<PacketInPool> {
fn send_packet(
&self,
sender_id: ComponentId,
store_addr: PoolAddr,
) -> Result<(), GenericSendError> {
self.try_send(PacketInPool::new(sender_id, store_addr))
.map_err(|e| match e {
cb::TrySendError::Full(_) => GenericSendError::QueueFull(None),
cb::TrySendError::Disconnected(_) => GenericSendError::RxDisconnected,
})
}
}
/// This is the primary structure used to send packets stored in a dedicated memory pool
/// structure.
#[derive(Clone)]
pub struct PacketSenderWithSharedPool<
Sender: PacketInPoolSender = mpsc::SyncSender<PacketInPool>,
PacketPool: CcsdsPacketPool = SharedPacketPool,
> {
pub sender: Sender,
pub shared_pool: RefCell<PacketPool>,
}
impl<Sender: PacketInPoolSender> PacketSenderWithSharedPool<Sender, SharedPacketPool> {
pub fn new_with_shared_packet_pool(
packet_sender: Sender,
shared_pool: &SharedStaticMemoryPool,
) -> Self {
Self {
sender: packet_sender,
shared_pool: RefCell::new(SharedPacketPool::new(shared_pool)),
}
}
}
impl<Sender: PacketInPoolSender, PacketStore: CcsdsPacketPool>
PacketSenderWithSharedPool<Sender, PacketStore>
{
pub fn new(packet_sender: Sender, shared_pool: PacketStore) -> Self {
Self {
sender: packet_sender,
shared_pool: RefCell::new(shared_pool),
}
}
}
impl<Sender: PacketInPoolSender, PacketStore: CcsdsPacketPool + Clone>
PacketSenderWithSharedPool<Sender, PacketStore>
{
pub fn shared_packet_store(&self) -> PacketStore {
let pool = self.shared_pool.borrow();
pool.clone()
}
}
impl<Sender: PacketInPoolSender, PacketStore: CcsdsPacketPool + Send> PacketSenderRaw
for PacketSenderWithSharedPool<Sender, PacketStore>
{
type Error = StoreAndSendError;
fn send_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> {
let mut shared_pool = self.shared_pool.borrow_mut();
let store_addr = shared_pool.add_raw_tc(packet)?;
drop(shared_pool);
self.sender
.send_packet(sender_id, store_addr)
.map_err(StoreAndSendError::Send)?;
Ok(())
}
}
impl<Sender: PacketInPoolSender, PacketStore: CcsdsPacketPool + PusTcPool + Send>
PacketSenderPusTc for PacketSenderWithSharedPool<Sender, PacketStore>
{
type Error = StoreAndSendError;
fn send_pus_tc(
&self,
sender_id: ComponentId,
_: &SpHeader,
pus_tc: &PusTcReader,
) -> Result<(), Self::Error> {
let mut shared_pool = self.shared_pool.borrow_mut();
let store_addr = shared_pool.add_raw_tc(pus_tc.raw_data())?;
drop(shared_pool);
self.sender
.send_packet(sender_id, store_addr)
.map_err(StoreAndSendError::Send)?;
Ok(())
}
}
impl<Sender: PacketInPoolSender, PacketStore: CcsdsPacketPool + Send> PacketSenderCcsds
for PacketSenderWithSharedPool<Sender, PacketStore>
{
type Error = StoreAndSendError;
fn send_ccsds(
&self,
sender_id: ComponentId,
_sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
self.send_packet(sender_id, tc_raw)
}
}
impl<Sender: PacketInPoolSender, PacketStore: CcsdsPacketPool + PusTmPool + Send> EcssTmSender
for PacketSenderWithSharedPool<Sender, PacketStore>
{
fn send_tm(
&self,
sender_id: crate::ComponentId,
tm: crate::pus::PusTmVariant,
) -> Result<(), crate::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) => {
let mut pool = self.shared_pool.borrow_mut();
let store_addr = pool.add_pus_tm_from_creator(&tm_creator)?;
send_addr(store_addr)
}
}
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use alloc::vec;
use std::sync::RwLock;
use crate::pool::{PoolProviderWithGuards, StaticMemoryPool, StaticPoolConfig};
use super::*;
use std::sync::mpsc;
pub(crate) fn send_with_sender<SendError>(
sender_id: ComponentId,
packet_sender: &(impl PacketSenderRaw<Error = SendError> + ?Sized),
packet: &[u8],
) -> Result<(), SendError> {
packet_sender.send_packet(sender_id, packet)
}
#[test]
fn test_basic_mpsc_channel_sender_bounded() {
let (tx, rx) = mpsc::channel();
let some_packet = vec![1, 2, 3, 4, 5];
send_with_sender(1, &tx, &some_packet).expect("failed to send packet");
let rx_packet = rx.try_recv().unwrap();
assert_eq!(some_packet, rx_packet.packet);
assert_eq!(1, rx_packet.sender_id);
}
#[test]
fn test_basic_mpsc_channel_receiver_dropped() {
let (tx, rx) = mpsc::channel();
let some_packet = vec![1, 2, 3, 4, 5];
drop(rx);
let result = send_with_sender(2, &tx, &some_packet);
assert!(result.is_err());
matches!(result.unwrap_err(), GenericSendError::RxDisconnected);
}
#[test]
fn test_basic_mpsc_sync_sender() {
let (tx, rx) = mpsc::sync_channel(3);
let some_packet = vec![1, 2, 3, 4, 5];
send_with_sender(3, &tx, &some_packet).expect("failed to send packet");
let rx_packet = rx.try_recv().unwrap();
assert_eq!(some_packet, rx_packet.packet);
assert_eq!(3, rx_packet.sender_id);
}
#[test]
fn test_basic_mpsc_sync_sender_receiver_dropped() {
let (tx, rx) = mpsc::sync_channel(3);
let some_packet = vec![1, 2, 3, 4, 5];
drop(rx);
let result = send_with_sender(0, &tx, &some_packet);
assert!(result.is_err());
matches!(result.unwrap_err(), GenericSendError::RxDisconnected);
}
#[test]
fn test_basic_mpsc_sync_sender_queue_full() {
let (tx, rx) = mpsc::sync_channel(1);
let some_packet = vec![1, 2, 3, 4, 5];
send_with_sender(0, &tx, &some_packet).expect("failed to send packet");
let result = send_with_sender(1, &tx, &some_packet);
assert!(result.is_err());
matches!(result.unwrap_err(), GenericSendError::QueueFull(None));
let rx_packet = rx.try_recv().unwrap();
assert_eq!(some_packet, rx_packet.packet);
}
#[test]
fn test_basic_shared_store_sender_unbounded_sender() {
let (tc_tx, tc_rx) = mpsc::channel();
let pool_cfg = StaticPoolConfig::new(vec![(2, 8)], true);
let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new(
StaticMemoryPool::new(pool_cfg),
)));
let some_packet = vec![1, 2, 3, 4, 5];
let tc_sender = PacketSenderWithSharedPool::new(tc_tx, shared_pool.clone());
send_with_sender(5, &tc_sender, &some_packet).expect("failed to send packet");
let packet_in_pool = tc_rx.try_recv().unwrap();
let mut pool = shared_pool.0.write().unwrap();
let read_guard = pool.read_with_guard(packet_in_pool.store_addr);
assert_eq!(read_guard.read_as_vec().unwrap(), some_packet);
assert_eq!(packet_in_pool.sender_id, 5)
}
#[test]
fn test_basic_shared_store_sender() {
let (tc_tx, tc_rx) = mpsc::sync_channel(10);
let pool_cfg = StaticPoolConfig::new(vec![(2, 8)], true);
let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new(
StaticMemoryPool::new(pool_cfg),
)));
let some_packet = vec![1, 2, 3, 4, 5];
let tc_sender = PacketSenderWithSharedPool::new(tc_tx, shared_pool.clone());
send_with_sender(5, &tc_sender, &some_packet).expect("failed to send packet");
let packet_in_pool = tc_rx.try_recv().unwrap();
let mut pool = shared_pool.0.write().unwrap();
let read_guard = pool.read_with_guard(packet_in_pool.store_addr);
assert_eq!(read_guard.read_as_vec().unwrap(), some_packet);
assert_eq!(packet_in_pool.sender_id, 5)
}
#[test]
fn test_basic_shared_store_sender_rx_dropped() {
let (tc_tx, tc_rx) = mpsc::sync_channel(10);
let pool_cfg = StaticPoolConfig::new(vec![(2, 8)], true);
let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new(
StaticMemoryPool::new(pool_cfg),
)));
let some_packet = vec![1, 2, 3, 4, 5];
drop(tc_rx);
let tc_sender = PacketSenderWithSharedPool::new(tc_tx, shared_pool.clone());
let result = send_with_sender(2, &tc_sender, &some_packet);
assert!(result.is_err());
matches!(
result.unwrap_err(),
StoreAndSendError::Send(GenericSendError::RxDisconnected)
);
}
#[test]
fn test_basic_shared_store_sender_queue_full() {
let (tc_tx, tc_rx) = mpsc::sync_channel(1);
let pool_cfg = StaticPoolConfig::new(vec![(2, 8)], true);
let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new(
StaticMemoryPool::new(pool_cfg),
)));
let some_packet = vec![1, 2, 3, 4, 5];
let tc_sender = PacketSenderWithSharedPool::new(tc_tx, shared_pool.clone());
send_with_sender(3, &tc_sender, &some_packet).expect("failed to send packet");
let result = send_with_sender(3, &tc_sender, &some_packet);
assert!(result.is_err());
matches!(
result.unwrap_err(),
StoreAndSendError::Send(GenericSendError::RxDisconnected)
);
let packet_in_pool = tc_rx.try_recv().unwrap();
let mut pool = shared_pool.0.write().unwrap();
let read_guard = pool.read_with_guard(packet_in_pool.store_addr);
assert_eq!(read_guard.read_as_vec().unwrap(), some_packet);
assert_eq!(packet_in_pool.sender_id, 3);
}
#[test]
fn test_basic_shared_store_store_error() {
let (tc_tx, tc_rx) = mpsc::sync_channel(1);
let pool_cfg = StaticPoolConfig::new(vec![(1, 8)], true);
let shared_pool = SharedPacketPool::new(&SharedStaticMemoryPool::new(RwLock::new(
StaticMemoryPool::new(pool_cfg),
)));
let some_packet = vec![1, 2, 3, 4, 5];
let tc_sender = PacketSenderWithSharedPool::new(tc_tx, shared_pool.clone());
send_with_sender(4, &tc_sender, &some_packet).expect("failed to send packet");
let result = send_with_sender(4, &tc_sender, &some_packet);
assert!(result.is_err());
matches!(
result.unwrap_err(),
StoreAndSendError::Store(PoolError::StoreFull(..))
);
let packet_in_pool = tc_rx.try_recv().unwrap();
let mut pool = shared_pool.0.write().unwrap();
let read_guard = pool.read_with_guard(packet_in_pool.store_addr);
assert_eq!(read_guard.read_as_vec().unwrap(), some_packet);
assert_eq!(packet_in_pool.sender_id, 4);
}
}

View File

@ -1,405 +0,0 @@
//! ECSS PUS packet routing components.
//!
//! The routing components consist of two core components:
//! 1. [PusDistributor] component which dispatches received packets to a user-provided handler.
//! 2. [PusServiceDistributor] trait which should be implemented by the user-provided PUS packet
//! handler.
//!
//! The [PusDistributor] implements the [ReceivesEcssPusTc], [ReceivesCcsdsTc] and the
//! [ReceivesTcCore] trait which allows to pass raw packets, CCSDS packets and PUS TC packets into
//! it. Upon receiving a packet, it performs the following steps:
//!
//! 1. It tries to extract the [SpHeader] and [spacepackets::ecss::tc::PusTcReader] objects from
//! the raw bytestream. If this process fails, a [PusDistribError::PusError] is returned to the
//! user.
//! 2. If it was possible to extract both components, the packet will be passed to the
//! [PusServiceDistributor::distribute_packet] method provided by the user.
//!
//! # Example
//!
//! ```rust
//! use spacepackets::ecss::WritablePusPacket;
//! use satrs::tmtc::pus_distrib::{PusDistributor, PusServiceDistributor};
//! use satrs::tmtc::{ReceivesTc, ReceivesTcCore};
//! use spacepackets::SpHeader;
//! use spacepackets::ecss::tc::{PusTcCreator, PusTcReader};
//!
//! struct ConcretePusHandler {
//! handler_call_count: u32
//! }
//!
//! // This is a very simple possible service provider. It increments an internal call count field,
//! // which is used to verify the handler was called
//! impl PusServiceDistributor for ConcretePusHandler {
//! type Error = ();
//! fn distribute_packet(&mut self, service: u8, header: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
//! assert_eq!(service, 17);
//! assert_eq!(pus_tc.len_packed(), 13);
//! self.handler_call_count += 1;
//! Ok(())
//! }
//! }
//!
//! let service_handler = ConcretePusHandler {
//! handler_call_count: 0
//! };
//! let mut pus_distributor = PusDistributor::new(service_handler);
//!
//! // Create and pass PUS ping telecommand with a valid APID
//! let mut space_packet_header = SpHeader::tc_unseg(0x002, 0x34, 0).unwrap();
//! let mut pus_tc = PusTcCreator::new_simple(&mut space_packet_header, 17, 1, None, true);
//! let mut test_buf: [u8; 32] = [0; 32];
//! let mut size = pus_tc
//! .write_to_bytes(test_buf.as_mut_slice())
//! .expect("Error writing TC to buffer");
//! let tc_slice = &test_buf[0..size];
//!
//! pus_distributor.pass_tc(tc_slice).expect("Passing PUS telecommand failed");
//!
//! // User helper function to retrieve concrete class. We check the call count here to verify
//! // that the PUS ping telecommand was routed successfully.
//! let concrete_handler = pus_distributor.service_distributor();
//! assert_eq!(concrete_handler.handler_call_count, 1);
//! ```
use crate::pus::ReceivesEcssPusTc;
use crate::tmtc::{ReceivesCcsdsTc, ReceivesTcCore};
use core::fmt::{Display, Formatter};
use spacepackets::ecss::tc::PusTcReader;
use spacepackets::ecss::{PusError, PusPacket};
use spacepackets::SpHeader;
#[cfg(feature = "std")]
use std::error::Error;
/// Trait for a generic distributor object which can distribute PUS packets based on packet
/// properties like the PUS service, space packet header or any other content of the PUS packet.
pub trait PusServiceDistributor {
type Error;
fn distribute_packet(
&mut self,
service: u8,
header: &SpHeader,
pus_tc: &PusTcReader,
) -> Result<(), Self::Error>;
}
/// Generic distributor object which dispatches received packets to a user provided handler.
pub struct PusDistributor<ServiceDistributor: PusServiceDistributor<Error = E>, E> {
service_distributor: ServiceDistributor,
}
impl<ServiceDistributor: PusServiceDistributor<Error = E>, E>
PusDistributor<ServiceDistributor, E>
{
pub fn new(service_provider: ServiceDistributor) -> Self {
PusDistributor {
service_distributor: service_provider,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PusDistribError<E> {
CustomError(E),
PusError(PusError),
}
impl<E: Display> Display for PusDistribError<E> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
PusDistribError::CustomError(e) => write!(f, "pus distribution error: {e}"),
PusDistribError::PusError(e) => write!(f, "pus distribution error: {e}"),
}
}
}
#[cfg(feature = "std")]
impl<E: Error> Error for PusDistribError<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::CustomError(e) => e.source(),
Self::PusError(e) => e.source(),
}
}
}
impl<ServiceDistributor: PusServiceDistributor<Error = E>, E: 'static> ReceivesTcCore
for PusDistributor<ServiceDistributor, E>
{
type Error = PusDistribError<E>;
fn pass_tc(&mut self, tm_raw: &[u8]) -> Result<(), Self::Error> {
// Convert to ccsds and call pass_ccsds
let (sp_header, _) = SpHeader::from_be_bytes(tm_raw)
.map_err(|e| PusDistribError::PusError(PusError::ByteConversion(e)))?;
self.pass_ccsds(&sp_header, tm_raw)
}
}
impl<ServiceDistributor: PusServiceDistributor<Error = E>, E: 'static> ReceivesCcsdsTc
for PusDistributor<ServiceDistributor, E>
{
type Error = PusDistribError<E>;
fn pass_ccsds(&mut self, header: &SpHeader, tm_raw: &[u8]) -> Result<(), Self::Error> {
let (tc, _) = PusTcReader::new(tm_raw).map_err(|e| PusDistribError::PusError(e))?;
self.pass_pus_tc(header, &tc)
}
}
impl<ServiceDistributor: PusServiceDistributor<Error = E>, E: 'static> ReceivesEcssPusTc
for PusDistributor<ServiceDistributor, E>
{
type Error = PusDistribError<E>;
fn pass_pus_tc(&mut self, header: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
self.service_distributor
.distribute_packet(pus_tc.service(), header, pus_tc)
.map_err(|e| PusDistribError::CustomError(e))
}
}
impl<ServiceDistributor: PusServiceDistributor<Error = E>, E: 'static>
PusDistributor<ServiceDistributor, E>
{
pub fn service_distributor(&self) -> &ServiceDistributor {
&self.service_distributor
}
pub fn service_distributor_mut(&mut self) -> &mut ServiceDistributor {
&mut self.service_distributor
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::queue::GenericSendError;
use crate::tmtc::ccsds_distrib::tests::{
generate_ping_tc, generate_ping_tc_as_vec, BasicApidHandlerOwnedQueue,
BasicApidHandlerSharedQueue,
};
use crate::tmtc::ccsds_distrib::{CcsdsDistributor, CcsdsPacketHandler};
use alloc::format;
use alloc::vec::Vec;
use spacepackets::ecss::PusError;
use spacepackets::CcsdsPacket;
#[cfg(feature = "std")]
use std::collections::VecDeque;
#[cfg(feature = "std")]
use std::sync::{Arc, Mutex};
fn is_send<T: Send>(_: &T) {}
pub struct PacketInfo {
pub service: u8,
pub apid: u16,
pub packet: Vec<u8>,
}
struct PusHandlerSharedQueue(Arc<Mutex<VecDeque<PacketInfo>>>);
#[derive(Default)]
struct PusHandlerOwnedQueue(VecDeque<PacketInfo>);
impl PusServiceDistributor for PusHandlerSharedQueue {
type Error = PusError;
fn distribute_packet(
&mut self,
service: u8,
sp_header: &SpHeader,
pus_tc: &PusTcReader,
) -> Result<(), Self::Error> {
let mut packet: Vec<u8> = Vec::new();
packet.extend_from_slice(pus_tc.raw_data());
self.0
.lock()
.expect("Mutex lock failed")
.push_back(PacketInfo {
service,
apid: sp_header.apid(),
packet,
});
Ok(())
}
}
impl PusServiceDistributor for PusHandlerOwnedQueue {
type Error = PusError;
fn distribute_packet(
&mut self,
service: u8,
sp_header: &SpHeader,
pus_tc: &PusTcReader,
) -> Result<(), Self::Error> {
let mut packet: Vec<u8> = Vec::new();
packet.extend_from_slice(pus_tc.raw_data());
self.0.push_back(PacketInfo {
service,
apid: sp_header.apid(),
packet,
});
Ok(())
}
}
struct ApidHandlerShared {
pub pus_distrib: PusDistributor<PusHandlerSharedQueue, PusError>,
pub handler_base: BasicApidHandlerSharedQueue,
}
struct ApidHandlerOwned {
pub pus_distrib: PusDistributor<PusHandlerOwnedQueue, PusError>,
handler_base: BasicApidHandlerOwnedQueue,
}
macro_rules! apid_handler_impl {
() => {
type Error = PusError;
fn valid_apids(&self) -> &'static [u16] {
&[0x000, 0x002]
}
fn handle_known_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
self.handler_base
.handle_known_apid(&sp_header, tc_raw)
.ok()
.expect("Unexpected error");
match self.pus_distrib.pass_ccsds(&sp_header, tc_raw) {
Ok(_) => Ok(()),
Err(e) => match e {
PusDistribError::CustomError(_) => Ok(()),
PusDistribError::PusError(e) => Err(e),
},
}
}
fn handle_unknown_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
self.handler_base
.handle_unknown_apid(&sp_header, tc_raw)
.ok()
.expect("Unexpected error");
Ok(())
}
};
}
impl CcsdsPacketHandler for ApidHandlerOwned {
apid_handler_impl!();
}
impl CcsdsPacketHandler for ApidHandlerShared {
apid_handler_impl!();
}
#[test]
fn test_pus_distribution_as_raw_packet() {
let mut pus_distrib = PusDistributor::new(PusHandlerOwnedQueue::default());
let tc = generate_ping_tc_as_vec();
let result = pus_distrib.pass_tc(&tc);
assert!(result.is_ok());
assert_eq!(pus_distrib.service_distributor_mut().0.len(), 1);
let packet_info = pus_distrib.service_distributor_mut().0.pop_front().unwrap();
assert_eq!(packet_info.service, 17);
assert_eq!(packet_info.apid, 0x002);
assert_eq!(packet_info.packet, tc);
}
#[test]
fn test_pus_distribution_combined_handler() {
let known_packet_queue = Arc::new(Mutex::default());
let unknown_packet_queue = Arc::new(Mutex::default());
let pus_queue = Arc::new(Mutex::default());
let pus_handler = PusHandlerSharedQueue(pus_queue.clone());
let handler_base = BasicApidHandlerSharedQueue {
known_packet_queue: known_packet_queue.clone(),
unknown_packet_queue: unknown_packet_queue.clone(),
};
let pus_distrib = PusDistributor::new(pus_handler);
is_send(&pus_distrib);
let apid_handler = ApidHandlerShared {
pus_distrib,
handler_base,
};
let mut ccsds_distrib = CcsdsDistributor::new(apid_handler);
let mut test_buf: [u8; 32] = [0; 32];
let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
// Pass packet to distributor
ccsds_distrib
.pass_tc(tc_slice)
.expect("Passing TC slice failed");
let recvd_ccsds = known_packet_queue.lock().unwrap().pop_front();
assert!(unknown_packet_queue.lock().unwrap().is_empty());
assert!(recvd_ccsds.is_some());
let (apid, packet) = recvd_ccsds.unwrap();
assert_eq!(apid, 0x002);
assert_eq!(packet.as_slice(), tc_slice);
let recvd_pus = pus_queue.lock().unwrap().pop_front();
assert!(recvd_pus.is_some());
let packet_info = recvd_pus.unwrap();
assert_eq!(packet_info.service, 17);
assert_eq!(packet_info.apid, 0x002);
assert_eq!(packet_info.packet, tc_slice);
}
#[test]
fn test_accessing_combined_distributor() {
let pus_handler = PusHandlerOwnedQueue::default();
let handler_base = BasicApidHandlerOwnedQueue::default();
let pus_distrib = PusDistributor::new(pus_handler);
let apid_handler = ApidHandlerOwned {
pus_distrib,
handler_base,
};
let mut ccsds_distrib = CcsdsDistributor::new(apid_handler);
let mut test_buf: [u8; 32] = [0; 32];
let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
ccsds_distrib
.pass_tc(tc_slice)
.expect("Passing TC slice failed");
let apid_handler_casted_back = ccsds_distrib.packet_handler_mut();
assert!(!apid_handler_casted_back
.handler_base
.known_packet_queue
.is_empty());
let handler_owned_queue = apid_handler_casted_back
.pus_distrib
.service_distributor_mut();
assert!(!handler_owned_queue.0.is_empty());
let packet_info = handler_owned_queue.0.pop_front().unwrap();
assert_eq!(packet_info.service, 17);
assert_eq!(packet_info.apid, 0x002);
assert_eq!(packet_info.packet, tc_slice);
}
#[test]
fn test_pus_distrib_error_custom_error() {
let error = PusDistribError::CustomError(GenericSendError::RxDisconnected);
let error_string = format!("{}", error);
assert_eq!(
error_string,
"pus distribution error: rx side has disconnected"
);
}
#[test]
fn test_pus_distrib_error_pus_error() {
let error = PusDistribError::<GenericSendError>::PusError(PusError::CrcCalculationMissing);
let error_string = format!("{}", error);
assert_eq!(
error_string,
"pus distribution error: crc16 was not calculated"
);
}
}

Some files were not shown because too many files have changed in this diff Show More