reticiulum-specification/flows/receive-resource.md
Rob cfd0d8249b Re-anchor against RNS 1.2.4 / LXMF 0.9.7 + track upstream distribution shift
Upstream RNS 1.2.4 (2026-05-07) announces it is "probably the last
release that is also published to GitHub" — pip continues until rnpkg
is complete and RNS is self-hosting. All 13 verifiers pass against
1.2.4 / 0.9.7; no wire-format, signing, or protocol behavior changed
between 1.2.0 and 1.2.4, so the changes here are purely currency:

- Pin tools/requirements.txt to rns==1.2.4 / lxmf==0.9.7 so the
  verifier stays reproducible if upstream stops mirroring to PyPI
  before the migration is ready.
- Add an "Upstream distribution shift" watch-list to todo.md (local
  Reticulum node, repo destination hash, rnpkg install/upgrade
  commands, rsg signature verification, mirroring source citations).
- Bump SPEC.md frontmatter and re-anchor ~50 line citations across
  Identity.py, Transport.py, Resource.py, Link.py, Reticulum.py,
  Packet.py, and LXMF/* (Identity.py drift was the heaviest at +13
  to +31 lines; Transport.py was variable). Fix one numeric
  (MAX_RANDOM_BLOBS = 32 → 64) and one semantic (§6.6.3 LRPROOF MTU
  clamp citation pointed at the wrong location — corrected to point
  at the transit-relay clamp at Transport.py:1539-1556).
- Update §10.4 decompression-bomb hazard to note upstream's 1.1.9 cap
  adoption, with citations to Resource.py:686-691 and Buffer.py:95-97
  plus a "do not use one-shot bz2.decompress()" warning.
- Re-anchor 11 flows/ files (version pins + ~30 line citations).
- Bump version labels in tools/README.md, test-vectors/README.md, and
  4 verifier docstrings + 2 hardcoded print strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 07:42:25 -04:00

5 KiB
Raw Permalink Blame History

Flow: receive a Resource (large body) over a Link

The inverse of send-resource.md. What happens chronologically on the receiver when an inbound Resource transfer arrives. Pinned against RNS 1.2.4; see ../SPEC.md §10 for the wire bytes.


Preconditions

  • Link is ACTIVE (§6).
  • Receiver registered a resource_strategy on the Link via set_resource_strategy(...)ACCEPT_NONE, ACCEPT_APP, or ACCEPT_ALL. Default ACCEPT_NONE rejects every Resource on the link; LXMF and NomadNet flip this to ACCEPT_APP so an app callback can decide.

Sequence

1. RESOURCE_ADV arrives

Inbound Link DATA packet with context = RESOURCE_ADV (0x02). Link.receive at RNS/Link.py:1065-1098 decrypts it and runs RNS.ResourceAdvertisement.unpack against the plaintext to extract the msgpack dict (§10.4).

2. Resource accept / reject decision

Branch by resource_strategy:

  • ACCEPT_NONE → call RNS.Resource.reject(adv_packet) which sends RESOURCE_RCL back; resource is dropped.
  • ACCEPT_APP → run the application callback link.callbacks.resource(adv). If it returns truthy, RNS.Resource.accept(...); otherwise reject.
  • ACCEPT_ALL → unconditional RNS.Resource.accept(...).

Resource.accept (RNS/Resource.py:167-244) constructs a receiver-side Resource object, copies fields from the advertisement, sets status = TRANSFERRING, and queues the first request.

3. Receiver issues the first RESOURCE_REQ

Resource.request_next() (RNS/Resource.py:931-981) builds the request body per §10.5:

exhausted_flag(1) [|| last_map_hash(4)] || resource_hash(32) || requested_map_hashes(N × 4)

N = link.window initially (default 4). Sent as Link DATA with context = RESOURCE_REQ (0x03).

4. Sender fulfills with RESOURCE part packets

For each requested map_hash, the sender (per flows/send-resource.md step 5) emits one Link DATA packet with context = RESOURCE (0x01), body = pre-encrypted part bytes. The receiver matches each arriving part to the hashmap by recomputing its 4-byte map_hash (Resource.receive_part line 831-932).

Successful match: parts[i] = part_data, consecutive_completed_height advances. The window grows by 1 each successful round (capped at window_max, with rate-detection upgrades to FAST or VERY_SLOW per §10.10).

5. Repeat steps 3-4 until received_count == total_parts

When the receiver has consumed every map_hash in the current segment, it issues another RESOURCE_REQ. If the hashmap is exhausted (exhausted_flag = 0xFF), the sender responds with a RESOURCE_HMU carrying the next hashmap segment (§10.7), and the loop continues.

6. Resource.assemble() reassembles, validates, decrypts

Resource.py:672-726:

  1. stream = b"".join(self.parts) — concatenate every part.
  2. data = link.decrypt(stream) — single Link Token decrypt of the whole blob (§10.12: encryption was applied to the whole concatenated body before splitting).
  3. Strip the 4-byte random_hash prefix.
  4. If compressed: bz2-decompress.
  5. calculated_hash = SHA256(data || random_hash). Compare to self.hash (= advertisement's h field). On match: status = COMPLETE. On mismatch: status = CORRUPT; cancel.
  6. If has_metadata: peel off the 3-byte length-prefixed msgpack metadata blob, write to meta_storagepath.
  7. Write the data to storagepath (file-backed) or hold in self.data (memory-backed).
  8. Call the application callback (the one passed to Resource.accept).

7. RESOURCE_PRF emission

Resource.prove() (line 755-766) sends back:

proof_data = resource_hash(32) || full_proof(32)
where full_proof = SHA256(data_with_random || resource_hash)

as a PROOF-type packet with context = RESOURCE_PRF (0x05). The sender's validate_proof matches proof_data[32:] against its precomputed expected_proof and transitions to COMPLETE (§10.8).

8. Multi-segment continuation

If segment_index < total_segments, the sender prepares and sends the next RESOURCE_ADV after receiving this segment's PRF. The receiver loops back to step 1 for the next segment. Each segment is a fully independent Resource transfer; the only thing that ties them together is the original_hash field in the advertisement.

9. Cancellation paths

RESOURCE_ICL (sender cancel) → receiver pops the matching incoming Resource and discards accumulated parts (Link.py:1131-1138). RESOURCE_RCL (sender hears the receiver rejected) → already handled receiver-side at step 2.


Source map

Step File Function / line
1 RNS/Link.py Link.receive RESOURCE_ADV branch, line 1065
2 RNS/Resource.py Resource.accept, Resource.reject, lines 155-244
3 RNS/Resource.py request_next, line 934
4 RNS/Resource.py receive_part, line 831
5 RNS/Link.py RESOURCE_HMU branch, line 1122
6 RNS/Resource.py assemble, line 672
7 RNS/Resource.py prove, line 755
8 RNS/Resource.py __prepare_next_segment, line 768
9 RNS/Link.py RESOURCE_ICL branch, line 1131