From 366825c7a00fd25eaeb41ebbb6e3195fd92d08ca Mon Sep 17 00:00:00 2001 From: Rob Date: Sun, 3 May 2026 16:02:59 -0400 Subject: [PATCH] =?UTF-8?q?Add=20=C2=A717=20implementation=20taxonomy:=20w?= =?UTF-8?q?ho=20needs=20which=20sections?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaced from real interop testing: a clean-room implementation hit the §2.3 HEADER_1->HEADER_2 conversion bug, while category-1 clients (Sideband, MeshChat, NomadNet) couldn't have hit it because they inherit upstream Python RNS's Transport.outbound which does the conversion automatically. The spec previously left this implicit. Without explicit guidance, a reader could over-engineer (re-implementing things their category-1 platform already handles) or under-engineer (assuming 'Sideband works, so my clean-room implementation works the same way' when in fact Sideband works *because* upstream RNS handles §2.3). Four sub-sections: §17.1 The three categories of Reticulum app: Cat 1: Upstream-RNS-based (import RNS) — Sideband, NomadNet, MeshChat, rncp, rnsh, rnstatus Cat 2: Wrappers / language bindings via FFI or subprocess to upstream Cat 3: Clean-room implementations — microReticulum, the Faketec repeater, anything from-scratch in C++/Rust/JS/Kotlin/Swift §17.2 Section-relevance table by category. Wire formats are reference for cat 1/2, must-implement for cat 3. Behavioural guidance (§7, §12, §13, §14, §15, §16) is critical for cat 3, mostly informational for cat 1/2. §17.3 §2.3 worked example. Cat 1/2: don't write §2.3 code — upstream's Transport.outbound at line 1074-1083 does it automatically. Cat 3: implement it yourself or your packets get silently dropped by transit relays per line 1497 (which only forwards HEADER_2 with matching transport_id). §17.4 Pragmatic implication: a quick .claude/settings.local.json: "Bash(python -c \"import RNS, LXMF; print\\('RNS:', RNS.__version__\\); print\\('LXMF:', LXMF.__version__\\)\")", SPEC.md:| **1: Upstream-RNS-based** | Python application that does `import RNS` and uses upstream's `Reticulum` / `Transport` / `Identity` / `Destination` / `Packet` / `Link` directly. Inherits all wire-level behavior from upstream. | Sideband (Mark Qvist's flagship), NomadNet, [`liamcottle/reticulum-meshchat`](https://github.com/liamcottle/reticulum-meshchat), `rncp`, `rnsh`, `rnstatus`, anything in `pip show rns` example code | SPEC.md:If you're not sure which category you're in: `grep -r "import RNS" your_codebase` is a quick check. Any hit means cat 1 (or cat 2 if it's behind an FFI wall). No hits means cat 3. tools/regen_identities.py:import RNS tools/verify_announce_app_data.py:import RNS tools/verify_announce_app_data.py:import RNS.vendor.umsgpack as umsgpack tools/verify_announce_roundtrip.py:import RNS tools/verify_destination_hash.py:import RNS tools/verify_link_handshake.py:import RNS tools/verify_lxmf_opportunistic.py:import RNS tools/verify_msgpack_quirk.py:import RNS tools/verify_msgpack_quirk.py:import RNS.vendor.umsgpack as umsgpack tools/verify_packet_header.py:import RNS tools/verify_path_request.py:import RNS tools/verify_proof_packet.py:import RNS tools/verify_stamps.py:import RNS tools/verify_token_crypto.py:import RNS tells you which category you're in. cat 1/2 readers can skip the implementation-depth sections; cat 3 readers need everything plus the verifiers as a regression suite. Test vectors moves to §18, Source map to §19. The provisional understanding is that this categorisation is correct; if real-world testing reveals a category boundary that doesn't hold the way described, the section gets revised or removed. Co-Authored-By: Claude Opus 4.7 (1M context) --- SPEC.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/SPEC.md b/SPEC.md index 51040cb..6aa5d93 100644 --- a/SPEC.md +++ b/SPEC.md @@ -2791,7 +2791,50 @@ For comparison: a desktop `rnsd` typically settles around 50-200 MB of memory in --- -## 17. Test vectors +## 17. Implementation taxonomy: who needs which sections + +Reticulum applications fall into three categories. **Most of this spec only matters if you're in category 3.** Categories 1 and 2 inherit upstream Python RNS's protocol implementation and pick up most of the wire-level correctness for free. + +This section exists to save category-1/2 readers from over-engineering, and to flag for category-3 readers exactly which spec sections are theirs to implement vs. theirs to verify against. + +### 17.1 The three categories + +| Category | Description | Examples | +|---|---|---| +| **1: Upstream-RNS-based** | Python application that does `import RNS` and uses upstream's `Reticulum` / `Transport` / `Identity` / `Destination` / `Packet` / `Link` directly. Inherits all wire-level behavior from upstream. | Sideband (Mark Qvist's flagship), NomadNet, [`liamcottle/reticulum-meshchat`](https://github.com/liamcottle/reticulum-meshchat), `rncp`, `rnsh`, `rnstatus`, anything in `pip show rns` example code | +| **2: Wrappers / language bindings** | Non-Python application whose Reticulum protocol layer is a wrapper around upstream Python RNS — typically via FFI, subprocess, or a network bridge to a co-resident `rnsd`. Inherits wire correctness from the wrapped layer. | Future native iOS / Android / desktop apps that embed CPython, `rnsh` clients in shell scripts, anything that uses `rnsd`'s socket interface | +| **3: Clean-room implementations** | Application that re-implements the Reticulum protocol layer in another language without calling into upstream. Bears full responsibility for wire-level correctness. | `attermann/microReticulum` (C++), `thatSFguy/reticulum-lora-repeater` (C++ via microReticulum), reticulum-mobile-app (Kotlin), reticulum-lora-webclient (JavaScript), any from-scratch implementation in Rust / Go / Swift / etc. | + +### 17.2 Section relevance by category + +Three classes of section in this spec: + +| Class | What it tells you | Cat 1 | Cat 2 | Cat 3 | +|---|---|---|---|---| +| **Wire format** (§1-§8, §10, §11) | What bytes appear on the wire and in what order | Reference only — you emit and parse correctly because upstream does | Reference only — your wrapper does the work | **You implement these.** Bug here → can't talk to anyone | +| **Implementation gotchas** (§9) | Things upstream does that surprise you when reading the manual | **Yes** — because the gotchas often manifest as application-layer behaviour you need to explain to users | **Yes** — same reason | **Yes** — and you need to reproduce the gotchas faithfully or peers reject your traffic | +| **Behavioural guidance** (§7, §12, §13, §14, §15, §16) | Threading, timing, transport-relay, memory caps, failure debug | Mostly informational — upstream handles it | Mostly informational | **Critical** — you implement everything here from scratch | +| **Test vectors / verifiers** (§18, `tools/`) | Round-trip-able byte sequences | Reference for understanding | Reference for understanding | **Required** — these are your regression suite | + +### 17.3 Worked example: §2.3 originator HEADER_1→HEADER_2 conversion + +A reader hitting §2.3 might wonder "do I need this?" Three different answers: + +- **Cat 1 (e.g. MeshChat, Sideband):** No — `RNS.Transport.outbound` at lines 1074-1083 does the conversion automatically when you call `Packet.send()` to a destination with `path_table[dest][HOPS] > 1`. Your app just calls `LXMessage.send()` or `Packet.send()` and §2.3 happens invisibly. You can read §2.3 to understand WHY some captures show HEADER_2 with a transport_id, but you have no code to write. +- **Cat 2 (wrappers):** Same as Cat 1 — the wrapped Python RNS does the conversion. Your wrapper is just relaying API calls. +- **Cat 3 (clean-room):** **Yes, you implement §2.3 yourself.** Failure to do so means your packets aren't forwarded by transit relays — they're processed and dropped silently per `Transport.py:1497` (only HEADER_2 packets with `transport_id == relay.identity.hash` enter the forwarding branch). The symptom is "messages I send through a relay never arrive, but direct-link messages do." Sideband works in shared-instance and direct-TCP modes both because **upstream** does the conversion; a clean-room app working only via shared-instance is masking the missing §2.3. + +### 17.4 Pragmatic implication + +If you're a category-1 or category-2 developer reading this spec for **operational understanding** — debugging an interop issue, writing a deployment guide, explaining behaviour to users — read §1-§9 and §13-§16; skip the implementation depth. + +If you're a category-3 developer building from scratch, you need everything. Verify each section as you go using `tools/verify_*.py`, and treat §14 (failure modes) as your fault-finding entry point when integration testing reveals a discrepancy. + +If you're not sure which category you're in: `grep -r "import RNS" your_codebase` is a quick check. Any hit means cat 1 (or cat 2 if it's behind an FFI wall). No hits means cat 3. + +--- + +## 18. Test vectors See [`test-vectors/`](test-vectors/). Currently populated: @@ -2803,7 +2846,7 @@ An implementation that round-trips every test vector — both directions — sho --- -## 18. Source map +## 19. Source map Upstream Python sources, in rough order of frequency-of-reference: