├── .gitignore ├── tools └── ci │ ├── Cargo.toml │ └── src │ └── main.rs ├── src ├── bin │ └── certs.rs ├── host │ ├── mod.rs │ ├── network_config.rs │ ├── config.rs │ ├── udp.rs │ ├── quic.rs │ └── tcp.rs ├── networks.rs ├── error │ ├── host_operation.rs │ ├── quic.rs │ └── mod.rs ├── node │ ├── tcp │ │ ├── net_config.rs │ │ ├── subscription.rs │ │ ├── mod.rs │ │ ├── active.rs │ │ └── idle.rs │ ├── quic │ │ ├── mod.rs │ │ ├── subscription.rs │ │ ├── active.rs │ │ └── idle.rs │ ├── udp │ │ ├── subscription.rs │ │ ├── mod.rs │ │ ├── active.rs │ │ └── idle.rs │ ├── mod.rs │ ├── config.rs │ └── network_config.rs ├── lib.rs └── msg.rs ├── tests ├── common │ └── mod.rs ├── host.rs └── integration.rs ├── .github └── workflows │ └── rust.yml ├── examples ├── node.rs ├── multiple_nodes.rs ├── subscription.rs ├── quic.rs ├── stress.rs ├── udp.rs ├── host.rs ├── external_runtime.rs └── host_and_single_node.rs ├── Cargo.toml ├── design └── notes.md ├── benches └── criterion.rs ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | store 4 | sync.sh 5 | check.sh 6 | examples/get_ip.rs 7 | .gitignore 8 | examples/node_subscription.rs 9 | logs 10 | .vscode/ 11 | benchmarks/criterion/target -------------------------------------------------------------------------------- /tools/ci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ci" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [features] 10 | quic = [] 11 | 12 | [dependencies] 13 | xshell = "0.2" -------------------------------------------------------------------------------- /src/bin/certs.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "quic")] 2 | use meadow::host::quic::generate_certs; 3 | #[cfg(feature = "quic")] 4 | use meadow::host::quic::QuicCertGenConfig; 5 | 6 | fn main() { 7 | #[cfg(feature = "quic")] 8 | generate_certs(QuicCertGenConfig::default()); 9 | #[cfg(not(feature = "quic"))] 10 | panic!("Must enable the \"quic\" feature to run"); 11 | } 12 | -------------------------------------------------------------------------------- /src/host/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | #[allow(clippy::module_inception)] 3 | pub mod host; 4 | pub mod network_config; 5 | #[cfg(feature = "quic")] 6 | pub mod quic; 7 | 8 | mod tcp; 9 | mod udp; 10 | 11 | pub use crate::host::config::*; 12 | pub use crate::host::host::*; 13 | pub use crate::host::network_config::{QuicConfig, TcpConfig, UdpConfig}; 14 | 15 | #[cfg(feature = "quic")] 16 | pub use crate::host::quic::generate_certs; 17 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | 3 | use meadow::prelude::*; 4 | 5 | /// Example test struct for docs and tests 6 | #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] 7 | #[repr(C)] 8 | pub struct Pose { 9 | pub x: f32, 10 | pub y: f32, 11 | } 12 | 13 | /// Example test struct for docs and tests, incompatible with Pose 14 | #[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)] 15 | pub struct NotPose { 16 | a: isize, 17 | } 18 | -------------------------------------------------------------------------------- /tests/host.rs: -------------------------------------------------------------------------------- 1 | use meadow::prelude::*; 2 | use rand::Rng; 3 | 4 | #[test] 5 | fn host_only_ops() { 6 | let sc = SledConfig::new().temporary(true); 7 | let mut host = HostConfig::default().with_sled_config(sc).build().unwrap(); 8 | let mut rng = rand::thread_rng(); 9 | for _i in 0..10 { 10 | let data: usize = rng.gen(); 11 | host.insert("test", data).unwrap(); 12 | let back: usize = host.get("test").unwrap().data; 13 | dbg!(&back); 14 | assert_eq!(data, back); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/networks.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use std::net::{IpAddr, Ipv4Addr}; 3 | 4 | /// Get the IP address on a network interface for this computer 5 | pub fn get_ip(interface_name: &str) -> Result { 6 | let interface = match pnet_datalink::interfaces() 7 | .into_iter() 8 | .find(|iface| iface.name == interface_name) 9 | { 10 | Some(interfaces) => interfaces, 11 | None => return Err(Error::InvalidInterface), 12 | }; 13 | 14 | let source_ip = match interface 15 | .ips 16 | .iter() 17 | .find(|ip| ip.is_ipv4()) 18 | .map(|ip| match ip.ip() { 19 | IpAddr::V4(ip) => ip, 20 | _ => unreachable!(), 21 | }) { 22 | Some(name) => name, 23 | None => return Err(Error::InvalidInterface), 24 | }; 25 | 26 | Ok(source_ip) 27 | } 28 | -------------------------------------------------------------------------------- /src/error/host_operation.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Display, Formatter}; 2 | use serde::*; 3 | 4 | use thiserror::Error; 5 | 6 | /// Errors coming from unsuccessful Host-side operations 7 | #[derive(Clone, Debug, Error, Eq, PartialEq, Serialize, Deserialize)] 8 | pub enum HostError { 9 | /// Unsuccessful SET operation 10 | #[error("Unsuccessful Host-side SET operation")] 11 | SetFailure, 12 | /// Unsuccessful Host-side GET operation 13 | #[error("Unsuccessful Host-side GET operation")] 14 | GetFailure, 15 | /// Unsuccessful Host connection 16 | #[error("Unsuccessful Host connection")] 17 | ConnectionError, 18 | /// Topic does not exist on Host 19 | #[error("Topic does not exist")] 20 | NonExistentTopic, 21 | } 22 | 23 | /// Enum for successful/failed Host operations 24 | #[derive(Debug, Deserialize, Serialize)] 25 | pub enum HostOperation { 26 | /// Successful Host-side operation 27 | SUCCESS, 28 | /// Failed Host-side operation 29 | FAILURE, 30 | } 31 | -------------------------------------------------------------------------------- /src/node/tcp/net_config.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::marker::PhantomData; 3 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use crate::NetworkConfig; 7 | 8 | impl NetworkConfig { 9 | /// Create a default config for specified network interface on port `25_000` 10 | pub fn default() -> Self { 11 | NetworkConfig { 12 | __interface: PhantomData, 13 | host_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25_000), 14 | max_buffer_size: 1024, 15 | cert_path: None, 16 | key_path: None, 17 | } 18 | } 19 | 20 | /// Define a custom address for the Host to which the Node will connect 21 | pub fn set_host_addr(mut self, host_addr: impl Into) -> Self { 22 | self.host_addr = host_addr.into(); 23 | self 24 | } 25 | 26 | /// Set a max buffer size for Host responses 27 | pub fn set_max_buffer_size(mut self, max_buffer_size: impl Into) -> Self { 28 | self.max_buffer_size = max_buffer_size.into(); 29 | self 30 | } 31 | } -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main, ci ] 6 | pull_request: 7 | branches: [ main ] 8 | types: 9 | - opened 10 | - reopened 11 | - synchronize 12 | - ready_for_review 13 | 14 | env: 15 | CARGO_TERM_COLOR: always 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} 21 | name: build-${{ matrix.toolchain }} 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | toolchain: 26 | - 1.81.0 27 | - stable 28 | 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v2 32 | 33 | - name: Install toolchain 34 | uses: dtolnay/rust-toolchain@master 35 | with: 36 | toolchain: ${{ matrix.toolchain }} 37 | profile: minimal 38 | components: clippy 39 | 40 | - name: Fmt 41 | run: rustup component add rustfmt 42 | 43 | - name: Default CI 44 | run: cargo run -p ci 45 | 46 | - name: QUIC CI 47 | run: cargo run -p ci --features quic 48 | -------------------------------------------------------------------------------- /src/node/tcp/subscription.rs: -------------------------------------------------------------------------------- 1 | use crate::node::network_config::{Blocking, Nonblocking, Tcp}; 2 | use crate::node::{Node, Subscription}; 3 | use crate::prelude::*; 4 | use std::ops::Deref; 5 | 6 | impl Node { 7 | pub async fn get_subscribed_data(&self) -> Result, crate::Error> { 8 | let data = self.subscription_data.lock().await.clone(); 9 | if let Some(msg) = data { 10 | Ok(msg) 11 | } else { 12 | Err(Error::NoSubscriptionValue) 13 | } 14 | } 15 | } 16 | 17 | //---- 18 | 19 | impl Node { 20 | pub fn get_subscribed_data(&self) -> Result, crate::Error> { 21 | let handle = match &self.rt_handle { 22 | Some(handle) => handle, 23 | None => return Err(Error::HandleAccess), 24 | }; 25 | handle.block_on(async { 26 | let data = self.subscription_data.lock().await.clone(); 27 | if let Some(msg) = data { 28 | Ok(msg) 29 | } else { 30 | Err(Error::NoSubscriptionValue) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/node/quic/mod.rs: -------------------------------------------------------------------------------- 1 | mod active; 2 | mod idle; 3 | mod subscription; 4 | 5 | use std::fs::File; 6 | use std::io::BufReader; 7 | use std::net::SocketAddr; 8 | use std::path::PathBuf; 9 | 10 | use crate::error::Quic::*; 11 | use crate::prelude::*; 12 | 13 | use quinn::ClientConfig; 14 | use rustls::Certificate; 15 | 16 | use tracing::*; 17 | 18 | pub fn generate_client_config_from_certs( 19 | cert_path: Option, 20 | ) -> Result { 21 | let mut certs = rustls::RootCertStore::empty(); 22 | 23 | let path = match cert_path { 24 | Some(path) => path, 25 | None => return Err(Error::Quic(NoProvidedCertPath)), 26 | }; 27 | 28 | let f = File::open(path)?; 29 | let mut cert_chain_reader = BufReader::new(f); 30 | let server_certs: Vec = rustls_pemfile::certs(&mut cert_chain_reader) 31 | .unwrap() 32 | .into_iter() 33 | .map(rustls::Certificate) 34 | .collect(); 35 | for cert in server_certs { 36 | if let Err(e) = certs.add(&cert) { 37 | error!("Error adding certificate: {:?}", e); 38 | return Err(Error::Quic(Webpki)); 39 | } 40 | } 41 | 42 | Ok(ClientConfig::with_root_certificates(certs)) 43 | } 44 | -------------------------------------------------------------------------------- /examples/node.rs: -------------------------------------------------------------------------------- 1 | use meadow::node::config::NodeConfig; 2 | use meadow::node::network_config::{Blocking, NetworkConfig, Tcp}; 3 | use meadow::node::{Idle, Node}; 4 | use std::thread; 5 | use std::time::Duration; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | struct Coordinate { 11 | x: f32, 12 | y: f32, 13 | } 14 | 15 | fn main() -> Result<(), meadow::Error> { 16 | let addr = "127.0.0.1:25000".parse::().unwrap(); 17 | let node: Node = NodeConfig::new("my_coordinate") 18 | .with_config(NetworkConfig::::default().set_host_addr(addr)) 19 | .build() 20 | .unwrap(); 21 | let node = node.activate()?; // unwrap(); 22 | 23 | let c = Coordinate { x: 4.0, y: 4.0 }; 24 | node.publish(c)?; 25 | 26 | for i in 0..5 { 27 | // Could get this by reading a GPS, for example 28 | let c = Coordinate { 29 | x: i as f32, 30 | y: i as f32, 31 | }; 32 | node.publish(c)?; 33 | thread::sleep(Duration::from_millis(1_000)); 34 | let result = node.request()?; 35 | println!("Got coordinate: {:?}", result); 36 | } 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /src/node/udp/subscription.rs: -------------------------------------------------------------------------------- 1 | use crate::node::network_config::{Blocking, Nonblocking, Udp}; 2 | use crate::node::Node; 3 | use crate::node::Subscription; 4 | use crate::prelude::*; 5 | use std::ops::Deref; 6 | 7 | impl Node { 8 | // Should actually return a 9 | pub async fn get_subscribed_data(&self) -> Result, crate::Error> { 10 | let data = self.subscription_data.lock().await.clone(); 11 | if let Some(msg) = data { 12 | Ok(msg) 13 | } else { 14 | Err(Error::NoSubscriptionValue) 15 | } 16 | } 17 | } 18 | 19 | impl Node { 20 | // Should actually return a 21 | 22 | pub fn get_subscribed_data(&self) -> Result, crate::Error> { 23 | let handle = match &self.rt_handle { 24 | Some(handle) => handle, 25 | None => return Err(Error::HandleAccess), 26 | }; 27 | 28 | handle.block_on(async { 29 | let data = self.subscription_data.lock().await.clone(); 30 | if let Some(msg) = data { 31 | Ok(msg) 32 | } else { 33 | Err(Error::NoSubscriptionValue) 34 | } 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/node/quic/subscription.rs: -------------------------------------------------------------------------------- 1 | use crate::node::network_config::{Nonblocking, Quic}; 2 | use crate::node::Node; 3 | use crate::node::Subscription; 4 | use crate::prelude::*; 5 | 6 | impl Node { 7 | // Should actually return a 8 | pub async fn get_subscribed_data(&self) -> Result, Error> { 9 | let data = self.subscription_data.clone(); 10 | let data = data.lock().await; 11 | match data.clone() { 12 | Some(data) => Ok(data), 13 | None => Err(Error::NoSubscriptionValue), 14 | } 15 | } 16 | } 17 | 18 | // ---------- 19 | 20 | use crate::node::network_config::Blocking; 21 | 22 | impl Node { 23 | // Should actually return a 24 | pub fn get_subscribed_data(&self) -> Result, Error> { 25 | let data = self.subscription_data.clone(); 26 | let handle = match &self.rt_handle { 27 | Some(handle) => handle, 28 | None => return Err(Error::HandleAccess), 29 | }; 30 | handle.block_on(async { 31 | let data = data.lock().await; 32 | match data.clone() { 33 | Some(data) => Ok(data), 34 | None => Err(Error::NoSubscriptionValue), 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "meadow" 3 | version = "0.4.0" 4 | description = "Robotics-focused middleware for embedded Linux" 5 | authors = ["Christopher Moran "] 6 | edition = "2018" 7 | license = "MPL-2.0" 8 | repository = "https://github.com/quietlychris/meadow" 9 | keywords = ["robotics", "middleware", "autonomy", "serde"] 10 | categories = ["science::robotics", "network-programming"] 11 | 12 | [lib] 13 | name = "meadow" 14 | 15 | [workspace] 16 | exclude = ["benchmarks"] 17 | members = ["tools/ci"] 18 | 19 | [features] 20 | default = [] 21 | quic = ["quinn", "rustls", "rustls-pemfile", "rcgen", "futures-util"] 22 | 23 | [dependencies] 24 | thiserror = "1.0" 25 | # de/serialization and message sending 26 | serde = {version = "1", default-features = false, features = ["derive"]} 27 | postcard = {version = ">=1.1.2", features = ["alloc"]} 28 | chrono = {version = "0.4", features = ["serde"]} 29 | # key value store, networking, and async 30 | sled = "0.34" 31 | pnet_datalink = "0.33" 32 | tokio = { version = "1", features = ["net", "rt-multi-thread", "io-util", "sync", "time"] } 33 | # logging 34 | tracing = "0.1" 35 | # QUIC support (optional) 36 | quinn = {version = "0.9", optional = true} 37 | rustls = { version = "0.20", features = ["dangerous_configuration", "quic"], optional = true} 38 | rustls-pemfile = {version = "1", optional = true} 39 | rcgen = {version = "0.9", optional = true} 40 | futures-util = {version = "0.3", optional = true} 41 | 42 | [dev-dependencies] 43 | # logging to file 44 | tracing-subscriber = {version = "^0.3.17", features = ["env-filter"]} 45 | tracing-appender = "0.2" 46 | # benchmarking 47 | criterion = "0.4" 48 | rand = "0.8" 49 | # Make tokio macro available 50 | tokio = { version = "1", features = ["macros", "signal"] } 51 | rayon = "1" 52 | 53 | [[bench]] 54 | name = "criterion" 55 | harness = false 56 | 57 | # The profile that 'dist' will build with 58 | [profile.dist] 59 | inherits = "release" 60 | lto = "thin" 61 | -------------------------------------------------------------------------------- /examples/multiple_nodes.rs: -------------------------------------------------------------------------------- 1 | use meadow::prelude::*; 2 | 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | /// Example test struct for docs and tests 7 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 8 | struct Pose { 9 | pub x: f32, 10 | pub y: f32, 11 | } 12 | 13 | const LABELS: usize = 36; 14 | fn main() -> Result<(), meadow::Error> { 15 | type N = Tcp; 16 | 17 | let mut host: Host = HostConfig::default().build()?; 18 | host.start()?; 19 | 20 | // Run n publish-subscribe loops in different processes 21 | let mut handles: Vec> = Vec::new(); 22 | for i in 0..LABELS { 23 | let handle = thread::spawn(move || { 24 | let thread_num = i; 25 | let pose = Pose { 26 | x: thread_num as f32, 27 | y: thread_num as f32, 28 | }; 29 | 30 | // Create a node 31 | let node: Node = NodeConfig::new("pose").build().unwrap(); 32 | let node = match node.activate() { 33 | Ok(node) => { 34 | println!("NODE_{} connected successfully", i); 35 | node 36 | } 37 | Err(_e) => { 38 | panic!("NODE_{} did NOT connect successfully", i); 39 | } 40 | }; 41 | 42 | node.publish(pose).unwrap(); 43 | thread::sleep(Duration::from_millis((100 / (i + 1)) as u64)); 44 | let result = node.request().unwrap(); 45 | println!("From thread {}, got: {:?}", thread_num, result.data); 46 | 47 | println!("Thread {} returning!", i); 48 | // thread::sleep(Duration::from_millis(10)); 49 | // std::process::exit(0); 50 | }); 51 | handles.push(handle); 52 | thread::sleep(Duration::from_millis(1)); 53 | } 54 | 55 | // thread::sleep(Duration::from_secs(10)); 56 | 57 | for handle in handles { 58 | handle.join().unwrap(); 59 | } 60 | println!("All threads have joined!"); 61 | host.stop()?; 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /examples/subscription.rs: -------------------------------------------------------------------------------- 1 | use meadow::prelude::*; 2 | use std::time::Duration; 3 | 4 | fn main() -> Result<(), meadow::Error> { 5 | // Set up logging 6 | logging(); 7 | 8 | type N = Tcp; 9 | 10 | let mut host: Host = HostConfig::default().with_udp_config(None).build()?; 11 | host.start()?; 12 | println!("Host should be running in the background"); 13 | 14 | // Get the host up and running 15 | let writer = NodeConfig::::new("subscription") 16 | .build()? 17 | .activate()?; 18 | 19 | // Create a subscription node with a query rate of 10 Hz 20 | let reader = NodeConfig::::new("subscription") 21 | .build()? 22 | .subscribe(Duration::from_millis(500))?; 23 | 24 | // Since subscribed topics are not guaranteed to exist, subscribed nodes always return Option 25 | //let _result = reader.get_subscribed_data(); 26 | //dbg!(_result); 27 | 28 | for i in 0..5usize { 29 | println!("publishing {}", i); 30 | writer.publish(i).unwrap(); 31 | std::thread::sleep(std::time::Duration::from_millis(1000)); 32 | let result = writer.request()?.data; 33 | dbg!(result); 34 | assert_eq!(writer.request()?.data, i); 35 | assert_eq!(reader.get_subscribed_data()?.data, i); 36 | } 37 | 38 | // host.stop()?; 39 | Ok(()) 40 | } 41 | 42 | fn logging() { 43 | use std::{fs::File, sync::Arc}; 44 | use tracing_subscriber::{filter, prelude::*}; 45 | 46 | // A layer that logs events to a file. 47 | let file = File::create("logs/debug.log"); 48 | let file = match file { 49 | Ok(file) => file, 50 | Err(error) => panic!("Error: {:?}", error), 51 | }; 52 | 53 | let log = tracing_subscriber::fmt::layer() 54 | .compact() 55 | .with_ansi(true) 56 | .with_line_number(true) 57 | .with_writer(Arc::new(file)); 58 | 59 | tracing_subscriber::registry() 60 | .with( 61 | log 62 | // Add an `INFO` filter to the stdout logging layer 63 | .with_filter(filter::LevelFilter::INFO), // .with_filter(filter::LevelFilter::WARN) 64 | // .with_filter(filter::LevelFilter::ERROR) 65 | ) 66 | .init(); 67 | } 68 | -------------------------------------------------------------------------------- /examples/quic.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "quic")] 2 | fn main() -> Result<(), meadow::Error> { 3 | use meadow::host::quic::QuicCertGenConfig; 4 | use meadow::prelude::*; 5 | use std::thread; 6 | use std::time::Duration; 7 | use tracing::*; 8 | 9 | logging(); 10 | 11 | generate_certs(QuicCertGenConfig::default()); 12 | let mut host: Host = HostConfig::default().build()?; 13 | host.start()?; 14 | debug!("Host should be running in the background"); 15 | 16 | // Get the writer up and running 17 | let node = NodeConfig::::new("pose") 18 | .build()? 19 | .activate()?; 20 | 21 | // Create a subscription node with a query rate of 10 Hz 22 | let reader = NodeConfig::::new("pose") 23 | .build()? 24 | .subscribe(Duration::from_millis(50))?; 25 | 26 | for i in 0..5 { 27 | node.publish(i)?; 28 | println!("Published {}", i); 29 | let value = node.request()?; 30 | assert_eq!(i, value.data); 31 | println!("QUIC request received with value {:?}", value); 32 | dbg!(node.topics()?); 33 | thread::sleep(Duration::from_millis(100)); 34 | println!("Received reply: {:?}", reader.get_subscribed_data()); 35 | } 36 | 37 | println!( 38 | "The size of an a meadow Host before shutdown is: {}", 39 | std::mem::size_of_val(&host) 40 | ); 41 | 42 | Ok(()) 43 | } 44 | 45 | #[cfg(not(feature = "quic"))] 46 | 47 | fn main() { 48 | panic!("Must enable the \"quic\" feature to run"); 49 | } 50 | 51 | #[cfg(feature = "quic")] 52 | fn logging() { 53 | use std::{fs::File, sync::Arc}; 54 | use tracing_subscriber::{filter, prelude::*}; 55 | 56 | // A layer that logs events to a file. 57 | let file = File::create("logs/debug.log"); 58 | let file = match file { 59 | Ok(file) => file, 60 | Err(error) => panic!("Error: {:?}", error), 61 | }; 62 | 63 | let log = tracing_subscriber::fmt::layer() 64 | .compact() 65 | .with_line_number(true) 66 | .with_writer(Arc::new(file)); 67 | 68 | tracing_subscriber::registry() 69 | .with( 70 | log 71 | // Add an `INFO` filter to the stdout logging layer 72 | .with_filter(filter::LevelFilter::DEBUG), 73 | ) 74 | .init(); 75 | } 76 | -------------------------------------------------------------------------------- /examples/stress.rs: -------------------------------------------------------------------------------- 1 | use meadow::prelude::*; 2 | use rand::Rng; 3 | // use rayon::par_iter; 4 | use std::thread; 5 | use std::time::{Duration, Instant}; 6 | 7 | fn main() -> Result<(), meadow::Error> { 8 | let mut host = HostConfig::default() 9 | .with_sled_config( 10 | SledConfig::default() 11 | .temporary(false) 12 | .path("./logs/stress.sled"), 13 | ) 14 | .build()?; 15 | host.start()?; 16 | 17 | let duration = Duration::from_secs(3); 18 | let n = 10; 19 | 20 | let tcp = thread::spawn(move || { 21 | run_tcp(n, duration).unwrap(); 22 | }); 23 | 24 | let udp = thread::spawn(move || { 25 | run_udp(n, duration).unwrap(); 26 | }); 27 | 28 | thread::sleep(duration); 29 | 30 | if let Err(e) = tcp.join() { 31 | println!("TCP error: {:?}", e); 32 | } 33 | 34 | if let Err(e) = udp.join() { 35 | println!("UDP error: {:?}", e); 36 | } 37 | 38 | let topics = host.topics(); 39 | for topic in &topics { 40 | let db = host.db(); 41 | let tree = db.open_tree(topic.as_bytes()).unwrap(); 42 | println!("Topic {} has {} stored values", topic, tree.len()); 43 | } 44 | 45 | Ok(()) 46 | } 47 | 48 | fn run_tcp(n: usize, duration: Duration) -> Result<(), meadow::Error> { 49 | let mut nodes = Vec::with_capacity(n); 50 | for i in 0..n { 51 | let topic = format!("tcp_{}", i); 52 | let node = NodeConfig::::new(topic).build()?; 53 | let node = node.activate()?; 54 | nodes.push(node); 55 | } 56 | 57 | let start = Instant::now(); 58 | let mut rng = rand::thread_rng(); 59 | while start.elapsed() < duration { 60 | for node in &mut nodes { 61 | node.publish(rng.gen())?; 62 | node.request()?; 63 | } 64 | } 65 | Ok(()) 66 | } 67 | 68 | fn run_udp(n: usize, duration: Duration) -> Result<(), meadow::Error> { 69 | let mut nodes = Vec::with_capacity(n); 70 | for i in 0..n { 71 | let topic = format!("udp_{}", i); 72 | let node = NodeConfig::::new(topic).build()?; 73 | let node = node.activate()?; 74 | nodes.push(node); 75 | } 76 | 77 | let start = Instant::now(); 78 | let mut rng = rand::thread_rng(); 79 | while start.elapsed() < duration { 80 | for node in &mut nodes { 81 | node.publish(rng.gen())?; 82 | node.request()?; 83 | } 84 | } 85 | Ok(()) 86 | } 87 | -------------------------------------------------------------------------------- /examples/udp.rs: -------------------------------------------------------------------------------- 1 | use meadow::prelude::*; 2 | use std::thread; 3 | use std::time::Duration; 4 | // For logging 5 | use std::{fs::File, sync::Arc}; 6 | use tracing_subscriber::filter::LevelFilter; 7 | use tracing_subscriber::EnvFilter; 8 | use tracing_subscriber::{filter, prelude::*}; 9 | 10 | fn main() -> Result<(), meadow::Error> { 11 | logging(); 12 | let mut host = { 13 | let date = chrono::Utc::now(); 14 | let stamp = format!( 15 | "{}_{}_UTC", 16 | date.date_naive(), 17 | date.time().format("%H:%M:%S") 18 | ); 19 | let sled_cfg = SledConfig::default() 20 | .path(format!("./logs/{}", stamp)) 21 | // If we wanted to keep the logs, we'd make this `false` 22 | .temporary(true); 23 | HostConfig::default() 24 | .with_udp_config(Some(UdpConfig::default("lo"))) 25 | .with_tcp_config(None) 26 | .with_sled_config(sled_cfg) 27 | .build()? 28 | }; 29 | host.start()?; 30 | println!("Started host"); 31 | 32 | let node = NodeConfig::::new("num") 33 | .build() 34 | .unwrap() 35 | .activate()?; 36 | node.publish(0 as f32)?; 37 | 38 | let subscriber = NodeConfig::::new("num") 39 | .build() 40 | .unwrap() 41 | .subscribe(Duration::from_millis(1))?; 42 | 43 | for i in 1..5 { 44 | node.publish(i as f32)?; 45 | thread::sleep(Duration::from_millis(10)); 46 | let _result = node.request()?; 47 | //dbg!(node.topics()?); 48 | // dbg!(result); 49 | dbg!(subscriber.get_subscribed_data()?); 50 | } 51 | 52 | host.stop()?; 53 | 54 | Ok(()) 55 | } 56 | 57 | fn logging() { 58 | // A layer that logs events to a file. 59 | let file = File::create("logs/debug.log"); 60 | let file = match file { 61 | Ok(file) => file, 62 | Err(error) => panic!("Error: {:?}", error), 63 | }; 64 | 65 | let filter = EnvFilter::builder() 66 | .with_default_directive(LevelFilter::DEBUG.into()) 67 | .from_env() 68 | .unwrap() 69 | .add_directive("meadow=info".parse().unwrap()); 70 | // .add_directive("sled=none".parse().unwrap()); 71 | 72 | let log = tracing_subscriber::fmt::layer() 73 | .compact() 74 | .with_ansi(false) 75 | .with_line_number(true) 76 | .with_target(false) 77 | .with_writer(Arc::new(file)); 78 | 79 | tracing_subscriber::registry() 80 | .with( 81 | log 82 | // Add an `INFO` filter to the stdout logging layer 83 | // sled logs tons of stuff with DEBUG and TRACE levels 84 | .with_filter(filter::LevelFilter::INFO) // .with_filter(filter::LevelFilter::WARN) 85 | .with_filter(filter), //.with_filter(filter::LevelFilter::ERROR) 86 | ) 87 | .init(); 88 | } 89 | -------------------------------------------------------------------------------- /src/host/network_config.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | /// Configuration for network interfaces 4 | #[derive(Debug, Clone, Eq, PartialEq)] 5 | pub struct NetworkConfig { 6 | pub interface: String, 7 | pub socket_num: u16, 8 | pub max_buffer_size: usize, 9 | pub max_name_size: usize, 10 | } 11 | 12 | impl NetworkConfig { 13 | // Create a default config for specified network interface on port `25_000` 14 | pub fn default(interface: impl Into) -> Self { 15 | NetworkConfig { 16 | interface: interface.into(), 17 | socket_num: 25_000, 18 | max_buffer_size: 10_000, 19 | max_name_size: 100, 20 | } 21 | } 22 | 23 | /// Set the socket number on the network port 24 | pub fn set_socket_num(mut self, socket_num: impl Into) -> NetworkConfig { 25 | self.socket_num = socket_num.into(); 26 | self 27 | } 28 | 29 | /// Set the maximum buffer size for packets intended to be received 30 | pub fn set_max_buffer_size(mut self, max_buffer_size: usize) -> NetworkConfig { 31 | self.max_buffer_size = max_buffer_size; 32 | self 33 | } 34 | 35 | /// Set the maximum buffer size for the name of each topic 36 | pub fn set_max_name_size(mut self, max_name_size: usize) -> NetworkConfig { 37 | self.max_name_size = max_name_size; 38 | self 39 | } 40 | } 41 | 42 | /// Strongly-typed alias of `NetworkConfig` for TCP configuration 43 | pub use NetworkConfig as TcpConfig; 44 | /// Strongly-typed alias of `NetworkConfig` for UDP configuration 45 | pub use NetworkConfig as UdpConfig; 46 | 47 | /// Configuration type containing `NetworkConfig` and certification path information 48 | #[derive(Debug, Clone, Eq, PartialEq)] 49 | pub struct QuicConfig { 50 | pub network_cfg: NetworkConfig, 51 | pub cert_path: PathBuf, 52 | pub key_path: PathBuf, 53 | } 54 | 55 | impl Default for QuicConfig { 56 | fn default() -> Self { 57 | QuicConfig { 58 | network_cfg: NetworkConfig { 59 | interface: "lo".into(), 60 | socket_num: 25_000, 61 | max_buffer_size: 10_000, 62 | max_name_size: 100, 63 | }, 64 | cert_path: Path::new("target").join("cert.pem"), 65 | key_path: Path::new("target").join("priv_key.pem"), 66 | } 67 | } 68 | } 69 | 70 | impl QuicConfig { 71 | /// Create a new `QuicConfig` on a defined network interface 72 | pub fn new(interface: impl Into) -> Self { 73 | QuicConfig { 74 | network_cfg: NetworkConfig { 75 | interface: interface.into(), 76 | socket_num: 25_000, 77 | max_buffer_size: 10_000, 78 | max_name_size: 100, 79 | }, 80 | cert_path: Path::new("target").join("cert.pem"), 81 | key_path: Path::new("target").join("priv_key.pem"), 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // If an async Future goes unused, toss a compile-time error 2 | #![deny(unused_must_use)] 3 | #![deny(rustdoc::broken_intra_doc_links)] 4 | #![deny(rustdoc::missing_crate_level_docs)] 5 | #![allow(unused_imports)] 6 | 7 | //! `meadow` is an experimental robotics-focused publish/request middleware 8 | //! for embedded Linux. It is built with a high preference for catching errors 9 | //! at compile-time over runtime and a focus on developer ergonomics, and can 10 | //! natively operate on any [`serde`](https://serde.rs/)-compatible data type. 11 | //! 12 | //! It uses a star-shaped network topology, with a focus 13 | //! on ease-of-use and transparent design and operation. It is more similar to 14 | //! [ZeroMQ](https://zguide.zeromq.org/docs/chapter1/) than to higher-level frameworks like [ROS/2](https://design.ros2.org/articles/discovery_and_negotiation.html), 15 | //! but uses a central coordination process similar to [MOOS-IvP](https://oceanai.mit.edu/ivpman/pmwiki/pmwiki.php?n=Helm.HelmDesignIntro#section2.4). 16 | //! Meadow currently supports the following messaging patterns over different 17 | //! transport protocols: 18 | //! 19 | //!| Protocol | Publish | Request | Subscribe | Encryption | 20 | //!|----------|-----------|------------|-----------|------------| 21 | //!| TCP | **X** | **X** | **X** | | 22 | //!| UDP | **X** | **X** | **X** | | 23 | //!| QUIC | **X** | **X** | **X** | **X** | 24 | //! 25 | 26 | /// Error types used by Meadow 27 | pub mod error; 28 | /// Central coordination process, which stores published data and responds to requests 29 | pub mod host; 30 | /// Message definitions for publish/request functions 31 | pub mod msg; 32 | /// Network-based utility module 33 | pub mod networks; 34 | /// Objects that publish and request strongly-typed data to named topics on the Host 35 | pub mod node; 36 | 37 | /// Re-export of Serde's `Serialize` and `Deserialize` traits 38 | pub use serde::{Deserialize, Serialize}; 39 | 40 | // Require that the README examples are valid 41 | // Will fail `cargo test` if not 42 | #[doc = include_str!("../README.md")] 43 | #[cfg(doctest)] 44 | pub struct ReadMeDocTests; 45 | 46 | pub use crate::error::Error; 47 | 48 | pub mod prelude { 49 | 50 | pub use crate::{Deserialize, Serialize}; 51 | pub use chrono::*; 52 | 53 | pub use crate::error::Error; 54 | pub use crate::msg::{GenericMsg, Message, Msg, MsgType}; 55 | pub use crate::networks::get_ip; 56 | 57 | pub use crate::host::{Host, HostConfig, SledConfig, Store, UdpConfig}; 58 | pub use crate::node::config::NodeConfig; 59 | pub use crate::node::config::RuntimeConfig; 60 | pub use crate::node::network_config::{Blocking, NetworkConfig, Nonblocking, Tcp, Udp}; 61 | pub use crate::node::{Active, Idle, Node, Subscription}; 62 | pub use sled::Db; 63 | 64 | #[cfg(feature = "quic")] 65 | pub use crate::host::{generate_certs, QuicConfig}; 66 | #[cfg(feature = "quic")] 67 | pub use crate::node::network_config::Quic; 68 | } 69 | -------------------------------------------------------------------------------- /examples/host.rs: -------------------------------------------------------------------------------- 1 | use meadow::prelude::*; 2 | 3 | /// Example test struct for docs and tests 4 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 5 | struct Pose { 6 | pub x: f32, 7 | pub y: f32, 8 | } 9 | 10 | fn main() -> Result<(), meadow::Error> { 11 | logging(); 12 | 13 | #[cfg(feature = "quic")] 14 | { 15 | use meadow::host::generate_certs; 16 | use meadow::host::quic::QuicCertGenConfig; 17 | 18 | generate_certs(QuicCertGenConfig::default()); 19 | } 20 | 21 | // Configure the Host with logging 22 | let mut host = { 23 | let date = chrono::Utc::now(); 24 | let stamp = format!( 25 | "{}_{}_UTC", 26 | date.date_naive(), 27 | date.time().format("%H:%M:%S") 28 | ); 29 | let sled_cfg = SledConfig::default() 30 | .path(format!("./logs/{}", stamp)) 31 | // If we wanted to keep the logs, we'd make this `false` 32 | .temporary(true); 33 | let mut config = HostConfig::default().with_sled_config(sled_cfg); 34 | #[cfg(feature = "quic")] 35 | { 36 | config = config 37 | .with_udp_config(None) 38 | .with_quic_config(Some(QuicConfig::default())) 39 | } 40 | config.build()? 41 | }; 42 | 43 | let n = 5; 44 | for i in 0..n { 45 | let pose = Pose { 46 | x: i as f32, 47 | y: i as f32, 48 | }; 49 | host.insert("pose", pose.clone())?; 50 | assert_eq!(host.get::("pose")?.data, pose); 51 | assert_eq!(host.get_nth_back::("pose", 0)?.data, pose); 52 | } 53 | let back = 3; 54 | let pose = Pose { 55 | x: (n - back) as f32, 56 | y: (n - back) as f32, 57 | }; 58 | // We use "back + 1" because we're zero-indexed 59 | assert_eq!(host.get_nth_back::("pose", back - 1)?.data, pose); 60 | let result = host.get_nth_back::("pose", 10); 61 | match result { 62 | Err(Error::NoNthValue) => (), 63 | _ => panic!("Should not be okay!"), 64 | } 65 | 66 | Ok(()) 67 | } 68 | 69 | fn logging() { 70 | use std::{fs::File, sync::Arc}; 71 | use tracing_subscriber::{filter, prelude::*}; 72 | 73 | // A layer that logs events to a file. 74 | let file = File::create("logs/debug.log"); 75 | let file = match file { 76 | Ok(file) => file, 77 | Err(error) => panic!("Error: {:?}", error), 78 | }; 79 | 80 | let log = tracing_subscriber::fmt::layer() 81 | .compact() 82 | .with_ansi(false) 83 | .with_line_number(true) 84 | .with_writer(Arc::new(file)); 85 | 86 | tracing_subscriber::registry() 87 | .with( 88 | log 89 | // Add an `INFO` filter to the stdout logging layer 90 | .with_filter(filter::LevelFilter::INFO), // .with_filter(filter::LevelFilter::WARN) 91 | // .with_filter(filter::LevelFilter::ERROR) 92 | ) 93 | .init(); 94 | } 95 | -------------------------------------------------------------------------------- /src/node/udp/mod.rs: -------------------------------------------------------------------------------- 1 | mod active; 2 | mod idle; 3 | mod subscription; 4 | 5 | use crate::msg::{GenericMsg, Message, Msg, MsgType}; 6 | use std::convert::TryInto; 7 | use std::sync::Arc; 8 | use tokio::net::UdpSocket; 9 | use tokio::sync::Mutex as TokioMutex; 10 | 11 | use tracing::*; 12 | 13 | use crate::error::Error; 14 | use std::io::{Error as IoError, ErrorKind}; 15 | use std::net::SocketAddr; 16 | 17 | /* #[inline] 18 | #[tracing::instrument(skip(buffer))] 19 | pub async fn await_response( 20 | socket: &UdpSocket, 21 | buffer: Arc>>, 22 | ) -> Result, Error> { 23 | socket.readable().await?; 24 | loop { 25 | let mut buf = buffer.lock().await; 26 | 27 | match socket.recv(&mut buf).await { 28 | Ok(0) => { 29 | info!("await_response received zero bytes"); 30 | continue; 31 | } 32 | Ok(n) => { 33 | // info!("await_response received {} bytes", n); 34 | let bytes = &buf[..n]; 35 | 36 | let generic = postcard::from_bytes::(bytes)?; 37 | match generic.msg_type { 38 | MsgType::Result(result) => { 39 | if let Err(e) = result { 40 | return Err(e); 41 | } 42 | } 43 | _ => { 44 | let msg: Msg = generic.try_into()?; 45 | return Ok(msg); 46 | } 47 | } 48 | } 49 | Err(e) => { 50 | if e.kind() == std::io::ErrorKind::WouldBlock { 51 | error!("Would block"); 52 | } 53 | continue; 54 | } 55 | } 56 | } 57 | } */ 58 | 59 | #[inline] 60 | #[tracing::instrument(skip(buffer))] 61 | pub async fn await_response( 62 | socket: &UdpSocket, 63 | buffer: Arc>>, 64 | ) -> Result { 65 | socket.readable().await?; 66 | loop { 67 | let mut buf = buffer.lock().await; 68 | 69 | match socket.recv(&mut buf).await { 70 | Ok(0) => { 71 | info!("await_response received zero bytes"); 72 | continue; 73 | } 74 | Ok(n) => { 75 | // info!("await_response received {} bytes", n); 76 | let bytes = &buf[..n]; 77 | let msg = postcard::from_bytes::(bytes)?; 78 | return Ok(msg); 79 | } 80 | Err(e) => { 81 | if e.kind() == std::io::ErrorKind::WouldBlock { 82 | error!("Would block"); 83 | } 84 | continue; 85 | } 86 | } 87 | } 88 | } 89 | 90 | #[inline] 91 | async fn send_msg( 92 | socket: &UdpSocket, 93 | packet: Vec, 94 | host_addr: SocketAddr, 95 | ) -> Result { 96 | socket.writable().await?; 97 | // NOTE: This used to be done 10 times in a row to make sure it got through 98 | let n = socket.send_to(&packet, host_addr).await?; 99 | Ok(n) 100 | } 101 | -------------------------------------------------------------------------------- /design/notes.md: -------------------------------------------------------------------------------- 1 | ## Meadow Design Notes 2 | 3 | Each message is serialized into two types of messages: 4 | 1. `Msg`, which is a strongly-typed variant that is primarily used in the user-facing APIs. 5 | 2. `GenericMsg`, which has structural overlap with `Msg`, but carries its data payload as an vector of bytes `Vec`. 6 | 7 | Meadow assumes that the `Host` process is reliable, and can not be crashed by actions on the `Node`. Data flow is generally considered to be push-pull from the `Node`-side; the `Node` should not be receiving data from a `Host` unless it has asked for it, and can not have data arbitrarily pushed to it by any other `Node` on the network. The `Host` is responsible for both logging and exchanging data. 8 | 9 | For all message types, the `Node` operates by: 10 | 11 | 1. Creating a strongly-typed message `Msg`, which carries both a data payload and a requested `Host`-side operation, s denoted by the `MsgType` field. 12 | 2. Converting that message to a `GenericMsg`. 13 | 3. Converting the `GenericMsg` to a vector of bytes `Vec` via `postcard`. 14 | 4. Creating a connection the `Host` located at a given address, which provides both the `Host` with address information of sender. 15 | 5. Sending the `Vec` over the connection to the host 16 | 6. The `Host` receives a vector of bytes `Vec` 17 | 7. The `Host` attempts to deserialize that `Vec` into a `GenericMsg`. 18 | 8. Using the `MsgType` of the `GenericMsg`, the `Host` performs an action, then may or may not send a reply to the `Node`. 19 | 1. `MsgType::Set` 20 | - Action: Insert this message into the database using the `topic` as the tree and the `timestamp` as the key. 21 | - Reply: No reply needed 22 | 2. `MsgType::Get` 23 | - Should be equivalent to a `MsgType::GetNth(0)` operation 24 | - Action: Retrieve the last message in the database on the `topic` and send it to the requester 25 | - Reply: Sends the retrieved message 26 | 3. `MsgType::Subscribe` 27 | - Typically derived from a strongly-typed `Msg`. 28 | - Action: Begin a `Host`-side blocking loop that will retrieve the last message on the `topic` and send it to the subscribed `Node` at a given `rate`. 29 | - Reply: Stream of messages at the specified rate 30 | 4. `MsgType::GetNth` 31 | - Action: Retrieve the n'th message back in the database log on the `topic` and send it to the requester 32 | - Reply: Sends the retrieved message 33 | 5. `MsgType::Topics` 34 | - Action: Create a list of all available topics, format them into `Vec`, then into a `Msg>` and then into `GenericMsg`. 35 | - Reply: Send the created message 36 | 37 | At any point during these operations, a failure can be had, which will be in the form of `meadow::Error` enum. This error type is serializable, and so can be included in `Msg` types. As a result, a failure of any of the `Host`-side actions will result in a `MsgType::Error(e)`-based `GenericMsg` being sent back to the `Node`, which is responsible for propagating this message. 38 | 39 | Meadow cues off of transport-layer level guarantees for if the results of Host-side actions should be communicated back to their originating Node. This means that both `Node` and `Node` expect that the Host will generate a `MsgType::Result` that acts as an `ACK` on the requested operation, even if the operation does not inherently require a response (i.e. `MsgType::Set` doesn't inherently expect a return value). Conversely, `Node` does *not* expect and `ACK`, in keeping with the -------------------------------------------------------------------------------- /src/host/config.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use chrono::Utc; 3 | 4 | // Tokio for async 5 | use tokio::sync::Mutex; // as TokioMutex; 6 | // Multi-threading primitives 7 | use std::sync::Arc; 8 | use std::sync::Mutex as StdMutex; 9 | // Misc other imports 10 | use crate::prelude::*; 11 | use std::result::Result; 12 | 13 | #[doc(hidden)] 14 | pub use sled::Config as SledConfig; 15 | 16 | /// Host configuration structure 17 | #[derive(Debug)] 18 | pub struct HostConfig { 19 | pub sled_cfg: sled::Config, 20 | pub tcp_cfg: Option, 21 | pub udp_cfg: Option, 22 | #[cfg(feature = "quic")] 23 | pub quic_cfg: Option, 24 | } 25 | 26 | impl Default for HostConfig { 27 | /// Create a new `HostConfig` with all default options 28 | fn default() -> HostConfig { 29 | // Default sled database configuration 30 | let date = Utc::now(); 31 | let stamp = format!( 32 | "{}_{}_UTC", 33 | date.date_naive(), 34 | date.time().format("%H:%M:%S") 35 | ); 36 | let sled_cfg = sled::Config::default() 37 | .path(format!("./logs/{}.sled", stamp)) 38 | .temporary(true); 39 | 40 | #[cfg(feature = "quic")] 41 | { 42 | return HostConfig { 43 | sled_cfg, 44 | tcp_cfg: Some(host::TcpConfig::default("lo")), 45 | udp_cfg: None, 46 | quic_cfg: Some(host::QuicConfig::default()), 47 | }; 48 | } 49 | #[cfg(not(feature = "quic"))] 50 | { 51 | return HostConfig { 52 | sled_cfg, 53 | tcp_cfg: Some(host::TcpConfig::default("lo")), 54 | udp_cfg: Some(host::UdpConfig::default("lo")), 55 | }; 56 | } 57 | } 58 | } 59 | 60 | impl HostConfig { 61 | /// Add the Sled database configuration to the Host configuration 62 | pub fn with_sled_config(mut self, sled_cfg: sled::Config) -> HostConfig { 63 | self.sled_cfg = sled_cfg; 64 | self 65 | } 66 | 67 | /// Assign a configuration to the Host `TcpListener` 68 | pub fn with_tcp_config(mut self, tcp_cfg: Option) -> HostConfig { 69 | self.tcp_cfg = tcp_cfg; 70 | self 71 | } 72 | 73 | /// Assign a configuration to the Host `UdpSocket` 74 | pub fn with_udp_config(mut self, udp_cfg: Option) -> HostConfig { 75 | self.udp_cfg = udp_cfg; 76 | self 77 | } 78 | 79 | /// Assign a configuration to the Host's QUIC `Endpoint` server 80 | #[cfg(feature = "quic")] 81 | pub fn with_quic_config(mut self, quic_cfg: Option) -> HostConfig { 82 | self.quic_cfg = quic_cfg; 83 | self 84 | } 85 | 86 | /// Construct a Host based on the `HostConfig`'s parameters 87 | pub fn build(self) -> Result { 88 | let runtime = match tokio::runtime::Runtime::new() { 89 | Ok(runtime) => runtime, 90 | Err(_e) => return Err(Error::RuntimeCreation), 91 | }; 92 | 93 | let connections = Arc::new(StdMutex::new(Vec::new())); 94 | let store: sled::Db = self.sled_cfg.open()?; 95 | 96 | Ok(Host { 97 | cfg: self, 98 | runtime, 99 | task_listen_tcp: None, 100 | connections, 101 | task_listen_udp: None, 102 | #[cfg(feature = "quic")] 103 | task_listen_quic: None, 104 | store, 105 | }) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/node/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod network_config; 3 | pub mod tcp; 4 | pub mod udp; 5 | 6 | #[cfg(feature = "quic")] 7 | pub mod quic; 8 | 9 | /// State marker for a Node that has not been connected to a Host 10 | #[derive(Debug)] 11 | pub struct Idle; 12 | /// State marker for a Node capable of manually sending publish/request messages 13 | #[derive(Debug)] 14 | pub struct Active; 15 | /// State marker for a Node with an active topic subscription 16 | #[derive(Debug)] 17 | pub struct Subscription; 18 | 19 | mod private { 20 | pub trait Sealed {} 21 | 22 | use crate::node::network_config::{Tcp, Udp}; 23 | impl Sealed for Udp {} 24 | impl Sealed for Tcp {} 25 | #[cfg(feature = "quic")] 26 | impl Sealed for crate::node::network_config::Quic {} 27 | 28 | use crate::node::{Active, Idle}; 29 | impl Sealed for Idle {} 30 | impl Sealed for Active {} 31 | 32 | use crate::node::network_config::{Blocking, Nonblocking}; 33 | impl Sealed for Blocking {} 34 | impl Sealed for Nonblocking {} 35 | } 36 | 37 | use tokio::io::AsyncWriteExt; 38 | use tokio::net::{TcpStream, UdpSocket}; 39 | use tokio::runtime::{Handle, Runtime}; 40 | use tokio::sync::Mutex as TokioMutex; 41 | use tokio::task::JoinHandle; 42 | use tokio::time::{sleep, Duration}; 43 | 44 | use tracing::*; 45 | 46 | use std::net::SocketAddr; 47 | 48 | use std::marker::{PhantomData, Sync}; 49 | use std::result::Result; 50 | use std::sync::Arc; 51 | 52 | extern crate alloc; 53 | use alloc::vec::Vec; 54 | use postcard::*; 55 | 56 | use crate::msg::*; 57 | use crate::node::network_config::{Block, Interface}; 58 | use crate::Error; 59 | use chrono::{DateTime, Utc}; 60 | 61 | // Quic stuff 62 | #[cfg(feature = "quic")] 63 | use quinn::Connection as QuicConnection; 64 | #[cfg(feature = "quic")] 65 | use quinn::{ClientConfig, Endpoint}; 66 | #[cfg(feature = "quic")] 67 | use rustls::Certificate; 68 | #[cfg(feature = "quic")] 69 | use std::fs::File; 70 | #[cfg(feature = "quic")] 71 | use std::io::BufReader; 72 | 73 | use crate::node::config::NodeConfig; 74 | use std::sync::Mutex; 75 | 76 | /// Strongly-typed Node capable of publish/request on Host 77 | #[derive(Debug)] 78 | pub struct Node { 79 | pub(crate) __state: PhantomData, 80 | pub(crate) __data_type: PhantomData, 81 | pub(crate) cfg: NodeConfig, 82 | pub(crate) runtime: Option, 83 | pub(crate) rt_handle: Option, 84 | pub(crate) topic: String, 85 | pub(crate) stream: Option, 86 | pub(crate) socket: Option, 87 | pub(crate) buffer: Arc>>, 88 | #[cfg(feature = "quic")] 89 | pub(crate) endpoint: Option, 90 | #[cfg(feature = "quic")] 91 | pub(crate) connection: Option, 92 | pub(crate) subscription_data: Arc>>>, 93 | pub(crate) task_subscribe: Option>, 94 | } 95 | 96 | impl Node { 97 | /// Get reference to the `Node`'s Tokio runtime if one exists 98 | pub fn runtime(&self) -> &Option { 99 | &self.runtime 100 | } 101 | 102 | /// Get reference to the `Node`'s runtime handle if one exists 103 | pub fn rt_handle(&self) -> &Option { 104 | &self.rt_handle 105 | } 106 | 107 | /// Get `Node`'s configuration 108 | pub fn config(&self) -> &NodeConfig { 109 | &self.cfg 110 | } 111 | 112 | /// Get `Node`'s topic 113 | pub fn topic(&self) -> String { 114 | self.topic.clone() 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/error/quic.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Display, Formatter}; 2 | use serde::*; 3 | 4 | use crate::Error; 5 | 6 | /// QUIC-related errors 7 | #[cfg(feature = "quic")] 8 | #[derive(Clone, Debug, thiserror::Error, PartialEq, Serialize, Deserialize)] 9 | pub enum Quic { 10 | /// Error acquiring owned connection 11 | #[error("Error acquiring owned connection")] 12 | Connection, 13 | /// Error reading .pem key file 14 | #[error("Error reading .pem key file")] 15 | ReadKeys, 16 | /// No certificate path was provided 17 | #[error("No certificate path was provided")] 18 | NoProvidedCertPath, 19 | /// Transparent `quin::ConnectError` 20 | #[error("`quinn::ConnectError`-based error")] 21 | ConnectError, 22 | /// Transparent `quinn::ConnectionError` 23 | #[error("`quinn::ConnectionError`-based error")] 24 | ConnectionError, 25 | /// Transparent `quinn::WriteError` 26 | #[error("`quinn::WriteError`-based error")] 27 | WriteError, 28 | /// Transparent `quinn::ReadError` 29 | #[error("`quinn::ReadError`-based error")] 30 | ReadError, 31 | /// Rustls-based webpki error around adding certificate to RootCertStore 32 | #[error("Rustls-based webpki error around adding certificate to RootCertStore")] 33 | Webpki, 34 | /// Transparent `rustls::Error` 35 | #[error("`rustls::Error`-based error")] 36 | RustlsError, 37 | } 38 | 39 | // ===== quinn::ConnectError ===== 40 | impl From for Quic { 41 | fn from(error: quinn::ConnectError) -> Self { 42 | // TO_DO: This could be more fleshed out 43 | Quic::ConnectError 44 | } 45 | } 46 | 47 | impl From for crate::Error { 48 | fn from(error: quinn::ConnectError) -> Self { 49 | // TO_DO: This could be more fleshed out 50 | Error::Quic(error.into()) 51 | } 52 | } 53 | 54 | // ===== quinn::ConnectionError ===== 55 | impl From for Quic { 56 | fn from(error: quinn::ConnectionError) -> Self { 57 | // TO_DO: This could be more fleshed out 58 | Quic::ConnectionError 59 | } 60 | } 61 | 62 | impl From for crate::Error { 63 | fn from(error: quinn::ConnectionError) -> Self { 64 | // TO_DO: This could be more fleshed out 65 | Error::Quic(error.into()) 66 | } 67 | } 68 | 69 | // ===== quinn::WriteError ===== 70 | 71 | impl From for Quic { 72 | fn from(error: quinn::WriteError) -> Self { 73 | // TO_DO: This could be more fleshed out 74 | Quic::WriteError 75 | } 76 | } 77 | 78 | impl From for crate::Error { 79 | fn from(error: quinn::WriteError) -> Self { 80 | // TO_DO: This could be more fleshed out 81 | Error::Quic(error.into()) 82 | } 83 | } 84 | 85 | // ===== quinn::ReadError ===== 86 | impl From for Quic { 87 | fn from(error: quinn::ReadError) -> Self { 88 | // TO_DO: This could be more fleshed out 89 | Quic::ReadError 90 | } 91 | } 92 | 93 | impl From for crate::Error { 94 | fn from(error: quinn::ReadError) -> Self { 95 | // TO_DO: This could be more fleshed out 96 | Error::Quic(error.into()) 97 | } 98 | } 99 | 100 | // ===== rustls::Error ===== 101 | 102 | impl From for Quic { 103 | fn from(error: rustls::Error) -> Self { 104 | // TO_DO: This could be more fleshed out 105 | Quic::RustlsError 106 | } 107 | } 108 | 109 | impl From for crate::Error { 110 | fn from(error: rustls::Error) -> Self { 111 | // TO_DO: This could be more fleshed out 112 | Error::Quic(error.into()) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /examples/external_runtime.rs: -------------------------------------------------------------------------------- 1 | use meadow::prelude::*; 2 | use std::{fs::File, sync::Arc}; 3 | use tracing::*; 4 | use tracing_subscriber::{filter, prelude::*}; 5 | 6 | use std::thread; 7 | use std::time::Duration; 8 | 9 | /// Example test struct for docs and tests 10 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 11 | struct Pose { 12 | pub x: f32, 13 | pub y: f32, 14 | } 15 | 16 | fn main() -> Result<(), meadow::Error> { 17 | logging(); 18 | 19 | // Configure the Host with logging 20 | let mut host = { 21 | let date = chrono::Utc::now(); 22 | let stamp = format!( 23 | "{}_{}_UTC", 24 | date.date_naive(), 25 | date.time().format("%H:%M:%S") 26 | ); 27 | let sled_cfg = SledConfig::default() 28 | .path(format!("./logs/{}", stamp)) 29 | // If we wanted to keep the logs, we'd make this `false` 30 | .temporary(true); 31 | HostConfig::default().with_sled_config(sled_cfg).build()? 32 | }; 33 | host.start()?; 34 | println!("Host should be running in the background"); 35 | 36 | let runtime = tokio::runtime::Runtime::new()?; 37 | let handle = runtime.handle().clone(); 38 | // Get the host up and running 39 | let node: Node = NodeConfig::new("pose") 40 | .with_runtime_config( 41 | RuntimeConfig::default() 42 | .with_owned_runtime(false) 43 | .with_rt_handle(Some(handle)), 44 | ) 45 | .build() 46 | .unwrap(); 47 | let node = node.activate()?; 48 | println!( 49 | "Node's runtime: {:?}, using handle {:?}", 50 | node.runtime(), 51 | node.rt_handle() 52 | ); 53 | debug!("Node should now be connected"); 54 | println!( 55 | "The size of an active meadow Node is: {}", 56 | std::mem::size_of_val(&node) 57 | ); 58 | 59 | // This following two functions should fail to compile 60 | // node.publish(NotPose::default())?; 61 | // let not_pose: NotPose = node.request()?; 62 | 63 | for i in 0..5 { 64 | // Could get this by reading a GPS, for example 65 | let pose = Pose { 66 | x: i as f32, 67 | y: i as f32, 68 | }; 69 | 70 | node.publish(pose.clone())?; 71 | println!("published {}", i); 72 | thread::sleep(Duration::from_millis(250)); 73 | let result: Msg = node.request()?; 74 | dbg!(node.topics()?); // .unwrap(); 75 | println!("Got position: {:?}", result.data); 76 | 77 | assert_eq!(pose, result.data); 78 | } 79 | 80 | println!( 81 | "The size of an a meadow Host before shutdown is: {}", 82 | std::mem::size_of_val(&host) 83 | ); 84 | let topics = host.topics(); 85 | for topic in &topics { 86 | let db = host.db(); 87 | let tree = db.open_tree(topic.as_bytes()).unwrap(); 88 | println!("Topic {} has {} stored values", topic, tree.len()); 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | fn logging() { 95 | // A layer that logs events to a file. 96 | let file = File::create("logs/debug.log"); 97 | let file = match file { 98 | Ok(file) => file, 99 | Err(error) => panic!("Error: {:?}", error), 100 | }; 101 | 102 | let log = tracing_subscriber::fmt::layer() 103 | .compact() 104 | .with_ansi(false) 105 | .with_line_number(true) 106 | .with_writer(Arc::new(file)); 107 | 108 | tracing_subscriber::registry() 109 | .with( 110 | log 111 | // Add an `INFO` filter to the stdout logging layer 112 | .with_filter(filter::LevelFilter::INFO), // .with_filter(filter::LevelFilter::WARN) 113 | // .with_filter(filter::LevelFilter::ERROR) 114 | ) 115 | .init(); 116 | } 117 | -------------------------------------------------------------------------------- /examples/host_and_single_node.rs: -------------------------------------------------------------------------------- 1 | use meadow::prelude::*; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | use tracing::*; 6 | 7 | /// Example test struct for docs and tests 8 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 9 | struct Pose { 10 | pub x: f32, 11 | pub y: f32, 12 | } 13 | 14 | #[tracing::instrument(skip_all)] 15 | fn main() -> Result<(), meadow::Error> { 16 | logging(); 17 | 18 | #[cfg(feature = "quic")] 19 | { 20 | use meadow::host::generate_certs; 21 | use meadow::host::quic::QuicCertGenConfig; 22 | 23 | generate_certs(QuicCertGenConfig::default()); 24 | } 25 | 26 | type N = Tcp; 27 | // Configure the Host with logging 28 | let mut host = { 29 | let date = chrono::Utc::now(); 30 | let stamp = format!( 31 | "{}_{}_UTC", 32 | date.date_naive(), 33 | date.time().format("%H:%M:%S") 34 | ); 35 | let sled_cfg = SledConfig::default() 36 | .path(format!("./logs/{}", stamp)) 37 | // If we wanted to keep the logs, we'd make this `false` 38 | .temporary(true); 39 | let mut config = HostConfig::default().with_sled_config(sled_cfg); 40 | #[cfg(feature = "quic")] 41 | { 42 | config = config 43 | .with_udp_config(None) 44 | .with_quic_config(Some(QuicConfig::default())) 45 | } 46 | config.build()? 47 | }; 48 | host.start()?; 49 | println!("Host should be running in the background"); 50 | 51 | // thread::sleep(Duration::from_secs(60)); 52 | 53 | println!("Starting node"); 54 | // Get the host up and running 55 | let node: Node = NodeConfig::new("pose").build().unwrap(); 56 | println!("Idle node built"); 57 | let node = node.activate().unwrap(); 58 | debug!("Node should now be connected"); 59 | println!( 60 | "The size of an active meadow Node is: {}", 61 | std::mem::size_of_val(&node) 62 | ); 63 | 64 | // This following two functions should fail to compile 65 | // node.publish(NotPose::default())?; 66 | // let not_pose: NotPose = node.request()?; 67 | 68 | for i in 0..5 { 69 | // Could get this by reading a GPS, for example 70 | let pose = Pose { 71 | x: i as f32, 72 | y: i as f32, 73 | }; 74 | 75 | node.publish(pose.clone())?; 76 | println!("published {}", i); 77 | thread::sleep(Duration::from_millis(250)); 78 | let result: Msg = node.request().unwrap(); 79 | dbg!(node.topics()?); // .unwrap(); 80 | println!("Got position: {:?}", result.data); 81 | 82 | assert_eq!(pose, result.data); 83 | } 84 | 85 | println!( 86 | "The size of an a meadow Host before shutdown is: {}", 87 | std::mem::size_of_val(&host) 88 | ); 89 | assert_eq!(host.topics(), node.topics()?.data); 90 | 91 | Ok(()) 92 | } 93 | 94 | fn logging() { 95 | use std::{fs::File, sync::Arc}; 96 | use tracing_subscriber::{filter, prelude::*}; 97 | 98 | // A layer that logs events to a file. 99 | let file = File::create("logs/debug.log"); 100 | let file = match file { 101 | Ok(file) => file, 102 | Err(error) => panic!("Error: {:?}", error), 103 | }; 104 | 105 | let log = tracing_subscriber::fmt::layer() 106 | .compact() 107 | .with_ansi(true) 108 | .with_line_number(true) 109 | .with_writer(Arc::new(file)); 110 | 111 | tracing_subscriber::registry() 112 | .with( 113 | log 114 | // Add an `INFO` filter to the stdout logging layer 115 | .with_filter(filter::LevelFilter::INFO), // .with_filter(filter::LevelFilter::WARN) 116 | // .with_filter(filter::LevelFilter::ERROR) 117 | ) 118 | .init(); 119 | } 120 | -------------------------------------------------------------------------------- /tools/ci/src/main.rs: -------------------------------------------------------------------------------- 1 | use xshell::{cmd, Shell}; 2 | 3 | // Adapted from the Bevy CI pipeline 4 | #[cfg(not(feature = "quic"))] 5 | fn main() { 6 | println!("Running CI without QUIC enabled"); 7 | // When run locally, results may differ from actual CI runs triggered by 8 | // .github/workflows/ci.yml 9 | // - Official CI runs latest stable 10 | // - Local runs use whatever the default Rust is locally 11 | let sh = Shell::new().expect("Couldn't create new xshell Shell environment"); 12 | 13 | // See if any code needs to be formatted 14 | cmd!(sh, "cargo fmt --all -- --check") 15 | .run() 16 | .expect("Please run 'cargo fmt --all' to format your code."); 17 | 18 | // See if clippy has any complaints. 19 | // - Type complexity must be ignored because we use huge templates for queries 20 | // - Could use `-D warnings` if we want to be really pedantic 21 | cmd!(sh, "cargo clippy --package meadow --all-targets -- -A clippy::type_complexity -W clippy::doc_markdown") 22 | .run() 23 | .expect("Please fix clippy errors in output above."); 24 | 25 | sh.set_var("RUSTDOCFLAGS", "-D warnings"); 26 | // Check the documentation format is valid 27 | cmd!(sh, "cargo doc --package meadow") 28 | .run() 29 | .expect("Please check that all documentation follows rustdoc standards"); 30 | 31 | cmd!(sh, "cargo build --examples --all --features=quic") 32 | .run() 33 | .expect("Please check that all documentation follows rustdoc standards"); 34 | 35 | // Run tests 36 | cmd!(sh, "cargo test --workspace -- --nocapture --test-threads=1") 37 | .run() 38 | .expect("Please fix failing tests in output above."); 39 | cmd!( 40 | sh, 41 | "cargo test --workspace --features=quic quic -- --nocapture --test-threads=1" 42 | ) 43 | .run() 44 | .expect("Please fix failing tests in output above."); 45 | // Run certain examples 46 | let examples = vec!["host_and_single_node", "stress", "host"]; 47 | for example in examples { 48 | cmd!(sh, "cargo run --example {example}") 49 | .run() 50 | .expect("Please fix failing tests in output above."); 51 | } 52 | 53 | // Run doc tests: these are ignored by `cargo test` 54 | cmd!(sh, "cargo test --doc --workspace") 55 | .run() 56 | .expect("Please fix failing doc-tests in output above."); 57 | } 58 | 59 | #[cfg(feature = "quic")] 60 | fn main() { 61 | println!("- Running CI with QUIC enabled"); 62 | // When run locally, results may differ from actual CI runs triggered by 63 | // .github/workflows/ci.yml 64 | // - Official CI runs latest stable 65 | // - Local runs use whatever the default Rust is locally 66 | let sh = Shell::new().expect("Couldn't create new xshell Shell environment"); 67 | 68 | // See if any code needs to be formatted 69 | cmd!(sh, "cargo fmt --all -- --check") 70 | .run() 71 | .expect("Please run 'cargo fmt --all' to format your code."); 72 | 73 | // See if clippy has any complaints. 74 | // - Type complexity must be ignored because we use huge templates for queries 75 | cmd!(sh, "cargo clippy --package meadow --all-targets --all-features -- -D warnings -A clippy::type_complexity -W clippy::doc_markdown") 76 | .run() 77 | .expect("Please fix clippy errors in output above."); 78 | 79 | sh.set_var("RUSTDOCFLAGS", "-D warnings"); 80 | // Check the documentation format is valid 81 | cmd!(sh, "cargo doc --package meadow") 82 | .run() 83 | .expect("Please check that all documentation follows rustdoc standards"); 84 | 85 | // Run tests 86 | cmd!( 87 | sh, 88 | "cargo test --workspace --features=quic -- --nocapture --test-threads=1" 89 | ) 90 | .run() 91 | .expect("Please fix failing tests in output above."); 92 | 93 | // Run doc tests: these are ignored by `cargo test` 94 | cmd!(sh, "cargo test --doc --workspace") 95 | .run() 96 | .expect("Please fix failing doc-tests in output above."); 97 | } 98 | -------------------------------------------------------------------------------- /src/node/config.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use std::result::Result; 3 | use std::sync::Arc; 4 | use tokio::runtime::Handle; 5 | use tokio::sync::Mutex as TokioMutex; 6 | 7 | use crate::node::network_config::*; 8 | use crate::node::Node; 9 | use crate::node::{Active, Idle}; 10 | use std::default::Default; 11 | use std::marker::PhantomData; 12 | use std::sync::Mutex; 13 | 14 | /// Defines whether the Node should own it's async runtime or use a provided handle to an external one 15 | #[derive(Debug, Clone)] 16 | pub struct RuntimeConfig { 17 | pub owned_runtime: bool, 18 | pub rt_handle: Option, 19 | } 20 | 21 | impl Default for RuntimeConfig { 22 | fn default() -> Self { 23 | RuntimeConfig { 24 | owned_runtime: true, 25 | rt_handle: None, 26 | } 27 | } 28 | } 29 | 30 | impl RuntimeConfig { 31 | /// Set whether the Node should own its own runtime 32 | pub fn with_owned_runtime(mut self, owned_runtime: bool) -> Self { 33 | self.owned_runtime = owned_runtime; 34 | self 35 | } 36 | 37 | /// Supply an external runtime handle 38 | pub fn with_rt_handle(mut self, rt_handle: Option) -> Self { 39 | self.rt_handle = rt_handle; 40 | self 41 | } 42 | } 43 | 44 | /// Configuration of strongly-typed Node 45 | #[derive(Debug, Clone)] 46 | pub struct NodeConfig { 47 | pub __data_type: PhantomData, 48 | pub topic: Option, 49 | pub network_cfg: NetworkConfig, 50 | pub runtime_cfg: RuntimeConfig, 51 | } 52 | 53 | impl NodeConfig 54 | where 55 | NetworkConfig: Default, 56 | { 57 | /// Create a named, strongly-typed Node without an assigned topic 58 | pub fn new(topic: impl Into) -> NodeConfig { 59 | NodeConfig { 60 | __data_type: PhantomData, 61 | topic: Some(topic.into()), 62 | network_cfg: NetworkConfig::::default(), 63 | runtime_cfg: RuntimeConfig::default(), 64 | } 65 | } 66 | 67 | /// Configure the TCP connection parameteres 68 | pub fn with_config(mut self, network_cfg: NetworkConfig) -> Self { 69 | self.network_cfg = network_cfg; 70 | self 71 | } 72 | 73 | pub fn with_runtime_config(mut self, runtime_cfg: RuntimeConfig) -> Self { 74 | self.runtime_cfg = runtime_cfg; 75 | self 76 | } 77 | } 78 | 79 | impl NodeConfig { 80 | /// Construct a Node from the specified configuration 81 | pub fn build(self) -> Result, Error> { 82 | let (runtime, rt_handle) = { 83 | if self.runtime_cfg.owned_runtime { 84 | let runtime = match tokio::runtime::Builder::new_multi_thread() 85 | .enable_all() 86 | .build() 87 | { 88 | Ok(runtime) => runtime, 89 | Err(_e) => return Err(Error::RuntimeCreation), 90 | }; 91 | let handle = runtime.handle().clone(); 92 | (Some(runtime), Some(handle)) 93 | } else if let Some(rt_handle) = self.runtime_cfg.rt_handle.clone() { 94 | (None, Some(rt_handle)) 95 | } else { 96 | return Err(Error::RuntimeCreation); 97 | } 98 | }; 99 | 100 | let topic = match &self.topic { 101 | Some(topic) => topic.to_owned(), 102 | None => panic!("Nodes must have an assigned topic to be built"), 103 | }; 104 | 105 | let max_buffer_size = self.network_cfg.max_buffer_size; 106 | 107 | Ok(Node:: { 108 | __state: PhantomData::, 109 | __data_type: PhantomData::, 110 | runtime, 111 | rt_handle, 112 | cfg: self, 113 | stream: None, 114 | socket: None, 115 | buffer: Arc::new(TokioMutex::new(vec![0u8; max_buffer_size])), 116 | //buffer: Arc::new(Vec::with_capacity(max_buffer_size)), 117 | #[cfg(feature = "quic")] 118 | endpoint: None, 119 | #[cfg(feature = "quic")] 120 | connection: None, 121 | topic, 122 | subscription_data: Arc::new(TokioMutex::new(None)), 123 | task_subscribe: None, 124 | }) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/node/network_config.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::marker::PhantomData; 3 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use crate::node::private; 7 | pub trait Interface: private::Sealed + Default {} 8 | pub trait Block: private::Sealed + Default + Sized {} 9 | 10 | #[derive(Debug, Clone, Default)] 11 | pub struct Tcp {} 12 | impl Interface for Tcp {} 13 | #[derive(Debug, Clone, Default)] 14 | pub struct Udp {} 15 | impl Interface for Udp {} 16 | 17 | #[derive(Debug, Clone, Default)] 18 | pub struct Quic {} 19 | #[cfg(feature = "quic")] 20 | impl Interface for Quic {} 21 | 22 | #[derive(Debug, Clone, Default)] 23 | pub struct Blocking; 24 | impl Block for Blocking {} 25 | 26 | #[derive(Debug, Clone, Default)] 27 | pub struct Nonblocking; 28 | impl Block for Nonblocking {} 29 | 30 | /// Configuration for network interfaces 31 | #[derive(Clone, Debug)] 32 | pub struct NetworkConfig 33 | where 34 | Interface: Default, 35 | { 36 | __interface: PhantomData, 37 | __block: PhantomData, 38 | /// Socket address for the connected Host 39 | pub host_addr: SocketAddr, 40 | /// Max buffer size that the Node will allocate for Host responses 41 | pub max_buffer_size: usize, 42 | pub cert_path: Option, 43 | pub key_path: Option, 44 | pub send_tries: usize, 45 | } 46 | 47 | impl Default for NetworkConfig { 48 | fn default() -> NetworkConfig { 49 | Self { 50 | __interface: PhantomData::, 51 | __block: PhantomData::, 52 | host_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25_000), 53 | max_buffer_size: 1024, 54 | cert_path: None, 55 | key_path: None, 56 | send_tries: 10, 57 | } 58 | } 59 | } 60 | 61 | impl NetworkConfig { 62 | /// Define a custom address for the Host to which the Node will connect 63 | pub fn set_host_addr(mut self, host_addr: impl Into) -> Self { 64 | self.host_addr = host_addr.into(); 65 | self 66 | } 67 | 68 | /// Set a max buffer size for Host responses 69 | pub fn set_max_buffer_size(mut self, max_buffer_size: impl Into) -> Self { 70 | self.max_buffer_size = max_buffer_size.into(); 71 | self 72 | } 73 | } 74 | 75 | impl Default for NetworkConfig { 76 | fn default() -> NetworkConfig { 77 | Self { 78 | __interface: PhantomData::, 79 | __block: PhantomData::, 80 | host_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25_000), 81 | max_buffer_size: 2048, 82 | cert_path: None, 83 | key_path: None, 84 | send_tries: 10, 85 | } 86 | } 87 | } 88 | 89 | impl NetworkConfig { 90 | /// Define a custom address for the Host to which the Node will connect 91 | pub fn set_host_addr(mut self, host_addr: impl Into) -> Self { 92 | self.host_addr = host_addr.into(); 93 | self 94 | } 95 | 96 | /// Set a max buffer size for Host responses 97 | pub fn set_max_buffer_size(mut self, max_buffer_size: impl Into) -> Self { 98 | self.max_buffer_size = max_buffer_size.into(); 99 | self 100 | } 101 | } 102 | 103 | impl Default for NetworkConfig { 104 | fn default() -> NetworkConfig { 105 | Self { 106 | __interface: PhantomData::, 107 | __block: PhantomData::, 108 | host_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25_000), 109 | max_buffer_size: 4096, 110 | send_tries: 10, 111 | cert_path: Some(Path::new("target").join("cert.pem")), 112 | key_path: Some(Path::new("target").join("priv_key.pem")), 113 | } 114 | } 115 | } 116 | 117 | impl NetworkConfig { 118 | /// Define a custom address for the Host to which the Node will connect 119 | pub fn set_host_addr(mut self, host_addr: impl Into) -> Self { 120 | self.host_addr = host_addr.into(); 121 | self 122 | } 123 | 124 | /// Set a max buffer size for Host responses 125 | pub fn set_max_buffer_size(mut self, max_buffer_size: impl Into) -> Self { 126 | self.max_buffer_size = max_buffer_size.into(); 127 | self 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /benches/criterion.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main}; 2 | use meadow::prelude::*; 3 | use rand::prelude::*; 4 | 5 | pub const KB: usize = 1024; 6 | 7 | /* 8 | fn meadow_instantiation(c: &mut criterion::Criterion) { 9 | /* 10 | c.bench_function("create_host", |b| { 11 | b.iter(|| { 12 | let mut host = HostConfig::default().build().unwrap(); 13 | host.start().unwrap(); 14 | }); 15 | }); 16 | */ 17 | 18 | c.bench_function("create_nodes", |b| { 19 | let mut host = HostConfig::default().build().unwrap(); 20 | host.start().unwrap(); 21 | b.iter(|| { 22 | let node = NodeConfig::::new("SIMPLE_NODE") 23 | .topic("number") 24 | .build() 25 | .expect("Error in create_nodes benchmark"); 26 | let _node = node.activate().unwrap(); 27 | }); 28 | host.stop().unwrap(); 29 | }); 30 | } 31 | */ 32 | 33 | fn tcp_message_sending(c: &mut criterion::Criterion) { 34 | // Open a Host 35 | let sc = SledConfig::new().temporary(true); 36 | let mut host = HostConfig::default().with_sled_config(sc).build().unwrap(); 37 | host.start().unwrap(); 38 | // Create and activate a Node 39 | let node = NodeConfig::::new("number") 40 | .build() 41 | .unwrap(); 42 | let node = node.activate().unwrap(); 43 | let val = 1; 44 | 45 | c.bench_function("tcp_publish_usize", |b| { 46 | b.iter(|| { 47 | node.publish(val).unwrap(); 48 | }); 49 | }); 50 | 51 | let tx = NodeConfig::::new("number") 52 | .build() 53 | .unwrap() 54 | .activate() 55 | .unwrap(); 56 | let rx = NodeConfig::::new("number") 57 | .build() 58 | .unwrap() 59 | .activate() 60 | .unwrap(); 61 | let val = 1.0f32; 62 | 63 | c.bench_function("tcp_publish_request_f32", |b| { 64 | // Open a Host 65 | 66 | b.iter(|| { 67 | tx.publish(val).unwrap(); 68 | match rx.request() { 69 | Ok(_num) => (), 70 | Err(e) => { 71 | eprintln!("{:?}", e); 72 | } 73 | }; 74 | }); 75 | }); 76 | 77 | for size in [1, KB, 2 * KB, 4 * KB, 8 * KB, 64 * KB].iter() { 78 | let bench_name = "msg_".to_owned() + &size.to_string(); 79 | let mut rng = rand::thread_rng(); 80 | // Create and activate a Node 81 | let tx = NodeConfig::>::new("number") 82 | .build() 83 | .unwrap() 84 | .activate() 85 | .unwrap(); 86 | let rx = NodeConfig::>::new("number") 87 | .build() 88 | .unwrap() 89 | .activate() 90 | .unwrap(); 91 | let mut nums: Vec = Vec::with_capacity(*size); 92 | for _ in 0..nums.len() { 93 | nums.push(rng.gen()); 94 | } 95 | 96 | c.bench_function(&bench_name, |b| { 97 | b.iter(|| { 98 | tx.publish(nums.clone()).unwrap(); 99 | let _result = rx.request().unwrap(); 100 | }); 101 | 102 | let result = rx.request().unwrap(); 103 | assert_eq!(nums, result.data); 104 | // host.stop().unwrap(); 105 | }); 106 | } 107 | 108 | host.stop().unwrap(); 109 | } 110 | 111 | fn host_inserts(c: &mut criterion::Criterion) { 112 | let sc = SledConfig::new().temporary(true); 113 | let mut host = HostConfig::default().with_sled_config(sc).build().unwrap(); 114 | host.start().unwrap(); 115 | 116 | for size in [1, KB, 2 * KB, 4 * KB, 8 * KB, 64 * KB].iter() { 117 | let bench_name = "host_insert_".to_owned() + &size.to_string(); 118 | 119 | let mut rng = rand::thread_rng(); 120 | let mut nums: Vec = Vec::with_capacity(*size); 121 | for _ in 0..nums.len() { 122 | nums.push(rng.gen()); 123 | } 124 | 125 | c.bench_function(&bench_name, |b| { 126 | b.iter(|| { 127 | host.insert("number", nums.clone()).unwrap(); 128 | }); 129 | 130 | let result: Msg> = host.get("number").unwrap(); 131 | assert_eq!(nums, result.data); 132 | // host.stop().unwrap(); 133 | }); 134 | } 135 | } 136 | 137 | criterion_group!(benches, tcp_message_sending, host_inserts); 138 | criterion_main!(benches); 139 | -------------------------------------------------------------------------------- /src/error/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | 3 | mod host_operation; 4 | pub use crate::error::host_operation::*; 5 | #[cfg(feature = "quic")] 6 | mod quic; 7 | #[cfg(feature = "quic")] 8 | pub use crate::error::quic::*; 9 | 10 | use core::fmt::{Display, Formatter}; 11 | use serde::*; 12 | use std::str::{FromStr, Utf8Error}; 13 | use thiserror::Error; 14 | 15 | /// Meadow's Error type 16 | #[derive(Debug, Clone, Error, Serialize, Deserialize, PartialEq)] 17 | pub enum Error { 18 | /// Errors based on Host operations 19 | #[error(transparent)] 20 | HostOperation(crate::error::host_operation::HostError), 21 | /// No subscription value exists 22 | #[error("No subscription value exists")] 23 | NoSubscriptionValue, 24 | /// Couldn't achieve lock on shared resource 25 | #[error("Couldn't achieve lock on shared resource")] 26 | LockFailure, 27 | /// Unable to produce IP address from specified interface 28 | #[error("Unable to produce IP address from specified interface")] 29 | InvalidInterface, 30 | /// Transparent `sled` error 31 | #[error("`sled::Error`-derived error")] 32 | Sled(SledError), 33 | /// Unable to create a Tokio runtime 34 | #[error("Unable to create a Tokio runtime")] 35 | RuntimeCreation, 36 | /// Transparent `postcard` 37 | #[error(transparent)] 38 | Postcard(#[from] postcard::Error), 39 | /// Transparent std `Utf-8` error 40 | #[error("`std::str::Utf8Error`-derived error")] 41 | Utf8, 42 | /// Error accessing an owned `TcpStream` 43 | #[error("Error accessing an owned TcpStream")] 44 | AccessStream, 45 | /// Error accessing an owned `UdpSocket` 46 | #[error("Error accessing an owned UdpSocket")] 47 | AccessSocket, 48 | /// `TcpStream` connection attempt failure 49 | #[error("TcpStream connection attempt failure")] 50 | StreamConnection, 51 | /// Transparent QUIC-related errors 52 | #[cfg(feature = "quic")] 53 | #[error(transparent)] 54 | Quic(#[from] crate::error::quic::Quic), 55 | /// Transparent `std::io` error 56 | #[error("std::io::Error-derived error")] 57 | Io { 58 | error_kind: String, 59 | raw_os_error: Option, 60 | }, 61 | #[error("Unable to access Tokio runtime handle")] 62 | HandleAccess, 63 | /// Topic does not exist on Host 64 | #[error("Topic `{0}` does not exist")] 65 | NonExistentTopic(String), 66 | /// Topic does not have value at specific n'th position 67 | #[error("Topic does not have value at specific n'th position")] 68 | NoNthValue, 69 | #[error("Undefined error")] 70 | Undefined, 71 | } 72 | 73 | /// This is the Result type used by meadow. 74 | pub type Result = ::core::result::Result; 75 | 76 | #[derive(Debug, Clone, Serialize, Deserialize, Error, PartialEq, Eq)] 77 | pub enum Sled { 78 | #[error("Collection not found")] 79 | CollectionNotFound, 80 | #[error("Unspecified sled-related error")] 81 | Other, 82 | } 83 | 84 | impl From for Sled { 85 | fn from(value: sled::Error) -> Self { 86 | match value { 87 | sled::Error::CollectionNotFound(ivec) => Sled::CollectionNotFound, 88 | _ => Sled::Other, 89 | } 90 | } 91 | } 92 | 93 | #[derive(Debug, Clone, Serialize, Deserialize, Error, PartialEq, Eq)] 94 | pub enum Postcard { 95 | #[error("Serde serialization error")] 96 | SerdeSerCustom, 97 | #[error("Serde deserialization error")] 98 | SerdeDeCustom, 99 | #[error("Other Postcard error")] 100 | Other, 101 | } 102 | 103 | impl From for Postcard { 104 | fn from(value: postcard::Error) -> Self { 105 | match value { 106 | postcard::Error::SerdeDeCustom => Postcard::SerdeDeCustom, 107 | postcard::Error::SerdeSerCustom => Postcard::SerdeSerCustom, 108 | _ => Postcard::Other, 109 | } 110 | } 111 | } 112 | 113 | impl From for Error { 114 | fn from(error: std::io::Error) -> Self { 115 | let error_kind = error.kind().to_string(); 116 | 117 | Error::Io { 118 | error_kind, 119 | raw_os_error: error.raw_os_error(), 120 | } 121 | } 122 | } 123 | 124 | #[derive(Debug, Clone, Serialize, Deserialize)] 125 | struct IoError { 126 | kind: String, 127 | raw_os_error: i32, 128 | } 129 | 130 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 131 | pub enum SledError { 132 | CollectionNotFound(Vec), 133 | Other, 134 | } 135 | 136 | impl From for Error { 137 | fn from(error: sled::Error) -> Self { 138 | match error { 139 | sled::Error::CollectionNotFound(ivec) => { 140 | let bytes: Vec = ivec.to_vec(); 141 | Error::Sled(SledError::CollectionNotFound(bytes)) 142 | } 143 | _ => Error::Sled(SledError::Other), 144 | } 145 | } 146 | } 147 | 148 | impl From for Error { 149 | fn from(error: Utf8Error) -> Self { 150 | Error::Utf8 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/node/tcp/mod.rs: -------------------------------------------------------------------------------- 1 | mod active; 2 | mod idle; 3 | mod subscription; 4 | 5 | extern crate alloc; 6 | 7 | use tokio::net::{TcpStream, UdpSocket}; 8 | use tokio::runtime::Runtime; 9 | use tokio::sync::Mutex as TokioMutex; 10 | use tokio::task::JoinHandle; 11 | use tokio::time::{sleep, Duration}; 12 | 13 | use tracing::*; 14 | 15 | use std::convert::TryInto; 16 | use std::net::SocketAddr; 17 | 18 | use std::marker::{PhantomData, Sync}; 19 | use std::result::Result; 20 | use std::sync::Arc; 21 | 22 | use alloc::vec::Vec; 23 | use postcard::from_bytes; 24 | use serde::{de::DeserializeOwned, Serialize}; 25 | 26 | use crate::msg::{GenericMsg, Message, Msg, MsgType}; 27 | use crate::node::network_config::Interface; 28 | use crate::Error; 29 | use chrono::{DateTime, Utc}; 30 | 31 | // Quic stuff 32 | #[cfg(feature = "quic")] 33 | use quinn::{ClientConfig, Connection as QuicConnection, Endpoint}; 34 | #[cfg(feature = "quic")] 35 | use rustls::Certificate; 36 | #[cfg(feature = "quic")] 37 | use std::fs::File; 38 | #[cfg(feature = "quic")] 39 | use std::io::BufReader; 40 | 41 | /// Attempts to create an async `TcpStream` connection with a Host at the specified socket address 42 | pub async fn try_connection(host_addr: SocketAddr) -> Result { 43 | let mut connection_attempts = 0; 44 | let mut stream: Option = None; 45 | while connection_attempts < 5 { 46 | match TcpStream::connect(host_addr).await { 47 | Ok(my_stream) => { 48 | stream = Some(my_stream); 49 | break; 50 | } 51 | Err(e) => { 52 | connection_attempts += 1; 53 | sleep(Duration::from_millis(1_000)).await; 54 | warn!("{:?}", e); 55 | } 56 | } 57 | } 58 | 59 | match stream { 60 | Some(stream) => Ok(stream), 61 | None => Err(Error::StreamConnection), 62 | } 63 | } 64 | 65 | /// Run the initial Node <=> Host connection handshake 66 | pub async fn handshake(stream: TcpStream, topic: String) -> Result { 67 | loop { 68 | if let Ok(()) = stream.writable().await { 69 | match stream.try_write(topic.as_bytes()) { 70 | Ok(_n) => { 71 | debug!("{}: Wrote {} bytes to host", topic, _n); 72 | debug!("{}: Successfully connected to host", topic); 73 | break; 74 | } 75 | Err(e) => { 76 | if e.kind() == std::io::ErrorKind::WouldBlock { 77 | } else { 78 | error!("NODE handshake error: {:?}", e); 79 | } 80 | } 81 | } 82 | }; 83 | } 84 | // TO_DO: Is there a better way to do this? 85 | // Pause after connection to avoid accidentally including published data in initial handshake 86 | sleep(Duration::from_millis(20)).await; 87 | 88 | Ok(stream) 89 | } 90 | 91 | /// Send a `GenericMsg` of `MsgType` from the Node to the Host 92 | #[inline] 93 | pub async fn send_msg(stream: &TcpStream, packet: Vec) -> Result<(), Error> { 94 | stream.writable().await?; 95 | 96 | // Write the request 97 | // TO_DO: This should be a loop with a maximum number of attempts 98 | loop { 99 | match stream.try_write(&packet) { 100 | Ok(_n) => { 101 | // debug!("Node successfully wrote {}-byte request to host",n); 102 | break; 103 | } 104 | Err(_e) => { 105 | // if e.kind() == std::io::ErrorKind::WouldBlock {} 106 | continue; 107 | } 108 | } 109 | } 110 | Ok(()) 111 | } 112 | 113 | /// Set Node to wait for response from Host, with data to be deserialized into `Msg`-type 114 | // #[tracing::instrument] 115 | /* #[inline] 116 | pub async fn await_response( 117 | stream: &TcpStream, 118 | buf: &mut [u8], //max_buffer_size: usize, 119 | ) -> Result, Error> { 120 | // TO_DO: This can be made cleaner 121 | loop { 122 | if let Err(e) = stream.readable().await { 123 | error!("{}", e); 124 | } 125 | match stream.try_read(buf) { 126 | Ok(0) => continue, 127 | Ok(n) => { 128 | let bytes = &buf[..n]; 129 | let generic = from_bytes::(bytes)?; 130 | match generic.msg_type { 131 | MsgType::Result(result) => { 132 | if let Err(e) = result { 133 | return Err(e); 134 | } 135 | } 136 | _ => { 137 | let specialized: Msg = generic.try_into()?; 138 | return Ok(specialized); 139 | } 140 | } 141 | } 142 | Err(_e) => { 143 | // if e.kind() == std::io::ErrorKind::WouldBlock {} 144 | debug!("Would block"); 145 | continue; 146 | } 147 | } 148 | } 149 | } */ 150 | 151 | #[inline] 152 | pub async fn await_response( 153 | stream: &TcpStream, 154 | buf: &mut [u8], //max_buffer_size: usize, 155 | ) -> Result { 156 | // TO_DO: This can be made cleaner 157 | loop { 158 | if let Err(e) = stream.readable().await { 159 | error!("{}", e); 160 | } 161 | match stream.try_read(buf) { 162 | Ok(0) => continue, 163 | Ok(n) => { 164 | let bytes = &buf[..n]; 165 | let msg = from_bytes::(bytes)?; 166 | return Ok(msg); 167 | } 168 | Err(_e) => { 169 | // if e.kind() == std::io::ErrorKind::WouldBlock {} 170 | debug!("Would block"); 171 | continue; 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/host/udp.rs: -------------------------------------------------------------------------------- 1 | // Tokio for async 2 | use tokio::net::UdpSocket; 3 | use tokio::runtime::Handle; 4 | use tokio::sync::Mutex; 5 | use tokio::time::{sleep, Duration}; // as TokioMutex; 6 | // Tracing for logging 7 | use tracing::*; 8 | // Postcard is the default de/serializer 9 | use crate::error::Error; 10 | use postcard::*; 11 | // Multi-threading primitives 12 | use std::sync::Arc; 13 | // Misc other imports 14 | use chrono::Utc; 15 | 16 | use crate::host::GenericStore; 17 | use crate::prelude::*; 18 | use std::convert::TryInto; 19 | 20 | /// Host process for handling incoming connections from Nodes 21 | #[tracing::instrument(skip(db))] 22 | #[inline] 23 | pub async fn process_udp( 24 | rt_handle: Handle, 25 | socket: UdpSocket, 26 | mut db: sled::Db, 27 | max_buffer_size: usize, 28 | ) { 29 | let mut buf = vec![0u8; max_buffer_size]; 30 | let s = Arc::new(socket); 31 | 32 | loop { 33 | // dbg!(&count); 34 | let s = s.clone(); 35 | match s.recv_from(&mut buf).await { 36 | Ok((0, _)) => break, // TO_DO: break or continue? 37 | Ok((n, return_addr)) => { 38 | let bytes = &buf[..n]; 39 | let msg: GenericMsg = match from_bytes(bytes) { 40 | Ok(msg) => msg, 41 | Err(e) => { 42 | error!("Had received Msg of {} bytes: {:?}, Error: {}", n, bytes, e); 43 | continue; 44 | } 45 | }; 46 | 47 | match msg.msg_type { 48 | MsgType::Set => { 49 | if let Err(e) = db.insert_generic(msg) { 50 | error!("{}", e); 51 | } 52 | } 53 | MsgType::Get => { 54 | let response = match db.get_generic_nth(&msg.topic, 0) { 55 | Ok(g) => g, 56 | Err(e) => GenericMsg::result(Err(e)), 57 | }; 58 | 59 | if let Ok(return_bytes) = response.as_bytes() { 60 | if let Ok(()) = s.writable().await { 61 | if let Err(e) = s.try_send_to(&return_bytes, return_addr) { 62 | error!("Error sending data back on UDP/GET: {}", e) 63 | }; 64 | }; 65 | } 66 | } 67 | MsgType::GetNth(n) => { 68 | let response = match db.get_generic_nth(&msg.topic, n) { 69 | Ok(g) => g, 70 | Err(e) => GenericMsg::result(Err(e)), 71 | }; 72 | 73 | if let Ok(return_bytes) = response.as_bytes() { 74 | if let Ok(()) = s.writable().await { 75 | if let Err(e) = s.try_send_to(&return_bytes, return_addr) { 76 | error!("Error sending data back on UDP/GET: {}", e) 77 | }; 78 | }; 79 | } 80 | } 81 | MsgType::Topics => { 82 | let response = match db.topics() { 83 | Ok(mut topics) => { 84 | topics.sort(); 85 | let msg = Msg::new(MsgType::Topics, "", topics); 86 | match msg.to_generic() { 87 | Ok(msg) => msg, 88 | Err(e) => GenericMsg::result(Err(e)), 89 | } 90 | } 91 | Err(e) => GenericMsg::result(Err(e)), 92 | }; 93 | 94 | if let Ok(return_bytes) = response.as_bytes() { 95 | if let Ok(()) = s.writable().await { 96 | if let Err(e) = s.try_send_to(&return_bytes, return_addr) { 97 | error!("Error sending data back on UDP/GET: {}", e) 98 | }; 99 | }; 100 | } 101 | } 102 | MsgType::Subscribe => { 103 | let specialized: Msg = msg.clone().try_into().unwrap(); 104 | let rate = specialized.data; 105 | 106 | let db = db.clone(); 107 | rt_handle.spawn(async move { 108 | loop { 109 | let response = match db.get_generic_nth(&msg.topic, 0) { 110 | Ok(g) => g, 111 | Err(e) => GenericMsg::result(Err(e)), 112 | }; 113 | 114 | if let Ok(return_bytes) = response.as_bytes() { 115 | if let Ok(()) = s.writable().await { 116 | if let Err(e) = s.try_send_to(&return_bytes, return_addr) { 117 | error!("Error sending data back on UDP/GET: {}", e) 118 | }; 119 | }; 120 | } 121 | 122 | sleep(rate).await; 123 | } 124 | }); 125 | } 126 | _ => {} 127 | } 128 | } 129 | Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { 130 | // println!("Error::WouldBlock: {:?}", e); 131 | continue; 132 | } 133 | Err(e) => { 134 | // println!("Error: {:?}", e); 135 | error!("Error: {:?}", e); 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/host/quic.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{ 2 | Error, HostOperation, 3 | Quic::{self, *}, 4 | }; 5 | use crate::host::GenericStore; 6 | use crate::prelude::*; 7 | use futures_util::lock::Mutex; 8 | use futures_util::StreamExt; 9 | use quinn::Connection as QuicConnection; 10 | use std::convert::TryInto; 11 | use std::sync::{Arc, Mutex as StdMutex}; 12 | use tokio::sync::Mutex as TokioMutex; 13 | use tokio::time::{sleep, Duration}; 14 | 15 | use chrono::Utc; 16 | use postcard::from_bytes; 17 | use postcard::to_allocvec; 18 | use quinn::{Endpoint, RecvStream, SendStream, ServerConfig}; 19 | use std::path::PathBuf; 20 | use std::{fs, fs::File, io::BufReader}; 21 | use tracing::*; 22 | 23 | /// Configuration struct for generating QUIC private key and certificates 24 | #[derive(Debug, Clone)] 25 | pub struct QuicCertGenConfig { 26 | subject_alt_names: Vec, 27 | cert_pem_path: PathBuf, 28 | priv_key_pem_path: PathBuf, 29 | } 30 | 31 | impl Default for QuicCertGenConfig { 32 | fn default() -> QuicCertGenConfig { 33 | QuicCertGenConfig { 34 | subject_alt_names: vec!["localhost".into()], 35 | cert_pem_path: "target/cert.pem".into(), 36 | priv_key_pem_path: "target/priv_key.pem".into(), 37 | } 38 | } 39 | } 40 | 41 | pub fn generate_certs(config: QuicCertGenConfig) { 42 | let cert = rcgen::generate_simple_self_signed(config.subject_alt_names) 43 | .expect("Error generating self-signed certificate"); 44 | let cert_pem = cert.serialize_pem().expect("Error serialzing "); 45 | fs::write(config.cert_pem_path, cert_pem).expect("Error writing certificate to file"); 46 | 47 | let priv_key_pem = cert.serialize_private_key_pem(); 48 | fs::write(config.priv_key_pem_path, priv_key_pem).expect("Error writing private key to file"); 49 | } 50 | 51 | pub fn read_certs_from_file( 52 | cert_path: impl Into, 53 | key_path: impl Into, 54 | ) -> Result<(Vec, rustls::PrivateKey), crate::Error> { 55 | let cert_file = File::open::(cert_path.into())?; 56 | 57 | let mut cert_chain_reader = BufReader::new(cert_file); 58 | let certs = rustls_pemfile::certs(&mut cert_chain_reader)?; 59 | 60 | let certs = certs.into_iter().map(rustls::Certificate).collect(); 61 | 62 | let key_file = File::open(key_path.into())?; 63 | 64 | let mut key_reader = BufReader::new(key_file); 65 | // Since our private key file starts with "BEGIN PRIVATE KEY" 66 | let mut keys = rustls_pemfile::pkcs8_private_keys(&mut key_reader)?; 67 | 68 | if keys.len() == 1 { 69 | let key = rustls::PrivateKey(keys.remove(0)); 70 | Ok((certs, key)) 71 | } else { 72 | Err(Error::Quic(ReadKeys)) 73 | } 74 | } 75 | 76 | pub async fn process_quic(stream: (SendStream, RecvStream), mut db: sled::Db, buf: &mut [u8]) { 77 | let (mut tx, mut rx) = stream; 78 | 79 | if let Ok(Some(n)) = rx.read(buf).await { 80 | let bytes = &buf[..n]; 81 | let msg: GenericMsg = match from_bytes(bytes) { 82 | Ok(msg) => msg, 83 | Err(e) => { 84 | error!("Had received Msg of {} bytes: {:?}, Error: {}", n, bytes, e); 85 | panic!("{}", e); 86 | } 87 | }; 88 | info!("{:?}", &msg); 89 | 90 | match msg.msg_type { 91 | MsgType::Result(result) => { 92 | if let Err(e) = result { 93 | error!("Received {}", e); 94 | } 95 | } 96 | MsgType::Set => { 97 | let response = GenericMsg::result(db.insert_generic(msg)); 98 | if let Ok(return_bytes) = response.as_bytes() { 99 | if let Err(e) = tx.write(&return_bytes).await { 100 | error!("{}", e); 101 | } 102 | } 103 | } 104 | MsgType::Get => { 105 | let response = match db.get_generic_nth(&msg.topic, 0) { 106 | Ok(g) => g, 107 | Err(e) => GenericMsg::result(Err(e)), 108 | }; 109 | if let Ok(return_bytes) = response.as_bytes() { 110 | if let Err(e) = tx.write(&return_bytes).await { 111 | error!("{}", e); 112 | } 113 | } 114 | } 115 | MsgType::GetNth(n) => { 116 | let response = match db.get_generic_nth(&msg.topic, n) { 117 | Ok(g) => g, 118 | Err(e) => GenericMsg::result(Err(e)), 119 | }; 120 | if let Ok(return_bytes) = response.as_bytes() { 121 | if let Err(e) = tx.write(&return_bytes).await { 122 | error!("{}", e); 123 | } 124 | } 125 | } 126 | MsgType::Topics => { 127 | let response = match db.topics() { 128 | Ok(mut topics) => { 129 | topics.sort(); 130 | let msg = Msg::new(MsgType::Topics, "", topics); 131 | match msg.to_generic() { 132 | Ok(msg) => msg, 133 | Err(e) => GenericMsg::result(Err(e)), 134 | } 135 | } 136 | Err(e) => GenericMsg::result(Err(e)), 137 | }; 138 | if let Ok(return_bytes) = response.as_bytes() { 139 | if let Err(e) = tx.write(&return_bytes).await { 140 | error!("{}", e); 141 | } 142 | } 143 | } 144 | MsgType::Subscribe => { 145 | let specialized: Msg = msg.clone().try_into().unwrap(); 146 | let rate = specialized.data; 147 | 148 | loop { 149 | let response = match db.get_generic_nth(&msg.topic, 0) { 150 | Ok(g) => g, 151 | Err(e) => GenericMsg::result(Err(e)), 152 | }; 153 | if let Ok(return_bytes) = response.as_bytes() { 154 | if let Err(e) = tx.write(&return_bytes).await { 155 | error!("{}", e); 156 | } 157 | } 158 | sleep(rate).await; 159 | } 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/meadow.svg)](https://crates.io/crates/meadow) 2 | [![Documentation](https://docs.rs/meadow/badge.svg)](https://docs.rs/meadow) 3 | ![CI](https://github.com/quietlychris/meadow/actions/workflows/rust.yml/badge.svg) 4 | [![License: MPL 2.0](https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg)](https://opensource.org/licenses/MPL-2.0) 5 | [![MSRV](https://img.shields.io/badge/MSRV-v1.81.0-blue)](https://github.com/foresterre/cargo-msrv) 6 | 7 | # Meadow 8 | 9 | `meadow` is an experimental robotics-focused middleware for embedded Linux. It is built with a high preference for catching errors at compile-time over runtime and a focus on developer ergonomics, and can natively operate on any [`serde`](https://serde.rs/)-compatible data type. 10 | 11 | ```rust 12 | use meadow::prelude::*; 13 | 14 | // `meadow` should be able to operate on any `serde`-compatible data types 15 | // (the standard library Debug and Clone traits are also required) 16 | #[derive(Debug, Clone, Serialize, Deserialize)] 17 | struct Coordinate { 18 | x: f32, 19 | y: f32, 20 | } 21 | 22 | fn main() -> Result<(), meadow::Error> { 23 | // The Host is running on localhost, but any network interface such as WiFi 24 | // or Ethernet are available as well 25 | let mut host: Host = HostConfig::default().build()?; 26 | host.start()?; 27 | // Other tasks can operate while the host is running in the background 28 | 29 | // Build a Node. Nodes can be either Blocking or Nonblocking and operate 30 | // over UDP, TCP, or QUIC as interfaces, which are proxies for un/reliable transport 31 | let addr = "127.0.0.1:25000".parse::().unwrap(); 32 | let node: Node = NodeConfig::new("position") 33 | .with_config(NetworkConfig::::default().set_host_addr(addr)) 34 | .build()?; 35 | // Nodes use strict typestates; without using the activate() method first, 36 | // the compiler won't let allow publish() or request() methods on an Idle Node 37 | let node: Node = node.activate()?; 38 | 39 | // Since Nodes are statically-typed, the following lines would fail at 40 | // compile-time due to type errors 41 | // node.publish(1usize).unwrap() 42 | // let result: bool = node.request().unwrap(); 43 | 44 | node.publish(Coordinate { x: 0.0, y: 0.0 })?; 45 | 46 | // Nodes can also be subscribers, which will request topic updates from the Host 47 | // at a given rate 48 | let subscriber = NodeConfig::::new("position") 49 | .build()? 50 | .subscribe(std::time::Duration::from_micros(100))?; 51 | 52 | for i in 0..5 { 53 | // Could get this by reading a GPS, for example 54 | let c = Coordinate { 55 | x: i as f32, 56 | y: i as f32, 57 | }; 58 | node.publish(c)?; 59 | let result: Msg = node.request()?; 60 | // or could use the value held by the subscribed node 61 | let subscription = subscriber.get_subscribed_data(); 62 | println!("request: {:?}, subscription: {:?}", result, subscription); 63 | } 64 | 65 | Ok(()) 66 | } 67 | ``` 68 | 69 | ## Messaging Patterns 70 | 71 | Meadow is more similar to [ZeroMQ](https://zguide.zeromq.org/docs/chapter1/) than to higher-level frameworks like [ROS/2](https://design.ros2.org/articles/discovery_and_negotiation.html), but uses central coordination process similar to [MOOS-IvP](https://oceanai.mit.edu/ivpman/pmwiki/pmwiki.php?n=Helm.HelmDesignIntro#section2.4), resulting in a star-shaped network topology. 72 | 73 | meadow currently supports the following messaging patterns: 74 | 75 | | Protocol | Publish | Request | Subscribe | Encryption | 76 | |----------|-----------|------------|-----------|------------| 77 | | TCP | **X** | **X** | **X** | | 78 | | UDP | **X** | **X** | **X** | | 79 | | QUIC | **X** | **X** | **X** | **X** | 80 | 81 | Meadow's subscriber functionality currently works a bit differently than many other middlewares; rather than having the most recent data on the subscribed topic pushed to it by the Host upon receive, the Host will the most recent data subscribed topic as a requested rate to the Node, which will cache it locally to be available on-demand rather than on-request. 82 | 83 | ## Key Dependencies 84 | Under the hood, `meadow` relies on: 85 | * [`sled`](https://github.com/spacejam/sled): High-performance embedded, thread-safe database 86 | * [`tokio`](https://tokio.rs): Asynchronous runtime, enabling a large number of simultaneous connections 87 | * [`postcard`](https://github.com/jamesmunns/postcard): Efficient `#![no_std]`-compatible, [serde](https://serde.rs/)-based de/serializer designed for embedded or constrained environments. `meadow` should be able to operate native on any `serde`-compatible data types. 88 | 89 | ## Benchmarks 90 | Preliminary benchmark data is showing round-trip message times (publish-request-reply) on `localhost` using the `--release` compilation profile, on the README's `Coordinate` data (strongly-typed, 8 bytes) to be <100 microseconds. Statistical benchmarks on different data profiles can be run via [`criterion`](https://github.com/bheisler/criterion.rs) via `cargo bench`. 91 | 92 | If you are doing robotics development, `meadow` is probably fast enough to move your data around (unless you're trying to do something like video streaming, in which case you should probably be using dedicated endpoints). 93 | 94 | ## Stability 95 | As mentioned above, this library should be considered *experimental*. While the goal is eventually to make this available at a level of maturity, stability, and reliability of other middlewares, `meadow` is not there yet. This library is being used as a dependency for robotics research, with interprocess communication focused on dozens of nodes on `localhost` or a few over a WLAN connection. While `meadow` can work for other use-cases, it has not been extensively tested in those areas. If you are using this library in other areas and come across issues or unexpected behavior, well-formatted bug reports or pull requests addressing those problems are welcomed. 96 | 97 | ## Additional Resources 98 | The following projects are built with Meadow: 99 | - [Turtlesim](https://github.com/quietlychris/turtlesim): Simple 2D autonomy simulator 100 | - [Orientation](https://github.com/quietlychris/orientation): Real-time 3D orientation visualization of a BNO055 IMU using Meadow and Bevy 101 | 102 | ## License 103 | 104 | This library is licensed under the [Mozilla Public License, version 2.0](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) (MPL-2.0) 105 | -------------------------------------------------------------------------------- /src/host/tcp.rs: -------------------------------------------------------------------------------- 1 | use tokio::io::AsyncWriteExt; 2 | // Tokio for async 3 | use tokio::net::TcpStream; 4 | use tokio::sync::Mutex; 5 | use tokio::time::{sleep, Duration}; // as TokioMutex; 6 | // Tracing for logging 7 | use tracing::*; 8 | // Postcard is the default de/serializer 9 | use postcard::*; 10 | // Multi-threading primitives 11 | use std::sync::Arc; 12 | // Misc other imports 13 | use chrono::Utc; 14 | 15 | use crate::error::{Error, HostOperation}; 16 | use crate::host::GenericStore; 17 | use crate::prelude::*; 18 | use std::convert::TryInto; 19 | use std::result::Result; 20 | 21 | /// Initiate a TCP connection with a Node 22 | #[inline] 23 | #[tracing::instrument] 24 | pub async fn handshake( 25 | stream: TcpStream, 26 | max_buffer_size: usize, 27 | max_name_size: usize, 28 | ) -> Result<(TcpStream, String), Error> { 29 | // Handshake 30 | let mut buf = vec![0u8; max_buffer_size]; 31 | // debug!("Starting handshake"); 32 | let mut _name: String = String::with_capacity(max_name_size); 33 | 34 | stream.readable().await?; 35 | 36 | let n = stream.try_read_buf(&mut buf)?; 37 | let name = std::str::from_utf8(&buf[..n])?.to_string(); 38 | 39 | // debug!("Returning from handshake: ({:?}, {})", &stream, &_name); 40 | Ok((stream, name)) 41 | } 42 | 43 | /// Host process for handling incoming connections from Nodes 44 | #[tracing::instrument(skip_all)] 45 | #[inline] 46 | pub async fn process_tcp(stream: TcpStream, mut db: sled::Db, max_buffer_size: usize) { 47 | let mut buf = vec![0u8; max_buffer_size]; 48 | loop { 49 | if let Err(e) = stream.readable().await { 50 | error!("{}", e); 51 | } 52 | // dbg!(&count); 53 | match stream.try_read(&mut buf) { 54 | Ok(0) => break, // TO_DO: break or continue? 55 | Ok(n) => { 56 | if let Err(e) = stream.writable().await { 57 | error!("{}", e); 58 | } 59 | 60 | let bytes = &buf[..n]; 61 | let msg: GenericMsg = match from_bytes(bytes) { 62 | Ok(msg) => { 63 | info!("{:?}", msg); 64 | msg 65 | } 66 | Err(e) => { 67 | error!("Had received Msg of {} bytes: {:?}, Error: {}", n, bytes, e); 68 | panic!("Had received Msg of {} bytes: {:?}, Error: {}", n, bytes, e); 69 | } 70 | }; 71 | 72 | info!("{:?}", msg.msg_type); 73 | 74 | match &msg.msg_type { 75 | MsgType::Subscribe => { 76 | start_subscription(msg.clone(), db.clone(), &stream).await; 77 | } 78 | MsgType::Get => { 79 | let response = match db.get_generic_nth(&msg.topic, 0) { 80 | Ok(g) => g, 81 | Err(e) => GenericMsg::result(Err(e)), 82 | }; 83 | if let Ok(return_bytes) = response.as_bytes() { 84 | if let Ok(()) = stream.writable().await { 85 | if let Err(e) = stream.try_write(&return_bytes) { 86 | error!("Error sending data back on TCP/TOPICS: {:?}", e); 87 | } 88 | } 89 | } 90 | } 91 | MsgType::GetNth(n) => { 92 | let response = match db.get_generic_nth(&msg.topic, *n) { 93 | Ok(g) => g, 94 | Err(e) => GenericMsg::result(Err(e)), 95 | }; 96 | if let Ok(return_bytes) = response.as_bytes() { 97 | if let Ok(()) = stream.writable().await { 98 | if let Err(e) = stream.try_write(&return_bytes) { 99 | error!("Error sending data back on TCP/TOPICS: {:?}", e); 100 | } 101 | } 102 | } 103 | } 104 | MsgType::Set => { 105 | let response = GenericMsg::result(db.insert_generic(msg)); 106 | if let Ok(return_bytes) = response.as_bytes() { 107 | if let Ok(()) = stream.writable().await { 108 | if let Err(e) = stream.try_write(&return_bytes) { 109 | error!("Error sending data back on TCP/TOPICS: {:?}", e); 110 | } 111 | } 112 | } 113 | } 114 | MsgType::Topics => { 115 | let response = match db.topics() { 116 | Ok(mut topics) => { 117 | topics.sort(); 118 | let msg = Msg::new(MsgType::Topics, "", topics); 119 | match msg.to_generic() { 120 | Ok(msg) => msg, 121 | Err(e) => GenericMsg::result(Err(e)), 122 | } 123 | } 124 | Err(e) => GenericMsg::result(Err(e)), 125 | }; 126 | 127 | if let Ok(return_bytes) = response.as_bytes() { 128 | if let Ok(()) = stream.writable().await { 129 | if let Err(e) = stream.try_write(&return_bytes) { 130 | error!("Error sending data back on TCP/TOPICS: {:?}", e); 131 | } 132 | } 133 | } 134 | } 135 | MsgType::Result(result) => match result { 136 | Ok(_) => (), 137 | Err(e) => error!("{}", e), 138 | }, 139 | } 140 | } 141 | Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { 142 | // println!("Error::WouldBlock: {:?}", e); 143 | continue; 144 | } 145 | Err(e) => { 146 | error!("Error: {:?}", e); 147 | } 148 | } 149 | } 150 | } 151 | 152 | async fn start_subscription(msg: GenericMsg, db: sled::Db, stream: &TcpStream) { 153 | let specialized: Msg = msg.clone().try_into().unwrap(); 154 | let rate = specialized.data; 155 | 156 | loop { 157 | let response = match db.get_generic_nth(&msg.topic, 0) { 158 | Ok(g) => g, 159 | Err(e) => GenericMsg::result(Err(e)), 160 | }; 161 | if let Ok(return_bytes) = response.as_bytes() { 162 | if let Ok(()) = stream.writable().await { 163 | if let Err(e) = stream.try_write(&return_bytes) { 164 | error!("Error sending data back on TCP/TOPICS: {:?}", e); 165 | } 166 | } 167 | } 168 | sleep(rate).await; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/msg.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use chrono::{DateTime, Utc}; 3 | use postcard::to_allocvec; 4 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 5 | use std::convert::{Into, TryInto}; 6 | use std::time::Duration; 7 | 8 | use std::fmt::Debug; 9 | /// Trait for Meadow-compatible data, requiring serde De\Serialize, Debug, and Clone 10 | pub trait Message: Serialize + DeserializeOwned + Debug + Sync + Send + Clone {} 11 | impl Message for T where T: Serialize + DeserializeOwned + Debug + Sync + Send + Clone {} 12 | 13 | /// Msg definitions for publish or request of topic data 14 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 15 | #[repr(C)] 16 | pub enum MsgType { 17 | /// Request SET operation on Host 18 | Set, 19 | /// Request GET operation on Host 20 | Get, 21 | /// Request `GetNth` operation on Host 22 | GetNth(usize), 23 | /// Request list of topics from Host 24 | Topics, 25 | /// Request start of subscribe operation from Host 26 | Subscribe, 27 | /// Communicate success or failure of certain Host-side operations 28 | Result(Result<(), crate::Error>), 29 | } 30 | 31 | /// Message format containing a strongly-typed data payload and associated metadata 32 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 33 | #[repr(C)] 34 | pub struct Msg { 35 | /// Type of `meadow` message 36 | pub msg_type: MsgType, 37 | /// Message timestamp in Utc 38 | pub timestamp: DateTime, 39 | /// Topic name 40 | pub topic: String, 41 | /// Name of message's data type (`String`-typed) 42 | pub data_type: String, 43 | /// Strongly-typed data payload 44 | pub data: T, 45 | } 46 | 47 | impl Msg { 48 | /// Create a new strongly-typed message (default timestamp is from `SystemTime` in UTC) 49 | pub fn new(msg_type: MsgType, topic: impl Into, data: T) -> Self { 50 | Msg { 51 | msg_type, 52 | timestamp: Utc::now(), 53 | topic: topic.into(), 54 | data_type: std::any::type_name::().to_string(), 55 | data, 56 | } 57 | } 58 | 59 | /// Set the message's topic 60 | pub fn set_topic(&mut self, topic: impl Into) { 61 | self.topic = topic.into(); 62 | } 63 | 64 | /// Set the message's timestamp 65 | pub fn set_timestamp(&mut self, timestamp: DateTime) { 66 | self.timestamp = timestamp; 67 | } 68 | 69 | /// Set the message's data payload 70 | pub fn set_data(&mut self, data: T) { 71 | self.data = data; 72 | } 73 | 74 | /// Attempt conversion to `GenericMsg` 75 | pub fn to_generic(self) -> Result { 76 | self.try_into() 77 | } 78 | } 79 | /// Message format containing a generic `Vec` data payload and associated metadata 80 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 81 | #[repr(C)] 82 | pub struct GenericMsg { 83 | /// Type of `meadow` message 84 | pub msg_type: MsgType, 85 | /// Message timestamp in Utc 86 | pub timestamp: DateTime, 87 | /// Topic name 88 | pub topic: String, 89 | /// Name of message's data type (`String`-typed) 90 | pub data_type: String, 91 | /// Generic byte-represented data payload 92 | pub data: Vec, 93 | } 94 | 95 | impl GenericMsg { 96 | /// Create a default `MsgType::Set` message for published messages 97 | #[inline] 98 | pub fn set(topic: impl Into, data: Vec) -> Self { 99 | GenericMsg { 100 | msg_type: MsgType::Set, 101 | timestamp: Utc::now(), 102 | topic: topic.into(), 103 | data_type: std::any::type_name::().to_string(), 104 | data, 105 | } 106 | } 107 | 108 | /// Create a default `MsgType::Get` message for requests 109 | #[inline] 110 | pub fn get(topic: impl Into) -> Self { 111 | GenericMsg { 112 | msg_type: MsgType::Get, 113 | timestamp: Utc::now(), 114 | topic: topic.into(), 115 | data_type: std::any::type_name::().to_string(), 116 | data: Vec::new(), 117 | } 118 | } 119 | 120 | /// Create a `MsgType::Subscribe` message to kick off subscriptions 121 | #[inline] 122 | pub fn subscribe(topic: impl Into, rate: Duration) -> Result { 123 | let msg = Msg::new(MsgType::Subscribe, topic, rate); 124 | msg.to_generic() 125 | } 126 | 127 | /// Create a `MsgType::GetNth` message for requests 128 | #[inline] 129 | pub fn get_nth(topic: impl Into, n: usize) -> Self { 130 | GenericMsg { 131 | msg_type: MsgType::GetNth(n), 132 | timestamp: Utc::now(), 133 | topic: topic.into(), 134 | data_type: std::any::type_name::().to_string(), 135 | data: Vec::new(), 136 | } 137 | } 138 | 139 | /// Create a default `MsgType::Topics` message 140 | #[inline] 141 | pub fn topics() -> Self { 142 | GenericMsg { 143 | msg_type: MsgType::Topics, 144 | timestamp: Utc::now(), 145 | topic: String::new(), 146 | data_type: std::any::type_name::<()>().to_string(), 147 | data: Vec::new(), 148 | } 149 | } 150 | 151 | /// Create a generic 152 | pub fn result(r: Result<(), Error>) -> Self { 153 | GenericMsg { 154 | msg_type: MsgType::Result(r), 155 | timestamp: Utc::now(), 156 | topic: String::new(), 157 | data_type: std::any::type_name::<()>().to_string(), 158 | data: Vec::new(), 159 | } 160 | } 161 | 162 | /// Directly insert a data payload of `u8` bytes into a `GenericMsg` 163 | pub fn set_data(&mut self, data: Vec) { 164 | self.data = data; 165 | } 166 | 167 | /// Convert `GenericMsg` into a `postcard`-encoded byte string 168 | pub fn as_bytes(&self) -> Result, postcard::Error> { 169 | postcard::to_allocvec(&self) 170 | } 171 | } 172 | 173 | impl TryInto> for GenericMsg { 174 | type Error = crate::Error; 175 | 176 | fn try_into(self) -> Result, Error> { 177 | let data = postcard::from_bytes::(&self.data[..])?; 178 | Ok(Msg { 179 | msg_type: self.msg_type, 180 | timestamp: self.timestamp, 181 | topic: self.topic.clone(), 182 | data_type: self.data_type.clone(), 183 | data, 184 | }) 185 | } 186 | } 187 | 188 | impl TryInto for Msg { 189 | type Error = crate::Error; 190 | 191 | fn try_into(self) -> Result { 192 | let data = postcard::to_allocvec(&self.data)?; 193 | Ok(GenericMsg { 194 | msg_type: self.msg_type, 195 | timestamp: self.timestamp, 196 | topic: self.topic.clone(), 197 | data_type: self.data_type.clone(), 198 | data, 199 | }) 200 | } 201 | } 202 | 203 | #[test] 204 | fn msg_conversions() { 205 | let msg: Msg = Msg::new(MsgType::Set, "value", 0); 206 | let generic = msg.clone().to_generic().unwrap(); 207 | let bytes = generic.clone().as_bytes().unwrap(); 208 | let generic_rc: GenericMsg = postcard::from_bytes(&bytes).unwrap(); 209 | dbg!(&generic_rc); 210 | assert_eq!(generic, generic_rc); 211 | let msg_rc: Msg = generic_rc.try_into().unwrap(); 212 | assert_eq!(msg, msg_rc); 213 | } 214 | -------------------------------------------------------------------------------- /src/node/udp/active.rs: -------------------------------------------------------------------------------- 1 | use crate::node::network_config::{Nonblocking, Udp}; 2 | use crate::node::Interface; 3 | use crate::node::Node; 4 | use crate::node::{Active, Idle}; 5 | use crate::prelude::*; 6 | use std::marker::PhantomData; 7 | use std::net::SocketAddr; 8 | use std::ops::DerefMut; 9 | use std::sync::Arc; 10 | use tokio::sync::Mutex as TokioMutex; 11 | 12 | use crate::node::udp::*; 13 | 14 | use chrono::Utc; 15 | 16 | use postcard::{from_bytes, to_allocvec}; 17 | #[cfg(feature = "quic")] 18 | use quinn::Connection as QuicConnection; 19 | use std::result::Result; 20 | use tracing::*; 21 | 22 | impl From> for Node { 23 | fn from(node: Node) -> Self { 24 | Self { 25 | __state: PhantomData, 26 | __data_type: PhantomData, 27 | cfg: node.cfg, 28 | runtime: node.runtime, 29 | rt_handle: node.rt_handle, 30 | stream: node.stream, 31 | topic: node.topic, 32 | socket: node.socket, 33 | buffer: node.buffer, 34 | #[cfg(feature = "quic")] 35 | endpoint: node.endpoint, 36 | #[cfg(feature = "quic")] 37 | connection: node.connection, 38 | subscription_data: node.subscription_data, 39 | task_subscribe: None, 40 | } 41 | } 42 | } 43 | 44 | use crate::node::Block; 45 | use std::fmt::Debug; 46 | 47 | impl Node { 48 | #[tracing::instrument] 49 | #[inline] 50 | async fn publish_internal(&self, val: T) -> Result<(), Error> { 51 | let packet = Msg::new(MsgType::Set, self.topic.clone(), val) 52 | .to_generic()? 53 | .as_bytes()?; 54 | 55 | let socket = match self.socket.as_ref() { 56 | Some(socket) => socket, 57 | None => return Err(Error::AccessSocket), 58 | }; 59 | 60 | socket 61 | .send_to(&packet, self.cfg.network_cfg.host_addr) 62 | .await?; 63 | Ok(()) 64 | } 65 | 66 | #[tracing::instrument] 67 | #[inline] 68 | async fn publish_msg_internal(&self, msg: Msg) -> Result<(), Error> { 69 | let packet = msg.to_generic()?.as_bytes()?; 70 | let socket = match self.socket.as_ref() { 71 | Some(socket) => socket, 72 | None => return Err(Error::AccessSocket), 73 | }; 74 | 75 | socket 76 | .send_to(&packet, self.cfg.network_cfg.host_addr) 77 | .await?; 78 | Ok(()) 79 | } 80 | 81 | #[tracing::instrument] 82 | #[inline] 83 | async fn request_nth_back_internal(&self, n: usize) -> Result, Error> { 84 | let packet = GenericMsg::get_nth::(self.topic.clone(), n).as_bytes()?; 85 | let buffer = self.buffer.clone(); 86 | 87 | if let Some(socket) = &self.socket { 88 | send_msg(socket, packet, self.cfg.network_cfg.host_addr).await?; 89 | let msg = await_response(socket, buffer).await?.try_into()?; 90 | Ok(msg) 91 | } else { 92 | Err(Error::AccessSocket) 93 | } 94 | } 95 | 96 | #[tracing::instrument] 97 | #[inline] 98 | async fn topics_internal(&self) -> Result>, Error> { 99 | let packet = GenericMsg::topics().as_bytes()?; 100 | let buffer = self.buffer.clone(); 101 | 102 | if let Some(socket) = &self.socket { 103 | send_msg(socket, packet, self.cfg.network_cfg.host_addr).await?; 104 | let msg = await_response(socket, buffer).await?.try_into()?; 105 | Ok(msg) 106 | } else { 107 | Err(Error::AccessSocket) 108 | } 109 | } 110 | } 111 | 112 | impl Node { 113 | #[tracing::instrument] 114 | #[inline] 115 | pub async fn publish(&self, val: T) -> Result<(), Error> { 116 | self.publish_internal(val).await?; 117 | Ok(()) 118 | } 119 | 120 | pub async fn publish_msg(&self, msg: Msg) -> Result<(), Error> { 121 | self.publish_msg_internal(msg).await?; 122 | Ok(()) 123 | } 124 | 125 | #[tracing::instrument] 126 | #[inline] 127 | pub async fn request(&self) -> Result, Error> { 128 | let msg = self.request_nth_back_internal(0).await?; 129 | Ok(msg) 130 | } 131 | 132 | #[tracing::instrument] 133 | #[inline] 134 | pub async fn topics(&self) -> Result>, Error> { 135 | let msg = self.topics_internal().await?; 136 | Ok(msg) 137 | } 138 | } 139 | 140 | //-------- 141 | 142 | use crate::node::network_config::Blocking; 143 | 144 | impl From> for Node { 145 | fn from(node: Node) -> Self { 146 | Self { 147 | __state: PhantomData, 148 | __data_type: PhantomData, 149 | cfg: node.cfg, 150 | runtime: node.runtime, 151 | rt_handle: node.rt_handle, 152 | stream: node.stream, 153 | topic: node.topic, 154 | socket: node.socket, 155 | buffer: node.buffer, 156 | #[cfg(feature = "quic")] 157 | endpoint: node.endpoint, 158 | #[cfg(feature = "quic")] 159 | connection: node.connection, 160 | subscription_data: node.subscription_data, 161 | task_subscribe: None, 162 | } 163 | } 164 | } 165 | 166 | impl Node { 167 | #[tracing::instrument] 168 | #[inline] 169 | pub fn publish(&self, val: T) -> Result<(), Error> { 170 | match &self.rt_handle { 171 | Some(handle) => handle.block_on(async { 172 | self.publish_internal(val).await?; 173 | Ok(()) 174 | }), 175 | None => Err(Error::HandleAccess), 176 | } 177 | } 178 | 179 | #[tracing::instrument] 180 | #[inline] 181 | pub fn publish_msg(&self, msg: Msg) -> Result<(), Error> { 182 | match &self.rt_handle { 183 | Some(handle) => handle.block_on(async { 184 | self.publish_msg_internal(msg).await?; 185 | Ok(()) 186 | }), 187 | None => Err(Error::HandleAccess), 188 | } 189 | } 190 | 191 | #[tracing::instrument] 192 | #[inline] 193 | pub fn request(&self) -> Result, Error> { 194 | match &self.rt_handle { 195 | Some(handle) => handle.block_on(async { 196 | let msg = self.request_nth_back_internal(0).await?; 197 | Ok(msg) 198 | }), 199 | None => Err(Error::HandleAccess), 200 | } 201 | } 202 | 203 | #[tracing::instrument] 204 | #[inline] 205 | pub fn request_nth_back(&self, n: usize) -> Result, Error> { 206 | match &self.rt_handle { 207 | Some(handle) => handle.block_on(async { 208 | let msg = self.request_nth_back_internal(n).await?; 209 | Ok(msg) 210 | }), 211 | None => Err(Error::HandleAccess), 212 | } 213 | } 214 | 215 | #[tracing::instrument] 216 | #[inline] 217 | pub fn topics(&self) -> Result>, Error> { 218 | match &self.rt_handle { 219 | Some(handle) => handle.block_on(async { 220 | let msg = self.topics_internal().await?; 221 | Ok(msg) 222 | }), 223 | None => Err(Error::HandleAccess), 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/node/quic/active.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Quic::*}; 2 | use crate::node::network_config::{Nonblocking, Quic}; 3 | use crate::node::Active; 4 | use crate::node::Node; 5 | use crate::prelude::*; 6 | 7 | use crate::msg::{GenericMsg, Message, Msg}; 8 | use std::convert::TryInto; 9 | 10 | use chrono::Utc; 11 | 12 | use crate::node::Block; 13 | use postcard::*; 14 | use quinn::Connection as QuicConnection; 15 | use std::fmt::Debug; 16 | use std::result::Result; 17 | use tracing::*; 18 | 19 | impl Node { 20 | #[tracing::instrument(skip_all)] 21 | #[inline] 22 | async fn publish_internal(&self, val: T) -> Result<(), Error> { 23 | let packet = Msg::new(MsgType::Set, self.topic.clone(), val) 24 | .to_generic()? 25 | .as_bytes()?; 26 | 27 | if let Some(connection) = &self.connection { 28 | match connection.open_bi().await { 29 | Ok((mut send, _recv)) => { 30 | debug!("Node succesfully opened stream from connection"); 31 | 32 | if let Ok(()) = send.write_all(&packet).await { 33 | if let Ok(()) = send.finish().await { 34 | debug!("Node successfully wrote packet to stream"); 35 | } 36 | } else { 37 | error!("Error writing packet to stream"); 38 | } 39 | } 40 | Err(e) => { 41 | warn!("{:?}", e); 42 | } 43 | }; 44 | 45 | Ok(()) 46 | } else { 47 | Err(Error::Quic(Connection)) 48 | } 49 | } 50 | 51 | #[tracing::instrument(skip_all)] 52 | #[inline] 53 | async fn publish_msg_internal(&self, msg: Msg) -> Result<(), Error> { 54 | let packet = msg.to_generic()?.as_bytes()?; 55 | 56 | if let Some(connection) = &self.connection { 57 | match connection.open_bi().await { 58 | Ok((mut send, _recv)) => { 59 | debug!("Node succesfully opened stream from connection"); 60 | 61 | if let Ok(()) = send.write_all(&packet).await { 62 | if let Ok(()) = send.finish().await { 63 | debug!("Node successfully wrote packet to stream"); 64 | } 65 | } else { 66 | error!("Error writing packet to stream"); 67 | } 68 | } 69 | Err(e) => { 70 | warn!("{:?}", e); 71 | } 72 | }; 73 | 74 | Ok(()) 75 | } else { 76 | Err(Error::Quic(Connection)) 77 | } 78 | } 79 | 80 | #[tracing::instrument(skip_all)] 81 | #[inline] 82 | async fn request_nth_back_internal(&self, n: usize) -> Result, Error> { 83 | let packet = GenericMsg::get_nth::(self.topic.clone(), n).as_bytes()?; 84 | 85 | let mut buf = self.buffer.lock().await; 86 | 87 | if let Some(connection) = self.connection.clone() { 88 | let (mut send, mut recv) = connection.open_bi().await?; 89 | debug!("Node succesfully opened stream from connection"); 90 | send.write_all(&packet).await?; 91 | // send.finish().await.map_err(WriteError)?; 92 | 93 | loop { 94 | match recv.read(&mut buf).await? { 95 | Some(0) => continue, 96 | Some(n) => { 97 | let bytes = &buf[..n]; 98 | let generic = from_bytes::(bytes)?; 99 | let msg = generic.try_into()?; 100 | 101 | return Ok(msg); 102 | } 103 | None => continue, 104 | } 105 | } 106 | } else { 107 | Err(Error::Quic(Connection)) 108 | } 109 | } 110 | 111 | #[tracing::instrument(skip_all)] 112 | #[inline] 113 | async fn topics_internal(&self) -> Result>, Error> { 114 | let packet = GenericMsg::topics().as_bytes()?; 115 | 116 | let mut buf = self.buffer.lock().await; 117 | 118 | let connection = self.connection.clone().ok_or(Connection)?; 119 | 120 | let (mut send, mut recv) = connection.open_bi().await?; 121 | debug!("Node succesfully opened stream from connection"); 122 | send.write_all(&packet).await?; 123 | send.finish().await?; 124 | 125 | let n = recv.read(&mut buf).await?.ok_or(Connection)?; 126 | let bytes = &buf[..n]; 127 | let reply = from_bytes::(bytes)?; 128 | let topics: Msg> = reply.try_into()?; 129 | Ok(topics) 130 | } 131 | } 132 | 133 | impl Node { 134 | #[tracing::instrument(skip_all)] 135 | #[inline] 136 | pub async fn publish(&self, val: T) -> Result<(), Error> { 137 | self.publish_internal(val).await?; 138 | Ok(()) 139 | } 140 | 141 | #[tracing::instrument(skip_all)] 142 | #[inline] 143 | pub async fn publish_msg(&self, msg: Msg) -> Result<(), Error> { 144 | self.publish_msg_internal(msg).await?; 145 | Ok(()) 146 | } 147 | 148 | #[tracing::instrument(skip_all)] 149 | #[inline] 150 | pub async fn request(&self) -> Result, Error> { 151 | let msg = self.request_nth_back_internal(0).await?; 152 | Ok(msg) 153 | } 154 | 155 | #[tracing::instrument(skip_all)] 156 | #[inline] 157 | pub async fn topics(&self) -> Result>, Error> { 158 | let msg = self.topics_internal().await?; 159 | Ok(msg) 160 | } 161 | } 162 | 163 | //----- 164 | 165 | use crate::node::network_config::Blocking; 166 | 167 | impl Node { 168 | #[tracing::instrument(skip_all)] 169 | #[inline] 170 | pub fn publish(&self, val: T) -> Result<(), Error> { 171 | match &self.rt_handle { 172 | Some(handle) => handle.block_on(async { 173 | self.publish_internal(val).await?; 174 | Ok(()) 175 | }), 176 | None => Err(Error::HandleAccess), 177 | } 178 | } 179 | 180 | #[tracing::instrument(skip_all)] 181 | #[inline] 182 | pub fn publish_msg(&self, msg: Msg) -> Result<(), Error> { 183 | match &self.rt_handle { 184 | Some(handle) => handle.block_on(async { 185 | self.publish_msg_internal(msg).await?; 186 | Ok(()) 187 | }), 188 | None => Err(Error::HandleAccess), 189 | } 190 | } 191 | 192 | #[tracing::instrument(skip_all)] 193 | #[inline] 194 | pub fn request(&self) -> Result, Error> { 195 | match &self.rt_handle { 196 | Some(handle) => handle.block_on(async { 197 | let msg = self.request_nth_back_internal(0).await?; 198 | Ok(msg) 199 | }), 200 | None => Err(Error::HandleAccess), 201 | } 202 | } 203 | 204 | #[tracing::instrument(skip_all)] 205 | #[inline] 206 | pub fn request_nth_back(&self, n: usize) -> Result, Error> { 207 | match &self.rt_handle { 208 | Some(handle) => handle.block_on(async { 209 | let msg = self.request_nth_back_internal(n).await?; 210 | Ok(msg) 211 | }), 212 | None => Err(Error::HandleAccess), 213 | } 214 | } 215 | 216 | #[tracing::instrument(skip_all)] 217 | #[inline] 218 | pub fn topics(&self) -> Result>, Error> { 219 | match &self.rt_handle { 220 | Some(handle) => handle.block_on(async { 221 | let msg = self.topics_internal().await?; 222 | Ok(msg) 223 | }), 224 | None => Err(Error::HandleAccess), 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/node/udp/idle.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | use crate::node::network_config::{Nonblocking, Udp}; 3 | use crate::Error; 4 | 5 | use crate::node::udp::send_msg; 6 | use crate::node::*; 7 | 8 | use tokio::net::UdpSocket; 9 | use tokio::sync::Mutex as TokioMutex; 10 | use tokio::time::{sleep, Duration}; 11 | 12 | use tracing::*; 13 | 14 | use std::convert::TryInto; 15 | use std::net::SocketAddr; 16 | use std::result::Result; 17 | use std::sync::Arc; 18 | 19 | use alloc::vec::Vec; 20 | use postcard::*; 21 | use std::marker::PhantomData; 22 | 23 | use crate::msg::*; 24 | 25 | impl From> for Node { 26 | fn from(node: Node) -> Self { 27 | Self { 28 | __state: PhantomData, 29 | __data_type: PhantomData, 30 | cfg: node.cfg, 31 | runtime: node.runtime, 32 | rt_handle: node.rt_handle, 33 | stream: node.stream, 34 | topic: node.topic, 35 | socket: node.socket, 36 | buffer: node.buffer, 37 | #[cfg(feature = "quic")] 38 | endpoint: node.endpoint, 39 | #[cfg(feature = "quic")] 40 | connection: node.connection, 41 | subscription_data: node.subscription_data, 42 | task_subscribe: None, 43 | } 44 | } 45 | } 46 | 47 | impl Node { 48 | #[tracing::instrument(skip(self))] 49 | pub async fn activate(mut self) -> Result, Error> { 50 | match { 51 | match UdpSocket::bind("[::]:0").await { 52 | Ok(socket) => { 53 | info!("Bound to socket: {:?}", &socket); 54 | Ok(socket) 55 | } 56 | Err(_e) => Err(Error::AccessSocket), 57 | } 58 | } { 59 | Ok(socket) => self.socket = Some(socket), 60 | Err(e) => return Err(e), 61 | }; 62 | 63 | Ok(Node::::from(self)) 64 | } 65 | 66 | #[tracing::instrument(skip(self))] 67 | pub async fn subscribe( 68 | mut self, 69 | rate: Duration, 70 | ) -> Result, Error> { 71 | let topic = self.topic.clone(); 72 | let subscription_data: Arc>>> = Arc::new(TokioMutex::new(None)); 73 | let data = Arc::clone(&subscription_data); 74 | let addr = self.cfg.network_cfg.host_addr; 75 | let buffer = self.buffer.clone(); 76 | 77 | let packet = GenericMsg::subscribe(topic, rate)?; 78 | 79 | let task_subscribe = tokio::spawn(async move { 80 | if let Ok(socket) = UdpSocket::bind("[::]:0").await { 81 | info!("Bound to socket: {:?}", &socket); 82 | loop { 83 | if let Err(e) = run_subscription::( 84 | packet.clone(), 85 | buffer.clone(), 86 | &socket, 87 | data.clone(), 88 | addr, 89 | ) 90 | .await 91 | { 92 | // dbg!(&e); 93 | error!("{:?}", e); 94 | } 95 | } 96 | } 97 | }); 98 | 99 | self.task_subscribe = Some(task_subscribe); 100 | 101 | let mut subscription_node = Node::::from(self); 102 | subscription_node.subscription_data = subscription_data; 103 | 104 | Ok(subscription_node) 105 | } 106 | } 107 | 108 | #[tracing::instrument(skip_all)] 109 | async fn run_subscription( 110 | packet: GenericMsg, 111 | buffer: Arc>>, 112 | socket: &UdpSocket, 113 | data: Arc>>>, 114 | addr: SocketAddr, 115 | ) -> Result<(), Error> { 116 | udp::send_msg(socket, packet.as_bytes()?, addr).await?; 117 | 118 | loop { 119 | let msg: Msg = udp::await_response(socket, buffer.clone()) 120 | .await? 121 | .try_into()?; 122 | info!("UDP Msg received: {:?}", &msg); 123 | let delta = Utc::now() - msg.timestamp; 124 | if delta <= chrono::Duration::zero() { 125 | info!("Data is not newer, skipping to next subscription iteration"); 126 | continue; 127 | } 128 | 129 | let mut data = data.lock().await; 130 | *data = Some(msg); 131 | info!("Inserted new subscription data!"); 132 | } 133 | } 134 | 135 | //-------- 136 | 137 | use crate::node::network_config::Blocking; 138 | 139 | impl From> for Node { 140 | fn from(node: Node) -> Self { 141 | Self { 142 | __state: PhantomData, 143 | __data_type: PhantomData, 144 | cfg: node.cfg, 145 | runtime: node.runtime, 146 | rt_handle: node.rt_handle, 147 | stream: node.stream, 148 | topic: node.topic, 149 | socket: node.socket, 150 | buffer: node.buffer, 151 | #[cfg(feature = "quic")] 152 | endpoint: node.endpoint, 153 | #[cfg(feature = "quic")] 154 | connection: node.connection, 155 | subscription_data: node.subscription_data, 156 | task_subscribe: None, 157 | } 158 | } 159 | } 160 | 161 | impl Node { 162 | #[tracing::instrument(skip(self))] 163 | pub fn activate(mut self) -> Result, Error> { 164 | let handle = match &self.rt_handle { 165 | Some(handle) => handle, 166 | None => return Err(Error::HandleAccess), 167 | }; 168 | 169 | match handle.block_on(async move { 170 | match UdpSocket::bind("[::]:0").await { 171 | Ok(socket) => { 172 | info!("Bound to socket: {:?}", &socket); 173 | Ok(socket) 174 | } 175 | Err(_e) => Err(Error::AccessSocket), 176 | } 177 | }) { 178 | Ok(socket) => self.socket = Some(socket), 179 | Err(e) => return Err(e), 180 | }; 181 | 182 | Ok(Node::::from(self)) 183 | } 184 | 185 | #[tracing::instrument(skip(self))] 186 | pub fn subscribe( 187 | mut self, 188 | rate: Duration, 189 | ) -> Result, Error> { 190 | let topic = self.topic.clone(); 191 | let subscription_data: Arc>>> = Arc::new(TokioMutex::new(None)); 192 | let data = Arc::clone(&subscription_data); 193 | let addr = self.cfg.network_cfg.host_addr; 194 | let buffer = self.buffer.clone(); 195 | 196 | let packet = GenericMsg::subscribe(topic, rate)?; 197 | 198 | let handle = match &self.rt_handle { 199 | Some(handle) => handle, 200 | None => return Err(Error::HandleAccess), 201 | }; 202 | 203 | let task_subscribe = handle.spawn(async move { 204 | if let Ok(socket) = UdpSocket::bind("[::]:0").await { 205 | info!("Bound to socket: {:?}", &socket); 206 | loop { 207 | if let Err(e) = run_subscription::( 208 | packet.clone(), 209 | buffer.clone(), 210 | &socket, 211 | data.clone(), 212 | addr, 213 | ) 214 | .await 215 | { 216 | // dbg!(&e); 217 | error!("{:?}", e); 218 | } 219 | } 220 | } 221 | }); 222 | 223 | self.task_subscribe = Some(task_subscribe); 224 | 225 | let mut subscription_node = Node::::from(self); 226 | subscription_node.subscription_data = subscription_data; 227 | 228 | Ok(subscription_node) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/node/tcp/active.rs: -------------------------------------------------------------------------------- 1 | use crate::error::HostOperation; 2 | use crate::node::network_config::Nonblocking; 3 | use crate::node::tcp::*; 4 | use crate::node::{Active, Node}; 5 | use crate::prelude::*; 6 | use crate::*; 7 | 8 | use std::convert::TryInto; 9 | use std::ops::DerefMut; 10 | 11 | use chrono::Utc; 12 | 13 | use postcard::{from_bytes, to_allocvec}; 14 | #[cfg(feature = "quic")] 15 | use quinn::Connection as QuicConnection; 16 | use std::result::Result; 17 | use tracing::*; 18 | 19 | use crate::node::network_config::{Interface, Tcp}; 20 | use crate::node::Block; 21 | use std::fmt::Debug; 22 | 23 | impl Node { 24 | #[tracing::instrument] 25 | #[inline] 26 | async fn publish_internal(&self, val: T) -> Result<(), Error> { 27 | let packet = Msg::new(MsgType::Set, self.topic.clone(), val) 28 | .to_generic()? 29 | .as_bytes()?; 30 | 31 | let stream = match self.stream.as_ref() { 32 | Some(stream) => stream, 33 | None => return Err(Error::AccessStream), 34 | }; 35 | 36 | // Send the publish message 37 | send_msg(stream, packet).await?; 38 | 39 | // Wait for the publish acknowledgement 40 | let mut buf = self.buffer.lock().await; 41 | loop { 42 | if let Ok(()) = stream.readable().await { 43 | match stream.try_read(&mut buf) { 44 | Ok(0) => continue, 45 | Ok(n) => { 46 | let bytes = &buf[..n]; 47 | match from_bytes::(bytes) { 48 | Ok(g) => match g.msg_type { 49 | MsgType::Result(result) => { 50 | if let Err(e) = result { 51 | error!("{}", e); 52 | } 53 | } 54 | _ => { 55 | info!("{:?}", &g); 56 | } 57 | }, 58 | Err(e) => { 59 | error!("{}", e); 60 | } 61 | } 62 | 63 | break; 64 | } 65 | Err(_e) => { 66 | // if e.kind() == std::io::ErrorKind::WouldBlock {} 67 | continue; 68 | } 69 | } 70 | } 71 | } 72 | Ok(()) 73 | } 74 | 75 | #[tracing::instrument] 76 | #[inline] 77 | async fn publish_msg_internal(&self, msg: Msg) -> Result<(), Error> { 78 | let packet = msg.to_generic()?.as_bytes()?; 79 | let stream = match self.stream.as_ref() { 80 | Some(stream) => stream, 81 | None => return Err(Error::AccessStream), 82 | }; 83 | 84 | // Send the publish message 85 | send_msg(stream, packet).await?; 86 | 87 | // Wait for the publish acknowledgement 88 | let mut buf = self.buffer.lock().await; 89 | loop { 90 | if let Ok(()) = stream.readable().await { 91 | match stream.try_read(&mut buf) { 92 | Ok(0) => continue, 93 | Ok(n) => { 94 | let bytes = &buf[..n]; 95 | 96 | match from_bytes::(bytes) { 97 | Ok(g) => match g.msg_type { 98 | MsgType::Result(result) => { 99 | if let Err(e) = result { 100 | error!("{}", e) 101 | } else { 102 | info!("{:?}", result); 103 | } 104 | } 105 | _ => (), 106 | }, 107 | Err(e) => { 108 | error!("{}", e); 109 | } 110 | } 111 | 112 | break; 113 | } 114 | Err(_e) => { 115 | // if e.kind() == std::io::ErrorKind::WouldBlock {} 116 | continue; 117 | } 118 | } 119 | } 120 | } 121 | Ok(()) 122 | } 123 | 124 | /// Request data from host on Node's assigned topic 125 | #[tracing::instrument] 126 | #[inline] 127 | async fn request_nth_back_internal(&self, n: usize) -> Result, Error> { 128 | let stream = match self.stream.as_ref() { 129 | Some(stream) => stream, 130 | None => return Err(Error::AccessStream), 131 | }; 132 | 133 | let packet = GenericMsg::get_nth::(self.topic.clone(), n).as_bytes()?; 134 | 135 | let mut buffer = self.buffer.lock().await; 136 | send_msg(stream, packet).await?; 137 | let msg = await_response(stream, &mut buffer).await?.try_into()?; 138 | Ok(msg) 139 | } 140 | 141 | #[tracing::instrument] 142 | #[inline] 143 | async fn topics_internal(&self) -> Result>, Error> { 144 | let stream = match self.stream.as_ref() { 145 | Some(stream) => stream, 146 | None => return Err(Error::AccessStream), 147 | }; 148 | 149 | let packet = GenericMsg::topics().as_bytes()?; 150 | 151 | let mut buffer = self.buffer.lock().await; 152 | send_msg(stream, packet).await?; 153 | let msg = await_response(stream, &mut buffer).await?.try_into()?; 154 | Ok(msg) 155 | } 156 | } 157 | 158 | impl Node { 159 | // TO_DO: The error handling in the async blocks need to be improved 160 | /// Send data to host on Node's assigned topic using `Msg` packet 161 | #[tracing::instrument] 162 | #[inline] 163 | pub async fn publish(&self, val: T) -> Result<(), Error> { 164 | self.publish_internal(val).await?; 165 | Ok(()) 166 | } 167 | 168 | #[tracing::instrument] 169 | #[inline] 170 | pub async fn publish_msg(&self, msg: Msg) -> Result<(), Error> { 171 | self.publish_msg_internal(msg).await?; 172 | Ok(()) 173 | } 174 | 175 | /// Request data from host on Node's assigned topic 176 | #[tracing::instrument] 177 | #[inline] 178 | pub async fn request(&self) -> Result, Error> { 179 | let msg = self.request_nth_back_internal(0).await?; 180 | Ok(msg) 181 | } 182 | 183 | /// Request n'th data from host on Node's assigned topic 184 | #[tracing::instrument] 185 | #[inline] 186 | pub async fn request_nth_back(&self, n: usize) -> Result, Error> { 187 | let msg = self.request_nth_back_internal(n).await?; 188 | Ok(msg) 189 | } 190 | 191 | #[tracing::instrument] 192 | #[inline] 193 | pub async fn topics(&self) -> Result>, Error> { 194 | let msg = self.topics_internal().await?; 195 | Ok(msg) 196 | } 197 | } 198 | 199 | use crate::node::network_config::Blocking; 200 | 201 | impl Node { 202 | // TO_DO: The error handling in the async blocks need to be improved 203 | /// Send data to host on Node's assigned topic using `Msg` packet 204 | #[tracing::instrument(skip_all)] 205 | #[inline] 206 | pub fn publish(&self, val: T) -> Result<(), Error> { 207 | match &self.rt_handle { 208 | Some(handle) => handle.block_on(async { 209 | self.publish_internal(val).await?; 210 | Ok(()) 211 | }), 212 | None => Err(Error::HandleAccess), 213 | } 214 | } 215 | 216 | #[tracing::instrument] 217 | #[inline] 218 | pub fn publish_msg(&self, msg: Msg) -> Result<(), Error> { 219 | match &self.rt_handle { 220 | Some(handle) => handle.block_on(async { 221 | self.publish_msg_internal(msg).await?; 222 | Ok(()) 223 | }), 224 | None => Err(Error::HandleAccess), 225 | } 226 | } 227 | 228 | /// Request data from host on Node's assigned topic 229 | #[tracing::instrument] 230 | #[inline] 231 | pub fn request(&self) -> Result, Error> { 232 | match &self.rt_handle { 233 | Some(handle) => handle.block_on(async { 234 | let msg = self.request_nth_back_internal(0).await?; 235 | Ok(msg) 236 | }), 237 | None => Err(Error::HandleAccess), 238 | } 239 | } 240 | 241 | /// Request data from host on Node's assigned topic 242 | #[tracing::instrument] 243 | #[inline] 244 | pub fn request_nth_back(&self, n: usize) -> Result, Error> { 245 | match &self.rt_handle { 246 | Some(handle) => handle.block_on(async { 247 | let msg = self.request_nth_back_internal(n).await?; 248 | Ok(msg) 249 | }), 250 | None => Err(Error::HandleAccess), 251 | } 252 | } 253 | 254 | #[tracing::instrument] 255 | #[inline] 256 | pub fn topics(&self) -> Result>, Error> { 257 | match &self.rt_handle { 258 | Some(handle) => handle.block_on(async { 259 | let msg = self.topics_internal().await?; 260 | Ok(msg) 261 | }), 262 | None => Err(Error::HandleAccess), 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/node/tcp/idle.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | use crate::prelude::*; 3 | 4 | use crate::node::network_config::{Nonblocking, Tcp}; 5 | use crate::node::*; 6 | 7 | use tcp::try_connection; 8 | use tokio::net::UdpSocket; 9 | use tokio::sync::Mutex as TokioMutex; 10 | use tokio::time::{sleep, Duration}; 11 | 12 | use tracing::*; 13 | 14 | use std::convert::TryInto; 15 | use std::net::SocketAddr; 16 | use std::result::Result; 17 | use std::sync::Arc; 18 | 19 | use alloc::vec::Vec; 20 | use postcard::{from_bytes, to_allocvec}; 21 | use std::marker::PhantomData; 22 | 23 | #[cfg(feature = "quic")] 24 | use quinn::Endpoint; 25 | 26 | use crate::msg::*; 27 | use chrono::Utc; 28 | 29 | impl From> for Node { 30 | fn from(node: Node) -> Self { 31 | Self { 32 | __state: PhantomData, 33 | __data_type: PhantomData, 34 | cfg: node.cfg, 35 | runtime: node.runtime, 36 | rt_handle: node.rt_handle, 37 | stream: node.stream, 38 | topic: node.topic, 39 | socket: node.socket, 40 | buffer: node.buffer, 41 | #[cfg(feature = "quic")] 42 | endpoint: node.endpoint, 43 | #[cfg(feature = "quic")] 44 | connection: node.connection, 45 | subscription_data: node.subscription_data, 46 | task_subscribe: None, 47 | } 48 | } 49 | } 50 | 51 | impl From> for Node { 52 | fn from(node: Node) -> Self { 53 | Self { 54 | __state: PhantomData, 55 | __data_type: PhantomData, 56 | cfg: node.cfg, 57 | runtime: node.runtime, 58 | rt_handle: node.rt_handle, 59 | stream: node.stream, 60 | topic: node.topic, 61 | socket: node.socket, 62 | buffer: node.buffer, 63 | #[cfg(feature = "quic")] 64 | endpoint: node.endpoint, 65 | #[cfg(feature = "quic")] 66 | connection: node.connection, 67 | subscription_data: node.subscription_data, 68 | task_subscribe: None, 69 | } 70 | } 71 | } 72 | 73 | use crate::node::tcp::handshake; 74 | use tokio::net::TcpStream; 75 | 76 | impl Node { 77 | /// Attempt connection from the Node to the Host located at the specified address 78 | #[tracing::instrument(skip_all)] 79 | pub async fn activate(mut self) -> Result, Error> { 80 | let addr = self.cfg.network_cfg.host_addr; 81 | let topic = self.topic.clone(); 82 | 83 | let stream: Result = { 84 | let stream = try_connection(addr).await?; 85 | let stream = handshake(stream, topic).await?; 86 | Ok(stream) 87 | }; 88 | if let Ok(stream) = stream { 89 | debug!( 90 | "Established Node<=>Host TCP stream: {:?}", 91 | stream.local_addr() 92 | ); 93 | self.stream = Some(stream); 94 | } 95 | 96 | Ok(Node::::from(self)) 97 | } 98 | 99 | #[tracing::instrument] 100 | pub async fn subscribe( 101 | mut self, 102 | rate: Duration, 103 | ) -> Result, Error> { 104 | let addr = self.cfg.network_cfg.host_addr; 105 | let topic = self.topic.clone(); 106 | 107 | let subscription_data: Arc>>> = Arc::new(TokioMutex::new(None)); 108 | let data = Arc::clone(&subscription_data); 109 | 110 | let buffer = self.buffer.clone(); 111 | let packet = GenericMsg::subscribe(&topic, rate)?; 112 | 113 | let task_subscribe = tokio::spawn(async move { 114 | if let Ok(stream) = try_connection(addr).await { 115 | if let Ok(stream) = handshake(stream, topic.clone()).await { 116 | loop { 117 | if let Err(e) = run_subscription::( 118 | packet.clone(), 119 | buffer.clone(), 120 | &stream, 121 | data.clone(), 122 | ) 123 | .await 124 | { 125 | error!("{:?}", e); 126 | } 127 | } 128 | } 129 | } 130 | }); 131 | self.task_subscribe = Some(task_subscribe); 132 | 133 | let mut subscription_node = Node::::from(self); 134 | subscription_node.subscription_data = subscription_data; 135 | 136 | Ok(subscription_node) 137 | } 138 | } 139 | 140 | use crate::node::tcp::{await_response, send_msg}; 141 | async fn run_subscription( 142 | packet: GenericMsg, 143 | buffer: Arc>>, 144 | stream: &TcpStream, 145 | data: Arc>>>, 146 | ) -> Result<(), Error> { 147 | send_msg(stream, packet.as_bytes()?).await?; 148 | 149 | let mut buffer = buffer.lock().await; 150 | loop { 151 | match await_response(stream, &mut buffer).await { 152 | Ok(msg) => { 153 | match TryInto::>::try_into(msg) { 154 | Ok(msg) => { 155 | let mut data = data.lock().await; 156 | use std::ops::DerefMut; 157 | match data.deref_mut() { 158 | Some(existing) => { 159 | let delta = msg.timestamp - existing.timestamp; 160 | // println!("The time difference between msg tx/rx is: {} us",delta); 161 | if delta <= chrono::Duration::zero() { 162 | // println!("Data is not newer, skipping to next subscription iteration"); 163 | continue; 164 | } 165 | 166 | *data = Some(msg); 167 | } 168 | None => { 169 | *data = Some(msg); 170 | } 171 | } 172 | } 173 | Err(e) => { 174 | error!("{}", e); 175 | } 176 | } 177 | } 178 | Err(e) => { 179 | error!("Subscription Error: {:?}", e); 180 | continue; 181 | } 182 | }; 183 | } 184 | } 185 | 186 | //------ 187 | 188 | impl From> for Node { 189 | fn from(node: Node) -> Self { 190 | Self { 191 | __state: PhantomData, 192 | __data_type: PhantomData, 193 | cfg: node.cfg, 194 | runtime: node.runtime, 195 | rt_handle: node.rt_handle, 196 | stream: node.stream, 197 | topic: node.topic, 198 | socket: node.socket, 199 | buffer: node.buffer, 200 | #[cfg(feature = "quic")] 201 | endpoint: node.endpoint, 202 | #[cfg(feature = "quic")] 203 | connection: node.connection, 204 | subscription_data: node.subscription_data, 205 | task_subscribe: None, 206 | } 207 | } 208 | } 209 | 210 | impl From> for Node { 211 | fn from(node: Node) -> Self { 212 | Self { 213 | __state: PhantomData, 214 | __data_type: PhantomData, 215 | cfg: node.cfg, 216 | runtime: node.runtime, 217 | rt_handle: node.rt_handle, 218 | stream: node.stream, 219 | topic: node.topic, 220 | socket: node.socket, 221 | buffer: node.buffer, 222 | #[cfg(feature = "quic")] 223 | endpoint: node.endpoint, 224 | #[cfg(feature = "quic")] 225 | connection: node.connection, 226 | subscription_data: node.subscription_data, 227 | task_subscribe: None, 228 | } 229 | } 230 | } 231 | 232 | use crate::node::network_config::Blocking; 233 | impl Node { 234 | /// Attempt connection from the Node to the Host located at the specified address 235 | #[tracing::instrument(skip_all)] 236 | pub fn activate(mut self) -> Result, Error> { 237 | let addr = self.cfg.network_cfg.host_addr; 238 | let topic = self.topic.clone(); 239 | 240 | let handle = match &self.rt_handle { 241 | Some(handle) => handle, 242 | None => return Err(Error::HandleAccess), 243 | }; 244 | 245 | let stream: Result = handle.block_on(async move { 246 | let stream = try_connection(addr).await?; 247 | let stream = handshake(stream, topic).await?; 248 | Ok(stream) 249 | }); 250 | if let Ok(stream) = stream { 251 | debug!( 252 | "Established Node<=>Host TCP stream: {:?}", 253 | stream.local_addr() 254 | ); 255 | self.stream = Some(stream); 256 | } 257 | 258 | Ok(Node::::from(self)) 259 | } 260 | 261 | #[tracing::instrument] 262 | pub fn subscribe( 263 | mut self, 264 | rate: Duration, 265 | ) -> Result, Error> { 266 | let addr = self.cfg.network_cfg.host_addr; 267 | let topic = self.topic.clone(); 268 | 269 | let subscription_data: Arc>>> = Arc::new(TokioMutex::new(None)); 270 | let data = Arc::clone(&subscription_data); 271 | 272 | let buffer = self.buffer.clone(); 273 | let packet = GenericMsg::subscribe(&topic, rate)?; 274 | 275 | let handle = match &self.rt_handle { 276 | Some(handle) => handle, 277 | None => return Err(Error::HandleAccess), 278 | }; 279 | 280 | let task_subscribe = handle.spawn(async move { 281 | if let Ok(stream) = try_connection(addr).await { 282 | if let Ok(stream) = handshake(stream, topic.clone()).await { 283 | loop { 284 | if let Err(e) = run_subscription::( 285 | packet.clone(), 286 | buffer.clone(), 287 | &stream, 288 | data.clone(), 289 | ) 290 | .await 291 | { 292 | error!("{:?}", e); 293 | } 294 | } 295 | } 296 | } 297 | }); 298 | self.task_subscribe = Some(task_subscribe); 299 | 300 | let mut subscription_node = Node::::from(self); 301 | subscription_node.subscription_data = subscription_data; 302 | 303 | Ok(subscription_node) 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/node/quic/idle.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | use crate::error::{Error, Quic::*}; 3 | use crate::*; 4 | 5 | use crate::node::network_config::{Nonblocking, Quic}; 6 | use crate::node::*; 7 | 8 | use std::path::PathBuf; 9 | 10 | use tokio::net::UdpSocket; 11 | use tokio::sync::Mutex as TokioMutex; 12 | use tokio::time::{sleep, Duration}; 13 | 14 | use tracing::*; 15 | 16 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 17 | use std::result::Result; 18 | use std::sync::Arc; 19 | 20 | use alloc::vec::Vec; 21 | use postcard::*; 22 | use std::convert::TryInto; 23 | use std::marker::PhantomData; 24 | 25 | // Quic 26 | use quinn::Endpoint; 27 | 28 | use crate::msg::*; 29 | use crate::node::quic::generate_client_config_from_certs; 30 | use chrono::Utc; 31 | 32 | impl From> for Node { 33 | fn from(node: Node) -> Self { 34 | Self { 35 | __state: PhantomData, 36 | __data_type: PhantomData, 37 | cfg: node.cfg, 38 | runtime: node.runtime, 39 | rt_handle: node.rt_handle, 40 | stream: node.stream, 41 | topic: node.topic, 42 | socket: node.socket, 43 | buffer: node.buffer, 44 | endpoint: node.endpoint, 45 | connection: node.connection, 46 | subscription_data: node.subscription_data, 47 | task_subscribe: None, 48 | } 49 | } 50 | } 51 | 52 | impl From> 53 | for Node 54 | { 55 | fn from(node: Node) -> Self { 56 | Self { 57 | __state: PhantomData, 58 | __data_type: PhantomData, 59 | cfg: node.cfg, 60 | runtime: node.runtime, 61 | rt_handle: node.rt_handle, 62 | stream: node.stream, 63 | topic: node.topic, 64 | socket: node.socket, 65 | buffer: node.buffer, 66 | endpoint: node.endpoint, 67 | connection: node.connection, 68 | subscription_data: node.subscription_data, 69 | task_subscribe: node.task_subscribe, 70 | } 71 | } 72 | } 73 | 74 | impl Node { 75 | /// Attempt connection from the Node to the Host located at the specified address 76 | //#[tracing::instrument(skip_all)] 77 | pub async fn activate(mut self) -> Result, Error> { 78 | debug!("Attempting QUIC connection"); 79 | 80 | self.create_connection().await?; 81 | 82 | Ok(Node::::from(self)) 83 | } 84 | 85 | async fn create_connection(&mut self) -> Result<(), Error> { 86 | let host_addr = self.cfg.network_cfg.host_addr; 87 | let cert_path = self.cfg.network_cfg.cert_path.clone(); 88 | 89 | let (endpoint, connection) = { 90 | // QUIC, needs to be done inside of a tokio context 91 | let client_cfg = generate_client_config_from_certs(cert_path)?; 92 | let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); 93 | 94 | let mut endpoint = Endpoint::client(client_addr)?; 95 | endpoint.set_default_client_config(client_cfg); 96 | 97 | // TO_DO: This shouldn't just be "localhost" 98 | let connection = endpoint.connect(host_addr, "localhost")?.await?; 99 | 100 | debug!("{:?}", &endpoint.local_addr()); 101 | 102 | Ok::<(Endpoint, quinn::Connection), Error>((endpoint, connection)) 103 | }?; 104 | self.endpoint = Some(endpoint); 105 | self.connection = Some(connection); 106 | Ok(()) 107 | } 108 | 109 | #[tracing::instrument(skip_all)] 110 | pub async fn subscribe( 111 | mut self, 112 | rate: Duration, 113 | ) -> Result, Error> { 114 | self.create_connection().await?; 115 | let connection = self.connection.clone(); 116 | let topic = self.topic.clone(); 117 | 118 | let subscription_data: Arc>>> = Arc::new(TokioMutex::new(None)); 119 | let data = Arc::clone(&subscription_data); 120 | 121 | let buffer = self.buffer.clone(); 122 | 123 | let packet = GenericMsg::subscribe(topic, rate)?; 124 | 125 | let task_subscribe = tokio::spawn(async move { 126 | if let Some(connection) = connection { 127 | loop { 128 | if let Err(e) = run_subscription::( 129 | packet.clone(), 130 | buffer.clone(), 131 | connection.clone(), 132 | data.clone(), 133 | ) 134 | .await 135 | { 136 | error!("{:?}", e); 137 | } 138 | } 139 | } 140 | }); 141 | 142 | self.task_subscribe = Some(task_subscribe); 143 | 144 | let mut subscription_node = Node::::from(self); 145 | subscription_node.subscription_data = subscription_data; 146 | 147 | Ok(subscription_node) 148 | } 149 | } 150 | 151 | #[tracing::instrument(skip_all)] 152 | async fn run_subscription( 153 | packet: GenericMsg, 154 | buffer: Arc>>, 155 | connection: quinn::Connection, 156 | data: Arc>>>, 157 | ) -> Result<(), Error> { 158 | let (mut send, mut recv) = connection.open_bi().await?; 159 | 160 | send.write_all(&packet.as_bytes()?).await?; 161 | send.finish().await?; 162 | 163 | loop { 164 | let mut buf = buffer.lock().await; 165 | 166 | if let Ok(Some(n)) = recv.read(&mut buf).await { 167 | let bytes = &buf[..n]; 168 | 169 | let generic = from_bytes::(bytes)?; 170 | info!("QUIC received generic: {:?}", &generic); 171 | let msg: Msg = generic.try_into()?; 172 | 173 | if let Some(data) = data.lock().await.as_ref() { 174 | debug!("Timestamp: {}", data.timestamp); 175 | let delta = msg.timestamp - data.timestamp; 176 | info!( 177 | "The time difference between QUIC subscription msg tx/rx is: {} us", 178 | delta 179 | ); 180 | if delta <= chrono::Duration::zero() { 181 | warn!("Data is not newer, skipping to next subscription iteration"); 182 | continue; 183 | } 184 | } 185 | 186 | let mut data = data.lock().await; 187 | *data = Some(msg); 188 | } 189 | } 190 | } 191 | 192 | // ----------------- 193 | 194 | use crate::node::network_config::Blocking; 195 | 196 | impl From> for Node { 197 | fn from(node: Node) -> Self { 198 | Self { 199 | __state: PhantomData, 200 | __data_type: PhantomData, 201 | cfg: node.cfg, 202 | runtime: node.runtime, 203 | rt_handle: node.rt_handle, 204 | stream: node.stream, 205 | topic: node.topic, 206 | socket: node.socket, 207 | buffer: node.buffer, 208 | endpoint: node.endpoint, 209 | connection: node.connection, 210 | subscription_data: node.subscription_data, 211 | task_subscribe: None, 212 | } 213 | } 214 | } 215 | 216 | impl From> for Node { 217 | fn from(node: Node) -> Self { 218 | Self { 219 | __state: PhantomData, 220 | __data_type: PhantomData, 221 | cfg: node.cfg, 222 | runtime: node.runtime, 223 | rt_handle: node.rt_handle, 224 | stream: node.stream, 225 | topic: node.topic, 226 | socket: node.socket, 227 | buffer: node.buffer, 228 | endpoint: node.endpoint, 229 | connection: node.connection, 230 | subscription_data: node.subscription_data, 231 | task_subscribe: node.task_subscribe, 232 | } 233 | } 234 | } 235 | 236 | impl Node { 237 | /// Attempt connection from the Node to the Host located at the specified address 238 | //#[tracing::instrument(skip_all)] 239 | pub fn activate(mut self) -> Result, Error> { 240 | debug!("Attempting QUIC connection"); 241 | 242 | self.create_connection()?; 243 | 244 | Ok(Node::::from(self)) 245 | } 246 | 247 | fn create_connection(&mut self) -> Result<(), Error> { 248 | let host_addr = self.cfg.network_cfg.host_addr; 249 | let cert_path = self.cfg.network_cfg.cert_path.clone(); 250 | 251 | let handle = match &self.rt_handle { 252 | Some(handle) => handle, 253 | None => return Err(Error::HandleAccess), 254 | }; 255 | 256 | let (endpoint, connection) = handle.block_on(async move { 257 | // QUIC, needs to be done inside of a tokio context 258 | let client_cfg = generate_client_config_from_certs(cert_path)?; 259 | let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); 260 | 261 | let mut endpoint = Endpoint::client(client_addr)?; 262 | endpoint.set_default_client_config(client_cfg); 263 | 264 | // TO_DO: This shouldn't just be "localhost" 265 | let connection = endpoint.connect(host_addr, "localhost")?.await?; 266 | 267 | debug!("{:?}", &endpoint.local_addr()); 268 | 269 | Ok::<(Endpoint, quinn::Connection), Error>((endpoint, connection)) 270 | })?; 271 | self.endpoint = Some(endpoint); 272 | self.connection = Some(connection); 273 | Ok(()) 274 | } 275 | 276 | #[tracing::instrument(skip_all)] 277 | pub fn subscribe( 278 | mut self, 279 | rate: Duration, 280 | ) -> Result, Error> { 281 | self.create_connection()?; 282 | let connection = self.connection.clone(); 283 | let topic = self.topic.clone(); 284 | 285 | let subscription_data: Arc>>> = Arc::new(TokioMutex::new(None)); 286 | let data = Arc::clone(&subscription_data); 287 | 288 | let buffer = self.buffer.clone(); 289 | 290 | let packet = GenericMsg::subscribe(topic, rate)?; 291 | 292 | let handle = match &self.rt_handle { 293 | Some(handle) => handle, 294 | None => return Err(Error::HandleAccess), 295 | }; 296 | let task_subscribe = handle.spawn(async move { 297 | if let Some(connection) = connection { 298 | loop { 299 | if let Err(e) = run_subscription::( 300 | packet.clone(), 301 | buffer.clone(), 302 | connection.clone(), 303 | data.clone(), 304 | ) 305 | .await 306 | { 307 | error!("{:?}", e); 308 | } 309 | } 310 | } 311 | }); 312 | 313 | self.task_subscribe = Some(task_subscribe); 314 | 315 | let mut subscription_node = Node::::from(self); 316 | subscription_node.subscription_data = subscription_data; 317 | 318 | Ok(subscription_node) 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | 3 | use meadow::prelude::*; 4 | mod common; 5 | use common::Pose; 6 | 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | #[cfg(feature = "quic")] 11 | use std::sync::Once; 12 | 13 | #[cfg(feature = "quic")] 14 | use meadow::host::quic::generate_certs; 15 | #[cfg(feature = "quic")] 16 | use meadow::host::quic::QuicCertGenConfig; 17 | 18 | #[cfg(feature = "quic")] 19 | static INIT: Once = Once::new(); 20 | 21 | #[cfg(feature = "quic")] 22 | pub fn initialize() { 23 | INIT.call_once(|| { 24 | generate_certs(QuicCertGenConfig::default()); 25 | }); 26 | } 27 | 28 | fn start_host() -> Result { 29 | let sc = SledConfig::new().temporary(true); 30 | let mut host = HostConfig::default().with_sled_config(sc).build()?; 31 | host.start()?; 32 | println!("Host should be running in the background"); 33 | Ok(host) 34 | } 35 | 36 | macro_rules! integrate_host_and_single_node { 37 | // macth like arm for macro 38 | ($a:ty) => { 39 | // macro expand to this code 40 | 41 | { 42 | type N = $a; 43 | println!("Running test on: {}", std::any::type_name::()); 44 | 45 | let _host = start_host().unwrap(); 46 | 47 | // Get the host up and running 48 | let node: Node = NodeConfig::new("pose").build().unwrap(); 49 | let node = node.activate().unwrap(); 50 | 51 | for i in 0..5 { 52 | // Could get this by reading a GPS, for example 53 | let pose = Pose { 54 | x: i as f32, 55 | y: i as f32, 56 | }; 57 | 58 | node.publish(pose.clone()).unwrap(); 59 | thread::sleep(Duration::from_millis(10)); 60 | let result = node.request().unwrap(); 61 | println!("Got position: {:?}", result); 62 | 63 | assert_eq!(pose, result.data); 64 | } 65 | } 66 | }; 67 | } 68 | 69 | #[test] 70 | fn integrate_host_and_single_node_macros() { 71 | integrate_host_and_single_node!(Tcp); 72 | #[cfg(not(feature = "quic"))] 73 | integrate_host_and_single_node!(Udp); 74 | #[cfg(feature = "quic")] 75 | integrate_host_and_single_node!(Quic); 76 | } 77 | 78 | macro_rules! custom_msg { 79 | // macth like arm for macro 80 | ($a:ty) => { 81 | // macro expand to this code 82 | 83 | { 84 | type N = $a; 85 | println!("Running test on: {}", std::any::type_name::()); 86 | 87 | let _host = start_host().unwrap(); 88 | 89 | // Get the host up and running 90 | let node: Node = NodeConfig::new("pose").build().unwrap(); 91 | let node = node.activate().unwrap(); 92 | 93 | for i in 0..5 { 94 | // Could get this by reading a GPS, for example 95 | let pose = Pose { 96 | x: i as f32, 97 | y: i as f32, 98 | }; 99 | 100 | let mut msg: Msg = Msg::new(MsgType::Set, "pose", pose.clone()); 101 | msg.set_timestamp(Utc::now()); 102 | 103 | node.publish_msg(msg).unwrap(); 104 | thread::sleep(Duration::from_millis(10)); 105 | let result = node.request().unwrap(); 106 | println!("Got position: {:?}", result); 107 | 108 | assert_eq!(pose, result.data); 109 | } 110 | } 111 | }; 112 | } 113 | 114 | #[test] 115 | fn custom_msg_macros() { 116 | custom_msg!(Tcp); 117 | #[cfg(not(feature = "quic"))] 118 | custom_msg!(Udp); 119 | #[cfg(feature = "quic")] 120 | custom_msg!(Quic); 121 | } 122 | 123 | macro_rules! request_non_existent_topic { 124 | // macth like arm for macro 125 | ($a:ty) => { 126 | // macro expand to this code 127 | 128 | { 129 | type N = $a; 130 | println!("Running test on: {}", std::any::type_name::()); 131 | // Test code goes below here 132 | 133 | let _host = start_host().unwrap(); 134 | 135 | let node: Node = 136 | NodeConfig::new("doesnt_exist").build().unwrap(); 137 | let node = node.activate().unwrap(); 138 | 139 | // Requesting a topic that doesn't exist should return a recoverable error 140 | for i in 0..5 { 141 | println!("on loop: {}", i); 142 | let result = node.request(); 143 | dbg!(&result); 144 | thread::sleep(Duration::from_millis(50)); 145 | } 146 | } 147 | }; 148 | } 149 | 150 | #[test] 151 | fn request_non_existent_topic_macros() { 152 | request_non_existent_topic!(Tcp); 153 | #[cfg(not(feature = "quic"))] 154 | request_non_existent_topic!(Udp); 155 | #[cfg(feature = "quic")] 156 | request_non_existent_topic!(Quic); 157 | } 158 | 159 | macro_rules! node_send_options { 160 | // macth like arm for macro 161 | ($a:ty) => { 162 | // macro expand to this code 163 | 164 | { 165 | type N = $a; 166 | println!("Running test on: {}", std::any::type_name::()); 167 | // Test code goes below here 168 | 169 | let _host = start_host().unwrap(); 170 | 171 | // Get the host up and running 172 | let node_a = NodeConfig::>::new("pose") 173 | .build() 174 | .unwrap() 175 | .activate() 176 | .unwrap(); 177 | let node_b = NodeConfig::>::new("pose") 178 | .build() 179 | .unwrap() 180 | .activate() 181 | .unwrap(); 182 | 183 | // Send Option with `Some(value)` 184 | node_a.publish(Some(1.0)).unwrap(); 185 | let result = node_b.request().unwrap(); 186 | dbg!(&result); 187 | assert_eq!(result.data.unwrap(), 1.0); 188 | 189 | // Send option with `None` 190 | node_a.publish(None).unwrap(); 191 | let result = node_b.request(); 192 | dbg!(&result); 193 | assert_eq!(result.unwrap().data, None); 194 | } 195 | }; 196 | } 197 | 198 | #[test] 199 | fn node_send_options_macros() { 200 | node_send_options!(Tcp); 201 | #[cfg(not(feature = "quic"))] 202 | node_send_options!(Udp); 203 | #[cfg(feature = "quic")] 204 | node_send_options!(Quic); 205 | } 206 | 207 | macro_rules! subscription_usize { 208 | // macth like arm for macro 209 | ($a:ty) => { 210 | // macro expand to this code 211 | 212 | { 213 | type N = $a; 214 | println!("Running test on: {}", std::any::type_name::()); 215 | // Test code goes below here 216 | 217 | let _host = start_host().unwrap(); 218 | 219 | // Get the host up and running 220 | let writer = NodeConfig::::new("subscription") 221 | .build() 222 | .unwrap() 223 | .activate() 224 | .unwrap(); 225 | 226 | // Create a subscription node with a query rate of 100 Hz 227 | let reader = writer 228 | .config() 229 | .clone() 230 | .build() 231 | .unwrap() 232 | .subscribe(Duration::from_millis(10)) 233 | .unwrap(); 234 | 235 | for i in 0..5 { 236 | let test_value = i as usize; 237 | writer.publish(test_value).unwrap(); 238 | std::thread::sleep(std::time::Duration::from_millis(100)); 239 | assert_eq!(reader.get_subscribed_data().unwrap().data, test_value); 240 | } 241 | } 242 | }; 243 | } 244 | 245 | #[test] 246 | fn subscription_usize_macros() { 247 | subscription_usize!(Tcp); 248 | #[cfg(not(feature = "quic"))] 249 | subscription_usize!(Udp); 250 | #[cfg(feature = "quic")] 251 | subscription_usize!(Quic); 252 | } 253 | 254 | macro_rules! no_subscribed_value { 255 | // macth like arm for macro 256 | ($a:ty) => { 257 | // macro expand to this code 258 | 259 | { 260 | type N = $a; 261 | println!("Running test on: {}", std::any::type_name::()); 262 | // Test code goes below here 263 | 264 | let _host = start_host().unwrap(); 265 | 266 | // Create a subscription node with a query rate of 10 Hz 267 | let reader = NodeConfig::::new("subscription") 268 | .build() 269 | .unwrap() 270 | .subscribe(Duration::from_millis(100)) 271 | .unwrap(); 272 | 273 | // Unwrapping on an error should lead to panic 274 | let _result: usize = reader.get_subscribed_data().unwrap().data; 275 | } 276 | }; 277 | } 278 | 279 | #[should_panic] 280 | #[test] 281 | fn no_subscribed_value_macros() { 282 | no_subscribed_value!(Tcp); 283 | #[cfg(not(feature = "quic"))] 284 | no_subscribed_value!(Udp); 285 | #[cfg(feature = "quic")] 286 | no_subscribed_value!(Quic); 287 | } 288 | 289 | macro_rules! topics_list { 290 | // macth like arm for macro 291 | ($a:ty) => { 292 | // macro expand to this code 293 | 294 | { 295 | type N = $a; 296 | println!("Running test on: {}", std::any::type_name::()); 297 | let host = start_host().unwrap(); 298 | 299 | // Get the host up and running 300 | let topics: Vec = ["a", "b", "c", "d", "e", "f"] 301 | .iter() 302 | .map(|x| x.to_string()) 303 | .collect(); 304 | dbg!(&topics); 305 | let mut nodes = Vec::with_capacity(topics.len()); 306 | for topic in topics.clone() { 307 | let node: Node = NodeConfig::new(topic).build().unwrap(); 308 | let node = node.activate().unwrap(); 309 | nodes.push(node); 310 | } 311 | 312 | for i in 0..topics.len() { 313 | nodes[i].publish(i).unwrap(); 314 | thread::sleep(Duration::from_micros(1_000)); 315 | assert_eq!(host.topics(), nodes[i].topics().unwrap().data); 316 | let t = if i == 0 { 317 | vec![topics[i].to_string()] 318 | } else { 319 | let mut t = topics[0..i + 1] 320 | .iter() 321 | .map(|x| x.to_string()) 322 | .collect::>(); 323 | t.sort(); 324 | t 325 | }; 326 | let mut nt = nodes[i].topics().unwrap().data; 327 | nt.sort(); 328 | assert_eq!(t, nt); 329 | } 330 | } 331 | }; 332 | } 333 | 334 | #[test] 335 | fn topics_list_macros() { 336 | topics_list!(Tcp); 337 | #[cfg(not(feature = "quic"))] 338 | topics_list!(Udp); 339 | #[cfg(feature = "quic")] 340 | topics_list!(Quic); 341 | } 342 | 343 | macro_rules! back_nth_operation { 344 | // macth like arm for macro 345 | ($a:ty) => { 346 | // macro expand to this code 347 | 348 | { 349 | type N = $a; 350 | println!("Running test on: {}", std::any::type_name::()); 351 | // Test code goes below here 352 | let _host = start_host().unwrap(); 353 | 354 | // Get the host up and running 355 | let node: Node = NodeConfig::new("pose").build().unwrap(); 356 | let node = node.activate().unwrap(); 357 | 358 | let n = 5; 359 | for i in 0..n { 360 | let pose = Pose { 361 | x: i as f32, 362 | y: i as f32, 363 | }; 364 | node.publish(pose.clone()).unwrap(); 365 | println!("Published {:?}", &pose); 366 | assert_eq!(node.request().unwrap().data, pose); 367 | assert_eq!(node.request_nth_back(0).unwrap().data, pose); 368 | } 369 | let back = 3; 370 | let pose = Pose { 371 | x: (n - back) as f32, 372 | y: (n - back) as f32, 373 | }; 374 | // We use "back + 1" because we're zero-indexed 375 | let _b = node.request_nth_back(back - 1).unwrap().data; 376 | assert_eq!(node.request_nth_back(back - 1).unwrap().data, pose); 377 | } 378 | }; 379 | } 380 | 381 | #[test] 382 | fn back_nth_operation_macros() { 383 | back_nth_operation!(Tcp); 384 | #[cfg(not(feature = "quic"))] 385 | back_nth_operation!(Udp); 386 | #[cfg(feature = "quic")] 387 | back_nth_operation!(Quic); 388 | } 389 | 390 | macro_rules! back_nth_operation_fallible { 391 | // macth like arm for macro 392 | ($a:ty) => { 393 | // macro expand to this code 394 | 395 | { 396 | type N = $a; 397 | println!("Running test on: {}", std::any::type_name::()); 398 | // Test code goes below here 399 | 400 | let _host = start_host().unwrap(); 401 | 402 | // Create a subscription node with a query rate of 10 Hz 403 | let reader = NodeConfig::::new("subscription") 404 | .build() 405 | .unwrap() 406 | .subscribe(Duration::from_millis(100)) 407 | .unwrap(); 408 | 409 | // Unwrapping on an error should lead to panic 410 | let _result: usize = reader.get_subscribed_data().unwrap().data; 411 | } 412 | }; 413 | } 414 | 415 | #[should_panic] 416 | #[test] 417 | fn back_nth_operation_fallible_macros() { 418 | back_nth_operation_fallible!(Tcp); 419 | #[cfg(not(feature = "quic"))] 420 | back_nth_operation_fallible!(Udp); 421 | #[cfg(feature = "quic")] 422 | back_nth_operation_fallible!(Quic); 423 | } 424 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------