Files
pz-rcon-data-bridge/wiki/Architecture.md

6.9 KiB

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 and 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:

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 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.

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.