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>
5 KiB
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_strategyon the Link viaset_resource_strategy(...)—ACCEPT_NONE,ACCEPT_APP, orACCEPT_ALL. DefaultACCEPT_NONErejects every Resource on the link; LXMF and NomadNet flip this toACCEPT_APPso 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→ callRNS.Resource.reject(adv_packet)which sendsRESOURCE_RCLback; resource is dropped.ACCEPT_APP→ run the application callbacklink.callbacks.resource(adv). If it returns truthy,RNS.Resource.accept(...); otherwise reject.ACCEPT_ALL→ unconditionalRNS.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:
stream = b"".join(self.parts)— concatenate every part.data = link.decrypt(stream)— single Link Token decrypt of the whole blob (§10.12: encryption was applied to the whole concatenated body before splitting).- Strip the 4-byte
random_hashprefix. - If
compressed: bz2-decompress. calculated_hash = SHA256(data || random_hash). Compare toself.hash(= advertisement'shfield). On match:status = COMPLETE. On mismatch:status = CORRUPT; cancel.- If
has_metadata: peel off the 3-byte length-prefixed msgpack metadata blob, write tometa_storagepath. - Write the data to
storagepath(file-backed) or hold inself.data(memory-backed). - 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 |