§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.

Protocol 66 Invariant — ESTOP always fits: The RCAN-Minimal 32-byte format is designed so ESTOP always fits in a single LoRa frame at SF12. ESTOP is the only command supported in RCAN-Minimal. Any implementation that cannot send ESTOP in a single frame MUST fall back to a higher encoding tier.

Transport Encoding Tiers

EncodingTransportFormatMax SizeSupported 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 NameCompact KeyCBOR TypeNotes
type (msg_type)tuintMessageType integer 1–31
id (msg_id)ibstr (16 bytes)UUID as raw bytes, not string
timestamptsuintUnix epoch seconds (32-bit)
source (from_rrn)fbstrCompressed RRN (see §19.3)
target (to_rrn)tobstrCompressed RRN
scopesuintScope bitmask (see below)
payloadpmap or bstrCBOR map or raw bytes
signaturesigbstr (64 bytes)Full Ed25519 signature
qosquint0, 1, or 2
prioritypruint0=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).

Security note: RCAN-Minimal uses a truncated 8-byte signature. This provides authentication against casual spoofing but NOT against a determined adversary with cryptographic resources. RCAN-Minimal is ONLY for ESTOP and ACK. It MUST NOT be used for control commands, consent, or any high-value operation.

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
0x0006ESTOP — emergency stop
0x0011ACK — 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" in supported_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