Add comprehensive wiki documentation for RconDataBridge, covering installation, configuration, protocol details, operation references, and internal architecture.
This commit is contained in:
169
wiki/Architecture.md
Normal file
169
wiki/Architecture.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Architecture
|
||||
|
||||
This page covers how RconDataBridge is built internally: the module map,
|
||||
the boot sequence, the security model, and the Project Zomboid engine
|
||||
quirks the mod works around. It's aimed at anyone reading or modifying the
|
||||
mod's own Lua code, rather than consuming the protocol from outside; for
|
||||
that, see [Protocol Reference](Protocol-Reference) and [Operations
|
||||
Reference](Operations-Reference).
|
||||
|
||||
## Boot sequence
|
||||
|
||||
On `Events.OnServerStarted`, bootstrap code runs, in order:
|
||||
|
||||
1. Run internal self-tests (JSON and base64 codecs), if enabled.
|
||||
2. Register the base RCON options (`ProtocolVersion`, `WorldStats`,
|
||||
`Request`, `Response`, `LastResponse`).
|
||||
3. Register every operation into the operation registry.
|
||||
4. Publish the initial `RconDataBridge_ProtocolVersion` value.
|
||||
5. Start the request pipeline and the world and player telemetry pollers.
|
||||
|
||||
`Events.OnServerStarted` is known to fire more than once per boot in
|
||||
Project Zomboid, so every step above guards against running twice: option
|
||||
registration checks for an existing option first and wraps the underlying
|
||||
engine call in a protected call as a second line of defense, and each
|
||||
poller's `init()` function is guarded by a local flag, so its event
|
||||
listeners are only ever added once.
|
||||
|
||||
## Request flow
|
||||
|
||||
Once booted, a single request travels through the modules like this:
|
||||
|
||||
```text
|
||||
RCON client
|
||||
|
|
||||
| changeoption RconDataBridge_Request <base64 JSON>
|
||||
v
|
||||
RconDataBridge_Request option (single mailbox slot)
|
||||
|
|
||||
| polled every N ticks (Poll Interval setting)
|
||||
v
|
||||
Request pipeline
|
||||
|-- base64 decode, size check ----> reject: SCHEMA_ERROR / PAYLOAD_TOO_LARGE
|
||||
|-- JSON decode, envelope check --> reject: SCHEMA_ERROR
|
||||
|-- idempotency check ------------> known id: replay the cached response
|
||||
|-- rate limit check -------------> reject: RATE_LIMITED
|
||||
v
|
||||
Operation registry (allowlist, exact-name lookup)
|
||||
|-- unknown op -------------------> reject: OP_NOT_ALLOWED
|
||||
v
|
||||
Operation handler (Ops/RDB_Op*.lua)
|
||||
|-- handler throws ---------------> reject: INTERNAL_ERROR
|
||||
v
|
||||
Response envelope { ok, data } or { ok: false, error }
|
||||
|
|
||||
| every outcome above, success or rejection, is also written to the audit log
|
||||
v
|
||||
RconDataBridge_Response / RconDataBridge_LastResponse
|
||||
|
|
||||
| showoptions, base64 decode
|
||||
v
|
||||
RCON client
|
||||
```
|
||||
|
||||
See [Protocol Reference](Protocol-Reference) for what each error code
|
||||
means and how a client should consume this cycle.
|
||||
|
||||
## Module responsibilities
|
||||
|
||||
### Options registry
|
||||
|
||||
A thin wrapper over Project Zomboid's server options store. Registering an
|
||||
option needs both an "add option" call and a separate "add to public
|
||||
options" call, or it never shows up in `showoptions`, since that command
|
||||
reads a different internal list than the one a plain add populates.
|
||||
|
||||
### Request pipeline
|
||||
|
||||
Polls `RconDataBridge_Request` on a configurable tick interval, since
|
||||
there's no "option changed" event. Decodes base64 to JSON, validates the
|
||||
envelope, checks idempotency and the rate limit, dispatches through the
|
||||
operation registry, publishes the response, and always logs the outcome to
|
||||
the audit trail, including rejections.
|
||||
|
||||
### Operation registry
|
||||
|
||||
The allowlist; Dispatch is an exact-name lookup into a table, nothing more;
|
||||
new operations are added one file per operation.
|
||||
|
||||
### Security
|
||||
|
||||
Payload size cap (4096 bytes decoded), envelope schema validation, and a
|
||||
global, not per-connection, sliding-window rate limit, since RCON requests
|
||||
carry no caller identity to limit by individually.
|
||||
|
||||
### Idempotency
|
||||
|
||||
Replays the cached response for a repeated request `id` within a
|
||||
configurable window, protecting side-effecting operations, such as
|
||||
`save_server`, from retries. After the window expires, the same `id` runs
|
||||
fresh.
|
||||
|
||||
### Config
|
||||
|
||||
A facade over admin-tunable settings. A small set of values, such as the
|
||||
maximum payload size, are fixed constants; everything else is read live
|
||||
from Sandbox Options. See [Configuration](Configuration).
|
||||
|
||||
### World stats
|
||||
|
||||
Refreshed roughly once an in-game minute from the engine's game clock,
|
||||
cached in memory, and mirrored directly to `RconDataBridge_WorldStats`, so
|
||||
`get_world_stats` and the option always agree, and reading the option skips
|
||||
a request/response round trip entirely.
|
||||
|
||||
### Player snapshot
|
||||
|
||||
The source of truth for player data, both online and offline, persisted so
|
||||
it survives a restart, since the engine has no API for offline player data
|
||||
on its own.
|
||||
|
||||
### JSON and base64 codecs
|
||||
|
||||
Self-contained shared codecs with no external dependencies. Each runs its
|
||||
own self-test at boot when the debug self-test setting is enabled.
|
||||
|
||||
## Security model
|
||||
|
||||
A handful of rules define the mod's security boundary and are treated as
|
||||
hard constraints rather than style preferences:
|
||||
|
||||
- **No new transport, port, or authentication layer.** RCON access is the
|
||||
only access control, by design.
|
||||
- **No dynamic operation dispatch.** The operation registry only ever does
|
||||
an exact-name table lookup; an RCON request can never cause arbitrary
|
||||
Lua code to run.
|
||||
- **All operations are explicitly allowlisted.** An unrecognized `op` value
|
||||
returns `OP_NOT_ALLOWED` rather than being interpreted in any way.
|
||||
- **Every remote action is logged**, with its request id, operation,
|
||||
result, and timestamp, including rejected requests, through the audit
|
||||
log described above.
|
||||
- **Requests are validated and bounded** on payload size, envelope schema,
|
||||
and rate, before an operation handler ever runs.
|
||||
|
||||
## Engine constraints the mod works around
|
||||
|
||||
A number of behaviours in the code exist specifically to work around gaps
|
||||
or quirks in the Project Zomboid server engine, rather than being
|
||||
arbitrary design choices. The straightforward ones:
|
||||
|
||||
| Constraint | Why | How it's handled |
|
||||
|-------------------------------------------------|-----------------------------|------------------------------------------------------|
|
||||
| No "option changed" event | Doesn't exist in the engine | Tick-throttled polling in the request pipeline |
|
||||
| No connect or disconnect event | Doesn't exist in the engine | Diffing the online roster against the last known set |
|
||||
| No offline-player registry API | Doesn't exist in the engine | Player snapshots are persisted independently |
|
||||
| `OnServerStarted` fires more than once per boot | Engine behavior | Every initializer guards on a local flag |
|
||||
|
||||
As a special client facing quirk:
|
||||
|
||||
### RCON responses can span multiple packets
|
||||
|
||||
Confirmed from the server's own RCON response handling: a large response,
|
||||
such as a `showoptions` call once this mod's options are added to the
|
||||
roughly 140 built-in ones, gets chunked across multiple packets with no
|
||||
explicit end-of-response marker.
|
||||
|
||||
This is a client-side concern rather than a mod-side one. Any RCON client
|
||||
must accumulate every packet sharing a request id rather than resolving on
|
||||
the first one it receives. See the client checklist in [Protocol
|
||||
Reference](Protocol-Reference#9-client-implementation-checklist).
|
||||
83
wiki/Configuration.md
Normal file
83
wiki/Configuration.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Configuration
|
||||
|
||||
RconDataBridge exposes its admin-tunable settings through Project Zomboid's
|
||||
native Sandbox Options system, the same "New Game, Sandbox Settings" screen
|
||||
also reachable in-game via the server admin panel on an already running
|
||||
server. This is a separate mechanism from the RCON `ServerOptions`
|
||||
transport the protocol itself uses (see [Protocol
|
||||
Reference](Protocol-Reference)); Sandbox Options is purely for these
|
||||
runtime knobs.
|
||||
|
||||
All values live under the RconDataBridge Sandbox Options page, and are
|
||||
applied live: an admin can change them at runtime with no server restart
|
||||
required. There is, however, no "value changed" event for Sandbox Options
|
||||
either, so a changed value takes effect the next time the relevant code
|
||||
path reads it, such as the next tick for poll cadence settings, rather than
|
||||
instantaneously.
|
||||
|
||||
## Options
|
||||
|
||||
### Debug Self-Tests
|
||||
|
||||
Boolean, default `true`.
|
||||
|
||||
Runs the mod's internal JSON and base64 codec self-tests and logs the
|
||||
result on every server boot. Diagnostic only; doesn't affect protocol
|
||||
behavior. Safe to turn off once you trust the install.
|
||||
|
||||
### Poll Interval (ticks)
|
||||
|
||||
Integer, default `30`, range 1 to 300.
|
||||
|
||||
How many server ticks pass between checks of the request mailbox
|
||||
(`RconDataBridge_Request`) and the online player roster. A lower value
|
||||
means more responsiveness to incoming requests and player connect or
|
||||
disconnect changes, at the cost of doing that check more often.
|
||||
|
||||
### Idempotency Window (seconds)
|
||||
|
||||
Integer, default `60`, range 1 to 3600.
|
||||
|
||||
How long a repeated request `id` replays its cached response instead of
|
||||
re-running the operation. Protects side-effecting operations, such as
|
||||
triggering a save, from duplicate execution on client retries. See
|
||||
[Protocol Reference, Idempotency](Protocol-Reference#6-idempotency).
|
||||
|
||||
### Rate Limit: Max Requests
|
||||
|
||||
Integer, default `10`, range 1 to 1000.
|
||||
|
||||
Maximum number of bridge requests allowed within one rate-limit window; see
|
||||
the next setting. This limit is global, not per connection, since RCON
|
||||
requests carry no distinguishable caller identity, so it's the only way to
|
||||
bound total load.
|
||||
|
||||
### Rate Limit: Window (seconds)
|
||||
|
||||
Integer, default `10`, range 1 to 3600.
|
||||
|
||||
Length of the rolling rate-limit window, in seconds. Combined with the
|
||||
setting above, the default is 10 requests per rolling 10 seconds.
|
||||
|
||||
A running server's current values for the rate-limit pair, reflecting any
|
||||
admin change and not just the defaults, are also readable at any time via
|
||||
the `get_bridge_status` operation. This is useful for a client that wants
|
||||
to self-tune its polling instead of hardcoding assumptions. See [Operations
|
||||
Reference](Operations-Reference#get_bridge_status).
|
||||
|
||||
## What's not configurable here, and why
|
||||
|
||||
Two protocol-level values are intentionally not exposed as Sandbox
|
||||
Options, even though they look like they could be:
|
||||
|
||||
- **Max request payload size** (4096 bytes decoded) and the underlying RCON
|
||||
option's max length are hardcoded. They're sized against the fixed
|
||||
capacity of the RCON option storing the request, after base64 expansion.
|
||||
An admin-set larger value here, with no matching change to that
|
||||
underlying capacity, could silently truncate a request instead of
|
||||
cleanly rejecting it with an error. Since that failure mode is worse than
|
||||
simply always rejecting payloads over the fixed limit, it isn't offered
|
||||
as a tunable.
|
||||
- **Protocol identifiers**, meaning the protocol version number, the fixed
|
||||
`RconDataBridge_*` option names, and the standard error codes, are
|
||||
fixed, documented, client-facing constants, not server-tunable behavior.
|
||||
46
wiki/Home.md
Normal file
46
wiki/Home.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# RconDataBridge
|
||||
|
||||
**RconDataBridge** is a server-side [Project Zomboid](https://projectzomboid.com/)
|
||||
(Build 42) mod that exposes structured world and player telemetry, plus a
|
||||
small allowlisted RPC surface, over the server's existing RCON connection.
|
||||
There is no new port and no new authentication layer to configure; it uses
|
||||
the RCON connection your server already has.
|
||||
|
||||
It rides entirely on two vanilla RCON commands, `changeoption` and
|
||||
`showoptions`, which every RCON client or library already speaks.
|
||||
|
||||
There is no client-side mod component. "Client" throughout this wiki means
|
||||
an external RCON tool, bot, or dashboard backend, not in-game Project
|
||||
Zomboid UI.
|
||||
|
||||
## Why?
|
||||
|
||||
Vanilla RCON gives you console commands and raw `showoptions` server
|
||||
settings, but nothing structured and nothing queryable, and no safe way to
|
||||
trigger a handful of specific server actions without giving a bot full
|
||||
console access. RconDataBridge adds a thin, versioned, allowlisted JSON
|
||||
protocol on top of that same connection.
|
||||
|
||||
## Wiki contents
|
||||
|
||||
- **[Installation](Installation)**: getting the mod onto a server and
|
||||
confirming it's running.
|
||||
- **[Configuration](Configuration)**: the admin-tunable Sandbox Options,
|
||||
such as rate limits and poll cadence.
|
||||
- **[Protocol Reference](Protocol-Reference)**: how the RCON transport
|
||||
works, covering the option map, base64 encoding, the request/response
|
||||
cycle, idempotency, rate limiting, and error codes.
|
||||
- **[Operations Reference](Operations-Reference)**: every operation, such
|
||||
as `get_player` and `list_players`, with its arguments, response shape, and errors.
|
||||
- **[Architecture](Architecture)**: how the mod is built internally,
|
||||
covering module responsibilities, the boot sequence, and the engine
|
||||
quirks it works around.
|
||||
|
||||
## Requirements and License
|
||||
|
||||
- A Project Zomboid dedicated server, Build 42.0.0 or newer, per the mod
|
||||
manifest's `versionMin`.
|
||||
- RCON enabled on the server, meaning `RCONPort` and `RCONPassword` are set
|
||||
in the server's `.ini`. This mod adds no transport of its own, so if RCON
|
||||
isn't reachable, neither is this mod.
|
||||
- Licensed under AGPLv3.
|
||||
71
wiki/Installation.md
Normal file
71
wiki/Installation.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Installation
|
||||
|
||||
RconDataBridge is a server-side-only mod. There's no client component to
|
||||
install; players don't need it, and it does nothing to the game client. It
|
||||
only needs to run on the dedicated server process itself.
|
||||
|
||||
## Requirements
|
||||
|
||||
- A Project Zomboid dedicated server, Build 42.0.0 or newer.
|
||||
- RCON enabled on that server, meaning `RCONPort` and `RCONPassword` are set
|
||||
in the server's `.ini` file. RconDataBridge has no transport of its own;
|
||||
every interaction happens over the RCON connection the server already
|
||||
exposes, so if RCON isn't reachable, this mod isn't reachable either.
|
||||
|
||||
## Getting the mod onto the server
|
||||
|
||||
Install it like any other Project Zomboid mod:
|
||||
|
||||
1. Copy, or Steam Workshop subscribe to, the mod so that its folder,
|
||||
containing `mod.info`, `poster.png`, and the `common/` tree, ends up
|
||||
under the server's `mods` search path (typically the server's
|
||||
`Zomboid/mods/` directory, or the Workshop cache if installed that way).
|
||||
2. Add the mod to the server config, either through the in-game or admin
|
||||
server settings screen, or by editing the server's `.ini` directly: add
|
||||
`RconDataBridge` to the `Mods=` list, and, if installed via Steam
|
||||
Workshop, the corresponding Workshop item id to `WorkshopItems=`.
|
||||
3. Restart the server. Lua mods in Project Zomboid have no hot reload, so
|
||||
any install or update requires a full server restart to take effect.
|
||||
|
||||
## Verifying it's running
|
||||
|
||||
Once the server is back up, connect with any RCON client and run:
|
||||
|
||||
```text
|
||||
showoptions
|
||||
```
|
||||
|
||||
You should see, among the roughly 140 built-in server options, a handful of
|
||||
`RconDataBridge_*` entries:
|
||||
|
||||
- `RconDataBridge_ProtocolVersion`
|
||||
- `RconDataBridge_WorldStats`
|
||||
- `RconDataBridge_Request`
|
||||
- `RconDataBridge_Response`
|
||||
- `RconDataBridge_LastResponse`
|
||||
|
||||
All of their values are base64 encoded; see [Protocol
|
||||
Reference](Protocol-Reference) before trying to read them by eye.
|
||||
|
||||
If the server has a very large `showoptions` output, be aware that Project
|
||||
Zomboid's RCON implementation chunks large responses across multiple
|
||||
packets with no explicit end-of-response marker. A client that stops
|
||||
reading after the first packet can appear to be missing these options
|
||||
intermittently, even though the mod registered them correctly; that's a
|
||||
client-side accumulation issue, not a sign the mod isn't running. If in
|
||||
doubt, check the server's own log for a `[RconDataBridge] bootstrap
|
||||
complete` line, which is printed once boot finishes successfully.
|
||||
|
||||
`RconDataBridge_PlayerStats_<id>` options are not present at boot; they're
|
||||
created lazily, the first time a `get_player` request is made for that
|
||||
specific player (see [Operations Reference](Operations-Reference)). Don't
|
||||
expect to see them until then.
|
||||
|
||||
## Configuring it
|
||||
|
||||
Everything tunable, such as rate limits, poll cadence, and the idempotency
|
||||
window, is exposed through Project Zomboid's own Sandbox Options system,
|
||||
under an `RconDataBridge` page reachable either when creating a new save
|
||||
("Sandbox Settings"), or from the in-game admin panel on an already running
|
||||
server. See [Configuration](Configuration) for the full list and what each
|
||||
value does.
|
||||
227
wiki/Operations-Reference.md
Normal file
227
wiki/Operations-Reference.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Operations Reference
|
||||
|
||||
This page describes every operation RconDataBridge currently registers,
|
||||
along with the shared data structures they return. For how to actually
|
||||
send a request and read a response, see [Protocol
|
||||
Reference](Protocol-Reference).
|
||||
|
||||
Operation names are flat, verb-first `snake_case`, such as `get_player`.
|
||||
Query `RconDataBridge_ProtocolVersion`'s `ops` array for the authoritative,
|
||||
currently registered list; this page may lag a build that added or removed
|
||||
an operation.
|
||||
|
||||
Each operation below is documented with an EmmyLua-style stub: a compact
|
||||
comment block giving its arguments, return shape, and possible error
|
||||
codes. This is documentation notation, not literal Lua you can call; an
|
||||
operation is invoked by sending its name and an `args` object over the
|
||||
wire protocol described in [Protocol Reference](Protocol-Reference), not
|
||||
by calling a function directly.
|
||||
|
||||
Response examples are shown as JSON5, with aligned fields and comments,
|
||||
purely for readability on this page. The actual wire format is always
|
||||
strict, compact JSON, no comments, no trailing commas, no whitespace, see
|
||||
[Protocol Reference § 4](Protocol-Reference#4-request-envelope).
|
||||
|
||||
## `get_world_stats`
|
||||
|
||||
```lua
|
||||
--- World telemetry. Not player-related; see get_player / list_players.
|
||||
--- @return WorldStats
|
||||
fn get_world_stats()
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
"worldAgeHours": 6.6,
|
||||
"worldAgeDays": 0.27,
|
||||
"nightsSurvived": 0,
|
||||
"date": {
|
||||
"year": 1993,
|
||||
"month": 6, // 0-indexed: 0 = January ... 11 = December
|
||||
"day": 8,
|
||||
"hour": 13,
|
||||
"minute": 37
|
||||
},
|
||||
"isNight": false,
|
||||
"isRaining": true,
|
||||
"timeMultiplier": 4.8, // current game-speed multiplier
|
||||
"generatedAt": 1787749440066 // ms epoch, when this snapshot was cached
|
||||
}
|
||||
```
|
||||
|
||||
This is the cached value, refreshed roughly once a minute
|
||||
in the background. The same data is also mirrored directly
|
||||
to `RconDataBridge_WorldStats`, so you can read it there
|
||||
without spending a request/response round trip at all.
|
||||
|
||||
The cache only advances while the world clock is actually running.
|
||||
On a server configured with `PauseEmpty=true`, the game clock, and
|
||||
therefore this data, freezes while no players are connected; this
|
||||
reflects the world's real state rather than a stale bridge.
|
||||
|
||||
## `get_player`
|
||||
|
||||
```lua
|
||||
--- Returns a player snapshot (see "Player snapshot structure" below).
|
||||
--- @param id string Exact, case-sensitive username.
|
||||
--- @return PlayerSnapshot
|
||||
--- @error PLAYER_NOT_FOUND No snapshot exists yet for that id, meaning
|
||||
--- either the username never connected to this server, or it hasn't
|
||||
--- been observed by the bridge yet.
|
||||
fn get_player(id)
|
||||
```
|
||||
|
||||
A successful lookup also publishes or refreshes
|
||||
`RconDataBridge_PlayerStats_<id>`, described below,
|
||||
so subsequent checks on that specific player can skip
|
||||
the request/response cycle entirely.
|
||||
|
||||
## `list_players`
|
||||
|
||||
```lua
|
||||
--- Lists every player the bridge has ever seen, online and offline.
|
||||
--- @param limit number? Page size. Default 50, capped at 200. A
|
||||
--- non-numeric or absent value falls back to the default rather than
|
||||
--- erroring. Optional.
|
||||
--- @param cursor string? The last `id` from a previous page's results;
|
||||
--- omit for the first page. Optional.
|
||||
--- @return { players: PlayerSnapshot[], nextCursor: string? }
|
||||
fn list_players(limit, cursor)
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
"players": [ /* PlayerSnapshot objects, sorted by id */ ],
|
||||
"nextCursor": "bob" // absent, not null, once you've reached the last page
|
||||
}
|
||||
```
|
||||
|
||||
`players` includes every player the bridge has ever seen,
|
||||
online and offline, not just currently connected ones.
|
||||
|
||||
## `save_server`
|
||||
|
||||
```lua
|
||||
--- Triggers a world save. Confirms the save was triggered, not that it
|
||||
--- completed; there's no separate completion signal.
|
||||
--- @return { triggered: true }
|
||||
--- @error SAVE_FAILED The underlying save call threw.
|
||||
fn save_server()
|
||||
```
|
||||
|
||||
## `get_bridge_status`
|
||||
|
||||
```lua
|
||||
--- Bridge health and introspection: uptime, current rate-limit window
|
||||
--- usage, and the live runtime configuration. Useful for a client to
|
||||
--- self-tune, for example backing off polling when the rate-limit
|
||||
--- window is nearly full, instead of guessing at hardcoded defaults.
|
||||
--- @return BridgeStatus
|
||||
fn get_bridge_status()
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
"version": 1,
|
||||
"bootedAt": 1787740000000, // ms epoch, server's last boot time
|
||||
"uptimeSeconds": 9440.2,
|
||||
"paused": false, // true when PauseEmpty=true and 0 players are online
|
||||
"rateLimit": {
|
||||
"currentWindowRequests": 3,
|
||||
"maxRequests": 10,
|
||||
"windowSeconds": 10
|
||||
},
|
||||
"config": {
|
||||
"maxPayloadBytes": 4096,
|
||||
"rateLimitMaxRequests": 10, // mirrors the live Sandbox Options value
|
||||
"rateLimitWindowSeconds": 10 // mirrors the live Sandbox Options value
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`paused` doesn't affect this operation itself; the bridge keeps
|
||||
responding either way. It does tell you the world clock is frozen, so
|
||||
`get_world_stats` and `RconDataBridge_WorldStats` won't advance until a
|
||||
player joins. `config` mirrors the bridge's current settings, not just the
|
||||
defaults, see [Configuration](Configuration): if an admin changed them at
|
||||
runtime, this reflects the live values.
|
||||
|
||||
## `get_audit_log`
|
||||
|
||||
```lua
|
||||
--- Read-only tail of the bridge's own audit trail, the same record of
|
||||
--- every processed and rejected request that's written to the server
|
||||
--- console and an internal ring buffer.
|
||||
--- @param limit number? Max entries to return. Default 50, capped at
|
||||
--- 200. A non-numeric or absent value falls back to the default
|
||||
--- rather than erroring. Optional.
|
||||
--- @return { entries: AuditEntry[] }
|
||||
fn get_audit_log(limit)
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"id": "ex1",
|
||||
"op": "get_world_stats",
|
||||
"ok": true,
|
||||
"ts": 1787749440066 // ms epoch
|
||||
},
|
||||
{
|
||||
"id": "ex2",
|
||||
"op": "get_player",
|
||||
"ok": false,
|
||||
"code": "PLAYER_NOT_FOUND", // present only when ok is false
|
||||
"ts": 1787749430012
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Entries are returned newest first. There's no cursor or pagination beyond
|
||||
`limit`; this is a log tail, not a stable dataset to page through. The
|
||||
underlying store holds at most the 500 most recent entries; `limit`
|
||||
can't retrieve more history than that.
|
||||
|
||||
## Player snapshot structure
|
||||
|
||||
Returned by `get_player`, embedded in `list_players`'s `players`
|
||||
array, and published to `RconDataBridge_PlayerStats_<id>`:
|
||||
|
||||
```json5
|
||||
{
|
||||
"id": "alice",
|
||||
"online": false,
|
||||
"characterId": 42, // live session id; not stable across reconnects
|
||||
"lastSeenAt": 1787486000123, // ms epoch: last refresh online, or disconnect/death time offline
|
||||
"stats": {
|
||||
"hoursSurvived": 17.4, // frozen at the last known value once offline
|
||||
"zombiesKilled": 83 // frozen at the last known value once offline
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Only these fields are ever collected: no position, no Steam ID,
|
||||
and no online ID beyond `characterId`.
|
||||
|
||||
Refresh triggers are a periodic sweep of the online roster, roughly once a
|
||||
second by default, player death, and connect/disconnect detection, which is
|
||||
also polled, since there's no connect or disconnect event available in the
|
||||
engine.
|
||||
|
||||
## `RconDataBridge_PlayerStats_<ID>`, the read-only side channel
|
||||
|
||||
This option exists purely as a convenience cache, not a source of truth;
|
||||
the request/response cycle via `get_player` always works and is
|
||||
authoritative. Two things to know before relying on it:
|
||||
|
||||
- **It's lazy.** The option for a given player is only created the first
|
||||
time someone calls `get_player` for that id, from any client, including
|
||||
the one reading it now, or a previous session. Don't assume it exists
|
||||
for a player you haven't explicitly queried at least once.
|
||||
- **`<ID>` is sanitized**, not the raw username: any character outside
|
||||
`[A-Za-z0-9_]` in the username is replaced with `_` to form a valid RCON
|
||||
option name. The `id` field inside the JSON payload is still the real,
|
||||
unsanitized username; use that as the source of truth if you need to
|
||||
disambiguate.
|
||||
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.
|
||||
8
wiki/_Sidebar.md
Normal file
8
wiki/_Sidebar.md
Normal file
@@ -0,0 +1,8 @@
|
||||
### RconDataBridge
|
||||
|
||||
* [Home](Home)
|
||||
* [Installation](Installation)
|
||||
* [Configuration](Configuration)
|
||||
* [Protocol Reference](Protocol-Reference)
|
||||
* [Operations Reference](Operations-Reference)
|
||||
* [Architecture](Architecture)
|
||||
Reference in New Issue
Block a user