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

97 lines
5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flow: receive a Resource (large body) over a Link
The inverse of [`send-resource.md`](send-resource.md). What happens chronologically on the receiver when an inbound Resource transfer arrives. Pinned against **RNS 1.2.0**; see [`../SPEC.md`](../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 |