diff --git a/EX1-RRCD.md b/EX1-RRCD.md new file mode 100644 index 0000000..0de7884 --- /dev/null +++ b/EX1-RRCD.md @@ -0,0 +1,406 @@ +# 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.* diff --git a/README.md b/README.md index 3cc51d3..c1432e8 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ policy-level features that are allowed by the spec: ## Extensions +### Suggested reading: EX1-RRCD.md + `rrcd` intentionally avoids adding new on-wire message types. Operator features use a hub-local convention: if a client sends a `MSG`/`NOTICE` whose body is a string beginning with `/`, and the command is recognized, the hub treats it as a