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:
140
crates/quicprochat-p2p/src/transport.rs
Normal file
140
crates/quicprochat-p2p/src/transport.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user