Merge pull request 'CCSDS and ECSS packet routing' (#8) from mueller/packet-routing into main

Reviewed-on: rust/fsrc-launchpad#8
This commit is contained in:
Robin Müller 2022-08-18 09:40:31 +02:00
commit 83d7aad757
25 changed files with 603 additions and 429 deletions

View File

@ -1,12 +1,13 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Check" type="CargoCommandRunConfiguration" factoryName="Cargo Command"> <configuration default="false" name="Check" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run" /> <option name="command" value="check" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" /> <option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" /> <option name="requiredFeatures" value="false" />
<option name="allFeatures" value="true" /> <option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" /> <option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" /> <option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" /> <option name="backtrace" value="SHORT" />
<envs /> <envs />
<option name="isRedirectInput" value="false" /> <option name="isRedirectInput" value="false" />

View File

@ -4,7 +4,7 @@
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" /> <option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" /> <option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" /> <option name="allFeatures" value="true" />
<option name="emulateTerminal" value="false" /> <option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" /> <option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" /> <option name="buildTarget" value="REMOTE" />

View File

@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Doctest" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="test --doc" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -4,9 +4,10 @@
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" /> <option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" /> <option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" /> <option name="allFeatures" value="true" />
<option name="emulateTerminal" value="false" /> <option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" /> <option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" /> <option name="backtrace" value="SHORT" />
<envs /> <envs />
<option name="isRedirectInput" value="false" /> <option name="isRedirectInput" value="false" />

60
Cargo.lock generated
View File

@ -110,7 +110,6 @@ dependencies = [
"js-sys", "js-sys",
"num-integer", "num-integer",
"num-traits", "num-traits",
"time",
"wasm-bindgen", "wasm-bindgen",
"winapi", "winapi",
] ]
@ -201,6 +200,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "downcast-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]] [[package]]
name = "embedded-hal" name = "embedded-hal"
version = "0.2.7" version = "0.2.7"
@ -216,14 +221,23 @@ name = "fsrc-core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bus", "bus",
"downcast-rs",
"num", "num",
"postcard 1.0.1", "once_cell",
"postcard",
"serde", "serde",
"spacepackets", "spacepackets",
"thiserror", "thiserror",
"zerocopy", "zerocopy",
] ]
[[package]]
name = "fsrc-example"
version = "0.1.0"
dependencies = [
"spacepackets",
]
[[package]] [[package]]
name = "hash32" name = "hash32"
version = "0.2.1" version = "0.2.1"
@ -411,9 +425,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.13.0" version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
@ -429,17 +443,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "postcard"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a25c0b0ae06fcffe600ad392aabfa535696c8973f2253d9ac83171924c58a858"
dependencies = [
"heapless",
"postcard-cobs",
"serde",
]
[[package]] [[package]]
name = "postcard" name = "postcard"
version = "1.0.1" version = "1.0.1"
@ -451,12 +454,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "postcard-cobs"
version = "0.1.5-pre"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c68cb38ed13fd7bc9dd5db8f165b7c8d9c1a315104083a2b10f11354c2af97f"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.39" version = "1.0.39"
@ -597,8 +594,8 @@ dependencies = [
"chrono", "chrono",
"crc", "crc",
"delegate", "delegate",
"num", "num-traits",
"postcard 0.7.3", "postcard",
"serde", "serde",
"zerocopy", "zerocopy",
] ]
@ -661,17 +658,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.1" version = "1.0.1"
@ -705,12 +691,6 @@ dependencies = [
"vcell", "vcell",
] ]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.82" version = "0.2.82"

View File

@ -3,4 +3,5 @@
members = [ members = [
"fsrc-core", "fsrc-core",
"spacepackets", "spacepackets",
"fsrc-example"
] ]

View File

@ -9,13 +9,20 @@ edition = "2021"
thiserror = "1.0" thiserror = "1.0"
bus = "2.2.3" bus = "2.2.3"
num = "0.4" num = "0.4"
spacepackets = { path = "../spacepackets"}
[dependencies.spacepackets]
path = "../spacepackets"
[dependencies.downcast-rs]
version = "1.2.0"
default-features = false
[dev-dependencies] [dev-dependencies]
postcard = { version = "1.0.1", features = ["use-std"] } postcard = { version = "1.0.1", features = ["use-std"] }
serde = "1.0.143" serde = "1.0.143"
zerocopy = "0.6.1" zerocopy = "0.6.1"
once_cell = "1.13.1"
[features] [features]
default = ["use_std"] default = ["use_std"]
use_std = [] use_std = ["downcast-rs/std"]

View File

@ -1,23 +0,0 @@
extern crate core;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::thread;
fn main() {
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7301);
let socket = UdpSocket::bind(&server_addr.clone()).expect("Error opening UDP socket");
let mut recv_buf = [0; 1024];
let jh = thread::spawn(move || {
let dummy_data = [1, 2, 3, 4];
let client = UdpSocket::bind("127.0.0.1:7300").expect("Connecting to UDP server failed");
client
.send_to(&dummy_data, &server_addr)
.expect(&*format!("Sending to {:?} failed", server_addr));
});
let (num_bytes, src) = socket.recv_from(&mut recv_buf).expect("UDP Receive error");
println!(
"Received {num_bytes} bytes from {src}: {:x?}",
&recv_buf[0..num_bytes]
);
jh.join().expect("Joining thread failed");
}

View File

@ -1,67 +0,0 @@
use postcard::{from_bytes, to_stdvec};
use serde::{Deserialize, Serialize};
use zerocopy::byteorder::{I32, U16};
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned};
#[derive(AsBytes, FromBytes, Unaligned, Debug, Eq, PartialEq)]
#[repr(C, packed)]
struct ZeroCopyTest {
some_bool: u8,
some_u16: U16<NetworkEndian>,
some_i32: I32<NetworkEndian>,
some_float: [u8; 4],
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct PostcardTest {
some_bool: u8,
some_u16: u16,
some_i32: i32,
some_float: f32,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct SliceSerTest<'slice> {
some_u8: u8,
some_u32: u32,
some_slice: &'slice [u8],
}
fn main() {
let pc_test = PostcardTest {
some_bool: true as u8,
some_u16: 0x42,
some_i32: -200,
some_float: 7.7_f32,
};
let out = to_stdvec(&pc_test).unwrap();
println!("{:#04x?}", out);
let sample_hk = ZeroCopyTest {
some_bool: true as u8,
some_u16: U16::from(0x42),
some_i32: I32::from(-200),
some_float: 7.7_f32.to_be_bytes(),
};
let mut slice = [0; 11];
sample_hk.write_to(slice.as_mut_slice());
println!("{:#04x?}", slice);
let ser_vec;
{
let test_buf = [0, 1, 2, 3];
let test_with_slice = SliceSerTest {
some_u8: 12,
some_u32: 1,
some_slice: test_buf.as_slice(),
};
ser_vec = to_stdvec(&test_with_slice).unwrap();
println!("{:#04x?}", out);
}
{
let test_deser: SliceSerTest = from_bytes(ser_vec.as_slice()).unwrap();
println!("{:?}", test_deser);
}
}

View File

@ -1,8 +0,0 @@
use std::any::Any;
/// This trait encapsulates being able to cast a trait object to its original concrete type
/// TODO: Add example code and maybe write derive macro because this code is always the same
pub trait AsAny {
fn as_any(&self) -> &dyn Any;
fn as_mut_any(&mut self) -> &mut dyn Any;
}

View File

@ -35,7 +35,7 @@ impl FsrcErrorRaw {
} }
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy, Default)]
pub struct SimpleStdErrorHandler {} pub struct SimpleStdErrorHandler {}
#[cfg(feature = "use_std")] #[cfg(feature = "use_std")]

