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:
2026-03-30 21:19:12 +02:00
parent d469999c2a
commit f9ac921a0c
20 changed files with 4042 additions and 6 deletions

View File

@@ -0,0 +1,140 @@
//! 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<u8>),
/// 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<u8>),
}
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())])),
}
}
}
/// 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,
}
/// 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<u8>,
}
/// 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<TransportPacket>;
/// Discover reachable peers on this transport.
/// Returns an empty vec if discovery is not supported.
async fn discover(&self) -> Result<Vec<TransportAddr>> {
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);
}
}