Add comprehensive wiki documentation for RconDataBridge, covering installation, configuration, protocol details, operation references, and internal architecture.
This commit is contained in:
225
wiki/Protocol-Reference.md
Normal file
225
wiki/Protocol-Reference.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Protocol Reference
|
||||
|
||||
This page describes how an external tool, such as an RCON client, a bot, or
|
||||
a dashboard backend, talks to RconDataBridge: connecting, the option map,
|
||||
the request/response cycle, idempotency, rate limiting, and error codes.
|
||||
For the specific operations you can call, see [Operations
|
||||
Reference](Operations-Reference).
|
||||
|
||||
## 1. Connecting
|
||||
|
||||
RconDataBridge has no transport of its own; it rides entirely on the
|
||||
server's existing RCON connection (the standard Source RCON protocol, the
|
||||
same one any RCON tool already speaks). Point your RCON client at the
|
||||
server's configured `RCONPort` and `RCONPassword` as usual. Only two RCON
|
||||
commands are involved:
|
||||
|
||||
- `changeoption <name> <value>`, used to write an option (only ever used
|
||||
for `RconDataBridge_Request`)
|
||||
- `showoptions`, used to read every option's current value (used to read
|
||||
everything else)
|
||||
|
||||
There is no dedicated bridge port and no separate authentication step.
|
||||
RCON access is the access control.
|
||||
|
||||
## 2. Option map
|
||||
|
||||
| Option | Direction | Contents |
|
||||
|-----------------------------------|------------------|-------------------------------------------------------------------------------------------|
|
||||
| `RconDataBridge_ProtocolVersion` | Server to client | `{version, ops}`, the bridge protocol version and the current allowlisted operation names |
|
||||
| `RconDataBridge_WorldStats` | Server to client | Cached world telemetry, refreshed roughly once a minute |
|
||||
| `RconDataBridge_PlayerStats_<ID>` | Server to client | Cached snapshot for one player, created lazily on first query |
|
||||
| `RconDataBridge_Request` | Client to server | Your request mailbox, a single slot rather than a queue |
|
||||
| `RconDataBridge_Response` | Server to client | The response to the most recently processed request |
|
||||
| `RconDataBridge_LastResponse` | Server to client | Millisecond epoch timestamp of that response |
|
||||
|
||||
Every value in this table is base64 encoded, in both directions, with no
|
||||
exceptions. Decode after reading, encode before writing.
|
||||
`RconDataBridge_LastResponse` is base64 of a plain decimal string, for
|
||||
example `MA==` decodes to `"0"`, not JSON; decode it the same way, then
|
||||
parse it as an integer.
|
||||
|
||||
Option names use underscores, never dots. Project Zomboid's own
|
||||
`changeoption` command validates the option name argument against `\w+`
|
||||
(word characters only) before it ever reaches option lookup, so a dotted
|
||||
name is silently rejected at the command parsing stage, before the bridge
|
||||
sees it at all.
|
||||
|
||||
### Why base64 everywhere?
|
||||
|
||||
`changeoption` strips every literal double-quote character from every
|
||||
argument as part of its own command line parsing. This happens
|
||||
unconditionally, so no quoting strategy avoids it, and raw JSON can never
|
||||
survive a `changeoption` call intact. Base64's alphabet contains no quote
|
||||
characters, so it survives the round trip untouched. This is a hard
|
||||
requirement for the client to server `Request` channel specifically.
|
||||
|
||||
It's not strictly required for the server to client options; those are
|
||||
written directly into the option store rather than going through
|
||||
`changeoption`'s command parser, so raw JSON would technically survive
|
||||
there too. They're base64 encoded anyway, deliberately, so a client only
|
||||
ever needs one rule, "everything is base64," instead of a special case for
|
||||
one channel.
|
||||
|
||||
## 3. The request/response cycle
|
||||
|
||||
There is no push notification and no long poll here; this is a single
|
||||
mailbox slot, not a queue. The cycle is:
|
||||
|
||||
1. Build a request envelope (see section 4).
|
||||
2. Encode it as compact JSON, with no whitespace, see the note in section
|
||||
4, then base64.
|
||||
3. Send `changeoption RconDataBridge_Request <base64>`.
|
||||
4. Poll `showoptions`, base64 decode `RconDataBridge_LastResponse`, and
|
||||
wait until it changes from whatever it was before your write, or just
|
||||
wait a fixed short delay and check that `RconDataBridge_Response`'s
|
||||
`id` field matches the id you sent.
|
||||
5. Base64 decode `RconDataBridge_Response` and parse it as JSON.
|
||||
|
||||
The server checks for a new request roughly once a second by default; see
|
||||
the Poll Interval setting in [Configuration](Configuration). There is no
|
||||
engine event for "an option changed," so this is a genuine poll, not a
|
||||
callback. Responses have consistently shown up within a few seconds of the
|
||||
request being written in practice, but this isn't a guaranteed bound;
|
||||
budget a few seconds of latency and don't assume sub-second turnaround.
|
||||
|
||||
Because it's a single mailbox, two requests in flight at once will race: if
|
||||
you send a second request before the first one's response has been read,
|
||||
you may only ever see the second response. Send one request, wait for its
|
||||
response, matched by `id`, then send the next. This bridge is not designed
|
||||
for concurrent callers.
|
||||
|
||||
## 4. Request envelope
|
||||
|
||||
```json
|
||||
{"v":1,"id":"unique-request-id","op":"get_player","args":{"id":"alice"}}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|--------|--------|----------|------------------------------------------------------------------------------|
|
||||
| `v` | number | yes | Must equal the current protocol version (`1`) |
|
||||
| `id` | string | yes | 1 to 128 characters. Your correlation id, see [idempotency](#6-idempotency) |
|
||||
| `op` | string | yes | One of the operations in [Operations Reference](Operations-Reference) |
|
||||
| `args` | object | no | Operation specific arguments; omit or use `{}` for operations that take none |
|
||||
|
||||
The JSON must be compact, with no whitespace anywhere. This isn't a style
|
||||
preference: RCON's command line parser only treats a double-quoted span as
|
||||
one argument, so a payload containing a literal space gets split into
|
||||
multiple arguments and fails to parse as a single value. Since the whole
|
||||
thing is base64 encoded regardless, this only matters for how you serialize
|
||||
the JSON before encoding it; just don't pretty-print it.
|
||||
|
||||
Size limit: the decoded JSON must be 4096 bytes or smaller, or the request
|
||||
is rejected with `PAYLOAD_TOO_LARGE` (see section 7) before it's even
|
||||
parsed.
|
||||
|
||||
## 5. Response envelope
|
||||
|
||||
Success:
|
||||
|
||||
```json
|
||||
{"v":1,"id":"unique-request-id","ok":true,"data":{}}
|
||||
```
|
||||
|
||||
Failure:
|
||||
|
||||
```json
|
||||
{"v":1,"id":"unique-request-id","ok":false,"error":{"code":"PLAYER_NOT_FOUND","message":"No registered player matched the supplied id."}}
|
||||
```
|
||||
|
||||
`id` always echoes the request's `id`, letting you confirm you're looking
|
||||
at the response to your request and not a stale one from before you
|
||||
polled. `data`'s shape depends on the operation; see [Operations
|
||||
Reference](Operations-Reference).
|
||||
|
||||
A response isn't strictly guaranteed, but you're very unlikely to see the
|
||||
gap in practice. If an operation's handler throws an unexpected error, a
|
||||
bug rather than a normal error condition, you get back `INTERNAL_ERROR`
|
||||
like any other error response; it doesn't go missing. Implement a
|
||||
client-side timeout anyway, see the latency note in section 3, rather than
|
||||
waiting indefinitely for a reply.
|
||||
|
||||
## 6. Idempotency
|
||||
|
||||
Repeating the same `id` within the configured idempotency window (60
|
||||
seconds by default, see [Configuration](Configuration)) replays the
|
||||
original cached response. The operation does not run again, and any
|
||||
different `args` you send along with the repeat are ignored. This exists
|
||||
for safe retries: you sent a request, didn't see a response in time, and
|
||||
want to resend without double-triggering a side-effecting operation, such
|
||||
as `save_server`; use the same `id` for that.
|
||||
|
||||
For routine, repeated polling, such as checking world stats every few
|
||||
minutes, generate a fresh `id` per logical request: a UUID, an incrementing
|
||||
counter, or anything else that won't collide. Reusing a fixed id like
|
||||
`"poll"` for every call means you'll only ever see the very first response
|
||||
for the whole idempotency window, then a fresh one, then the same pattern
|
||||
again, which isn't what you want for periodic sampling.
|
||||
|
||||
After the window expires, an id is no longer considered a retry; reusing it
|
||||
triggers a completely fresh execution, not an error.
|
||||
|
||||
## 7. Rate limiting and errors
|
||||
|
||||
The bridge allows a limited number of requests per rolling time window (10
|
||||
requests per 10 seconds by default, admin-tunable, see
|
||||
[Configuration](Configuration)), globally rather than per RCON connection,
|
||||
since RCON requests carry no distinguishable caller identity. Exceeding it
|
||||
returns `RATE_LIMITED` rather than queuing or blocking.
|
||||
|
||||
| Error code | Meaning |
|
||||
|---------------------|----------------------------------------------------------------------------------------------------------|
|
||||
| `SCHEMA_ERROR` | Malformed base64 or JSON, or the envelope is missing or misshaped |
|
||||
| `PAYLOAD_TOO_LARGE` | Decoded request exceeds the size limit (4096 bytes) |
|
||||
| `OP_NOT_ALLOWED` | `op` isn't a recognized operation, see [Operations Reference](Operations-Reference) for the current list |
|
||||
| `RATE_LIMITED` | Too many requests in the current rate-limit window |
|
||||
| `PLAYER_NOT_FOUND` | `get_player` found no snapshot for the given id |
|
||||
| `SAVE_FAILED` | `save_server` triggered but the underlying save call failed |
|
||||
| `INTERNAL_ERROR` | An operation's handler threw an unexpected error, a bug rather than a normal error condition |
|
||||
|
||||
A rejected request, any error above, is still logged server-side and still
|
||||
publishes an error response. The only case with no response at all is the
|
||||
unhandled internal error edge case described in section 5.
|
||||
|
||||
## 8. `RconDataBridge_ProtocolVersion` structure
|
||||
|
||||
```json
|
||||
{"version": 1, "ops": ["get_audit_log", "get_bridge_status", "get_player", "get_world_stats", "list_players", "save_server"]}
|
||||
```
|
||||
|
||||
Check `version` against the protocol version you were built against before
|
||||
assuming anything else in this reference still applies. Check `ops` before
|
||||
calling an operation you're not certain is still registered on this build.
|
||||
This array is generated live from the server's actual allowlist, so it
|
||||
never lags a build that added or removed an operation, even if this wiki
|
||||
page temporarily does.
|
||||
|
||||
## 9. Client implementation checklist
|
||||
|
||||
- Generate a fresh, unique `id` per logical request (section 6); don't
|
||||
hardcode one.
|
||||
- Serialize compact JSON, with no whitespace, before base64 encoding
|
||||
(section 4).
|
||||
- Decode every `RconDataBridge_*` value you read, including
|
||||
`LastResponse` (section 2); nothing comes back as raw JSON.
|
||||
- Match responses to requests by `id`, not just by "the response changed."
|
||||
A response could still be sitting there from a previous call if you poll
|
||||
too early.
|
||||
- Implement a timeout of a few seconds rather than polling forever; some
|
||||
failure modes produce no response at all (section 5).
|
||||
- Don't fire a second request before you've read the first one's response.
|
||||
There's one mailbox slot, not a queue (section 3).
|
||||
- Treat unknown or extra response fields as forward-compatible; don't fail
|
||||
parsing on fields not documented here.
|
||||
- Accumulate multi-packet RCON responses. A large `showoptions` output,
|
||||
this mod's options plus every built-in server option, can exceed a
|
||||
single RCON packet. Project Zomboid's RCON framing chunks large responses
|
||||
across multiple packets sharing the same request id, with no explicit
|
||||
end-of-response marker, so a client that resolves on the first packet
|
||||
will intermittently see truncated or missing option values that are, in
|
||||
fact, present server-side. Accumulate every packet for a given request
|
||||
id, using a short quiet-read timeout or a trailer-command trick such as
|
||||
sending a cheap follow-up command and reading until its response shows
|
||||
up, before treating a response as complete. This is a general RCON
|
||||
client concern, not specific to this mod, but it's the most common cause
|
||||
of "options intermittently missing" reports.
|
||||
Reference in New Issue
Block a user