View File

@ -5,7 +5,7 @@ pub type GroupId = u16;
pub type UniqueId = u16; pub type UniqueId = u16;
pub type EventRaw = u32; pub type EventRaw = u32;
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Severity { pub enum Severity {
INFO = 1, INFO = 1,
LOW = 2, LOW = 2,
@ -27,7 +27,7 @@ impl TryFrom<u8> for Severity {
} }
} }
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Event { pub struct Event {
severity: Severity, severity: Severity,
group_id: GroupId, group_id: GroupId,

View File

@ -6,7 +6,7 @@ use std::thread;
use std::thread::JoinHandle; use std::thread::JoinHandle;
use std::time::Duration; use std::time::Duration;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Eq)]
pub enum OpResult { pub enum OpResult {
Ok, Ok,
TerminationRequested, TerminationRequested,

View File

@ -1,18 +1,24 @@
use crate::hal::host::udp_server::ReceiveResult::{IoError, ReceiverError};
use crate::tmtc::ReceivesTc; use crate::tmtc::ReceivesTc;
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::vec::Vec; use std::vec::Vec;
pub struct UdpTmtcServer { pub struct UdpTmtcServer<E> {
socket: UdpSocket, socket: UdpSocket,
recv_buf: Vec<u8>, recv_buf: Vec<u8>,
tc_receiver: Box<dyn ReceivesTc>, tc_receiver: Box<dyn ReceivesTc<Error = E>>,
} }
impl UdpTmtcServer { pub enum ReceiveResult<E> {
pub fn new<A: ToSocketAddrs, E>( IoError(std::io::Error),
ReceiverError(E),
}
impl<E> UdpTmtcServer<E> {
pub fn new<A: ToSocketAddrs>(
addr: A, addr: A,
max_recv_size: usize, max_recv_size: usize,
tc_receiver: Box<dyn ReceivesTc>, tc_receiver: Box<dyn ReceivesTc<Error = E>>,
) -> Result<Self, std::io::Error> { ) -> Result<Self, std::io::Error> {
Ok(Self { Ok(Self {
socket: UdpSocket::bind(addr)?, socket: UdpSocket::bind(addr)?,
@ -21,9 +27,14 @@ impl UdpTmtcServer {
}) })
} }
pub fn recv_tc(&mut self) -> Result<(usize, SocketAddr), std::io::Error> { pub fn recv_tc(&mut self) -> Result<(usize, SocketAddr), ReceiveResult<E>> {
let res = self.socket.recv_from(&mut self.recv_buf)?; let res = self
self.tc_receiver.pass_tc(&self.recv_buf[0..res.0]); .socket
.recv_from(&mut self.recv_buf)
.map_err(|e| IoError(e))?;
self.tc_receiver
.pass_tc(&self.recv_buf[0..res.0])
.map_err(|e| ReceiverError(e))?;
Ok(res) Ok(res)
} }
} }

View File

@ -1,5 +1,12 @@
//! # Core components of the Flight Software Rust Crate (FSRC) collection //! # Core components of the Flight Software Rust Crate (FSRC) collection
pub mod any; //!
//! This is a collection of Rust crates which can be used to build On-Board Software for remote
//! systems like satellites of rovers. It has special support for space tailored protocols
//! like the ones provided by CCSDS and ECSS.
//!
//! The crates can generally be used in a `no_std` environment, but some crates expect that the
//! [alloc](https://doc.rust-lang.org/alloc) crate is available to allow boxed trait objects.
//! These are used to supply user code to the crates.
pub mod error; pub mod error;
pub mod event_man; pub mod event_man;
pub mod events; pub mod events;
@ -8,3 +15,5 @@ pub mod hal;
pub mod objects; pub mod objects;
pub mod pool; pub mod pool;
pub mod tmtc; pub mod tmtc;
extern crate downcast_rs;

View File

@ -7,7 +7,7 @@
//! //!
//! # Examples //! # Examples
//! //!
//! ``` //! ```rust
//! use std::any::Any; //! use std::any::Any;
//! use std::error::Error; //! use std::error::Error;
//! use fsrc_core::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject}; //! use fsrc_core::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
@ -29,9 +29,6 @@
//! } //! }
//! //!
//! impl SystemObject for ExampleSysObj { //! impl SystemObject for ExampleSysObj {
//! fn as_any(&self) -> &dyn Any {
//! self
//! }
//! //!
//! fn get_object_id(&self) -> &ObjectId { //! fn get_object_id(&self) -> &ObjectId {
//! &self.id //! &self.id
@ -45,18 +42,17 @@
//! //!
//! impl ManagedSystemObject for ExampleSysObj {} //! impl ManagedSystemObject for ExampleSysObj {}
//! //!
//!
//! let mut obj_manager = ObjectManager::default(); //! let mut obj_manager = ObjectManager::default();
//! let obj_id = ObjectId { id: 0, name: "Example 0"}; //! let obj_id = ObjectId { id: 0, name: "Example 0"};
//! let example_obj = ExampleSysObj::new(obj_id, 42); //! let example_obj = ExampleSysObj::new(obj_id, 42);
//! obj_manager.insert(Box::new(example_obj)); //! obj_manager.insert(Box::new(example_obj));
//! let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get(&obj_id); //! let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&obj_id);
//! let example_obj = obj_back_casted.unwrap(); //! let example_obj = obj_back_casted.unwrap();
//! assert_eq!(example_obj.id, obj_id); //! assert_eq!(example_obj.id, obj_id);
//! assert_eq!(example_obj.dummy, 42); //! assert_eq!(example_obj.dummy, 42);
//! ``` //! ```
use std::any::Any; use downcast_rs::Downcast;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
@ -68,13 +64,14 @@ pub struct ObjectId {
/// Each object which is stored inside the [object manager][ObjectManager] needs to implemented /// Each object which is stored inside the [object manager][ObjectManager] needs to implemented
/// this trait /// this trait
pub trait SystemObject { pub trait SystemObject: Downcast {
fn as_any(&self) -> &dyn Any;
fn get_object_id(&self) -> &ObjectId; fn get_object_id(&self) -> &ObjectId;
fn initialize(&mut self) -> Result<(), Box<dyn Error>>; fn initialize(&mut self) -> Result<(), Box<dyn Error>>;
} }
downcast_rs::impl_downcast!(SystemObject);
pub trait ManagedSystemObject: SystemObject + Any + Send {} pub trait ManagedSystemObject: SystemObject + Send {}
downcast_rs::impl_downcast!(ManagedSystemObject);
/// Helper module to manage multiple [ManagedSystemObjects][ManagedSystemObject] by mapping them /// Helper module to manage multiple [ManagedSystemObjects][ManagedSystemObject] by mapping them
/// using an [object ID][ObjectId] /// using an [object ID][ObjectId]
@ -114,19 +111,26 @@ impl ObjectManager {
Ok(init_success) Ok(init_success)
} }
/// Retrieve an object stored inside the manager. The type to retrieve needs to be explicitly /// Retrieve a reference to an object stored inside the manager. The type to retrieve needs to
/// passed as a generic parameter /// be explicitly passed as a generic parameter or specified on the left hand side of the
pub fn get<T: Any>(&self, key: &ObjectId) -> Option<&T> { /// expression.
pub fn get_ref<T: ManagedSystemObject>(&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>(&mut self, key: &ObjectId) -> Option<&mut T> {
self.obj_map self.obj_map
.get(key) .get_mut(key)
.and_then(|o| o.as_ref().as_any().downcast_ref::<T>()) .and_then(|o| o.downcast_mut::<T>())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject}; use crate::objects::{ManagedSystemObject, ObjectId, ObjectManager, SystemObject};
use std::any::Any;
use std::error::Error; use std::error::Error;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
@ -148,10 +152,6 @@ mod tests {
} }
impl SystemObject for ExampleSysObj { impl SystemObject for ExampleSysObj {
fn as_any(&self) -> &dyn Any {
self
}
fn get_object_id(&self) -> &ObjectId { fn get_object_id(&self) -> &ObjectId {
&self.id &self.id
} }
@ -171,10 +171,6 @@ mod tests {
} }
impl SystemObject for OtherExampleObject { impl SystemObject for OtherExampleObject {
fn as_any(&self) -> &dyn Any {
self
}
fn get_object_id(&self) -> &ObjectId { fn get_object_id(&self) -> &ObjectId {
&self.id &self.id
} }
@ -199,7 +195,7 @@ mod tests {
let res = obj_manager.initialize(); let res = obj_manager.initialize();
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 1); assert_eq!(res.unwrap(), 1);
let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get(&expl_obj_id); let obj_back_casted: Option<&ExampleSysObj> = obj_manager.get_ref(&expl_obj_id);
assert!(obj_back_casted.is_some()); assert!(obj_back_casted.is_some());
let expl_obj_back_casted = obj_back_casted.unwrap(); let expl_obj_back_casted = obj_back_casted.unwrap();
assert_eq!(expl_obj_back_casted.dummy, 42); assert_eq!(expl_obj_back_casted.dummy, 42);
@ -219,7 +215,7 @@ mod tests {
let res = obj_manager.initialize(); let res = obj_manager.initialize();
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 2); assert_eq!(res.unwrap(), 2);
let obj_back_casted: Option<&OtherExampleObject> = obj_manager.get(&second_obj_id); let obj_back_casted: Option<&OtherExampleObject> = obj_manager.get_ref(&second_obj_id);
assert!(obj_back_casted.is_some()); assert!(obj_back_casted.is_some());
let expl_obj_back_casted = obj_back_casted.unwrap(); let expl_obj_back_casted = obj_back_casted.unwrap();
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test")); assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));
@ -266,7 +262,7 @@ mod tests {
let obj_man_0 = obj_manager.clone(); let obj_man_0 = obj_manager.clone();
let jh0 = thread::spawn(move || { let jh0 = thread::spawn(move || {
let locked_man = obj_man_0.lock().expect("Mutex lock failed"); let locked_man = obj_man_0.lock().expect("Mutex lock failed");
let obj_back_casted: Option<&ExampleSysObj> = locked_man.get(&expl_obj_id); let obj_back_casted: Option<&ExampleSysObj> = locked_man.get_ref(&expl_obj_id);
assert!(obj_back_casted.is_some()); assert!(obj_back_casted.is_some());
let expl_obj_back_casted = obj_back_casted.unwrap(); let expl_obj_back_casted = obj_back_casted.unwrap();
assert_eq!(expl_obj_back_casted.dummy, 42); assert_eq!(expl_obj_back_casted.dummy, 42);
@ -276,7 +272,7 @@ mod tests {
let jh1 = thread::spawn(move || { let jh1 = thread::spawn(move || {
let locked_man = obj_manager.lock().expect("Mutex lock failed"); let locked_man = obj_manager.lock().expect("Mutex lock failed");
let obj_back_casted: Option<&OtherExampleObject> = locked_man.get(&second_obj_id); let obj_back_casted: Option<&OtherExampleObject> = locked_man.get_ref(&second_obj_id);
assert!(obj_back_casted.is_some()); assert!(obj_back_casted.is_some());
let expl_obj_back_casted = obj_back_casted.unwrap(); let expl_obj_back_casted = obj_back_casted.unwrap();
assert_eq!(expl_obj_back_casted.string, String::from("Hello Test")); assert_eq!(expl_obj_back_casted.string, String::from("Hello Test"));

View File

@ -110,7 +110,7 @@ pub struct LocalPool {
} }
/// Simple address type used for transactions with the local pool. /// Simple address type used for transactions with the local pool.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct StoreAddr { pub struct StoreAddr {
pool_idx: u16, pool_idx: u16,
packet_idx: NumBlocks, packet_idx: NumBlocks,
@ -124,13 +124,13 @@ impl StoreAddr {
} }
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum StoreIdError { pub enum StoreIdError {
InvalidSubpool(u16), InvalidSubpool(u16),
InvalidPacketIdx(u16), InvalidPacketIdx(u16),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum StoreError { pub enum StoreError {
/// Requested data block is too large /// Requested data block is too large
DataTooLarge(usize), DataTooLarge(usize),

View File

@ -1,76 +1,201 @@
use crate::any::AsAny; //! CCSDS packet routing components.
use crate::error::FsrcErrorHandler; //!
use crate::tmtc::{ReceivesTc, FROM_BYTES_SLICE_TOO_SMALL_ERROR, FROM_BYTES_ZEROCOPY_ERROR}; //! The routing components consist of two core components:
//! 1. [CcsdsDistributor] component which dispatches received packets to a user-provided handler
//! 2. [ApidPacketHandler] trait which should be implemented by the user-provided packet handler.
//!
//! The [CcsdsDistributor] implements the [ReceivesCcsdsTc] and [ReceivesTc] 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 [PacketError] is
//! returned to the user
//! 2. If a valid APID is found and matches one of the APIDs provided by
//! [ApidPacketHandler::valid_apids], it will pass the packet to the user provided
//! [ApidPacketHandler::handle_known_apid] function. If no valid APID is found, the packet
//! will be passed to the [ApidPacketHandler::handle_unknown_apid] function.
//!
//! # Example
//!
//! ```rust
//! use fsrc_core::tmtc::ccsds_distrib::{ApidPacketHandler, CcsdsDistributor};
//! use fsrc_core::tmtc::ReceivesTc;
//! use spacepackets::{CcsdsPacket, SpHeader};
//! use spacepackets::tc::PusTc;
//!
//! #[derive (Default)]
//! struct ConcreteApidHandler {
//! known_call_count: u32,
//! unknown_call_count: u32
//! }
//!
//! impl ConcreteApidHandler {
//! fn mutable_foo(&mut self) {}
//! }
//!
//! impl ApidPacketHandler 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(Box::new(apid_handler));
//!
//! // Create and pass PUS telecommand with a valid APID
//! let mut space_packet_header = SpHeader::tc(0x002, 0x34, 0).unwrap();
//! let mut pus_tc = PusTc::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(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(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");
//!
//! // User helper function to retrieve concrete class
//! let concrete_handler_ref: &ConcreteApidHandler = ccsds_distributor
//! .apid_handler_ref()
//! .expect("Casting back to concrete type failed");
//! assert_eq!(concrete_handler_ref.known_call_count, 1);
//! assert_eq!(concrete_handler_ref.unknown_call_count, 1);
//!
//! // It's also possible to retrieve a mutable reference
//! let mutable_ref: &mut ConcreteApidHandler = ccsds_distributor
//! .apid_handler_mut()
//! .expect("Casting back to concrete type failed");
//! mutable_ref.mutable_foo();
//! ```
use crate::tmtc::{ReceivesCcsdsTc, ReceivesTc};
use downcast_rs::Downcast;
use spacepackets::{CcsdsPacket, PacketError, SpHeader}; use spacepackets::{CcsdsPacket, PacketError, SpHeader};
pub trait ApidPacketHandler: AsAny { /// 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.
///
/// This trait automatically implements the [downcast_rs::Downcast] to allow a more convenient API
/// to cast trait objects back to their concrete type after the handler was passed to the
/// distributor.
pub trait ApidPacketHandler: Downcast {
type Error;
fn valid_apids(&self) -> &'static [u16]; fn valid_apids(&self) -> &'static [u16];
fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]); fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8])
fn handle_unknown_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>;
} }
pub struct CcsdsDistributor { downcast_rs::impl_downcast!(ApidPacketHandler assoc Error);
pub apid_handler: Box<dyn ApidPacketHandler>,
error_handler: Box<dyn FsrcErrorHandler>, /// The CCSDS distributor dispatches received CCSDS packets to a user provided packet handler.
pub struct CcsdsDistributor<E> {
/// User provided APID handler stored as a generic trait object.
/// It can be cast back to the original concrete type using the [Self::apid_handler_ref] or
/// the [Self::apid_handler_mut] method.
pub apid_handler: Box<dyn ApidPacketHandler<Error = E>>,
} }
impl CcsdsDistributor { #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub fn new( pub enum CcsdsError<E> {
apid_handler: Box<dyn ApidPacketHandler>, CustomError(E),
error_handler: Box<dyn FsrcErrorHandler>, PacketError(PacketError),
) -> Self {
CcsdsDistributor {
apid_handler,
error_handler,
} }
impl<E: 'static> ReceivesCcsdsTc for CcsdsDistributor<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 ReceivesTc for CcsdsDistributor { impl<E: 'static> ReceivesTc for CcsdsDistributor<E> {
fn pass_tc(&mut self, tm_raw: &[u8]) { type Error = CcsdsError<E>;
let sp_header = match SpHeader::from_raw_slice(tm_raw) {
Ok(header) => header, fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
Err(e) => { let sp_header = SpHeader::from_raw_slice(tc_raw).map_err(|e| CcsdsError::PacketError(e))?;
match e { self.dispatch_ccsds(&sp_header, tc_raw)
PacketError::FromBytesSliceTooSmall(missmatch) => {
self.error_handler.error_with_two_params(
FROM_BYTES_SLICE_TOO_SMALL_ERROR,
missmatch.found as u32,
missmatch.expected as u32,
);
}
PacketError::FromBytesZeroCopyError => {
self.error_handler.error(FROM_BYTES_ZEROCOPY_ERROR);
}
_ => {
// TODO: Unexpected error
} }
} }
return;
impl<E: 'static> CcsdsDistributor<E> {
pub fn new(apid_handler: Box<dyn ApidPacketHandler<Error = E>>) -> Self {
CcsdsDistributor { apid_handler }
} }
};
/// This function can be used to retrieve a reference to the concrete instance of the APID
/// handler after it was passed to the distributor. See the
/// [module documentation][crate::tmtc::ccsds_distrib] for an fsrc-example.
pub fn apid_handler_ref<T: ApidPacketHandler<Error = E>>(&self) -> Option<&T> {
self.apid_handler.downcast_ref::<T>()
}
/// This function can be used to retrieve a mutable reference to the concrete instance of the
/// APID handler after it was passed to the distributor.
pub fn apid_handler_mut<T: ApidPacketHandler<Error = E>>(&mut self) -> Option<&mut T> {
self.apid_handler.downcast_mut::<T>()
}
fn dispatch_ccsds(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) -> Result<(), CcsdsError<E>> {
let apid = sp_header.apid(); let apid = sp_header.apid();
let valid_apids = self.apid_handler.valid_apids(); let valid_apids = self.apid_handler.valid_apids();
for &valid_apid in valid_apids { for &valid_apid in valid_apids {
if valid_apid == apid { if valid_apid == apid {
return self.apid_handler.handle_known_apid(&sp_header, tm_raw); return self
.apid_handler
.handle_known_apid(sp_header, tc_raw)
.map_err(|e| CcsdsError::CustomError(e));
} }
} }
self.apid_handler.handle_unknown_apid(&sp_header, tm_raw); self.apid_handler
.handle_unknown_apid(sp_header, tc_raw)
.map_err(|e| CcsdsError::CustomError(e))
} }
} }
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use super::*; use super::*;
use crate::error::SimpleStdErrorHandler;
use crate::tmtc::ccsds_distrib::{ApidPacketHandler, CcsdsDistributor}; use crate::tmtc::ccsds_distrib::{ApidPacketHandler, CcsdsDistributor};
use spacepackets::tc::PusTc; use spacepackets::tc::PusTc;
use spacepackets::CcsdsPacket; use spacepackets::CcsdsPacket;
use std::any::Any;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
pub fn generate_ping_tc(buf: &mut [u8]) -> &[u8] {
let mut sph = SpHeader::tc(0x002, 0x34, 0).unwrap();
let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
let size = pus_tc.write_to(buf).expect("Error writing TC to buffer");
assert_eq!(size, 13);
&buf[0..size]
}
pub struct BasicApidHandlerSharedQueue { pub struct BasicApidHandlerSharedQueue {
pub known_packet_queue: Arc<Mutex<VecDeque<(u16, Vec<u8>)>>>, pub known_packet_queue: Arc<Mutex<VecDeque<(u16, Vec<u8>)>>>,
pub unknown_packet_queue: Arc<Mutex<VecDeque<(u16, Vec<u8>)>>>, pub unknown_packet_queue: Arc<Mutex<VecDeque<(u16, Vec<u8>)>>>,
@ -82,65 +207,66 @@ pub(crate) mod tests {
pub unknown_packet_queue: VecDeque<(u16, Vec<u8>)>, pub unknown_packet_queue: VecDeque<(u16, Vec<u8>)>,
} }
impl AsAny for BasicApidHandlerSharedQueue {
fn as_any(&self) -> &dyn Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self
}
}
impl ApidPacketHandler for BasicApidHandlerSharedQueue { impl ApidPacketHandler for BasicApidHandlerSharedQueue {
type Error = ();
fn valid_apids(&self) -> &'static [u16] { fn valid_apids(&self) -> &'static [u16] {
&[0x000, 0x002] &[0x000, 0x002]
} }
fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) { fn handle_known_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
let mut vec = Vec::new(); let mut vec = Vec::new();
vec.extend_from_slice(tc_raw); vec.extend_from_slice(tc_raw);
self.known_packet_queue Ok(self
.known_packet_queue
.lock() .lock()
.unwrap() .unwrap()
.push_back((sp_header.apid(), vec)); .push_back((sp_header.apid(), vec)))
} }
fn handle_unknown_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) { fn handle_unknown_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
let mut vec = Vec::new(); let mut vec = Vec::new();
vec.extend_from_slice(tc_raw); vec.extend_from_slice(tc_raw);
self.unknown_packet_queue Ok(self
.unknown_packet_queue
.lock() .lock()
.unwrap() .unwrap()
.push_back((sp_header.apid(), vec)); .push_back((sp_header.apid(), vec)))
}
}
impl AsAny for BasicApidHandlerOwnedQueue {
fn as_any(&self) -> &dyn Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self
} }
} }
impl ApidPacketHandler for BasicApidHandlerOwnedQueue { impl ApidPacketHandler for BasicApidHandlerOwnedQueue {
type Error = ();
fn valid_apids(&self) -> &'static [u16] { fn valid_apids(&self) -> &'static [u16] {
&[0x000, 0x002] &[0x000, 0x002]
} }
fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) { fn handle_known_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
let mut vec = Vec::new(); let mut vec = Vec::new();
vec.extend_from_slice(tc_raw); vec.extend_from_slice(tc_raw);
self.known_packet_queue.push_back((sp_header.apid(), vec)); Ok(self.known_packet_queue.push_back((sp_header.apid(), vec)))
} }
fn handle_unknown_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) { fn handle_unknown_apid(
&mut self,
sp_header: &SpHeader,
tc_raw: &[u8],
) -> Result<(), Self::Error> {
let mut vec = Vec::new(); let mut vec = Vec::new();
vec.extend_from_slice(tc_raw); vec.extend_from_slice(tc_raw);
self.unknown_packet_queue.push_back((sp_header.apid(), vec)); Ok(self.unknown_packet_queue.push_back((sp_header.apid(), vec)))
} }
} }
@ -152,22 +278,17 @@ pub(crate) mod tests {
known_packet_queue: known_packet_queue.clone(), known_packet_queue: known_packet_queue.clone(),
unknown_packet_queue: unknown_packet_queue.clone(), unknown_packet_queue: unknown_packet_queue.clone(),
}; };
let error_handler = SimpleStdErrorHandler {}; let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
let mut ccsds_distrib =
CcsdsDistributor::new(Box::new(apid_handler), Box::new(error_handler));
let mut sph = SpHeader::tc(0x002, 0x34, 0).unwrap();
let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
pus_tc let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
.write_to(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); ccsds_distrib.pass_tc(tc_slice).expect("Passing TC failed");
ccsds_distrib.pass_tc(&test_buf);
let recvd = known_packet_queue.lock().unwrap().pop_front(); let recvd = known_packet_queue.lock().unwrap().pop_front();
assert!(unknown_packet_queue.lock().unwrap().is_empty()); assert!(unknown_packet_queue.lock().unwrap().is_empty());
assert!(recvd.is_some()); assert!(recvd.is_some());
let (apid, packet) = recvd.unwrap(); let (apid, packet) = recvd.unwrap();
assert_eq!(apid, 0x002); assert_eq!(apid, 0x002);
assert_eq!(packet.as_slice(), test_buf); assert_eq!(packet, tc_slice);
} }
#[test] #[test]
@ -178,16 +299,14 @@ pub(crate) mod tests {
known_packet_queue: known_packet_queue.clone(), known_packet_queue: known_packet_queue.clone(),
unknown_packet_queue: unknown_packet_queue.clone(), unknown_packet_queue: unknown_packet_queue.clone(),
}; };
let error_handler = SimpleStdErrorHandler {}; let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
let mut ccsds_distrib =
CcsdsDistributor::new(Box::new(apid_handler), Box::new(error_handler));
let mut sph = SpHeader::tc(0x004, 0x34, 0).unwrap(); let mut sph = SpHeader::tc(0x004, 0x34, 0).unwrap();
let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true); let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
pus_tc pus_tc
.write_to(test_buf.as_mut_slice()) .write_to(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); .expect("Error writing TC to buffer");
ccsds_distrib.pass_tc(&test_buf); ccsds_distrib.pass_tc(&test_buf).expect("Passing TC failed");
let recvd = unknown_packet_queue.lock().unwrap().pop_front(); let recvd = unknown_packet_queue.lock().unwrap().pop_front();
assert!(known_packet_queue.lock().unwrap().is_empty()); assert!(known_packet_queue.lock().unwrap().is_empty());
assert!(recvd.is_some()); assert!(recvd.is_some());

