Files
quicproquo/crates/quicprochat-p2p/src/transport_manager.rs
Christian Nennemann f9ac921a0c feat(p2p): mesh stack, LoRa mock transport, and relay demo
Implement transport abstraction (TCP/iroh), announce and routing table,
multi-hop mesh router, truncated-address link layer, and LoRa mock
medium with fragmentation plus EU868-style duty-cycle accounting.
Add mesh_lora_relay_demo and scripts/mesh-demo.sh. Relax CBOR vs JSON
size assertion to match fixed-size cryptographic overhead. Extend
.gitignore for nested targets and node_modules.

Made-with: Cursor
2026-03-30 21:19:12 +02:00

182 lines
5.4 KiB
Rust

//! 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<Box<dyn MeshTransport>>,
}
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<dyn MeshTransport>) {
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<dyn MeshTransport>] {
&self.transports
}
/// Get info for all registered transports.
pub fn transport_info(&self) -> Vec<TransportInfo> {
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<TransportPacket> {
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());
}
}