Add §12 transport-relay behaviour (Tier 3 — TIER 3 COMPLETE)
Closes Tier 3 in a single consolidated section because all five items
share state (path_table, announce_table, link_table, reverse_table,
tunnels) and are emergent behaviours of the same Transport.inbound
dispatch logic.
Seven sub-sections:
§12.1 transport_enabled toggle — leaf clients populate path_table
only for destinations they personally need; transport-mode
nodes populate it for everything they hear about.
§12.2 DATA forwarding rules — three-case branch on remaining_hops
(>1 forward as HEADER_2 with new transport_id; ==1 strip
transport_id and forward as HEADER_1 broadcast; ==0 local).
LINKREQUEST forwarding extras (link_table entry + §6.6 MTU
clamp). Non-LINKREQUEST gets a reverse_table entry.
§12.3 ANNOUNCE rebroadcasting — announce_table retransmit queue,
per-interface ANNOUNCE_CAP airtime budget, announce_queue
drain order (lowest-hop-count first), random_blob replay
defence with MAX_RANDOM_BLOBS sliding window, and the
PATH_RESPONSE short-circuit (path-responses go on a
specific interface, not broadcast).
§12.4 Path table management — entry shape (IDX_PT_* indexes),
three TTLs by interface mode (AP_PATH_TIME 1h, ROAMING_PATH_TIME
4h, PATHFINDER_E 30 days), stale-paths eviction, persistence
to storagepath/paths.
§12.5 Reverse-table link transport — LRPROOF forwarding via
link_table validation against the destination's known
long-term Ed25519 pub, Link DATA forwarding once
link_table[IDX_LT_VALIDATED] is set, PROOF receipt
forwarding via reverse_table (one-shot pop on use,
REVERSE_TIMEOUT bound for memory).
§12.6 Tunnels and shared-instance protocol — discovery_path_requests
recursive search (15s timeout), tunnels[] persistence across
interface flap, shared-instance protocol (regular Reticulum
packets over TCP loopback; the 'sharing' is Transport state,
not wire format).
§12.7 Source map.
Old §12 Test vectors -> §13; old §13 Source map -> §14. Section
order preserves protocol content before appendices.
TIER 3 COMPLETE. All Tier 1, 2, and 3 spec gaps closed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5f67277b62
commit
ee5ba48802
2 changed files with 277 additions and 32 deletions
250
SPEC.md
250
SPEC.md
|
|
@ -1998,7 +1998,253 @@ None of these are wire-spec — they're caller conventions on top of §13. A Ret
|
|||
|
||||
---
|
||||
|
||||
## 12. Test vectors
|
||||
## 12. Transport-relay behaviour
|
||||
|
||||
Everything in §1-§11 applies to both leaf clients and transport-mode nodes. This section covers what additionally runs on a node configured with `enable_transport = Yes` in the `[reticulum]` config — i.e. a node whose role is to forward traffic for others. Reticulum's relay is host-routed (no broadcast flooding except for path-discovery), keyed by the `path_table` populated from announces.
|
||||
|
||||
A leaf client can ignore §12 entirely. Implementations that target the rnsd-replacement or repeater use case need every sub-section.
|
||||
|
||||
### 12.1 The `transport_enabled` toggle
|
||||
|
||||
`Reticulum.transport_enabled()` returns the value of the `enable_transport` config option (default `False`). Setting it to `True`:
|
||||
|
||||
- Allows the node to populate `path_table`, `announce_table`, `link_table`, `reverse_table`, and `tunnels` for non-local destinations (a leaf only populates path entries it personally needs).
|
||||
- Enables the §12.2 DATA forwarding branches in `Transport.inbound`.
|
||||
- Enables the §12.3 ANNOUNCE rebroadcast branch.
|
||||
- Enables `Transport.identity` — the transport node's own identity, used for `transport_id` insertion in HEADER_2 packets (§2.3) and as the `requesting_transport_instance` field in path requests (§7.1).
|
||||
|
||||
A clean-room implementation testing forwarding without operating as a real transport node SHOULD respect the same flag: ignoring the toggle and unconditionally forwarding turns every implementation into a network-flooding hazard.
|
||||
|
||||
### 12.2 DATA forwarding rules
|
||||
|
||||
For an inbound DATA packet (`packet_type == DATA`, `destination_type` not LINK) where:
|
||||
|
||||
- `packet.transport_id == Transport.identity.hash` (the originator picked us as the next hop), AND
|
||||
- `packet.destination_hash` is in `Transport.path_table`,
|
||||
|
||||
the relay rewrites the wire bytes according to `path_table[dest][HOPS]` and re-transmits on `path_table[dest][RVCD_IF]`. From `RNS/Transport.py:1497-1573`, three cases by `remaining_hops`:
|
||||
|
||||
#### 12.2.1 `remaining_hops > 1` — forward as HEADER_2
|
||||
|
||||
Increment hops (already done by `Transport.inbound` line 1395), replace the transport_id with the next-hop transport_id from the path table, keep the rest of the packet:
|
||||
|
||||
```
|
||||
new_raw = packet.raw[0:1] # flags byte unchanged
|
||||
new_raw += struct.pack("!B", packet.hops) # incremented hops byte
|
||||
new_raw += next_hop # 16B transport_id (new next hop)
|
||||
new_raw += packet.raw[18:] # original dest_hash + ctx + body
|
||||
```
|
||||
|
||||
The flags byte high nibble is unchanged — the packet stays HEADER_2 with the TRANSPORT bit set. Final wire form is `flags(1) || hops+1(1) || new_transport_id(16) || dest_hash(16) || ctx(1) || body`.
|
||||
|
||||
#### 12.2.2 `remaining_hops == 1` — strip transport headers, forward as HEADER_1 broadcast
|
||||
|
||||
The destination is one hop away on the next-hop interface; no further transport_id is needed. Convert to HEADER_1 with BROADCAST transport type:
|
||||
|
||||
```
|
||||
new_flags = (HEADER_1 << 6) | (BROADCAST << 4) | (packet.flags & 0x0F)
|
||||
new_raw = struct.pack("!B", new_flags)
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
new_raw += packet.raw[18:] # original dest_hash + ctx + body (transport_id stripped)
|
||||
```
|
||||
|
||||
This is the inverse of the §2.3 originator HEADER_1→HEADER_2 conversion: the relay strips the transport_id when the packet has reached its last hop.
|
||||
|
||||
#### 12.2.3 `remaining_hops == 0` — local destination, just bump hops
|
||||
|
||||
The destination is registered on the relay itself (it's both our path-table next-hop AND a local destination). Just increment hops and pass through unchanged for local processing — the standard `Destination.receive` path takes over from there.
|
||||
|
||||
#### 12.2.4 LINKREQUEST forwarding extras
|
||||
|
||||
When the forwarded packet is a `LINKREQUEST`, the relay also writes a `link_table` entry keyed by the link_id (computed via §6.3's `link_id_from_lr_packet`). Entry contents (`Transport.py:1553-1561`):
|
||||
|
||||
```
|
||||
[ now, # 0 IDX_LT_TIMESTAMP
|
||||
next_hop, # 1 IDX_LT_NH_ID — next-hop transport_id
|
||||
outbound_interface, # 2 IDX_LT_NH_IF
|
||||
remaining_hops, # 3 IDX_LT_REM_HOPS
|
||||
packet.receiving_interface, # 4 IDX_LT_RCVD_IF
|
||||
packet.hops, # 5 IDX_LT_TAKEN_HOPS
|
||||
packet.destination_hash, # 6 IDX_LT_DSTHASH
|
||||
False, # 7 IDX_LT_VALIDATED
|
||||
proof_timeout ] # 8 IDX_LT_PROOF_TMO
|
||||
```
|
||||
|
||||
This entry is what lets the relay forward the eventual LRPROOF back to the initiator on the reverse path (§12.5) and forward subsequent Link DATA in both directions.
|
||||
|
||||
The relay also performs the §6.6 MTU clamp at this point: if the LINKREQUEST carries signalling and the next-hop interface's HW_MTU is smaller than the requested value, the signalling bytes in `new_raw` are rewritten in place with the clamped MTU before transmission.
|
||||
|
||||
#### 12.2.5 Non-LINKREQUEST DATA — reverse_table entry
|
||||
|
||||
For any other forwarded DATA (the much-more-common opportunistic LXMF case), the relay writes a `reverse_table` entry keyed by `packet.getTruncatedHash()` (`Transport.py:1567-1571`):
|
||||
|
||||
```
|
||||
[ packet.receiving_interface, # 0 IDX_RT_RCVD_IF — interface to send PROOF back through
|
||||
outbound_interface, # 1 IDX_RT_OUTB_IF — interface forward was sent on
|
||||
time.time() ] # 2 IDX_RT_TIMESTAMP
|
||||
```
|
||||
|
||||
The reverse_table is what lets the eventual PROOF receipt (§6.5) trace its way back to the originator without consulting the path_table again — see §12.5.
|
||||
|
||||
### 12.3 ANNOUNCE rebroadcasting
|
||||
|
||||
When an inbound ANNOUNCE validates (per §4.5) AND the destination is non-local AND `transport_enabled OR is_from_local_client`, the relay queues a rebroadcast. From `Transport.py:1810-1890`:
|
||||
|
||||
```python
|
||||
if (Reticulum.transport_enabled() or is_from_local_client) and packet.context != PATH_RESPONSE:
|
||||
if not rate_blocked:
|
||||
Transport.announce_table[packet.destination_hash] = [
|
||||
now, retransmit_timeout, retries,
|
||||
received_from, announce_hops, packet,
|
||||
local_rebroadcasts, block_rebroadcasts, attached_interface,
|
||||
]
|
||||
```
|
||||
|
||||
The `announce_table` entry queues a delayed retransmit; the actual emission happens in the periodic `Transport.jobs` cycle which scans the table for entries whose `retransmit_timeout` has elapsed and fires them on each suitable interface.
|
||||
|
||||
#### 12.3.1 Announce cap (`ANNOUNCE_CAP`)
|
||||
|
||||
`Reticulum.ANNOUNCE_CAP = 2.0` (default 2% of airtime, configurable via `[reticulum] announce_cap`). Each interface tracks its outbound announce airtime and when the rolling-window utilization exceeds the cap, further announces are queued in `interface.announce_queue` rather than transmitted immediately. `process_announce_queue` (`RNS/Interfaces/Interface.py:232-272`) drains the queue at a rate the cap permits, picking the lowest-hop-count entry first.
|
||||
|
||||
The cap is per-interface, not global — a relay with multiple interfaces budgets each one independently, which lets a fast TCP backbone interface announce freely while the same node throttles announces on a slow LoRa interface. Without per-interface caps, a single high-rate interface would starve every other.
|
||||
|
||||
#### 12.3.2 `random_blob` replay defence
|
||||
|
||||
§4.5 step 6.3 already documents this from the receiver's perspective; for the rebroadcast logic, the relay only queues an announce if the new `random_blob` (the 10-byte `random_hash` field, treated as an opaque blob for routing purposes) is **not** already in the cached `random_blobs` list for this destination. The list is capped at `Transport.MAX_RANDOM_BLOBS` (default 32) entries, sliding-window. This prevents an announce from looping through a multi-relay topology because each relay only forwards each unique blob once.
|
||||
|
||||
#### 12.3.3 Path-response announces don't rebroadcast
|
||||
|
||||
`packet.context == PATH_RESPONSE` short-circuits the rebroadcast branch (line 1822). Path-response announces travel back along the reverse path from the responder to the requester (see §7.2 and `flows/path-discovery.md`), and the relay's job is to forward them on a single specific interface (`attached_interface`), not re-broadcast to the whole mesh. Mishandling this would multiply path-response traffic by the relay fanout.
|
||||
|
||||
### 12.4 Path table management
|
||||
|
||||
`Transport.path_table[destination_hash]` entry shape (`Transport.py:3439-3446`):
|
||||
|
||||
```
|
||||
[ timestamp, # 0 IDX_PT_TIMESTAMP — when last refreshed
|
||||
next_hop, # 1 IDX_PT_NEXT_HOP — 16B transport_id of next hop
|
||||
hops, # 2 IDX_PT_HOPS — distance to destination
|
||||
expires, # 3 IDX_PT_EXPIRES — unix-seconds eviction time
|
||||
random_blobs, # 4 IDX_PT_RANDBLOBS — sliding window of recent blobs
|
||||
receiving_interface, # 5 IDX_PT_RVCD_IF — interface to forward on
|
||||
packet ] # 6 IDX_PT_PACKET — cached announce packet for path-? response
|
||||
```
|
||||
|
||||
#### 12.4.1 TTLs
|
||||
|
||||
Three different expiry constants based on the `attached_interface.mode`:
|
||||
|
||||
| Mode | TTL constant | Default seconds | Used for |
|
||||
|---|---|---|---|
|
||||
| `MODE_ACCESS_POINT` | `Transport.AP_PATH_TIME` | 1 hour | Hub-and-spoke topologies (TCP servers, BLE gateways) |
|
||||
| `MODE_ROAMING` | `Transport.ROAMING_PATH_TIME` | 4 hours | Mobile devices that disappear and reappear |
|
||||
| (default) | `Transport.PATHFINDER_E` | 30 days | Stable backbone interfaces |
|
||||
|
||||
The wide spread of defaults reflects expected churn rates: AP-mode interfaces have many short-lived clients; roaming devices come and go; backbone TCP relays are essentially permanent.
|
||||
|
||||
#### 12.4.2 Eviction
|
||||
|
||||
`Transport.jobs` runs a `stale_paths` accumulator that walks `path_table` and pops entries whose `expires` timestamp has passed (`Transport.py:747-769`). Eviction is silent — no notification to the application; the next outbound message to the destination just re-discovers it via `request_path` per §7.1.
|
||||
|
||||
A relay also evicts path entries whose underlying interface has been removed (`receiving_interface not in Transport.interfaces`). This handles the case where a TCP client disconnects.
|
||||
|
||||
#### 12.4.3 Persistence
|
||||
|
||||
If `[reticulum] persist_paths = Yes`, the path_table is serialized to `{storagepath}/paths` (a pickled dict in upstream RNS) so it survives restarts. The repeater repo's `pre_build.py` adds a "skip redundant path writes" patch to avoid hammering the on-board flash on nRF52 — for clean-room implementations, the persistence cadence is implementation-private.
|
||||
|
||||
### 12.5 Reverse-table link transport
|
||||
|
||||
Once a Link's LINKREQUEST has been forwarded by a relay (§12.2.4 wrote the `link_table` entry), every subsequent Link packet — DATA, KEEPALIVE, PROOF, LINKCLOSE — must be forwarded by the same relay in the appropriate direction. `Transport.inbound` uses `link_table` and `reverse_table` for this:
|
||||
|
||||
#### 12.5.1 LRPROOF forwarding
|
||||
|
||||
When an LRPROOF arrives whose `dest_hash` (= link_id) is in the relay's `link_table` AND the proof arrives on the next-hop interface (`packet.receiving_interface == link_entry[IDX_LT_NH_IF]`), the relay validates the signature against the destination's known long-term public key (recalled via `Identity.recall(link_entry[DSTHASH])`) and forwards on the receive interface (`Transport.py:2110-2138`):
|
||||
|
||||
```python
|
||||
new_raw = packet.raw[0:1] + struct.pack("!B", packet.hops) + packet.raw[2:]
|
||||
Transport.transmit(link_entry[IDX_LT_RCVD_IF], new_raw)
|
||||
link_table[packet.destination_hash][IDX_LT_VALIDATED] = True
|
||||
```
|
||||
|
||||
After validation, the link_table entry is marked `validated`, and from now on the relay forwards Link DATA in both directions transparently.
|
||||
|
||||
#### 12.5.2 Link DATA forwarding
|
||||
|
||||
For a `DATA` packet with `destination_type == LINK` whose `dest_hash` is in `link_table`, the relay forwards on the appropriate direction's interface. The link_table entry remembers both sides via `IDX_LT_NH_IF` (toward initiator end) and `IDX_LT_RCVD_IF` (toward responder end); the relay picks based on which interface the inbound packet arrived on.
|
||||
|
||||
#### 12.5.3 PROOF receipt forwarding via `reverse_table`
|
||||
|
||||
`Transport.py:2196-2205`. When a PROOF arrives whose `dest_hash` is in `reverse_table` (i.e. an opportunistic-DATA proof being routed back to its originator), the relay pops the entry, checks the proof arrived on the correct outbound interface (`receiving_interface == reverse_entry[IDX_RT_OUTB_IF]`), and forwards on the originally-receiving interface:
|
||||
|
||||
```python
|
||||
new_raw = packet.raw[0:1] + struct.pack("!B", packet.hops) + packet.raw[2:]
|
||||
Transport.transmit(reverse_entry[IDX_RT_RCVD_IF], new_raw)
|
||||
```
|
||||
|
||||
Reverse_table entries are popped on use (one-shot routing) and aged out by `Transport.jobs` after `Transport.REVERSE_TIMEOUT` (default `30s`). This bounds the relay's memory regardless of whether the proof ever arrives.
|
||||
|
||||
### 12.6 Tunnels and shared-instance protocol
|
||||
|
||||
Two related state mechanisms a transport node maintains:
|
||||
|
||||
#### 12.6.1 `discovery_path_requests`
|
||||
|
||||
When a transport-enabled relay receives a path? for a destination it doesn't know AND doesn't have a local client to forward to, it records a `discovery_path_requests[dest_hash]` entry (`Transport.py:2949-2963`):
|
||||
|
||||
```python
|
||||
pr_entry = {
|
||||
"destination_hash": destination_hash,
|
||||
"timeout": time.time() + PATH_REQUEST_TIMEOUT, # 15s
|
||||
"requesting_interface": attached_interface,
|
||||
}
|
||||
Transport.discovery_path_requests[destination_hash] = pr_entry
|
||||
```
|
||||
|
||||
Then forwards the path? to every other interface preserving the original tag. This is recursive transport-mode discovery — the relay is acting as a search proxy. When the response announce eventually arrives back, the relay forwards it on `requesting_interface` (the one the original path? came from), and the entry is aged out.
|
||||
|
||||
#### 12.6.2 `tunnels`
|
||||
|
||||
A tunnel is an interface-level path mechanism for handling temporarily-disconnected interfaces (e.g. a mobile peer that comes and goes). The `tunnels[interface_tunnel_id]` state lets the relay reconstruct paths through the interface when it reconnects, without requiring all paths to be re-discovered from scratch. The shape (`Transport.py:783-832`):
|
||||
|
||||
```
|
||||
[ now, # 0 IDX_TT_TIMESTAMP
|
||||
expires, # 1 IDX_TT_EXPIRES — TUNNEL_TIMEOUT
|
||||
paths_dict, # 2 IDX_TT_PATHS — dest_hash → path-entry
|
||||
... ]
|
||||
```
|
||||
|
||||
Each path inside the tunnel's `paths_dict` mirrors a `path_table` entry. When the tunnel's interface returns, the relay re-installs every path from the tunnel into the active `path_table`, jump-starting connectivity. Without this, every reconnection would require a full announce flood across the mesh.
|
||||
|
||||
`TUNNEL_TIMEOUT` defaults to substantially longer than path TTLs because tunnels persist across interface flap.
|
||||
|
||||
#### 12.6.3 Shared-instance protocol
|
||||
|
||||
When multiple processes on one host share a single Reticulum stack (via `share_instance = Yes` in the rnsd config), one process owns `Transport` and the others connect to it as **local clients** via a small TCP loopback interface. The shared instance treats local-client traffic specially:
|
||||
|
||||
- `from_local_client` and `for_local_client` are computed on every inbound packet (`Transport.py:1450-1454`).
|
||||
- Path-table entries with `IDX_PT_HOPS == 0` mean "destination is a local client" — the §2.3 originator-side HEADER_1 conversion applies for hops==1 too, so the shared instance gets a transport_id-tagged packet (`Transport.py:1094-1105`).
|
||||
- Local-client originated path? requests are forwarded to every external interface, fanning out the search across the shared mesh (§7.2 dispatch branch 3).
|
||||
|
||||
The wire protocol for shared-instance loopback is just the same Reticulum packets over a TCP loopback interface — no special framing or commands. What's "shared" is the path_table and announce dispatch, not the wire format.
|
||||
|
||||
### 12.7 Source map for §12
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `RNS/Transport.py:1497-1573` | DATA forwarding (HEADER_1↔HEADER_2 conversion for relay) |
|
||||
| `RNS/Transport.py:1553-1561` | `link_table` entry shape |
|
||||
| `RNS/Transport.py:1567-1571` | `reverse_table` entry shape |
|
||||
| `RNS/Transport.py:1810-1969` | ANNOUNCE rebroadcast queue and per-interface dispatch |
|
||||
| `RNS/Transport.py:2110-2138` | LRPROOF forwarding via `link_table` |
|
||||
| `RNS/Transport.py:2196-2205` | PROOF receipt forwarding via `reverse_table` |
|
||||
| `RNS/Transport.py:3439-3446` | `path_table` entry shape (IDX_PT_*) |
|
||||
| `RNS/Transport.py:747-832` | stale-path / stale-tunnel eviction |
|
||||
| `RNS/Transport.py:2949-2963` | `discovery_path_requests` recursive search |
|
||||
| `RNS/Interfaces/Interface.py:232-272` | per-interface `announce_queue` and `ANNOUNCE_CAP` enforcement |
|
||||
|
||||
---
|
||||
|
||||
## 13. Test vectors
|
||||
|
||||
See [`test-vectors/`](test-vectors/). Currently populated:
|
||||
|
||||
|
|
@ -2010,7 +2256,7 @@ An implementation that round-trips every test vector — both directions — sho
|
|||
|
||||
---
|
||||
|
||||
## 13. Source map
|
||||
## 14. Source map
|
||||
|
||||
Upstream Python sources, in rough order of frequency-of-reference:
|
||||
|
||||
|
|
|
|||
59
todo.md
59
todo.md
|
|
@ -288,37 +288,36 @@ re-research.
|
|||
and the `should_use_implicit_proof()` config switch are
|
||||
documented in §6.5.1-§6.5.2 with full citations.
|
||||
|
||||
### Tier 3 — required to act as a transport node / relay
|
||||
### Tier 3 — required to act as a transport node / relay (DONE)
|
||||
|
||||
- [ ] **SPEC.md §7.7 (new): DATA forwarding rules.** Forwarding non-
|
||||
local DATA per `path_table[dest][NEXT_HOP]`, with hop increment,
|
||||
MTU-fit check, blackhole avoidance, and IFAC re-signing.
|
||||
Currently mentioned only obliquely in §2.3 / §7.6. The full
|
||||
forwarding logic is the bulk of `RNS/Transport.py::inbound`'s
|
||||
~800-line dispatch table at lines 1499-1620. The repeater repo
|
||||
patches microReticulum to enable this — see commit
|
||||
`Add DATA and PROOF forwarding patches for transport repeating`.
|
||||
- [ ] **SPEC.md §4.6 (new): ANNOUNCE rebroadcasting.** Including the
|
||||
announce-cap (`RNS.Reticulum.ANNOUNCE_CAP`, default 2% airtime),
|
||||
the announce queue, the `path_responses` cache, and the
|
||||
`random_blob` history that lets a relay drop replays. Most of
|
||||
`RNS/Transport.py:1196-1300, 1810-1969`.
|
||||
- [ ] **SPEC.md §7.8 (new): path table management.** TTL-based expiry
|
||||
(`Transport.AP_PATH_TIME`, `ROAMING_PATH_TIME`, `DESTINATION_TIMEOUT`),
|
||||
eviction on stale-link, persistence-across-reboot file format.
|
||||
Hooks: `RNS/Transport.py:747-769` (stale_paths accumulator) and
|
||||
the `paths` file under `storagepath`.
|
||||
- [ ] **SPEC.md §7.9 (new): tunnels and shared-instance protocol.**
|
||||
`tunnels`, `discovery_path_requests`, `RNS/Reticulum.py::is_connected_to_shared_instance`
|
||||
— how a process talks to a co-resident `rnsd`. Spec's §7.6
|
||||
covers one symptom (TCP OUT default) but not the actual
|
||||
shared-instance wire format.
|
||||
- [ ] **SPEC.md §6.x (new): reverse table + link transport.** When a
|
||||
Link's path crosses a relay, the relay must forward both
|
||||
directions of every Link DATA + PROOF using
|
||||
`Transport.reverse_table` (`RNS/Transport.py:2087-2204`).
|
||||
Distinct from path-table forwarding — different lookup, different
|
||||
lifecycle.
|
||||
All five Tier 3 items consolidated into SPEC.md §12 "Transport-relay
|
||||
behaviour" (single section, seven sub-sections) since they share state
|
||||
(path_table, announce_table, link_table, reverse_table, tunnels,
|
||||
discovery_path_requests):
|
||||
|
||||
- [x] **DATA forwarding rules** — §12.2 covers the three-case branch
|
||||
on remaining_hops (>1 forward as HEADER_2 with new transport_id;
|
||||
==1 strip transport_id and forward as HEADER_1 broadcast; ==0
|
||||
local destination, just bump hops). LINKREQUEST gets an extra
|
||||
link_table entry and the §6.6 MTU clamp; non-LINKREQUEST DATA
|
||||
gets a reverse_table entry.
|
||||
- [x] **ANNOUNCE rebroadcasting** — §12.3 covers the announce_table
|
||||
retransmit queue, per-interface ANNOUNCE_CAP throttling and
|
||||
announce_queue, random_blob replay defence with MAX_RANDOM_BLOBS
|
||||
sliding-window cap, and the PATH_RESPONSE short-circuit.
|
||||
- [x] **Path table management** — §12.4 covers the entry shape, three
|
||||
TTL constants by interface mode (AP/ROAMING/default 30 days),
|
||||
stale-paths eviction in Transport.jobs, and persistence to
|
||||
storagepath/paths.
|
||||
- [x] **Tunnels and shared-instance protocol** — §12.6 covers
|
||||
discovery_path_requests recursive search, the tunnels[] state
|
||||
that survives interface flap, and the shared-instance wire
|
||||
protocol (just regular Reticulum packets over a TCP loopback;
|
||||
what's "shared" is the Transport state, not the wire format).
|
||||
- [x] **Reverse-table link transport** — §12.5 covers LRPROOF
|
||||
forwarding via link_table, Link DATA forwarding in both
|
||||
directions once the link_table entry is validated, and PROOF
|
||||
receipt forwarding via reverse_table (one-shot pop on use).
|
||||
|
||||
## Spec polishing (lower priority)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue