# EX1-RRCD: rrcd Extensions to the RRC Specification If you're reading this document, you're probably implementing something that needs to talk to `rrcd`, or you're building your own hub and want to understand what liberties we've taken with the core RRC specification. This is that document. The RRC specification is intentionally minimal and deliberately vague about certain implementation details, presumably because the authors enjoy watching implementers squirm. We don't hate you *quite* that much, so we're documenting our extensions here. **Important**: If you're implementing a basic RRC client, you can safely ignore most of this document. The core protocol works fine. These extensions are optional capabilities that clients may choose to support (or not). ## Philosophy rrcd implements the core RRC protocol as specified, with the following principles: 1. **Wire format compatibility first**: All core message types (`HELLO`, `WELCOME`, `JOIN`, `JOINED`, `PART`, `PARTED`, `MSG`, `NOTICE`, `PING`, `PONG`, `ERROR`) are implemented per spec. 2. **Envelope keys are unsigned integers**: If you send string keys in your CBOR maps, we will reject your messages. The spec says unsigned integers. We mean it. 3. **Bodies are CBOR maps with unsigned integer keys**: Not bitmasks, not strings, not "whatever feels right". Unsigned. Integer. Keys. 4. **Capabilities are advisory**: When we advertise capabilities, they're hints. You're allowed to ignore them if you hate yourself. ## Extension: Resource Transfer (T_RESOURCE_ENVELOPE) **Message Type**: `50` (`T_RESOURCE_ENVELOPE`) **Capability Key**: `0` (`CAP_RESOURCE_ENVELOPE`) **Status**: Implemented (optional, configurable) The RRC specification has no concept of large message delivery beyond "chunk it yourself, good luck." This is fine for small messages but becomes obnoxious for: - Large MOTD/greeting text - Binary blobs (theoretically) - Anything approaching the link MTU We added a resource transfer mechanism using Reticulum's built-in `RNS.Resource` class. This is **entirely optional** - clients that don't support it will simply not receive messages that exceed MTU. (If you're a client implementer and you want to receive verbose MOTDs, you'll need to support this.) ### Protocol Flow 1. **Sender sends `T_RESOURCE_ENVELOPE` message**: This is a regular RRC envelope (type `50`) containing metadata about the incoming resource. **Envelope structure**: ```python { 0: 1, # protocol version (K_V) 1: 50, # message type T_RESOURCE_ENVELOPE (K_T) 2: <8-byte-id>, # message ID (K_ID) 3: , # millisecond timestamp (K_TS) 4: , # sender identity hash (K_SRC) 5: , # optional: room name (K_ROOM) 6: # body (K_BODY) - see below } ``` **Body structure** (unsigned integer keys): ```python { 0: , # B_RES_ID: 8 bytes, unique identifier 1: , # B_RES_KIND: string ("notice", "motd", "blob") 2: , # B_RES_SIZE: integer, total bytes 3: , # B_RES_SHA256: 32 bytes (optional but recommended) 4: # B_RES_ENCODING: string, e.g. "utf-8" (optional) } ``` 2. **Sender advertises Reticulum Resource**: Immediately after sending the envelope, the sender creates an `RNS.Resource` and advertises it over the link. The resource ID in the envelope is for correlation only (currently unused but reserved for future use). 3. **Receiver accepts or rejects**: The receiver may accept the resource (if it recognizes the `kind` and the size is acceptable) or reject it (if resources are disabled, size exceeds limits, or no matching expectation exists). 4. **Resource transfer completes**: Reticulum handles the chunked transfer. On completion, the receiver verifies the SHA256 hash (if provided) and dispatches the payload based on `kind`. ### Resource Kinds - **`"notice"`**: UTF-8 text delivered as a `NOTICE` message after reconstruction. Used for large announcements. - **`"motd"`**: UTF-8 text delivered as a `NOTICE` message, specifically the hub's message-of-the-day. Sent after `WELCOME`. - **`"blob"`**: Binary data (reserved for future use; currently unused). **Encoding**: For text-based kinds (`notice`, `motd`), the `B_RES_ENCODING` field should specify the text encoding (default: `"utf-8"`). For `blob`, encoding is irrelevant. ### Configuration Resource transfer is controlled by hub configuration: ```toml enable_resource_transfer = true # default: true max_resource_bytes = 262144 # 256 KiB default max_pending_resource_expectations = 8 resource_expectation_ttl_s = 30.0 ``` Clients: if you don't want to deal with resources, don't advertise `CAP_RESOURCE_ENVELOPE` in your `HELLO`. The hub will fall back to chunked `NOTICE` messages (which may be truncated if they exceed MTU). ### Why? Because the RRC spec doesn't define how to send a 5KB MOTD over a 500-byte MTU link without making everyone cry. That's why. ## Extension: WELCOME Minimalism + Greeting-via-NOTICE The RRC specification is vague about what goes in the `WELCOME` body. Some implementations send the entire hub greeting, user count, room list, kitchen sink, and a partridge in a pear tree. We don't do that. **rrcd's WELCOME body contains**: - `B_WELCOME_HUB` (key `0`): Hub name (string) - `B_WELCOME_VER` (key `1`): Hub version (string) - `B_WELCOME_CAPS` (key `2`): Capabilities map (optional, currently unused) That's it. No greeting, no room list, no user count. Why? Because `WELCOME` needs to fit in a single packet on low-MTU links. If the hub has a greeting configured, it's delivered **after** `WELCOME` via one or more `NOTICE` messages (chunked to fit MTU, or sent via resource transfer if supported). **Client implementers**: Don't expect the hub greeting in the `WELCOME` body. Wait for the `NOTICE` message(s) that follow. Or don't. We're not your supervisor. ## Extension: HELLO Legacy Nickname Field **Body Key**: `64` (`B_HELLO_NICK_LEGACY`) **Status**: Deprecated, supported for compatibility Some pre-specification implementations sent the client nickname in the `HELLO` body under key `64`. This is **deprecated**. The RRC spec defines envelope-level nickname field (`K_NICK`, key `7`), which should be used instead. rrcd supports **both**: - If `K_NICK` (envelope-level) is present, use it. - If `B_HELLO_NICK_LEGACY` (body key `64`) is present and `K_NICK` is absent, fall back to it. **Client implementers**: Use `K_NICK` (envelope key `7`). Don't use body key `64` unless you enjoy living in the past. ## Extension: Hub Commands The RRC specification has no concept of "hub commands" or "slash commands" beyond what individual implementations invent. rrcd implements a set of IRC-style commands for room and hub management. **How it works**: Any `MSG` message sent to a room (or without a room field) that starts with `/` is interpreted as a command. If the command is recognized, it's handled by the hub and **not forwarded** to the room. If unrecognized, it's forwarded as a normal chat message (so you can still say "/shrug" without triggering a command parser meltdown). ### Global/Hub Commands These work from any room (or no room): - `/reload`: Reload hub configuration (server operator only) - `/stats`: Display hub statistics (server operator only) - `/who [room]`: List members in a room - `/names [room]`: Alias for `/who` ### Room Management Commands Room founders and operators can use these: - `/register `: Register a room (makes it persistent). Founder only, must be present in room. - `/unregister `: Unregister a room. Founder only, must be present in room. - `/topic [text]`: View or set room topic. Operators can always set; regular users can set if `-t` mode. - `/mode `: Set room modes (see below). ### Moderation Commands - `/kick `: Remove a user from a room (operator only) - `/kline add|del|list [hash]`: Global ban by identity hash (server operator only) - `/ban add|del|list [hash]`: Room-specific ban (operator only) - `/invite add|del|list [hash]`: Manage invite list for invite-only rooms (operator only) - `/op `: Grant operator status (operator only) - `/deop `: Remove operator status (operator only, cannot deop founder) - `/voice `: Grant voice in moderated rooms (operator only) - `/devoice `: Remove voice (operator only) ### Room Modes IRC-style mode flags (set via `/mode `): - `+m` / `-m`: Moderated (only voiced/ops can speak) - `+i` / `-i`: Invite-only (must be invited to join) - `+t` / `-t`: Topic protected (only operators can set topic) - `+n` / `-n`: No outside messages (must be in room to send messages) - `+k ` / `-k`: Room key/password (must provide key to join) - `+r` / `-r`: Registered room (read-only; use `/register` or `/unregister`) - `+o ` / `-o `: Grant/remove operator status - `+v ` / `-v `: Grant/remove voice **Note**: These commands and modes are **not** part of the RRC specification. They are rrcd-specific and hub-local. Other hubs may implement entirely different command sets (or none at all). Clients should not assume these commands exist. ## Extension: Room Registry and Persistence The RRC specification says nothing about persistent rooms. rrcd implements a room registry system that persists room state (modes, operators, bans, topic, etc.) to disk. **Registry file**: `~/.rrcd/rooms.toml` (configurable via `room_registry_path`) **Registered rooms**: - Survive hub restarts - Retain operators, bans, topic, modes - Can be configured with default modes (`+nrt` by default) - Are pruned if unused for a configurable period (default: 30 days) **Unregistered rooms**: - Exist only while members are present - Ephemeral state (lost on last member departure) **Founder**: The first person to create a room is the founder. Only the founder can register or unregister the room. Founders cannot be de-opped. This is entirely hub-local and transparent to clients. Clients don't need to do anything special. ## Extension: Invite Timeout When a room is invite-only (`+i`), operators can add users to the invite list via `/invite add `. Invites have a configurable timeout (default: 900 seconds / 15 minutes). This prevents the invite list from growing unbounded. Expired invites are pruned periodically. **Configuration**: ```toml room_invite_timeout_s = 900.0 ``` ## Extension: Nickname Normalization The RRC spec says nicknames are "advisory" and may be "ridiculous." rrcd normalizes nicknames: - Maximum length: configurable (default: 32 characters) - Leading/trailing whitespace stripped - Control characters rejected - Empty nicknames rejected If a nickname fails validation, it's rejected and the user is assigned no nickname (hash-only identification). **Configuration**: ```toml nick_max_chars = 32 ``` ## Extension: Rate Limiting To prevent abuse, rrcd implements per-session rate limiting using a token bucket algorithm. **Default**: 240 messages per minute **Configuration**: ```toml rate_limit_msgs_per_minute = 240 ``` If a client exceeds the rate limit, excess messages are dropped (not queued). The client is **not** disconnected or notified. This is intentional: rate limits are for abuse prevention, not chat flow control. ## Extension: Ping/Pong Timeout The RRC spec defines `PING` and `PONG` messages but doesn't specify timeout behavior. rrcd allows configurable ping intervals and timeouts: ```toml ping_interval_s = 0.0 # 0 = disabled ping_timeout_s = 0.0 # 0 = no timeout ``` If enabled, the hub sends `PING` periodically. If a client fails to respond with `PONG` within the timeout, the connection is terminated. **Default**: Disabled (because Reticulum already has link-level keepalives). ## Extension: Trusted Identities Server operators can configure a list of trusted identity hashes. Trusted identities bypass certain checks (currently unused, reserved for future use). ```toml trusted_identities = [ "a1b2c3d4...", ] ``` This is a hub-local concept and not exposed to clients. ## Extension: Banned Identities (K-Lines) Server operators can ban identity hashes globally via `/kline` commands or configuration: ```toml banned_identities = [ "deadbeef...", ] ``` Banned identities are rejected at connection establishment (before `HELLO`). ## Extension: Statistics Tracking rrcd tracks various counters (messages sent/received, bytes in/out, resources transferred, etc.). Server operators can view stats via `/stats`. **This is hub-local and not exposed to regular users.** ## What We Deliberately Did NOT Extend Some things we intentionally **did not** add, despite IRC implementing them: - **Channel services (ChanServ, NickServ, etc.)**: Not needed. Identity hashes are cryptographically unique. Use them. - **Server-to-server linking**: RRC is designed for single-hub deployments over Reticulum. Federating hubs is out of scope. - **DCC/file transfer**: Use Reticulum's file transfer mechanisms directly if you need them. - **Flood protection beyond rate limiting**: We rate limit. If you're getting flooded, ban the offender. ## For Implementers: Compatibility Checklist If you're implementing a client or another hub, here's what you need to know: ### Minimum Compatibility (Basic RRC Client) - Implement core message types (HELLO, WELCOME, JOIN, JOINED, PART, PARTED, MSG, NOTICE, PING, PONG, ERROR) - Use CBOR encoding - Use unsigned integer keys in envelopes and bodies - Handle `K_NICK` (envelope key 7) for nicknames - Gracefully ignore unknown message types ### Enhanced Compatibility (Recommended) - Support `T_RESOURCE_ENVELOPE` (message type 50) and Reticulum resources - Advertise `CAP_RESOURCE_ENVELOPE` in your `HELLO` capabilities if you support resources - Expect hub greeting to arrive via `NOTICE` messages after `WELCOME` - Handle chunked `NOTICE` messages (multiple messages with the same content type) ### Full Compatibility (Hub Implementers) - Implement resource transfer with envelope-first protocol - Keep `WELCOME` minimal (hub name, version, caps only) - Chunk large messages or use resources to stay within MTU - Support legacy `B_HELLO_NICK_LEGACY` (body key 64) for old clients - Normalize and validate nicknames before accepting them - Implement rate limiting to prevent abuse - Consider implementing room persistence (optional) ## Non-Normative Advice for the Weary 1. **Use resources**: If you're sending anything over ~500 bytes, use resource transfer. Your users will thank you. 2. **Ignore capabilities you don't support**: We won't be offended. Much. 3. **Don't over-engineer**: The RRC spec is minimal for a reason. Don't add features just because IRC has them. 4. **Test with low MTU**: If your client works over a 500-byte MTU link, it'll work everywhere. 5. **Read the rrcd source**: If this doc is unclear, the code is (arguably) clearer. Or at least executable. --- *If you find a bug, inconsistency, or deeply offensive opinion in this document, file an issue. Or don't. We'll probably find it eventually.*