| name | resources |
| description | Reticulum's resource transfer system for large data over links. Use when working with resource transfers, windowing, hashmaps, segments, compression, file transfers, or resource states. |
Resources
This skill provides knowledge about Reticulum's resource transfer system for large data over links. Invoke when the user mentions large data transfer, resource transfer, windowing, hashmap, segments, compression, file transfer, or resource states.
Resource Overview
Resources are Reticulum's mechanism for reliably transferring arbitrary amounts of data over a link. They automatically handle:
- Sequencing - Data split into numbered parts
- Compression - Optional bz2 compression
- Coordination - Dynamic window-based flow control
- Checksumming - Hash verification at multiple levels
- Segmentation - Large files split into manageable segments
Resources work exclusively over established links, never directly. They're designed to efficiently transfer data of any size - from a few kilobytes to gigabytes - while adapting to link quality.
Resource States
NONE = 0x00
QUEUED = 0x01
ADVERTISED = 0x02
TRANSFERRING = 0x03
AWAITING_PROOF = 0x04
ASSEMBLING = 0x05
COMPLETE = 0x06
FAILED = 0x07
CORRUPT = 0x08
REJECTED = 0x00
State Flow - Sender Side
NONE → QUEUED → ADVERTISED → TRANSFERRING → AWAITING_PROOF → COMPLETE
↓ ↓ ↓ ↓
└──────────┴──────────────┴───────────────┴───────→ FAILED
NONE: Resource object created but not yet advertised.
QUEUED: Resource ready to advertise but link is busy with another transfer. Waiting for link availability.
ADVERTISED: Advertisement packet sent to receiver. Waiting for first part request.
TRANSFERRING: Receiver has accepted and is requesting parts. Sender transmitting data.
AWAITING_PROOF: All parts transmitted. Waiting for receiver's completion proof.
COMPLETE: Receiver sent valid proof. Transfer successful.
FAILED: Transfer failed due to timeout, network error, or cancellation.
State Flow - Receiver Side
NONE → TRANSFERRING → ASSEMBLING → COMPLETE
↓ ↓
└────────────────┴──────────────────→ FAILED/CORRUPT
TRANSFERRING: Advertisement accepted, requesting and receiving parts.
ASSEMBLING: All parts received. Decrypting, decompressing, and verifying integrity.
COMPLETE: Assembly successful, hash verified. Resource data ready.
CORRUPT: Hash verification failed. Data integrity compromised.
Key Constants
WINDOW = 4
WINDOW_MIN = 2
WINDOW_MAX_SLOW = 10
WINDOW_MAX_VERY_SLOW = 4
WINDOW_MAX_FAST = 75
WINDOW_FLEXIBILITY = 4
RATE_FAST = 6250
RATE_VERY_SLOW = 250
FAST_RATE_THRESHOLD = 4
VERY_SLOW_RATE_THRESHOLD = 2
MAX_EFFICIENT_SIZE = 1048575
METADATA_MAX_SIZE = 16777215
AUTO_COMPRESS_MAX_SIZE = 67108864
MAX_RETRIES = 16
MAX_ADV_RETRIES = 4
SENDER_GRACE_TIME = 10.0
RETRY_GRACE_TIME = 0.25
PER_RETRY_DELAY = 0.5
MAPHASH_LEN = 4
RANDOM_HASH_SIZE = 4
WINDOW_MIN (2): Absolute minimum window size. Even on worst connections, at least 2 parts can be outstanding.
WINDOW_MAX_FAST (75): Maximum window for fast links (>50 Kbps sustained). Allows high throughput without overwhelming receiver.
MAX_EFFICIENT_SIZE (1,048,575 bytes): Maximum size for a single resource segment. Larger resources are split into multiple segments. Limited to 3 bytes in advertisement (0xFFFFFF).
MAPHASH_LEN (4 bytes): Each data part is identified by a 4-byte hash. Collision probability is negligible within a resource.
RANDOM_HASH_SIZE (4 bytes): Random prefix added to resource data before hashing. Prevents hash collisions between similar resources.
Resource Advertisement Format
Resource transfer begins with an advertisement packet using umsgpack:
context = RNS.Packet.RESOURCE_ADV
advertisement = {
"t": transfer_size,
"d": data_size,
"n": number_of_parts,
"h": resource_hash,
"r": random_hash,
"o": original_hash,
"i": segment_index,
"l": total_segments,
"q": request_id,
"f": flags,
"m": hashmap_segment
}
flags = (
(has_metadata << 5) |
(is_response << 4) |
(is_request << 3) |
(is_split << 2) |
(compressed << 1) |
(encrypted << 0)
)
Hashmap Segmentation
The full hashmap may be too large for a single advertisement. Only the first segment is sent:
HASHMAP_MAX_LEN = floor((Link.MDU - 134) / MAPHASH_LEN)
Overhead calculation: Advertisement umsgpack structure uses ~134 bytes for fields, leaving room for hashmap.
Window-Based Transfer Flow
Resources use a sliding window protocol for efficient, adaptive transfer:
Initial Window Setup
window = 4
window_min = 2
window_max = 10
window_flexibility = 4
Request Cycle
-
Receiver requests parts:
outstanding_parts = 0
requested_hashes = b""
for part_index in range(consecutive_completed + 1, consecutive_completed + window + 1):
if parts[part_index] is None and hashmap[part_index] is not None:
requested_hashes += hashmap[part_index]
outstanding_parts += 1
request_packet = RNS.Packet(link, hash + requested_hashes, context=RNS.Packet.RESOURCE_REQ)
-
Sender transmits requested parts:
for requested_hash in requested_hashes:
part = find_part_by_hash(requested_hash)
part.send()
-
Receiver processes parts:
parts[index] = part_data
if index == consecutive_completed + 1:
consecutive_completed = index
while parts[consecutive_completed + 1] is not None:
consecutive_completed += 1
Dynamic Window Adjustment
Window size adapts based on performance:
if outstanding_parts == 0:
if window < window_max:
window += 1
if (window - window_min) > (window_flexibility - 1):
window_min += 1
if timeout:
if window > window_min:
window -= 1
if window_max > window_min:
window_max -= 1
if (window_max - window) > (window_flexibility - 1):
window_max -= 1
Rate-Based Window Limits
The window maximum adjusts based on measured transfer rate:
rtt = response_time - request_time
req_resp_cost = response_packet_size + request_packet_size
req_resp_rtt_rate = req_resp_cost / rtt
if req_resp_rtt_rate > RATE_FAST:
fast_rate_rounds += 1
if fast_rate_rounds >= FAST_RATE_THRESHOLD:
window_max = WINDOW_MAX_FAST
if req_resp_rtt_rate < RATE_VERY_SLOW:
very_slow_rate_rounds += 1
if very_slow_rate_rounds >= VERY_SLOW_RATE_THRESHOLD:
window_max = WINDOW_MAX_VERY_SLOW
This creates an adaptive system:
- Fast links: window can grow to 75 parts in flight
- Normal links: window capped at 10 parts
- Very slow links: window capped at 4 parts
- All links: minimum of 2 parts maintained
Hashmap Updates (HMU)
When the receiver exhausts its current hashmap segment, it requests more:
if hashmap[next_part_index] is None:
hashmap_exhausted = 0xFF
hmu_request = bytes([hashmap_exhausted]) + last_received_hash + resource_hash
segment_number = part_index // HASHMAP_MAX_LEN
hashmap_start = segment_number * HASHMAP_MAX_LEN
hashmap_end = min((segment_number + 1) * HASHMAP_MAX_LEN, total_parts)
hashmap_segment = b""
for i in range(hashmap_start, hashmap_end):
hashmap_segment += hashmap[i]
hmu_packet = umsgpack.packb([segment_number, hashmap_segment])
RNS.Packet(link, resource_hash + hmu_packet, context=RNS.Packet.RESOURCE_HMU).send()
This keeps advertisement packets small while supporting resources with thousands of parts.
Compression with bz2
Resources can optionally compress data before transfer:
if auto_compress and data_size <= AUTO_COMPRESS_MAX_SIZE:
compressed_data = bz2.compress(uncompressed_data)
if len(compressed_data) < len(uncompressed_data):
resource_data = random_hash + compressed_data
compressed_flag = True
else:
resource_data = random_hash + uncompressed_data
compressed_flag = False
Compression is skipped for data larger than 64MB to avoid excessive CPU and memory usage.
Decompression on Receive
if compressed_flag:
data = bz2.decompress(encrypted_data)
else:
data = encrypted_data
calculated_hash = SHA256(data + random_hash)
if calculated_hash == advertised_hash:
Typical compression ratios for text data: 3:1 to 10:1. Binary data varies widely.
Data Integrity Verification
Resources use multiple levels of hashing:
Level 1: Part Hashes (4 bytes each)
part_data = resource_data[i * sdu : (i+1) * sdu]
part_hash = SHA256(part_data + random_hash)[:4]
hashmap.append(part_hash)
Prevents:
- Requesting wrong parts
- Accepting corrupted parts
- Part ordering errors
Level 2: Resource Hash (32 bytes)
resource_hash = SHA256(encrypted_data + random_hash)
Advertised to receiver. Verified after assembly.
Level 3: Completion Proof (32 bytes)
decrypted_data = link.decrypt(encrypted_resource)
proof = SHA256(decrypted_data + resource_hash)
expected_proof = SHA256(original_data + resource_hash)
if proof == expected_proof:
status = COMPLETE
This three-level system ensures end-to-end integrity even with encryption.
Resource Segmentation
Resources larger than MAX_EFFICIENT_SIZE (1MB - 1) are split:
total_segments = ceil(total_size / MAX_EFFICIENT_SIZE)
Segment Coordination
segment_1 = Resource(data, link, segment_index=1, original_hash=hash1)
The receiver assembles segments into a single file, verifying each segment before combining.
Metadata Support
Resources can include structured metadata:
metadata = {
"filename": "document.pdf",
"size": 1024000,
"hash": "sha256:...",
"created": 1704067200,
}
resource = Resource(data, link, metadata=metadata)
metadata_size = len(umsgpack.packb(metadata))
size_bytes = struct.pack(">I", metadata_size)[1:]
segment_data = size_bytes + umsgpack.packb(metadata) + data
Only the first segment of a multi-segment resource contains metadata.
Timeout and Retry Logic
Resources implement sophisticated timeout handling:
Advertisement Timeout
timeout = link.rtt * link.traffic_timeout_factor + PROCESSING_GRACE
if no_request_received_within(timeout):
if retries_left > 0:
resend_advertisement()
retries_left -= 1
else:
cancel_resource()
Part Transfer Timeout (Receiver)
expected_inflight_rate = measured_data_rate
expected_tof = (outstanding_parts * sdu * 8) / expected_inflight_rate
timeout = last_activity + (PART_TIMEOUT_FACTOR * expected_tof) + RETRY_GRACE_TIME
if time.time() > timeout:
if retries_left > 0:
window -= 1
request_next()
retries_left -= 1
else:
cancel_resource()
Proof Timeout (Sender)
timeout = last_part_sent + (rtt * PROOF_TIMEOUT_FACTOR) + SENDER_GRACE_TIME
if time.time() > timeout:
if retries_left > 0:
request_cache(expected_proof_packet_hash)
retries_left -= 1
else:
cancel_resource()
The grace times account for processing delays, especially on resource-constrained devices.
Transfer Efficiency
Resource overhead varies by size:
Small Resource (10 KB, ~24 parts)
Advertisement: ~230 bytes
Part requests: ~105 bytes × ~6 requests = ~630 bytes
Parts: ~19 bytes header × 24 = ~456 bytes
Proof: ~115 bytes
Total overhead: ~1,431 bytes
Payload: ~10,240 bytes
Efficiency: 87.7%
Large Resource (1 MB, ~2,400 parts)
Advertisement: ~230 bytes
Hashmap updates: ~1,200 bytes × 32 updates = ~38,400 bytes
Part requests: ~105 bytes × ~75 requests = ~7,875 bytes
Parts: ~19 bytes × 2,400 = ~45,600 bytes
Proof: ~115 bytes
Total overhead: ~92,220 bytes
Payload: ~1,048,575 bytes
Efficiency: 91.9%
Efficiency improves with size due to fixed overhead amortization. Fast links with large windows achieve near-optimal throughput.
Resource Cancellation
Either side can cancel a resource:
cancel_packet = RNS.Packet(link, resource_hash, context=RNS.Packet.RESOURCE_ICL)
reject_packet = RNS.Packet(link, resource_hash, context=RNS.Packet.RESOURCE_RCL)
Cancellation is immediate and does not wait for acknowledgment.
Progress Tracking
Resources support progress callbacks:
def progress_callback(resource):
progress = resource.get_progress()
segment_progress = resource.get_segment_progress()
print(f"Overall: {progress*100:.1f}%")
print(f"Segment {resource.segment_index}/{resource.total_segments}: {segment_progress*100:.1f}%")
resource = Resource(data, link, progress_callback=progress_callback)
Progress is calculated differently for single vs. multi-segment resources to provide accurate overall progress.
Implementation Notes
Collision Guard
The sender maintains a collision guard to prevent hash collisions in the hashmap:
COLLISION_GUARD_SIZE = 2 * WINDOW_MAX + HASHMAP_MAX_LEN
This ensures requested parts can be uniquely identified.
Memory Management
Large resources use temporary files to avoid memory exhaustion:
if data_size > MAX_EFFICIENT_SIZE:
data_file = tempfile.TemporaryFile()
data_file.write(data)
resource = Resource(data_file, link)
storage_path = RNS.Reticulum.resourcepath + "/" + original_hash.hex()
This allows transferring multi-gigabyte files on memory-constrained devices.
Watchdog Thread
Each resource runs a watchdog thread monitoring timeouts:
def watchdog():
while status < ASSEMBLING:
calculate_next_timeout()
sleep(min(timeout, WATCHDOG_MAX_SLEEP))
if timeout_occurred():
handle_timeout()
The watchdog ensures timely timeout detection without blocking the main thread.
Further Reading
For detailed protocol specifications:
references/wire-examples.py - Advertisement packet construction and window flow
Related skills:
links - Link establishment required for resource transfer
packets-wire-format - Resource packet contexts (RESOURCE_ADV, RESOURCE_REQ, etc.)
cryptography-identity - Hash functions and encryption used
Source References:
https://github.com/markqvist/Reticulum/RNS/Resource.py (complete implementation)
https://github.com/markqvist/Reticulum/docs/source/understanding.rst (resource overview)