# Phase 2: Mesh as Federation Transport ## Context Phase 1 built three independent communication silos: Tor-HTTP for federation, Meshcore LoRa for offline mesh, Nostr relays for discovery. They share no common abstraction — federation hardcodes Tor SOCKS5, mesh has its own serial listener, peer messaging uses a separate HTTP POST. Adding a new transport (LAN/mDNS) or changing routing logic means touching every caller. Phase 2 unifies these under a `NodeTransport` trait with automatic transport selection per peer (Mesh > LAN > Tor), chunked LoRa messaging with Reed-Solomon FEC for payloads >160 bytes, CBOR delta compression for state sync, mDNS LAN discovery, and a "mesh only" off-grid mode. The v2.0 roadmap (`docs/roadmap-v2.0.md`) also calls for a visual topology map and CRDT-based state sync — those are deferred to Phase 5 (Mesh Intelligence), but the transport abstraction here enables them. ## Dependencies (Verified) ```toml ciborium = "0.2.2" # CBOR serde (134M downloads, active) reed-solomon-erasure = "6.0" # FEC (4M downloads, MIT) mdns-sd = "0.18.2" # LAN peer discovery (1.86M, active) ``` ## Architecture ``` core/archipelago/src/transport/ ├── mod.rs — NodeTransport trait, TransportKind, TransportRouter, PeerRegistry ├── tor.rs — TorTransport (wraps node_message.rs SOCKS5 logic) ├── mesh_transport.rs — MeshTransport (bridges MeshService cmd channel) ├── lan.rs — LanTransport (mdns-sd + direct HTTP) ├── chunking.rs — Split/reassemble with Reed-Solomon FEC └── delta.rs — CBOR delta encoding for NodeStateSnapshot ``` ### Unified Peer Identity DID is the canonical identifier. Transport-specific addresses are attributes: ```rust pub struct PeerRecord { pub did: String, pub pubkey_hex: String, pub name: Option, pub trust_level: TrustLevel, // Transport capabilities pub mesh_contact_id: Option, pub lan_address: Option, pub onion_address: Option, // Freshness pub last_mesh: Option, pub last_lan: Option, pub last_tor: Option, } ``` ### Transport Routing ``` TransportRouter::send_to_peer(did, payload) 1. Look up PeerRecord by DID 2. Filter to transports where peer has address AND transport is available AND not stale 3. Sort by priority: Mesh(1) > LAN(2) > Tor(3) 4. If mesh_only_mode: only allow Mesh 5. Try each in order, fall back on failure ``` ### Chunking Protocol (for LoRa >160B) Per-chunk structure (8 byte header + 124 byte payload = 132B, fits in 160B after encryption): ``` [0x01] [msg_id: u32 LE] [chunk_idx: u8] [total: u8] [is_parity: u8] [payload: 124B] ``` Reed-Solomon: 25% parity shards. 4 data chunks = 1 parity chunk. Any N-of-(N+parity) reconstructs. Max practical: ~10 chunks (~1.2KB) before LoRa airtime becomes unreasonable. ### CBOR Delta Sync Full JSON state: ~500-2000 bytes. CBOR delta when only CPU/mem changed: ~30-50 bytes. Fits single LoRa chunk. ```rust pub struct StateDelta { pub ts: String, pub v: u8, pub apps: Option>, // Only changed apps pub apps_rm: Option>, // Removed app IDs pub cpu: Option, pub mem_u: Option, // ... only changed fields, short names for wire size } ``` ## Implementation Steps ### Step 1: Add deps to Cargo.toml **File**: `core/archipelago/Cargo.toml` Add ciborium, reed-solomon-erasure, mdns-sd. ### Step 2: Create `transport/mod.rs` — Core trait + PeerRegistry + TransportRouter **New file**: `core/archipelago/src/transport/mod.rs` - `NodeTransport` trait: `kind()`, `is_available()`, `send(address, payload)` - `TransportKind` enum: `Mesh = 1, Lan = 2, Tor = 3` - `TransportMessage`: `{from_did, payload: Vec, message_type}` - `PeerRegistry`: unified peer store (DID -> PeerRecord), JSON persistence to `transport-peers.json` - `TransportRouter`: holds Vec>, routes by priority with fallback - Register `mod transport;` in `core/archipelago/src/lib.rs` or main.rs ### Step 3: Create `transport/chunking.rs` — Reed-Solomon FEC **New file**: `core/archipelago/src/transport/chunking.rs` - `encode_chunked(data: &[u8]) -> Vec>` — split + FEC parity shards - `ChunkReassembler` — state machine tracking pending messages, attempts reconstruction when enough chunks arrive - `decode_chunked(chunks: &[Option>]) -> Result>` — reconstruct from data+parity - Unit tests: roundtrip, missing chunks, corrupted chunks ### Step 4: Create `transport/delta.rs` — CBOR delta sync **New file**: `core/archipelago/src/transport/delta.rs` - `compute_delta(prev, curr: &NodeStateSnapshot) -> StateDelta` - `apply_delta(base: &NodeStateSnapshot, delta: &StateDelta) -> NodeStateSnapshot` - `encode_cbor(delta) -> Vec` via ciborium - `decode_cbor(bytes) -> Result` - Unit tests: delta correctness, CBOR roundtrip, size comparison vs JSON ### Step 5: Create `transport/tor.rs` — TorTransport **New file**: `core/archipelago/src/transport/tor.rs` - Wraps existing `node_message::send_to_peer()` pattern (reqwest + SOCKS5) - Reuses: `node_message.rs:66` send logic, `node_message.rs:115` health check - `is_available()` checks SOCKS5 proxy reachable at 127.0.0.1:9050 - Receives handled by existing HTTP handler in `api/handler.rs` ### Step 6: Create `transport/mesh_transport.rs` — MeshTransport **New file**: `core/archipelago/src/transport/mesh_transport.rs` - Holds reference to `Arc>>` (same as RpcHandler) - `send()` routes through `MeshService::send_message()` for small payloads - For payloads >124B: chunk with `chunking::encode_chunked()`, send each chunk via `MeshCommand::SendText` - 200ms inter-chunk delay for LoRa airtime fairness - `is_available()` checks mesh device connected via `MeshService::status()` ### Step 7: Create `transport/lan.rs` — LanTransport **New file**: `core/archipelago/src/transport/lan.rs` - Uses `mdns-sd::ServiceDaemon` for discovery - Advertises: `_archipelago._tcp.local.` with TXT records: `did=..., pubkey=..., version=...` - Browses for peers, updates PeerRegistry when found - `send()`: direct HTTP POST to `http://{ip}:5678/archipelago/node-message` (same endpoint as Tor, no proxy) - Non-blocking init — if Avahi not available, fail gracefully ### Step 8: Add `mesh_only_mode` to MeshConfig **File**: `core/archipelago/src/mesh/mod.rs` - Add `pub mesh_only_mode: Option` to MeshConfig (with `#[serde(default)]`) - TransportRouter reads this flag to restrict routing ### Step 9: Wire TransportRouter into server.rs **File**: `core/archipelago/src/server.rs` (~line 136, after mesh service init) - Create PeerRegistry, load from disk - Create TorTransport, MeshTransport, LanTransport - Start LAN advertisement + discovery - Create TransportRouter with all three + mesh_only flag - Spawn background task bridging `MeshEvent::IdentityReceived` -> PeerRegistry - Store router on RpcHandler ### Step 10: Add transport RPC endpoints **New file**: `core/archipelago/src/api/rpc/transport.rs` **Modify**: `core/archipelago/src/api/rpc/mod.rs` (add dispatch routes) Endpoints: - `transport.status` — available transports, mesh_only flag, peer count - `transport.peers` — unified peer list with per-peer transport capabilities + preferred transport - `transport.send` — send message via best transport (by DID) - `transport.set-mode` — toggle mesh_only mode ### Step 11: Add opt-in federation sync via transport **File**: `core/archipelago/src/federation.rs` - Add `sync_with_peer_via_transport()` alongside existing `sync_with_peer()` - Uses CBOR delta encoding when transport is mesh (saves bandwidth) - Falls back to full JSON over Tor for backward compat - Background sync task checks for TransportRouter, uses it if available ### Step 12: Frontend — transport store + UI indicators **New file**: `neode-ui/src/stores/transport.ts` — Pinia store for transport status **Modify**: `neode-ui/src/views/Mesh.vue` — "OFF-GRID" indicator when mesh_only mode **Modify**: `neode-ui/mock-backend.js` — add transport.* mock RPC responses ### Step 13: Mock backend transport data **File**: `neode-ui/mock-backend.js` Add `transport.status`, `transport.peers`, `transport.send`, `transport.set-mode` mock responses with realistic data showing mixed transport capabilities. ## Files Summary ### New (8) 1. `core/archipelago/src/transport/mod.rs` 2. `core/archipelago/src/transport/tor.rs` 3. `core/archipelago/src/transport/mesh_transport.rs` 4. `core/archipelago/src/transport/lan.rs` 5. `core/archipelago/src/transport/chunking.rs` 6. `core/archipelago/src/transport/delta.rs` 7. `core/archipelago/src/api/rpc/transport.rs` 8. `neode-ui/src/stores/transport.ts` ### Modified (8) 1. `core/archipelago/Cargo.toml` — add 3 deps 2. `core/archipelago/src/lib.rs` or main — `mod transport;` 3. `core/archipelago/src/server.rs` — init router, bridge events 4. `core/archipelago/src/api/rpc/mod.rs` — dispatch + store router 5. `core/archipelago/src/mesh/mod.rs` — `mesh_only_mode` config field 6. `core/archipelago/src/federation.rs` — `sync_with_peer_via_transport()` 7. `neode-ui/src/views/Mesh.vue` — off-grid indicator 8. `neode-ui/mock-backend.js` — transport.* mock data ### Reused Existing Code - `node_message.rs:66-112` — Tor SOCKS5 send logic -> wrapped by TorTransport - `node_message.rs:115-135` — Tor health check -> TorTransport.is_available() - `mesh/mod.rs:209-282` — MeshService::send_message() -> called by MeshTransport - `mesh/listener.rs:35-37` — MeshCommand channel -> used for chunked sends - `mesh/crypto.rs` — X25519 ECDH + ChaCha20-Poly1305 -> reused as-is - `federation.rs:36-49` — FederatedNode struct -> PeerRecord wraps this - `federation.rs:179-191` — update_node_state() -> adapted for CBOR deltas ## Verification 1. **Unit tests** (run on dev server): ```bash cargo test --all-features -- transport cargo test --all-features -- chunking cargo test --all-features -- delta ``` 2. **Frontend type check**: ```bash cd neode-ui && npm run type-check ``` 3. **Dev mode test**: ```bash cd neode-ui && npm start # Navigate to /dashboard/mesh — verify OFF-GRID toggle ``` 4. **Deploy + integration test**: ```bash ./scripts/deploy-to-target.sh --live # On .228: curl transport.status RPC # Verify mDNS advertises _archipelago._tcp.local. # If mesh device connected: send chunked message >160B to .198 # Verify federation sync uses CBOR delta when available ``` ## Implementation Order **Week 1**: Steps 1-4 (deps, trait, chunking, delta — pure library code with unit tests) **Week 2**: Steps 5-7 (three transport implementations) **Week 3**: Steps 8-11 (integration: server wiring, RPC, federation migration) **Week 4**: Steps 12-13 (frontend UI, mock data, deploy + test)