//! Multi-transport manager for routing packets across different backends. //! //! The [`TransportManager`] holds multiple [`MeshTransport`] implementations //! and selects the best one for a given [`TransportAddr`] variant. //! //! [`crate::transport_lora::LoRaTransport`] performs MTU-aware fragmentation internally; use //! [`crate::transport_lora::split_for_mtu`] only when chunking at a higher layer. use anyhow::{bail, Result}; use crate::transport::{MeshTransport, TransportAddr, TransportInfo}; /// Manages multiple mesh transports and routes packets to the best available one. pub struct TransportManager { transports: Vec>, } impl TransportManager { /// Create an empty transport manager. pub fn new() -> Self { Self { transports: Vec::new(), } } /// Register a transport backend. pub fn add(&mut self, transport: Box) { self.transports.push(transport); } /// Send data, choosing the best transport for the given address type. /// /// The selection heuristic matches the [`TransportAddr`] variant to the /// transport whose name corresponds to that variant (iroh for `Iroh`, /// tcp for `Socket`, etc). Falls back to trying each transport in order. pub async fn send(&self, dest: &TransportAddr, data: &[u8]) -> Result<()> { let target_name = match dest { TransportAddr::Iroh(_) => "iroh-quic", TransportAddr::Socket(_) => "tcp", TransportAddr::LoRa(_) => "lora", TransportAddr::Serial(_) => "serial", TransportAddr::Raw(_) => "", }; // First, try the transport whose name matches the address type. for t in &self.transports { if t.info().name == target_name { return t.send(dest, data).await; } } // Fallback: try each transport in order until one succeeds. let mut last_err = None; for t in &self.transports { match t.send(dest, data).await { Ok(()) => return Ok(()), Err(e) => last_err = Some(e), } } match last_err { Some(e) => Err(e), None => bail!("no transports registered"), } } /// List all registered transports. pub fn transports(&self) -> &[Box] { &self.transports } /// Get info for all registered transports. pub fn transport_info(&self) -> Vec { self.transports.iter().map(|t| t.info()).collect() } /// Shut down all transports. pub async fn close_all(&self) -> Result<()> { for t in &self.transports { t.close().await?; } Ok(()) } } impl Default for TransportManager { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; use crate::transport::TransportPacket; /// A mock transport that accepts any send and returns a fixed name. struct MockTransport { name: String, } impl MockTransport { fn new(name: &str) -> Self { Self { name: name.to_string(), } } } #[async_trait::async_trait] impl MeshTransport for MockTransport { fn info(&self) -> TransportInfo { TransportInfo { name: self.name.clone(), mtu: 1500, bitrate: 1_000_000, bidirectional: true, } } async fn send(&self, _dest: &TransportAddr, _data: &[u8]) -> Result<()> { Ok(()) } async fn recv(&self) -> Result { bail!("MockTransport does not support recv") } } #[tokio::test] async fn routes_socket_to_tcp() { let mut mgr = TransportManager::new(); mgr.add(Box::new(MockTransport::new("tcp"))); mgr.add(Box::new(MockTransport::new("iroh-quic"))); let addr = TransportAddr::Socket("127.0.0.1:8080".parse().unwrap()); let result = mgr.send(&addr, b"test data").await; assert!(result.is_ok()); } #[tokio::test] async fn routes_iroh_to_iroh_transport() { let mut mgr = TransportManager::new(); mgr.add(Box::new(MockTransport::new("tcp"))); mgr.add(Box::new(MockTransport::new("iroh-quic"))); let addr = TransportAddr::Iroh(vec![0xAA; 32]); let result = mgr.send(&addr, b"test data").await; assert!(result.is_ok()); } #[tokio::test] async fn no_transports_returns_error() { let mgr = TransportManager::new(); let addr = TransportAddr::Socket("127.0.0.1:8080".parse().unwrap()); let result = mgr.send(&addr, b"data").await; assert!(result.is_err()); } #[tokio::test] async fn transport_info_lists_all() { let mut mgr = TransportManager::new(); mgr.add(Box::new(MockTransport::new("tcp"))); mgr.add(Box::new(MockTransport::new("iroh-quic"))); let infos = mgr.transport_info(); assert_eq!(infos.len(), 2); assert_eq!(infos[0].name, "tcp"); assert_eq!(infos[1].name, "iroh-quic"); } #[tokio::test] async fn close_all_succeeds() { let mut mgr = TransportManager::new(); mgr.add(Box::new(MockTransport::new("tcp"))); mgr.add(Box::new(MockTransport::new("iroh-quic"))); let result = mgr.close_all().await; assert!(result.is_ok()); } }