3-RRC: Wire Encoding, Constants, and Numeric Assignments

Version: 0.1.2

This document exists so that two people, writing two separate RRC implementations, do not accidentally invent two different protocols while both claiming success.

1-RRC explained what the system is. 2-RRC explained what messages exist and how sessions behave. This document locks down the numbers, field keys, and expectations that appear on the wire so that CBOR blobs mean the same thing everywhere.

If you skip this document, your implementation will still work, but only with itself. That is not a victory.

Canonical Encoding Rules

All RRC messages are encoded as CBOR maps. The map keys are unsigned integers. String keys are not allowed. This is not because strings are evil, but because numeric keys are smaller, faster to parse, and harder to misspell.

All integer values are encoded as unsigned integers unless explicitly stated otherwise. Binary values are encoded as CBOR byte strings. Human-readable text is encoded as CBOR text strings using UTF-8.

Ordering of keys in the CBOR map does not matter. Receivers must not assume any specific ordering and must not reject messages solely because the sender arranged keys differently.

Unknown keys must be ignored. This is not optional. If your implementation explodes because it saw a field it did not understand, that is your fault.

CBOR Encoding Size Reference

Understanding CBOR encoding overhead is essential for staying within the Reticulum MTU constraints. This section provides the byte counts for RRC fields to help implementers calculate message sizes.

CBOR Encoding Basics

In CBOR, each value has a type byte that includes both the major type and additional information. For small values, this can be very compact.

Map overhead:

  • Maps with 0-23 fields: 1 byte for the map header

Integer keys (0-23):

  • Each key 0-23: 1 byte

Unsigned integer values:

  • Values 0-23: 1 byte (encoded directly in the type byte)

  • Values 24-255: 2 bytes (type byte + 1 data byte)

  • Values 256-65535: 3 bytes (type byte + 2 data bytes)

  • Values up to 2^32-1: 5 bytes (type byte + 4 data bytes)

  • Values up to 2^64-1: 9 bytes (type byte + 8 data bytes)

Byte strings:

  • Length 0-23: 1 byte (type+length) + N bytes data

  • Length 24-255: 2 bytes (type + length byte) + N bytes data

  • Length 256-65535: 3 bytes (type + 2 length bytes) + N bytes data

Text strings (UTF-8):

  • Same encoding as byte strings, but major type 3 instead of 2

Fixed Field Sizes

RRC mandates specific fixed lengths for core envelope fields to ensure predictable sizing and interoperability. The following measurements are based on actual CBOR encoding using Python’s cbor2 library in the reference implementation (rrcd).

CBOR Map Header: 1 byte (for maps with 0-23 fields)

Field Number

Field Name

Value Size

Key

Type/Length

Total

Notes

0

Protocol Version

1 byte

1

2

Value 0-23 fits in 1 byte

1

Message Type

1 byte

1

2

Values 0-255

2

Message ID

8 bytes

1

1

10

Byte string (fixed 8)

3

Timestamp

8 bytes

1

1

10

Uint64 (ms since epoch)

4

Sender Identity

16 bytes

1

1

18

Byte string (fixed 16)

Total fixed overhead: 1 (map) + 2 + 2 + 10 + 10 + 18 = 43 bytes

Variable Field Sizes

Text strings are encoded as UTF-8, where characters may use 1-4 bytes each. CBOR overhead is determined by the total byte length of the UTF-8 encoded string, not the character count.

Field Number

Field Name

Type

Overhead Formula

Typical Overhead

5

Room Name

text string

1 (key) + 1-2 (type+len) + N

2-3 bytes

6

Body

varies

1 (key) + 1-3 (type+len) + N

2-4 bytes

7

Nickname

text string

1 (key) + 1-2 (type+len) + N

2-3 bytes

CBOR text string overhead by length:

  • 0-23 bytes: 1 byte (type+length encoded together)

  • 24-255 bytes: 2 bytes (type + separate 1-byte length)

  • 256-65535 bytes: 3 bytes (type + separate 2-byte length)

Concrete examples:

  • “lobby” (5 ASCII chars = 5 bytes): 1 key + 1 type+len + 5 data = 7 bytes total

  • “café” (4 chars = 5 UTF-8 bytes): 1 key + 1 type+len + 5 data = 7 bytes total

  • “学习” (2 Chinese chars = 6 UTF-8 bytes): 1 key + 1 type+len + 6 data = 8 bytes total

  • 350-byte message body: 1 key + 3 type+len + 350 data = 354 bytes total

Minimum Envelope Overhead

A minimal RRC envelope with only required fields totals 43 bytes:

  • CBOR map header: 1 byte

  • Field 0 (Protocol Version): 2 bytes

  • Field 1 (Message Type): 2 bytes

  • Field 2 (Message ID): 10 bytes

  • Field 3 (Timestamp): 10 bytes

  • Field 4 (Sender Identity): 18 bytes

When adding optional variable fields:

  • Field 5 (Room Name): 2-3 bytes overhead + UTF-8 byte length

  • Field 6 (Body): 2-4 bytes overhead + content length

  • Field 7 (Nickname): 2-3 bytes overhead + UTF-8 byte length

Reticulum MTU Constraints

Reticulum packets have a default MTU of 500 bytes structured as:

[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]

Worst case (32-byte addresses): 465 bytes available for RRC envelope

  • Fixed overhead: 43 bytes

  • Remaining for variable fields: 422 bytes

Best case (16-byte addresses): 481 bytes available for RRC envelope

  • Fixed overhead: 43 bytes

  • Remaining for variable fields: 438 bytes

Implementers should always budget for the worst-case scenario (422 bytes for variable content).

Practical Size Budget Example

A worst-case MSG envelope with recommended hub limits:

Component

Calculation

Bytes

Fixed overhead

Map + Fields 0-4

43

Room name (64B)

3 overhead + 64 data

67

Nickname (32B)

3 overhead + 32 data

35

Body (350B)

4 overhead + 350 data

354

Total

499

Result: ✓ Fits within 500-byte Reticulum MTU with 1 byte to spare.

Note: Room names and nicknames measured in bytes (not characters), accounting for UTF-8 encoding where characters may use 1-4 bytes each.

Envelope Structure

Every RRC message shares the same top-level envelope. This envelope is a CBOR map containing the following fields.

Field 0 - Protocol Version

This value identifies the RRC protocol version used to encode the message. For this specification, the value must be 1. Messages with other versions may be ignored or rejected.

Field 1 - Message Type

This is an unsigned integer identifying what kind of message this is. The numeric values for message types are defined later in this document.

Field 2 - Message Identifier

This is an 8-byte random identifier chosen by the sender using a cryptographically secure random source (e.g., os.urandom(8)). It must be unique within the scope of the sender’s current session. It does not need to be globally unique or persistent across reconnects. It exists so implementations can correlate messages if they care to.

Field 3 - Timestamp

This is an unsigned integer representing milliseconds since the Unix epoch, according to the sender’s clock. CBOR encodes this as a 64-bit unsigned integer (type byte + 8 data bytes). It is advisory only. No one is expected to agree on time.

Field 4 - Sender Identity

This is a 16-byte value containing the Reticulum identity hash of the sender encoded as a CBOR byte string. This field is mandatory even though the Link already authenticates the peer, because forwarded messages need an explicit source identifier.

Field 5 - Room Name

This is a text string identifying the room the message applies to. If a message does not apply to a room, this field may be omitted.

Field 6 - Body

This field contains the payload of the message. Its structure and meaning depend entirely on the message type.

Field 7 - Nickname

This is an optional text string containing a human-readable nickname for the sender.

It is advisory only. It may be omitted, empty, ridiculous, or all three. The hub may ignore it, sanitize it, replace it, or drop it on the floor entirely. Clients may display it if present, or may choose to rely on identity hashes, locally cached labels, or whatever else they think is a good idea.

If both sides send this field, they are not negotiating. They are making suggestions.

No other top-level fields are defined by this document.

Envelope field keys 0 through 39 are reserved for core protocol use. Extensions may define additional top-level fields using keys 40 through 63. Keys 64 and above are reserved for future specifications, which is a polite way of saying “don’t squat there unless you enjoy avoidable pain.”

Message Type Assignments

Message type values are fixed. They are not suggestions.

Type

Name

1

HELLO

2

WELCOME

10

JOIN

11

JOINED

12

PART

13

PARTED

20

MSG

21

NOTICE

30

PING

31

PONG

40

ERROR

10 - 19: Room Membership Messages

Type 10 - JOIN

The JOIN message uses type 10. This message is sent by the client to request entry into a room.

Type 11 - JOINED

The JOINED message uses type 11. This message is sent by the hub to confirm room membership.

Type 12 - PART

The PART message uses type 12. This message is sent by the client to leave a room.

Type 13 - PARTED

The PARTED message uses type 13. This message is sent by the hub to confirm that a client has left a room.

20 - 29: Messages Intended for Rooms and People

Type 20 - MSG

The MSG message uses type 20. This message carries chat content intended for a room.

Type 21 - NOTICE

The NOTICE message uses type 21. This message carries informational or non-conversational content.

40 and Up: Error and Status Messages

Type 40 - ERROR

The ERROR message uses type 40. This message reports a failure or refusal.

Values from 0 to 63 are reserved for core protocol use. Values from 64 upward are available for extensions, experiments, and future specifications. If you collide with yourself there, that is your own adventure.

Message Body Definitions

The body field exists so message-specific data does not leak into the envelope and turn everything into a special case.

HELLO body key assignments

For HELLO, the body is a CBOR map with unsigned integer keys. It may contain session-specific data used for exchanging version information or program capabilities, as well as human-facing metadata.

All body fields are advisory and optional. Any human-facing metadata is considered an extension of the protocol and is outside the scope of this specification.

Key

Name

Type

Description

0

Client Name

text string

Client software name (optional)

1

Client Version

text string

Client version string (optional)

2

Capabilities

map/array

Capability hints (optional)

WELCOME body key assignments

For WELCOME, the body is also a CBOR map with unsigned integer keys. It may contain session-specific data used for exchanging version information or program capabilities, as well as human-facing metadata.

All body fields are advisory and optional. Any human-facing metadata is considered an extension of the protocol and is outside the scope of this specification.

Key

Name

Type

Description

0

Hub Name

text string

Human-friendly hub name

1

Hub Version

text string

Hub version string (optional)

2

Capabilities

map/array

Capability hints (optional)

3

Hub Limits

map

Configured hub limits (optional)

Capabilities Field

The Capabilities field (key 2) in both HELLO and WELCOME bodies is intended for exchanging information about supported features. Its structure is left open-ended to allow flexibility. Implementations may choose to use a CBOR map with string keys representing capability names and boolean values indicating support, or an array of strings listing supported capabilities.

Hub Limits Field

The Hub Limits field (key 3) in the WELCOME body is a CBOR map that may contain information about the hub’s configured limits. Possible keys include:

  • max_nick_bytes: unsigned integer indicating the maximum length of nicknames in bytes.

  • max_rooms_per_session: unsigned integer indicating the maximum number of rooms a client may join in a single session.

  • max_room_name_bytes: unsigned integer indicating the maximum length of room names in bytes.

  • max_msg_body_bytes: unsigned integer indicating the maximum size of message bodies in bytes.

  • rate_limit_msgs_per_minute: unsigned integer indicating the maximum number of messages a client may send per minute.

These keys are only examples. Hubs may define additional limit keys as needed. Clients should treat this information as advisory and may choose to enforce limits locally based on the received values.

Other message body definitions:

Unknown body keys must be ignored. Keys 0 through 63 are reserved for core body fields defined by this document. Keys 64 and above may be used for extensions.

For JOIN, the body is empty or omitted. The room name is taken from the envelope field.

For JOINED, the body may contain a list of current members. If present, this list is advisory and not guaranteed to be complete or current.

For PART, the body is empty or omitted.

For PARTED, the body may be empty, omitted, or may contain the identity of the client that parted, for confirmation purposes.

For MSG, the body contains the message payload. The simplest and most common form is a text string. Implementations may choose to use structured payloads, but hubs must treat the body as opaque data and forward it unchanged.

For NOTICE, the body follows the same rules as MSG but carries a different semantic meaning.

For PING and PONG, the body may be omitted or may contain arbitrary data to allow round-trip correlation. Receivers must echo the body back unchanged in the corresponding response if it is present.

