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
This commit is contained in:
181
crates/quicprochat-p2p/src/transport_manager.rs
Normal file
181
crates/quicprochat-p2p/src/transport_manager.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
//! 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user