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:
96
crates/quicprochat-p2p/examples/mesh_lora_relay_demo.rs
Normal file
96
crates/quicprochat-p2p/examples/mesh_lora_relay_demo.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
//! Simulated mesh leg: **A (LoRa)** → **B (LoRa + TCP relay)** → **C (TCP)** → zurück über B → **A**.
|
||||
//!
|
||||
//! Uses [`quicprochat_p2p::transport_lora::LoRaMockMedium`] — keine Hardware.
|
||||
//!
|
||||
//! ```text
|
||||
//! Node A Node B Node C
|
||||
//! LoRa addr 0x01 LoRa 0x02 + TCP listen TCP (WiFi / LAN)
|
||||
//! │ │ │
|
||||
//! └──── LoRa ───────┘ │
|
||||
//! └──────── TCP ──────────────┘
|
||||
//! ```
|
||||
//!
|
||||
//! Run: `cargo run -p quicprochat-p2p --example mesh_lora_relay_demo`
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use quicprochat_p2p::transport::{MeshTransport, TransportAddr};
|
||||
use quicprochat_p2p::transport_lora::{DutyCycleTracker, LoRaConfig, LoRaMockMedium};
|
||||
use quicprochat_p2p::transport_tcp::TcpTransport;
|
||||
|
||||
const ADDR_A: [u8; 4] = [0x01, 0, 0, 0];
|
||||
const ADDR_B: [u8; 4] = [0x02, 0, 0, 0];
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let medium = LoRaMockMedium::new();
|
||||
let duty = Arc::new(DutyCycleTracker::new(3_600_000));
|
||||
|
||||
let lora_a = medium
|
||||
.connect(ADDR_A, LoRaConfig::default(), Arc::clone(&duty))
|
||||
.await?;
|
||||
let lora_b = medium
|
||||
.connect(ADDR_B, LoRaConfig::default(), Arc::clone(&duty))
|
||||
.await?;
|
||||
|
||||
let tcp_b = TcpTransport::bind("127.0.0.1:0").await?;
|
||||
let tcp_c = TcpTransport::bind("127.0.0.1:0").await?;
|
||||
|
||||
let c_listen = tcp_c.local_addr();
|
||||
let b_listen = tcp_b.local_addr();
|
||||
let c_addr = TransportAddr::Socket(c_listen);
|
||||
let b_addr = TransportAddr::Socket(b_listen);
|
||||
|
||||
println!(
|
||||
"LoRa mock mesh demo: B relays LoRa <-> TCP (B TCP {}, C TCP {})",
|
||||
b_listen, c_listen
|
||||
);
|
||||
|
||||
let relay = tokio::spawn(async move {
|
||||
for _ in 0..2 {
|
||||
tokio::select! {
|
||||
p = lora_b.recv() => {
|
||||
let p = p.expect("B LoRa recv");
|
||||
println!("B: LoRa from {} -> TCP ({} bytes)", p.from, p.data.len());
|
||||
tcp_b.send(&c_addr, &p.data).await.expect("B TCP send to C");
|
||||
}
|
||||
p = tcp_b.recv() => {
|
||||
let p = p.expect("B TCP recv");
|
||||
println!("B: TCP -> LoRa A ({} bytes)", p.data.len());
|
||||
lora_b
|
||||
.send(&TransportAddr::LoRa(ADDR_A), &p.data)
|
||||
.await
|
||||
.expect("B LoRa send to A");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let c_task = tokio::spawn(async move {
|
||||
let pkt = tcp_c.recv().await.expect("C TCP recv");
|
||||
println!("C: got {} bytes from B relay", pkt.data.len());
|
||||
assert_eq!(pkt.data, b"hello via mesh");
|
||||
tcp_c
|
||||
.send(&b_addr, b"ack from C")
|
||||
.await
|
||||
.expect("C TCP send");
|
||||
});
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
|
||||
lora_a
|
||||
.send(&TransportAddr::LoRa(ADDR_B), b"hello via mesh")
|
||||
.await?;
|
||||
|
||||
let reply = lora_a.recv().await?;
|
||||
println!("A: LoRa reply {} bytes", reply.data.len());
|
||||
assert_eq!(reply.data, b"ack from C");
|
||||
|
||||
c_task.await.expect("node C task panicked");
|
||||
relay.await.expect("relay task panicked");
|
||||
|
||||
lora_a.close().await.ok();
|
||||
println!("Done: LoRa + TCP relay path OK.");
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user