For ERROR, the body should be a text string describing the error in plain language. It may optionally be a CBOR map if structured error information is desired, but clients must handle simple text at minimum.

Room Name Normalization

Room names are encoded as text strings. Hubs should normalize room names internally, typically by converting them to lowercase. The exact normalization method is an implementation detail, but the observable behavior should be case-insensitive room matching.

Clients should not assume that room names preserve original casing.

Identity Encoding

The sender identity field contains the Reticulum identity hash encoded as a CBOR byte string. No additional structure is imposed by RRC.

Clients and hubs must not reinterpret, truncate, or re-encode this value. It is passed through as-is and treated as opaque data.

Error Handling on the Wire

Malformed messages may be ignored. Messages with unknown message types must be ignored. Messages with missing required envelope fields may be ignored or may trigger an ERROR, at the receiver’s discretion.

If a hub sends an ERROR and then closes the Link, that is considered valid behavior. Clients should not attempt to argue with a closed socket.

Forward Compatibility Rules

Implementations must follow three simple rules to remain compatible over time.

They must ignore unknown envelope keys. They must ignore unknown message types. They must not repurpose existing numeric assignments.

Breaking any of these rules means you are no longer speaking RRC, regardless of how confident you feel.

Examples

Example: Client Sending a Chat Message (MSG)

A client sending “Hello, world!” to the #lobby room:

Field Number

Field Name

Value

0

Protocol Version

1

1

Message Type

20 (MSG)

2

Message ID

b”\x7a\x3f\x8e\x12\x45\xc9\xa1\x6d” (8 random bytes)

3

Timestamp

1737849600000 (2026-01-26 00:00:00 UTC)

4

Sender Identity

b”\x9c\x7e…\x4a\x2f” (16-byte RNS identity hash)

5

Room Name

“#lobby”

6

Body

“Hello, world!”

7

Nickname

“alice”

Encoded size: ~43 (fixed) + 8 (room) + 15 (body) + 7 (nickname) = ~73 bytes

Example: Client Joining a Room (JOIN)

A client requesting to join the #general room:

Field Number

Field Name

Value

0

Protocol Version

1

1

Message Type

10 (JOIN)

2

Message ID

b”\xb4\x5c\x2a\x89\x13\xd7\x6f\x1e” (8 random bytes)

3

Timestamp

1737849610000

4

Sender Identity

b”\x9c\x7e…\x4a\x2f” (16-byte RNS identity hash)

5

Room Name

“#general”

6

Body

(omitted)

7

Nickname

“alice”

Encoded size: ~43 (fixed) + 10 (room) + 7 (nickname) = ~60 bytes

Example: Hub Sending Welcome (WELCOME)

A hub welcoming a client and providing its limits:

Field Number

Field Name

Value

0

Protocol Version

1

1

Message Type

2 (WELCOME)

2

Message ID

b”\x31\xa8\xf4\x5d\x92\xc6\x7b\x3e” (8 random bytes)

3

Timestamp

1737849590000

4

Sender Identity

b”\x1f\x8a…\xb3\xc5” (16-byte hub identity hash)

5

Room Name

(omitted)

6

Body

{0: “ExampleHub”, 1: “0.1.0”, 3: {0: 32, 1: 64, …}}

7

Nickname

(omitted)

Body breakdown:

  • Key 0: Hub Name = “ExampleHub”

  • Key 1: Hub Version = “0.1.0”

  • Key 3: Hub Limits = map with keys 0-4 (max_rooms=32, max_room_name=64, etc.)

Encoded size: ~43 (fixed) + ~40 (body with limits) = ~83 bytes

Example: Error Response (ERROR)

A hub rejecting a request due to rate limiting:

Field Number

Field Name

Value

0

Protocol Version

1

1

Message Type

40 (ERROR)

2

Message ID

b”\xe7\x9a\x4c\x18\xd3\x2f\x61\xb5” (8 random bytes)

3

Timestamp

1737849625000

4

Sender Identity

b”\x1f\x8a…\xb3\xc5” (16-byte hub identity hash)

5

Room Name

(omitted)

6

Body

“Rate limit exceeded. Try again later.”

7

Nickname

(omitted)

Encoded size: ~43 (fixed) + ~42 (error message) = ~85 bytes