Skip to content

A protocol envelope: revision check, session token, stale-packet protection and strict decoding ​

Status: Accepted (2026-06-15)

Context ​

DarqlineMP speaks raw, fixed-layout UDP. Without an envelope around the packets, several classes of bugs are open before any gameplay packet is written:

  • an incompatible build connects and sends bytes the server tries to interpret;
  • a client is known only by its address, so a stray or old datagram from that address passes as valid state;
  • UDP reorders packets, so a late PlayerSync rolls a player back to an older snapshot;
  • a delayed packet about a player who has left can bring that player back, and once the player_id is reused it can be mistaken for the new owner;
  • a lazy decoder pads short packets with zeros and lets NaN or infinity reach the game.

Combat packets and everything after them rely on these being impossible, so the envelope comes first.

Decision ​

  • The revision is checked in the handshake only. ClientHello carries protocol_version, and the server compares it by strict equality. On a mismatch it does not answer at all, so an incompatible client times out on connect (fail-closed). The client checks the revision in ServerHello as well. Packets after the handshake carry no version byte: both sides are already known to be compatible, and a per-packet byte would cost on every frame for nothing. See Handshake.
  • Every Client → Server packet after the handshake echoes the session token. The server issues it in ServerHello and drops any packet whose token does not match the one bound to the sender address. This discards stray and stale datagrams, including those of an earlier connection from the same address. The token is a secret, separate from the player's public generation (ADR 0042).
  • Server → Client packets about a player carry its generation (player_token). The generation is unique for the lifetime of the server process and grows monotonically. A receiver keeps, per player_id, the live generation and a tombstone of the generation that has just left. A new generation means the id was reused (even if its PlayerLeave was lost); PlayerLeave removes only the matching generation; a straggler of a departed or older generation is dropped. The generation is public and authenticates nothing: it protects against stale packets and id reuse, not against spoofing.
  • Continuous state carries a sequence number. The client numbers each PlayerSync; the server drops a frame that a newer one has overtaken and relays seq unchanged in RemotePlayerSync; a receiver applies a frame only if its seq is newer than the last one of the same generation. Discrete events do not use seq: their order comes from the reliable event lane. The exact rules are in Sequence and generation.
  • Decoding is strict on both sides. A packet must be exactly as long as its layout, and a frame with any non-finite float is dropped. A malformed packet is dropped whole, never partially applied. See Strict decoding.
  • The server's codec is canonical. The wire layout lives in one place per implementation. The server's codec writes committed golden fixtures (docs/fixtures/protocol/*.bin); its tests check that encoding still produces those bytes and that decoding them yields the same values, and every other codec checks itself against the same files. A format change therefore cannot be blessed by two generators that agree with each other.
  • No backward compatibility. A client of another revision is refused at the handshake; there is no negotiation of older formats.

Consequences ​

  • Gameplay packets are built on a transport that refuses incompatible builds, rejects stale and foreign datagrams and never applies a half-decoded packet.
  • Every wire change bumps PROTOCOL_VERSION, and clients and servers must be upgraded together.
  • Reliability is not part of the envelope: a lost packet is still lost. Events that must arrive use the reliable event lane, and death and respawn have their own life-state rules on top of the generation.

Alternatives considered ​

  • A version byte on every packet. It adds a byte to every frame, while compatibility is already settled by the handshake. Rejected.
  • Identifying a client by its address alone. Any datagram from that address, including an old one, would pass as valid. Rejected.

Documentation licensed under CC BY 4.0. DarqlineMP is not affiliated with Rockstar Games or Take-Two Interactive. Grand Theft Auto is a trademark of Take-Two Interactive Software, Inc.