View File

@ -1,32 +1,62 @@
//! 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
use crate::error::{FsrcErrorRaw, FsrcGroupIds}; use crate::error::{FsrcErrorRaw, FsrcGroupIds};
use spacepackets::{PacketError, SpHeader}; use spacepackets::tc::PusTc;
use spacepackets::SpHeader;
pub mod ccsds_distrib; pub mod ccsds_distrib;
pub mod pus_distrib; pub mod pus_distrib;
const RAW_PACKET_ERROR: &str = "raw-tmtc"; const _RAW_PACKET_ERROR: &str = "raw-tmtc";
const _CCSDS_ERROR: &str = "ccsds-tmtc"; const _CCSDS_ERROR: &str = "ccsds-tmtc";
const _PUS_ERROR: &str = "pus-tmtc"; const _PUS_ERROR: &str = "pus-tmtc";
// TODO: A macro for general and unknown errors would be nice // TODO: A macro for general and unknown errors would be nice
const FROM_BYTES_SLICE_TOO_SMALL_ERROR: FsrcErrorRaw = FsrcErrorRaw::new( const _FROM_BYTES_SLICE_TOO_SMALL_ERROR: FsrcErrorRaw = FsrcErrorRaw::new(
FsrcGroupIds::Tmtc as u8, FsrcGroupIds::Tmtc as u8,
0, 0,
RAW_PACKET_ERROR, _RAW_PACKET_ERROR,
"FROM_BYTES_SLICE_TOO_SMALL_ERROR", "FROM_BYTES_SLICE_TOO_SMALL_ERROR",
); );
const FROM_BYTES_ZEROCOPY_ERROR: FsrcErrorRaw = FsrcErrorRaw::new( const _FROM_BYTES_ZEROCOPY_ERROR: FsrcErrorRaw = FsrcErrorRaw::new(
FsrcGroupIds::Tmtc as u8, FsrcGroupIds::Tmtc as u8,
1, 1,
RAW_PACKET_ERROR, _RAW_PACKET_ERROR,
"FROM_BYTES_ZEROCOPY_ERROR", "FROM_BYTES_ZEROCOPY_ERROR",
); );
/// Generic trait for object which can receive any telecommands 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 ReceivesTc { pub trait ReceivesTc {
fn pass_tc(&mut self, tc_raw: &[u8]); type Error;
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error>;
} }
/// Generic trait for object which can receive CCSDS space packets, for fsrc-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 { pub trait ReceivesCcsdsTc {
fn pass_ccsds(&mut self, header: &SpHeader, tc_raw: &[u8]) -> Result<(), PacketError>; type Error;
fn pass_ccsds(&mut self, header: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error>;
}
/// Generic trait for objects which can receive ECSS PUS telecommands. This trait is
/// implemented by the [crate::tmtc::pus_distrib::PusDistributor] objects to allow passing PUS TC
/// packets into it.
pub trait ReceivesEcssPusTc {
type Error;
fn pass_pus_tc(&mut self, header: &SpHeader, pus_tc: &PusTc) -> Result<(), Self::Error>;
} }

