§19 — Constrained Transport Encoding v1.6
Earlier RCAN protocol drafts were entirely HTTP-based. A standard RCAN message with JWT and Ed25519 signature is 400–800 bytes — impossible over LoRa (255 bytes/frame), tight over BLE (251 byte MTU), and wasteful over constrained cellular. Subsequent revisions define three transport encodings plus BLE fragmentation rules.
Transport Encoding Tiers
| Encoding | Transport | Format | Max Size | Supported Messages |
|---|---|---|---|---|
| RCAN-HTTP | WiFi, Ethernet, 4G/5G (unconstrained) | Full JSON over HTTP/1.1 or HTTP/2 | 64 KB | All 31 message types |
| RCAN-Compact | WiFi/LTE-constrained, BLE (non-ESTOP) | CBOR with mandatory field abbreviations | 512 bytes | All message types (with size budgets) |
| RCAN-Minimal | LoRa, SMS, BLE (ESTOP only) | Fixed 32-byte binary frame | 32 bytes | ESTOP (type 6) and ACK only |
RCAN-HTTP (Existing)
RCAN-HTTP is unchanged from v1.5. Full JSON envelope, up to 64 KB per message, transmitted over HTTPS. All message types supported. Used as the baseline for transport negotiation.
POST /api/v1/message HTTP/1.1
Content-Type: application/json
Authorization: Bearer <jwt>
Content-Length: 487
{ "id": "uuid", "type": 6, "priority": 3, ... } RCAN-HTTP is the only encoding that supports multi-modal payloads (§5.4). All other encodings MUST use reference mode (ref_url) for binary data.
RCAN-Compact
RCAN-Compact uses CBOR (RFC 7049) encoding with mandatory single-character field abbreviations. It is intended for WiFi or LTE links where bandwidth is scarce but not extreme (e.g., 1–10 KB/s).
Field Abbreviation Map
| Full Field Name | Compact Key | CBOR Type | Notes |
|---|---|---|---|
type (msg_type) | t | uint | MessageType integer 1–31 |
id (msg_id) | i | bstr (16 bytes) | UUID as raw bytes, not string |
timestamp | ts | uint | Unix epoch seconds (32-bit) |
source (from_rrn) | f | bstr | Compressed RRN (see §19.3) |
target (to_rrn) | to | bstr | Compressed RRN |
scope | s | uint | Scope bitmask (see below) |
payload | p | map or bstr | CBOR map or raw bytes |
signature | sig | bstr (64 bytes) | Full Ed25519 signature |
qos | q | uint | 0, 1, or 2 |
priority | pr | uint | 0=LOW 1=NORMAL 2=HIGH 3=SAFETY |
Scope Bitmask
// Scope encoded as uint bitmask instead of string array
SCOPE_DISCOVER = 0x01
SCOPE_STATUS = 0x02
SCOPE_CONTROL = 0x04
SCOPE_CONFIG = 0x08
SCOPE_TRAINING = 0x10
SCOPE_SAFETY = 0x20
SCOPE_OBSERVER = 0x40
// Example: ["control", "status"] → 0x06 Byte Budget Targets
- ESTOP message: target < 200 bytes (no payload needed)
- STATUS message: target < 512 bytes (position, battery, mode)
- COMMAND message: target < 512 bytes (typical action command)
Example: ESTOP in RCAN-Compact
// JSON (469 bytes including JWT):
{ "id": "550e8400-e29b-41d4-a716-446655440000", "type": 6, "priority": 3,
"source": "rcan://rcan.dev/acme/arm/v1/001", "target": "rcan://rcan.dev/acme/arm/v1/002",
"payload": {"action": "ESTOP"}, "timestamp": 1741000000, "scope": ["safety"],
"signature": "ed25519:64byteshex...", "qos": 2 }
// CBOR/Compact (≈ 148 bytes):
a7 # map(7)
61 74 06 # "t": 6 (msg_type=SAFETY)
61 69 50 ... # "i": 16-byte UUID bytes
62 74 73 1a ... # "ts": uint32 unix timestamp
61 66 48 ... # "f": 8-byte compressed RRN-from
62 74 6f 48 ... # "to": 8-byte compressed RRN-to
61 73 20 # "s": 0x20 (SCOPE_SAFETY bitmask)
63 73 69 67 58 40 # "sig": 64-byte Ed25519 signature Content-Type Header
// HTTP header for RCAN-Compact
Content-Type: application/rcan+cbor; version=1.6; encoding=compact RCAN-Minimal
RCAN-Minimal is a fixed 32-byte binary frame for extreme bandwidth constraints: LoRa, SMS (160 byte limit, base64 overhead considered), and BLE (single packet).
32-Byte Frame Layout
Byte offset Length Field Description
───────────────────────────────────────────────────────
0x00 2 type Message type: 0x0006=ESTOP, 0x0011=ACK
0x02 8 rrn_from RRN-from: 8 compressed bytes (see §19.3)
0x0A 8 rrn_to RRN-to: 8 compressed bytes
0x12 4 timestamp_unix32 Unix epoch seconds (uint32, big-endian)
0x16 8 sig_truncated First 8 bytes of Ed25519 signature
0x1E 2 checksum CRC-16/CCITT over bytes 0x00–0x1D
Total: 32 bytes Type Field Values (RCAN-Minimal)
| Value (uint16 BE) | Meaning |
|---|---|
0x0006 | ESTOP — emergency stop |
0x0011 | ACK — acknowledgement of received ESTOP |
RRN Compression (§19.3)
Full RRURIs like rcan://registry.acme.com/org/model/v1/unit-042 are compressed to 8 bytes using a registered-hash scheme:
// 8-byte compressed RRN:
// [2B registry-id hash][2B org hash][2B model hash][2B unit-id hash]
// Each hash: first 2 bytes of SHA-256(component-string)
// Robots pre-register their compressed RRN with their local registry.
// Collision probability in a fleet of 1000 robots: ~1 in 4 billion. LoRa SF12 Timing
LoRa SF12, BW=125kHz, CR=4/5:
Data rate: ~250 bps
32 bytes = 256 bits
Air time: ~1.0 second (including preamble: ~1.2s total)
→ ESTOP in RCAN-Minimal fits in a single LoRa frame at all standard LoRa settings.
→ At SF7 (fastest): ~60ms air time for 32 bytes.
LoRa duty cycle (EU 868 MHz, 1%): max 36s/hour continuous TX
→ At SF12: ~30 ESTOP messages per hour on EU bands
→ At SF7: ~600 ESTOP messages per hour Verification on Receipt
RECEIVING ROBOT (verifying RCAN-Minimal ESTOP):
1. Parse frame: check length == 32 bytes
2. Verify CRC-16 over bytes 0–29
3. Decompress rrn_from → full RURI
4. Check timestamp freshness: now - timestamp <= 10s (SAFETY window)
5. Lookup sender's Ed25519 public key (from local cache)
6. Compute Ed25519 signature over bytes 0–21 (type+rrn_from+rrn_to+timestamp)
7. Compare computed_sig[0:8] == frame.sig_truncated (truncated compare)
8. If all pass: execute ESTOP immediately
9. Send RCAN-Minimal ACK (type=0x0011) back to sender BLE L2CAP Framing
BLE L2CAP has an MTU of 251 bytes (BLE 4.2+) or up to 512 bytes (BLE 5.x with LE Data Length Extension). RCAN-Compact messages exceeding the MTU MUST be fragmented.
Fragmentation Protocol
// BLE L2CAP RCAN fragment header (4 bytes prepended to each fragment):
Byte 0: fragment_flags // 0x00=middle, 0x01=first, 0x02=last, 0x03=only
Byte 1: frag_index // Fragment sequence number (0-based)
Byte 2-3: total_length // uint16 BE: total CBOR payload length
// Fragment assembly:
// - First fragment (flags=0x01): store total_length, start buffer
// - Middle fragments (flags=0x00): append to buffer
// - Last fragment (flags=0x02): append to buffer, parse CBOR
// - Only fragment (flags=0x03): skip reassembly, parse directly
// MTU 251 bytes - 4 byte header = 247 bytes payload per fragment
// Max fragments per message: ceil(512 / 247) = 3 for RCAN-Compact max size BLE L2CAP Channel
// BLE RCAN service UUID (128-bit):
RCAN_SERVICE_UUID = "6e726361-6e2d-7263-616e-726361726361"
// ("nrcan-rcan-rcan" encoded as ASCII UUID)
// Characteristics:
RCAN_TX_CHAR_UUID = "6e726361-6e2d-0001-0000-726361726361" // Robot → Client
RCAN_RX_CHAR_UUID = "6e726361-6e2d-0002-0000-726361726361" // Client → Robot
RCAN_MTU_CHAR_UUID = "6e726361-6e2d-0003-0000-726361726361" // Negotiated MTU (read-only) RCAN-Minimal frames (32 bytes) are sent unfragmented over BLE. RCAN-Compact messages MUST use the L2CAP fragmentation protocol above when exceeding the MTU.
Transport Negotiation
Robots advertise their supported transport encodings in the DISCOVER response payload:
// DISCOVER response (v1.6) — payload addition
{
"rcan_version": "1.6",
"capabilities": ["control", "safety", "training"],
"p66_conformance_pct": 94.0,
"supported_transports": ["http", "compact", "minimal", "ble"],
"transport_config": {
"http_max_kb": 64,
"compact_max_bytes": 512,
"ble_mtu": 251,
"lora_region": "EU868",
"lora_sf": 12
}
} Transport Selection Rules
- Senders MUST select the highest-capability encoding that the receiver supports AND that fits the message type.
- ESTOP over LoRa MUST use RCAN-Minimal (single frame guaranteed).
- ESTOP over BLE MAY use RCAN-Minimal (32 bytes, well within MTU) or RCAN-Compact if the sender prefers full signature.
- All non-ESTOP messages MUST use RCAN-Compact or RCAN-HTTP (not RCAN-Minimal).
- If the receiver does not list
"minimal"insupported_transports, senders MUST NOT use RCAN-Minimal.
The supported_transports list is also exposed in the Protocol 66 manifest (see Safety & P66 Conformance).
rcan-py Implementation
from rcan.transport import CompactEncoder, MinimalEncoder, encode_for_transport
# Auto-select encoding based on transport
msg_bytes = encode_for_transport(
msg=estop_message,
transport="lora", # "http" | "compact" | "minimal" | "ble"
receiver_capabilities=["minimal"],
)
# → returns 32-byte RCAN-Minimal frame for LoRa
# Manual CBOR compact encoding
encoder = CompactEncoder()
cbor_bytes = encoder.encode(msg)
assert len(cbor_bytes) < 200 # ESTOP budget
# Decode on receipt
msg = CompactEncoder().decode(cbor_bytes)
# RCAN-Minimal encode/decode
minimal_frame = MinimalEncoder.encode_estop(
from_ruri="rcan://acme.com/org/arm/v1/001",
to_ruri="rcan://acme.com/org/arm/v1/002",
signing_key=private_key,
)
assert len(minimal_frame) == 32 Implementation Notes
- CBOR library requirement: Implementations MUST use a CBOR library that correctly handles indefinite-length items — do NOT use CBOR streaming encoding for RCAN-Compact (receivers need total_length for BLE fragmentation).
- Replay prevention on Minimal: The 32-byte frame has a 4-byte Unix timestamp. RCAN-Minimal receivers MUST still apply the 10-second ESTOP replay window. If the timestamp cannot be checked (no local clock), the message MUST be rejected.
- JWT absence in Minimal: RCAN-Minimal does not carry a JWT. Authentication relies on the Ed25519 signature (truncated) and the sender's public key cached from a prior RCAN-HTTP/Compact exchange. Robots MUST NOT accept RCAN-Minimal from senders whose public keys are not already cached.
- SMS transport: RCAN-Minimal frames can be base64-encoded (≈43 chars) and sent as SMS. Ensure the SMS sender's number is pre-registered with the target robot.
- BLE MTU negotiation: Always negotiate the highest MTU the client supports before sending RCAN-Compact over BLE. A larger MTU means fewer fragments and lower latency for STATUS/COMMAND messages.
See Also
- Safety & P66 Conformance —
supported_transportsin Protocol 66 manifest - §5.4 Multi-Modal Payloads — size limits by transport tier
- §8.3 Replay Attack Prevention — timestamp freshness applies to all encodings
- Message Type Reference — which types are supported per encoding
- §5.3 QoS & Delivery Guarantees — QoS interaction with constrained transports