//! Transport abstraction for pluggable mesh backends. //! //! Every mesh transport (iroh QUIC, TCP, LoRa, Serial) implements the //! [`MeshTransport`] trait. The [`TransportAddr`] enum provides a //! transport-agnostic address type. use std::fmt; use anyhow::Result; /// Transport-agnostic peer address. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum TransportAddr { /// iroh node ID (32-byte public key) with optional relay info. Iroh(Vec), /// IP socket address for TCP/UDP transports. Socket(std::net::SocketAddr), /// LoRa device address (4 bytes). LoRa([u8; 4]), /// Serial port identifier. Serial(String), /// Opaque bytes for unknown/future transports. Raw(Vec), } impl fmt::Display for TransportAddr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Iroh(id) => write!(f, "iroh:{}", hex::encode(&id[..4.min(id.len())])), Self::Socket(addr) => write!(f, "tcp:{addr}"), Self::LoRa(addr) => write!(f, "lora:{}", hex::encode(addr)), Self::Serial(port) => write!(f, "serial:{port}"), Self::Raw(data) => write!(f, "raw:{}", hex::encode(&data[..4.min(data.len())])), } } } /// Transport capability level for crypto mode selection. /// /// Ordered from worst to best so max_by_key picks the best transport. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum TransportCapability { /// Very low bandwidth, severely duty-cycled (LoRa SF11-SF12, serial). /// MLS-Lite without signature preferred. SeverelyConstrained = 0, /// Low bandwidth, duty-cycled (LoRa SF7-SF10). /// Classical MLS marginal, prefer MLS-Lite with sig. Constrained = 1, /// Medium bandwidth (BLE, slower WiFi). /// Supports full MLS with classical crypto. Medium = 2, /// High-bandwidth, low-latency (QUIC, TCP, WiFi). /// Supports full MLS with PQ-KEM, large KeyPackages. Unconstrained = 3, } impl TransportCapability { /// Determine capability from bitrate and MTU. pub fn from_metrics(bitrate_bps: u64, mtu: usize) -> Self { match (bitrate_bps, mtu) { (b, _) if b >= 1_000_000 => Self::Unconstrained, // ≥1 Mbps (b, m) if b >= 10_000 && m >= 200 => Self::Medium, // ≥10 kbps, decent MTU (b, m) if b >= 1_000 || m >= 100 => Self::Constrained, // ≥1 kbps _ => Self::SeverelyConstrained, } } /// Recommended crypto mode for this capability level. pub fn recommended_crypto(&self) -> CryptoMode { match self { Self::Unconstrained => CryptoMode::MlsHybrid, Self::Medium => CryptoMode::MlsClassical, Self::Constrained => CryptoMode::MlsLiteSigned, Self::SeverelyConstrained => CryptoMode::MlsLiteUnsigned, } } /// Whether full MLS is viable on this transport. pub fn supports_mls(&self) -> bool { matches!(self, Self::Unconstrained | Self::Medium) } } /// Crypto mode for mesh messaging. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CryptoMode { /// Full MLS with X25519 + ML-KEM-768 hybrid. MlsHybrid, /// Full MLS with classical X25519 only. MlsClassical, /// MLS-Lite with Ed25519 signature. MlsLiteSigned, /// MLS-Lite without signature (smallest overhead). MlsLiteUnsigned, } impl CryptoMode { /// Approximate overhead in bytes for this mode. pub fn overhead_bytes(&self) -> usize { match self { Self::MlsHybrid => 2700, // PQ KeyPackage alone Self::MlsClassical => 400, // Classical KeyPackage + message Self::MlsLiteSigned => 262, // MLS-Lite with sig Self::MlsLiteUnsigned => 129, // MLS-Lite minimal } } } /// Metadata about a transport's capabilities. #[derive(Clone, Debug)] pub struct TransportInfo { /// Human-readable transport name. pub name: String, /// Maximum transmission unit in bytes. pub mtu: usize, /// Estimated bitrate in bits/second. pub bitrate: u64, /// Whether this transport supports bidirectional communication. pub bidirectional: bool, } impl TransportInfo { /// Compute capability level from this transport's metrics. pub fn capability(&self) -> TransportCapability { TransportCapability::from_metrics(self.bitrate, self.mtu) } /// Recommended crypto mode for this transport. pub fn recommended_crypto(&self) -> CryptoMode { self.capability().recommended_crypto() } } /// Received packet from a transport. #[derive(Clone, Debug)] pub struct TransportPacket { /// Source address of the sender. pub from: TransportAddr, /// Raw packet data. pub data: Vec, } /// A pluggable mesh transport backend. /// /// Implementations provide send/receive over a specific medium (QUIC, TCP, LoRa, etc). #[async_trait::async_trait] pub trait MeshTransport: Send + Sync { /// Transport metadata (name, MTU, bitrate). fn info(&self) -> TransportInfo; /// Send raw bytes to a destination. async fn send(&self, dest: &TransportAddr, data: &[u8]) -> Result<()>; /// Receive the next incoming packet. Blocks until data arrives. async fn recv(&self) -> Result; /// Discover reachable peers on this transport. /// Returns an empty vec if discovery is not supported. async fn discover(&self) -> Result> { Ok(Vec::new()) } /// Gracefully shut down this transport. async fn close(&self) -> Result<()> { Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn transport_addr_display_iroh() { let addr = TransportAddr::Iroh(vec![0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02]); assert_eq!(addr.to_string(), "iroh:deadbeef"); } #[test] fn transport_addr_display_iroh_short() { let addr = TransportAddr::Iroh(vec![0xAB, 0xCD]); assert_eq!(addr.to_string(), "iroh:abcd"); } #[test] fn transport_addr_display_socket() { let addr = TransportAddr::Socket("127.0.0.1:9000".parse().unwrap()); assert_eq!(addr.to_string(), "tcp:127.0.0.1:9000"); } #[test] fn transport_addr_display_lora() { let addr = TransportAddr::LoRa([0x01, 0x02, 0x03, 0x04]); assert_eq!(addr.to_string(), "lora:01020304"); } #[test] fn transport_addr_display_serial() { let addr = TransportAddr::Serial("/dev/ttyUSB0".to_string()); assert_eq!(addr.to_string(), "serial:/dev/ttyUSB0"); } #[test] fn transport_addr_display_raw() { let addr = TransportAddr::Raw(vec![0xFF, 0xEE, 0xDD, 0xCC, 0xBB]); assert_eq!(addr.to_string(), "raw:ffeeddcc"); } #[test] fn transport_addr_display_raw_short() { let addr = TransportAddr::Raw(vec![0x01]); assert_eq!(addr.to_string(), "raw:01"); } #[test] fn transport_addr_equality() { let a = TransportAddr::Socket("127.0.0.1:8080".parse().unwrap()); let b = TransportAddr::Socket("127.0.0.1:8080".parse().unwrap()); let c = TransportAddr::Socket("127.0.0.1:9090".parse().unwrap()); assert_eq!(a, b); assert_ne!(a, c); } #[test] fn capability_ordering() { // Higher value = better capability assert!(TransportCapability::Unconstrained > TransportCapability::Medium); assert!(TransportCapability::Medium > TransportCapability::Constrained); assert!(TransportCapability::Constrained > TransportCapability::SeverelyConstrained); // max_by_key should pick the best let caps = vec![ TransportCapability::Constrained, TransportCapability::Unconstrained, TransportCapability::Medium, ]; let best = caps.into_iter().max().unwrap(); assert_eq!(best, TransportCapability::Unconstrained); } #[test] fn capability_recommended_crypto() { assert_eq!( TransportCapability::Unconstrained.recommended_crypto(), CryptoMode::MlsHybrid ); assert_eq!( TransportCapability::Medium.recommended_crypto(), CryptoMode::MlsClassical ); assert_eq!( TransportCapability::Constrained.recommended_crypto(), CryptoMode::MlsLiteSigned ); assert_eq!( TransportCapability::SeverelyConstrained.recommended_crypto(), CryptoMode::MlsLiteUnsigned ); } #[test] fn transport_info_capability() { let tcp_info = TransportInfo { name: "tcp".to_string(), mtu: 1500, bitrate: 100_000_000, // 100 Mbps bidirectional: true, }; assert_eq!(tcp_info.capability(), TransportCapability::Unconstrained); assert_eq!(tcp_info.recommended_crypto(), CryptoMode::MlsHybrid); let lora_info = TransportInfo { name: "lora".to_string(), mtu: 51, bitrate: 300, bidirectional: true, }; assert_eq!(lora_info.capability(), TransportCapability::SeverelyConstrained); assert_eq!(lora_info.recommended_crypto(), CryptoMode::MlsLiteUnsigned); } #[test] fn crypto_mode_overhead() { assert!(CryptoMode::MlsHybrid.overhead_bytes() > 2000); assert!(CryptoMode::MlsClassical.overhead_bytes() < 500); assert!(CryptoMode::MlsLiteSigned.overhead_bytes() < 300); assert!(CryptoMode::MlsLiteUnsigned.overhead_bytes() < 150); } }