View File

@ -1,67 +1,146 @@
use crate::any::AsAny; //! ECSS PUS packet routing components.
use crate::error::FsrcErrorHandler; //!
use crate::tmtc::{ReceivesCcsdsTc, ReceivesTc}; //! The routing components consist of two core components:
//! 1. [PusDistributor] component which dispatches received packets to a user-provided handler.
//! 2. [PusServiceProvider] trait which should be implemented by the user-provided PUS packet
//! handler.
//!
//! The [PusDistributor] implements the [ReceivesEcssPusTc], [ReceivesCcsdsTc] and the [ReceivesTc]
//! 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 [PusTc] 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
//! [PusServiceProvider::handle_pus_tc_packet] method provided by the user.
//!
//! # Example
//!
//! ```rust
//! use fsrc_core::tmtc::pus_distrib::{PusDistributor, PusServiceProvider};
//! use fsrc_core::tmtc::ReceivesTc;
//! use spacepackets::SpHeader;
//! use spacepackets::tc::PusTc;
//! 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 PusServiceProvider for ConcretePusHandler {
//! type Error = ();
//! fn handle_pus_tc_packet(&mut self, service: u8, header: &SpHeader, pus_tc: &PusTc) -> 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(Box::new(service_handler));
//!
//! // Create and pass PUS ping telecommand with a valid APID
//! let mut space_packet_header = SpHeader::tc(0x002, 0x34, 0).unwrap();
//! let mut pus_tc = PusTc::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(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_ref: &ConcretePusHandler = pus_distributor
//! .service_provider_ref()
//! .expect("Casting back to concrete type failed");
//! assert_eq!(concrete_handler_ref.handler_call_count, 1);
//! ```
use crate::tmtc::{ReceivesCcsdsTc, ReceivesEcssPusTc, ReceivesTc};
use downcast_rs::Downcast;
use spacepackets::ecss::{PusError, PusPacket}; use spacepackets::ecss::{PusError, PusPacket};
use spacepackets::tc::PusTc; use spacepackets::tc::PusTc;
use spacepackets::{CcsdsPacket, PacketError, SpHeader}; use spacepackets::SpHeader;
pub trait PusServiceProvider: AsAny { pub trait PusServiceProvider: Downcast {
fn handle_pus_tc_packet(&mut self, service: u8, apid: u16, pus_tc: &PusTc); type Error;
fn handle_pus_tc_packet(
&mut self,
service: u8,
header: &SpHeader,
pus_tc: &PusTc,
) -> Result<(), Self::Error>;
}
downcast_rs::impl_downcast!(PusServiceProvider assoc Error);
pub struct PusDistributor<E> {
pub service_provider: Box<dyn PusServiceProvider<Error = E>>,
} }
pub struct PusDistributor { impl<E> PusDistributor<E> {
pub service_provider: Box<dyn PusServiceProvider>, pub fn new(service_provider: Box<dyn PusServiceProvider<Error = E>>) -> Self {
error_handler: Box<dyn FsrcErrorHandler>, PusDistributor { service_provider }
}
} }
impl ReceivesTc for PusDistributor { #[derive(Debug, Copy, Clone, PartialEq, Eq)]
fn pass_tc(&mut self, tm_raw: &[u8]) { pub enum PusDistribError<E> {
CustomError(E),
PusError(PusError),
}
impl<E: 'static> ReceivesTc for PusDistributor<E> {
type Error = PusDistribError<E>;
fn pass_tc(&mut self, tm_raw: &[u8]) -> Result<(), Self::Error> {
// Convert to ccsds and call pass_ccsds // Convert to ccsds and call pass_ccsds
match SpHeader::from_raw_slice(tm_raw) { let sp_header = SpHeader::from_raw_slice(tm_raw)
Ok(sp_header) => { .map_err(|e| PusDistribError::PusError(PusError::PacketError(e)))?;
self.pass_ccsds(&sp_header, tm_raw).unwrap(); self.pass_ccsds(&sp_header, tm_raw)
}
Err(error) => {
// TODO: Error handling
}
}
} }
} }
impl ReceivesCcsdsTc for PusDistributor { impl<E: 'static> ReceivesCcsdsTc for PusDistributor<E> {
fn pass_ccsds(&mut self, _header: &SpHeader, tm_raw: &[u8]) -> Result<(), PacketError> { type Error = PusDistribError<E>;
// TODO: Better error handling fn pass_ccsds(&mut self, header: &SpHeader, tm_raw: &[u8]) -> Result<(), Self::Error> {
let (tc, _) = match PusTc::new_from_raw_slice(tm_raw) { let (tc, _) =
Ok(tuple) => tuple, PusTc::new_from_raw_slice(tm_raw).map_err(|e| PusDistribError::PusError(e))?;
Err(e) => { self.pass_pus_tc(header, &tc)
match e {
PusError::VersionNotSupported(_) => {}
PusError::IncorrectCrc(_) => {}
PusError::RawDataTooShort(_) => {}
PusError::NoRawData => {}
PusError::CrcCalculationMissing => {}
PusError::PacketError(_) => {}
} }
return Ok(());
} }
};
impl<E: 'static> ReceivesEcssPusTc for PusDistributor<E> {
type Error = PusDistribError<E>;
fn pass_pus_tc(&mut self, header: &SpHeader, pus_tc: &PusTc) -> Result<(), Self::Error> {
self.service_provider self.service_provider
.handle_pus_tc_packet(tc.service(), tc.apid(), &tc); .handle_pus_tc_packet(pus_tc.service(), header, pus_tc)
Ok(()) .map_err(|e| PusDistribError::CustomError(e))
}
}
impl<E: 'static> PusDistributor<E> {
pub fn service_provider_ref<T: PusServiceProvider<Error = E>>(&self) -> Option<&T> {
self.service_provider.downcast_ref::<T>()
}
pub fn service_provider_mut<T: PusServiceProvider<Error = E>>(&mut self) -> Option<&mut T> {
self.service_provider.downcast_mut::<T>()
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::error::SimpleStdErrorHandler;
use crate::tmtc::ccsds_distrib::tests::{ use crate::tmtc::ccsds_distrib::tests::{
BasicApidHandlerOwnedQueue, BasicApidHandlerSharedQueue, generate_ping_tc, BasicApidHandlerOwnedQueue, BasicApidHandlerSharedQueue,
}; };
use crate::tmtc::ccsds_distrib::{ApidPacketHandler, CcsdsDistributor}; use crate::tmtc::ccsds_distrib::{ApidPacketHandler, CcsdsDistributor};
use spacepackets::ecss::PusError;
use spacepackets::tc::PusTc; use spacepackets::tc::PusTc;
use std::any::Any; use spacepackets::CcsdsPacket;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -74,103 +153,93 @@ mod tests {
pub pus_queue: VecDeque<(u8, u16, Vec<u8>)>, pub pus_queue: VecDeque<(u8, u16, Vec<u8>)>,
} }
impl AsAny for PusHandlerSharedQueue {
fn as_any(&self) -> &dyn Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self
}
}
impl PusServiceProvider for PusHandlerSharedQueue { impl PusServiceProvider for PusHandlerSharedQueue {
fn handle_pus_tc_packet(&mut self, service: u8, apid: u16, pus_tc: &PusTc) { type Error = PusError;
fn handle_pus_tc_packet(
&mut self,
service: u8,
sp_header: &SpHeader,
pus_tc: &PusTc,
) -> Result<(), Self::Error> {
let mut vec: Vec<u8> = Vec::new(); let mut vec: Vec<u8> = Vec::new();
pus_tc pus_tc.append_to_vec(&mut vec)?;
.append_to_vec(&mut vec) Ok(self
.expect("Appending raw PUS TC to vector failed"); .pus_queue
self.pus_queue
.lock() .lock()
.unwrap() .expect("Mutex lock failed")
.push_back((service, apid, vec)); .push_back((service, sp_header.apid(), vec)))
}
}
impl AsAny for PusHandlerOwnedQueue {
fn as_any(&self) -> &dyn Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self
} }
} }
impl PusServiceProvider for PusHandlerOwnedQueue { impl PusServiceProvider for PusHandlerOwnedQueue {
fn handle_pus_tc_packet(&mut self, service: u8, apid: u16, pus_tc: &PusTc) { type Error = PusError;
fn handle_pus_tc_packet(
&mut self,
service: u8,
sp_header: &SpHeader,
pus_tc: &PusTc,
) -> Result<(), Self::Error> {
let mut vec: Vec<u8> = Vec::new(); let mut vec: Vec<u8> = Vec::new();
pus_tc pus_tc.append_to_vec(&mut vec)?;
.append_to_vec(&mut vec) Ok(self.pus_queue.push_back((service, sp_header.apid(), vec)))
.expect("Appending raw PUS TC to vector failed");
self.pus_queue.push_back((service, apid, vec));
} }
} }
struct ApidHandlerShared { struct ApidHandlerShared {
pub pus_distrib: PusDistributor, pub pus_distrib: PusDistributor<PusError>,
handler_base: BasicApidHandlerSharedQueue, pub handler_base: BasicApidHandlerSharedQueue,
} }
impl AsAny for ApidHandlerShared { struct ApidHandlerOwned {
fn as_any(&self) -> &dyn Any { pub pus_distrib: PusDistributor<PusError>,
self handler_base: BasicApidHandlerOwnedQueue,
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self
}
} }
macro_rules! apid_handler_impl { macro_rules! apid_handler_impl {
() => { () => {
type Error = PusError;
fn valid_apids(&self) -> &'static [u16] { fn valid_apids(&self) -> &'static [u16] {
&[0x000, 0x002] &[0x000, 0x002]
} }
fn handle_known_apid(&mut self, sp_header: &SpHeader, tc_raw: &[u8]) { fn handle_known_apid(
self.handler_base.handle_known_apid(&sp_header, tc_raw); &mut self,
self.pus_distrib sp_header: &SpHeader,
.pass_ccsds(&sp_header, tc_raw) tc_raw: &[u8],
.expect("Passing PUS packet failed"); ) -> 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]) { fn handle_unknown_apid(
self.handler_base.handle_unknown_apid(&sp_header, tc_raw); &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 ApidPacketHandler for ApidHandlerShared { impl ApidPacketHandler for ApidHandlerOwned {
apid_handler_impl!(); apid_handler_impl!();
} }
struct ApidHandlerOwned { impl ApidPacketHandler for ApidHandlerShared {
pub pus_distrib: PusDistributor,
handler_base: BasicApidHandlerOwnedQueue,
}
impl AsAny for ApidHandlerOwned {
fn as_any(&self) -> &dyn Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self
}
}
impl ApidPacketHandler for ApidHandlerOwned {
apid_handler_impl!(); apid_handler_impl!();
} }
@ -187,27 +256,22 @@ mod tests {
unknown_packet_queue: unknown_packet_queue.clone(), unknown_packet_queue: unknown_packet_queue.clone(),
}; };
let error_handler = SimpleStdErrorHandler {};
let pus_distrib = PusDistributor { let pus_distrib = PusDistributor {
service_provider: Box::new(pus_handler), service_provider: Box::new(pus_handler),
error_handler: Box::new(error_handler),
}; };
let apid_handler = ApidHandlerShared { let apid_handler = ApidHandlerShared {
pus_distrib, pus_distrib,
handler_base, handler_base,
}; };
let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
let mut ccsds_distrib =
CcsdsDistributor::new(Box::new(apid_handler), Box::new(error_handler));
let mut sph = SpHeader::tc(0x002, 0x34, 0).unwrap();
let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
.write_to(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); // Pass packet to distributor
let tc_slice = &test_buf[0..size]; ccsds_distrib
ccsds_distrib.pass_tc(tc_slice); .pass_tc(tc_slice)
.expect("Passing TC slice failed");
let recvd_ccsds = known_packet_queue.lock().unwrap().pop_front(); let recvd_ccsds = known_packet_queue.lock().unwrap().pop_front();
assert!(unknown_packet_queue.lock().unwrap().is_empty()); assert!(unknown_packet_queue.lock().unwrap().is_empty());
assert!(recvd_ccsds.is_some()); assert!(recvd_ccsds.is_some());
@ -226,32 +290,25 @@ mod tests {
fn test_as_any_cast() { fn test_as_any_cast() {
let pus_handler = PusHandlerOwnedQueue::default(); let pus_handler = PusHandlerOwnedQueue::default();
let handler_base = BasicApidHandlerOwnedQueue::default(); let handler_base = BasicApidHandlerOwnedQueue::default();
let error_handler = SimpleStdErrorHandler {};
let pus_distrib = PusDistributor { let pus_distrib = PusDistributor {
service_provider: Box::new(pus_handler), service_provider: Box::new(pus_handler),
error_handler: Box::new(error_handler),
}; };
let apid_handler = ApidHandlerOwned { let apid_handler = ApidHandlerOwned {
pus_distrib, pus_distrib,
handler_base, handler_base,
}; };
let mut ccsds_distrib = let mut ccsds_distrib = CcsdsDistributor::new(Box::new(apid_handler));
CcsdsDistributor::new(Box::new(apid_handler), Box::new(error_handler));
let mut sph = SpHeader::tc(0x002, 0x34, 0).unwrap();
let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
let mut test_buf: [u8; 32] = [0; 32]; let mut test_buf: [u8; 32] = [0; 32];
let size = pus_tc let tc_slice = generate_ping_tc(test_buf.as_mut_slice());
.write_to(test_buf.as_mut_slice())
.expect("Error writing TC to buffer"); ccsds_distrib
let tc_slice = &test_buf[0..size]; .pass_tc(tc_slice)
ccsds_distrib.pass_tc(tc_slice); .expect("Passing TC slice failed");
let apid_handler_casted_back: &mut ApidHandlerOwned = ccsds_distrib let apid_handler_casted_back: &mut ApidHandlerOwned = ccsds_distrib
.apid_handler .apid_handler_mut()
.as_mut_any()
.downcast_mut::<ApidHandlerOwned>()
.expect("Cast to concrete type ApidHandler failed"); .expect("Cast to concrete type ApidHandler failed");
assert!(!apid_handler_casted_back assert!(!apid_handler_casted_back
.handler_base .handler_base
@ -259,10 +316,8 @@ mod tests {
.is_empty()); .is_empty());
let handler_casted_back: &mut PusHandlerOwnedQueue = apid_handler_casted_back let handler_casted_back: &mut PusHandlerOwnedQueue = apid_handler_casted_back
.pus_distrib .pus_distrib
.service_provider .service_provider_mut()
.as_mut_any() .expect("Cast to concrete type PusHandlerOwnedQueue failed");
.downcast_mut::<PusHandlerOwnedQueue>()
.expect("Cast to concrete type PusHandler failed");
assert!(!handler_casted_back.pus_queue.is_empty()); assert!(!handler_casted_back.pus_queue.is_empty());
let (service, apid, packet_raw) = handler_casted_back.pus_queue.pop_front().unwrap(); let (service, apid, packet_raw) = handler_casted_back.pus_queue.pop_front().unwrap();
assert_eq!(service, 17); assert_eq!(service, 17);

8
fsrc-example/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "fsrc-example"
version = "0.1.0"
edition = "2021"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
[dependencies.spacepackets]
path = "../spacepackets"

View File

@ -0,0 +1,16 @@
use fsrc_example::{OBSW_SERVER_ADDR, SERVER_PORT};
use spacepackets::tc::PusTc;
use spacepackets::SpHeader;
use std::net::{IpAddr, SocketAddr, UdpSocket};
fn main() {
let mut buf = [0; 32];
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let mut sph = SpHeader::tc(0x02, 0, 0).unwrap();
let pus_tc = PusTc::new_simple(&mut sph, 17, 1, None, true);
let client = UdpSocket::bind("127.0.0.1:7300").expect("Connecting to UDP server failed");
let size = pus_tc.write_to(&mut buf).expect("Creating PUS TC failed");
client
.send_to(&buf[0..size], &addr)
.expect(&*format!("Sending to {:?} failed", addr));
}

View File

@ -0,0 +1,15 @@
use fsrc_example::{OBSW_SERVER_ADDR, SERVER_PORT};
use std::net::{IpAddr, SocketAddr, UdpSocket};
fn main() {
let mut recv_buf = [0; 1024];
let addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
let socket = UdpSocket::bind(&addr).expect("Error opening UDP socket");
loop {
let (num_bytes, src) = socket.recv_from(&mut recv_buf).expect("UDP Receive error");
println!(
"Received TM with len {num_bytes} from {src}: {:x?}",
&recv_buf[0..num_bytes]
);
}
}

4
fsrc-example/src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
use std::net::Ipv4Addr;
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
pub const SERVER_PORT: u16 = 7301;

@ -1 +1 @@
Subproject commit fde3fe9cba62b816e004a174821b2d4760003d23 Subproject commit 35073a45a536051e3852696c501d7afa1b36a808