reticiulum-specification/flows/receive-resource.md
Rob 282d5d59eb Add five companion flow docs
- 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>
2026-05-03 12:21:05 -04:00

5 KiB
Raw 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.0; 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: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:

  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