- flows/receive-resource.md: inverse of send-resource. ADV
ingestion, accept/reject decision, request_next loop,
receive_part insertion, assemble + decrypt + hash-validate,
RESOURCE_PRF emission, multi-segment continuation.
- flows/receive-link-lxmf.md: responder side of the link
handshake plus inbound LXMF DATA handling. validate_request
-> handshake -> prove (LRPROOF emission) -> link_established
callback wires delivery_packet. PACKET-form inbound runs
delivery_packet directly; RESOURCE-form inbound runs through
delivery_resource_advertised + delivery_resource_concluded
pipeline.
- flows/send-announce.md: random_hash construction (5B random +
5B BE-uint40 timestamp), optional ratchet rotation, signed_data
assembly, sign + pack, the broadcast emission. Notes that
ANNOUNCE packets are NOT encrypted (Packet.pack special-cases
line 189-191) and the periodic re-announce loop drives 5-15min
cadence.
- flows/forward-announce.md: relay-side rebroadcast for
transport-mode nodes. Eligibility checks (transport_enabled,
not PATH_RESPONSE, not rate_blocked), announce_table queue,
Transport.jobs drain with PATH_REQUEST_GRACE = 0.4s,
per-interface announce_queue with ANNOUNCE_CAP = 2.0% airtime
enforcement, lowest-hop-count-first emission order, hops byte
increment, local-rebroadcast counter for loop break.
- flows/send-propagated-lxmf.md: PROPAGATED method end to end.
LXMessage.pack with body encrypted to recipient (propagation
node never decrypts), Link establishment to the propagation
node, optional propagation stamp (1000 PoW rounds vs 3000 for
regular stamps), submission via Link DATA or Resource,
state goes to SENT (not DELIVERED — recipient pulls via /get
later per §5.8.3).
flows/README.md status table updated; receive-propagated-lxmf.md
added as the only remaining ⏳ flow.
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.0; 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:934-983) 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 |