feat(mesh): optional plain-TCP Reticulum interface (radio-less Aurora interop)
Adds an additive, loopback-only TCP server/client interface to reticulum-daemon and the Rust mesh wiring, alongside the unchanged serial RNode path. Aurora (~/aurora) already speaks standard RNS/LXMF over plain TCP by default, the same way Sideband already proved interop over LoRa (docs/RETICULUM-TRANSPORT-PROGRESS.md gates #2/#3) — this closes the gap so that interop is provable without scarce LoRa hardware, and gives archy a path to eventually be dialed by an Aurora client. Verified: daemon TCP transport round-trip, bidirectional LXMF DM against a scripted RNS/LXMF stand-in for Aurora's Dart stack (content + dest-hash match both directions), cargo check/test -p archipelago green (108 mesh tests, 0 regressions), and a real MeshService::start() end-to-end test spawning the daemon in TCP client mode with no serial probe. TCP server mode is hard-gated to loopback in both Python and Rust — WAN/LAN exposure is a deliberate future decision, not part of this change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ owns. Stay out of `meshtastic.rs`/`protocol.rs` to avoid collisions.
|
||||
| 2c | `MeshConfig.device_kind` reflashable-board pin | ✅ **DONE** this session (was the one open Phase-2 item) |
|
||||
| 3 | Frontend (~8 label/CSS spots) | ✅ DONE (scoped down — see note below) |
|
||||
| 4 | Multi-device (run all 3 radios at once) + per-network channels | ⏳ not started (follow-on, after 0–3) |
|
||||
| 5 | Aurora interop — optional plain-TCP Reticulum interface (radio-less) | ✅ **DONE + verified 2026-07-03** — see checkpoint below. Real Aurora GUI test still open (manual follow-up). |
|
||||
|
||||
## Checkpoint 2026-06-30 (late session — read this first if cut off)
|
||||
|
||||
@@ -290,3 +291,82 @@ Phase 0 gates #1–#3 are now **all passed**. What's left:
|
||||
second board will likely report the same `0001` stock serial since CP2102 modules commonly
|
||||
ship with an unprogrammed default, so this may still need a different disambiguator.
|
||||
6. Phase 4 (run all 3 radios at once) — still not started, follow-on after the above.
|
||||
|
||||
## Checkpoint 2026-07-03 — Phase 5: Aurora interop via plain-TCP Reticulum (radio-less)
|
||||
|
||||
**Why:** `~/aurora` (a separate Flutter off-grid messenger) already runs real RNS + LXMF
|
||||
(`LxmfRouter`, comment "interop with Sideband/NomadNet/MeshChat" in `rns_service.dart`), and its
|
||||
**default** connectivity mode is plain TCP (`RnsTcpInterface`/`RnsTcpServerInterface`), not radio —
|
||||
it ships a static bootstrap list of public RNS hubs on port 4242. Archy's daemon could previously
|
||||
only bring up a serial-RNode interface, so it was unreachable by Aurora (or any TCP-based RNS/LXMF
|
||||
client) at all, and every interop proof was bottlenecked on scarce LoRa hardware. This phase adds
|
||||
an **optional, additive, loopback-only plain-TCP interface**, proves interop with a scripted
|
||||
RNS/LXMF stand-in (the same class of proof the Sideband gate already established), and leaves the
|
||||
serial/RNode path completely unchanged.
|
||||
|
||||
**Done, all verified:**
|
||||
1. `reticulum-daemon/reticulum_daemon.py` — `_write_rns_config()` gained a third branch
|
||||
(`--tcp-listen HOST:PORT` → `TCPServerInterface`, `--tcp-connect HOST:PORT` repeatable →
|
||||
`TCPClientInterface`), mutually exclusive with `--serial-port`. `--tcp-listen` is hard-gated to
|
||||
loopback (`_require_loopback`) — archy is otherwise Tor-first for inter-node traffic, so a
|
||||
WAN/LAN-exposed Reticulum port is a deliberate future decision, not something this phase does
|
||||
silently. Verified: `--selftest` regression still passes; two daemon processes (server +
|
||||
client, throwaway identities) reached `connected: true` on both sides via `mesh.status`-daemon
|
||||
RPC, live `TCPServerInterface`/`TCPClientInterface` visible in `get_interface_stats()`.
|
||||
2. **Bidirectional LXMF DM gate against a scripted Aurora stand-in** (Python RNS+LXMF client
|
||||
dialing as a `TCPClientInterface` + running its own `LXMRouter` — a legitimate protocol-level
|
||||
proxy for Aurora's Dart stack, same wire format): forward (stand-in → archy daemon) and reverse
|
||||
(archy daemon → stand-in) both delivered with matching content and correct source/dest hashes,
|
||||
confirmed via the daemon's own `recv`/`delivered` RPC events. Direct TCP analogue of the
|
||||
already-passed Sideband gate (RF → TCP, Sideband → scripted stand-in).
|
||||
3. **Rust wiring**, fully additive — the serial/RNode path is byte-for-byte unchanged:
|
||||
- `mesh/reticulum.rs`: new `ReticulumInterface` enum (`Serial`/`TcpServer`/`TcpClient`) threads
|
||||
through `daemon_command()`/`spawn()`; `open()` (serial) now just wraps
|
||||
`ReticulumInterface::Serial` — same `probe_rnode` gate as before. New
|
||||
`open_tcp_server()`/`open_tcp_client()` associated fns skip `probe_rnode` entirely (the
|
||||
"spawn without a physical RNode" path); `open_tcp_server` hard-enforces
|
||||
`is_loopback_host()` (mirrors the Python-side guard).
|
||||
- `mesh/types.rs`: new `ReticulumTcpConfig` enum (`Server { bind }` / `Client { connect }`).
|
||||
- `mesh/mod.rs`: `MeshConfig.reticulum_tcp: Option<ReticulumTcpConfig>` (`#[serde(default)]`,
|
||||
`None` by default — no migration, zero behavior change when unset); threaded into
|
||||
`start()` → `spawn_mesh_listener`.
|
||||
- `listener/mod.rs` / `listener/session.rs`: `reticulum_tcp` param threaded through
|
||||
`spawn_mesh_listener`/`run_mesh_session`; new leading branch — if set, a new
|
||||
`open_reticulum_tcp()` helper dispatches to `open_tcp_server`/`open_tcp_client`; otherwise
|
||||
falls through to the **untouched** existing `preferred_path`/`auto_detect_and_open` logic.
|
||||
- Deliberately **not** wired into `mesh.configure`/the frontend — dev/verification-only surface
|
||||
for now (hand-edit `mesh-config.json`), consistent with how narrowly scoped this phase is.
|
||||
- `cargo check -p archipelago` + `cargo test -p archipelago` (mesh module): **108 passed, 0
|
||||
failed, 1 ignored** (the pre-existing hardware-gated `probe_rnode_detects_real_hardware`) —
|
||||
zero regression to the serial/RNode path, provable without any hardware.
|
||||
4. **End-to-end Rust integration test** (`mesh::tests::mesh_service_connects_over_reticulum_tcp_client`,
|
||||
`#[ignore]`d — spawns real subprocesses, skipped in the default `cargo test` run the same way
|
||||
the rest of the mesh suite skips hardware-gated tests): a real `MeshService::start()` spawns the
|
||||
daemon in TCP **client** mode (no serial probe at all), dials a second stand-alone daemon
|
||||
instance in TCP **server** mode (the Aurora-side role), and reaches `device_connected: true` /
|
||||
`device_type: Reticulum` via the exact `MeshService::status()` call the `mesh.status` RPC uses.
|
||||
Passed in ~2.6s. Run manually: `cargo test -p archipelago -- --ignored
|
||||
mesh_service_connects_over_reticulum_tcp` (needs `reticulum-daemon/.venv`, see below).
|
||||
|
||||
**Environment note:** this session's Rust toolchain drift — system `rustc` (apt, 1.85.0) is too
|
||||
old for code already on `main` (`u32::is_multiple_of` in `health_monitor.rs`, stabilized upstream
|
||||
after 1.85); a pre-installed rustup toolchain at
|
||||
`~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu` (1.96.0) builds clean. Not something this
|
||||
phase's changes caused — pre-existing, just newly hit. Put that toolchain's `bin/` first on `PATH`
|
||||
if `cargo check`/`test` reports `E0658 unsigned_is_multiple_of`.
|
||||
|
||||
**Explicitly NOT done (out of scope for this phase, see plan non-goals):**
|
||||
- Real Aurora Flutter GUI verification — this dev sandbox has no `flutter`, no `$DISPLAY`, and no
|
||||
`reticulum-dart` sibling checked out (Aurora's actual RNS implementation lives in that separate
|
||||
repo; Aurora's CI clones it fresh at build time). The scripted-stand-in gate above is the
|
||||
protocol-level substitute. **Manual follow-up**: point a real Aurora build's TCP hub list (or an
|
||||
ad hoc connect) at an archy node's `--tcp-listen` address and confirm an LXMF DM in the actual
|
||||
app UI.
|
||||
- Any non-loopback (LAN/WAN) TCP bind — hard-gated off on purpose; a real "Aurora hub" deployment
|
||||
needs its own security review given archy's Tor-first posture for inter-node traffic.
|
||||
- LXMF propagation-node / always-on-hub role for archy (bridging Aurora's offline BLE peers) —
|
||||
bigger architectural + storage commitment.
|
||||
- Identity unification between archy's and Aurora's independent Nostr/secp256k1 keys — both
|
||||
already have separate Nostr identities with no derivation link; out of scope here.
|
||||
- `mesh.configure` RPC / frontend exposure of `reticulum_tcp` — stays hand-edit-only until/unless
|
||||
it becomes user-facing.
|
||||
|
||||
Reference in New Issue
Block a user