Skip to main content

FigNet Protocol Specification (v1)

This document is the language-agnostic specification of the FigNet on-the-wire protocol envelope and the recommended v1 handshake model:

  • WSS (TLS required) for control-plane.
  • RUDP for in-room gameplay data-plane, authorized via JoinTickets minted over TLS.

At the end of this document you will find the Entangle module message ID overview and how it fits into the control/data plane split.

See also: Configuration for the configuration schema and recommended server/client settings. See also: Secure Session for the current UDP secure-session design used by the active runtime.


1) Normative language

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119.


2) Terms

  • Frame: a single transport-delivered message unit (one WebSocket binary message, one ENet packet, etc.).
  • Envelope: FigNet's fixed prefix on every frame.
  • Payload: bytes after the envelope; message-specific encoding.
  • Message ID: u16 identifier of the message type.
  • Callback ID: 7-bit value used to correlate request/response messages.

3) Transport requirements

3.1 WSS (TLS required)

  • Control-plane transport MUST be WebSocket Secure (wss) with TLS required.
  • Authentication material (tokens, tickets, secrets) MUST only be exchanged over wss.

3.2 RUDP (gameplay)

  • Gameplay transport SHOULD be a reliable UDP library (ENet/LiteNetLib style).
  • RUDP admission MUST require a JoinTicket minted by the control-plane over TLS.

3.3 Message boundaries

FigNet's core envelope does not include a length prefix.

  • A transport MUST provide message boundaries (WebSocket, ENet, etc.), OR
  • You MUST implement an outer length-prefix framing (only needed if you ever use raw TCP streams).

4) Byte order

All numeric fields in the FigNet envelope are little-endian.


5) Core envelope format

FigNet defines a minimal envelope around each frame.

Current behavior note:

  • plaintext datagram packets use packet magic
  • encrypted datagram packets do not use packet magic
  • stream transports do not use packet magic

For the current secure-session handshake, packet authentication flow, pinned-key validation model, and encrypted datagram layout, use Secure Session as the primary reference.

5.1 Magic prefix

When enabled, frames begin with a 2-byte magic number:

  • MAGIC = 0xC0DE (u16, little-endian; bytes DE C0)

This is used only as a fast sanity check; integrity is provided by the transport.

5.2 Envelope layouts

A) Magic disabled

Offset Size Field
0 2 msg_id (u16 LE)
2 1 flags (u8)
3 N payload bytes
Offset Size Field
0 2 magic (u16 LE) == 0xC0DE
2 2 msg_id (u16 LE)
4 1 flags (u8)
5 N payload bytes

5.3 Flags byte layout

bit 7 is_encrypted (1 = app-layer encrypted datagram payload)
bits 0..6 callback_id (0..127)

Rules:

  • callback_id == 0 means no correlation.
  • callback_id != 0 means a correlated response is expected.
  • Responders MUST echo the request's callback_id in the response.

6) Callback / RPC semantics

6.1 Correlation rules

  • Callback IDs are scoped to a connection.
  • Callback IDs MUST be in range 1..127 for requests.
  • Callback IDs MUST be echoed by responders for the matching response.

6.2 Example (abstract)

Client -> Server: msg_id=JOIN_ROOM_REQUEST, callback_id=5
Server -> Client: msg_id=JOIN_ROOM_RESPONSE, callback_id=5

7) Encryption

Current active behavior:

  • datagram transports use the secure session described in Secure Session
  • stream transports rely on TLS/WSS
  • XOR is no longer part of the active protocol

For encrypted datagram payloads:

  • flags.is_encrypted = 1
  • packet magic is omitted
  • the encrypted frame also carries a 4-byte sequence and a 16-byte AEAD tag

For stream transports:

  • app-layer secure-session encryption is not used
  • the envelope remains plaintext at the app layer
  • transport security is provided by TLS/WSS

8.1 Goals

  • Explicit protocol version and capability negotiation.
  • Strong authentication only over TLS (wss).
  • Ticket issuance for switching to RUDP.

8.2 Handshake state machine

stateDiagram-v2
[*] --> Connected
Connected --> HelloDone: HELLO/HELLO_ACK
HelloDone --> Authed: AUTH_OK
Authed --> LobbyReady
LobbyReady --> TicketIssued: JOIN_ROOM_RESPONSE(ticket)
TicketIssued --> [*]

8.3 Messages (conceptual)

FigNet core SHOULD define dedicated message IDs for the following.

HELLO (client -> server; WSS)

Purpose: version/capability negotiation.

Payload fields (recommended):

  • protocol_major (u16)
  • protocol_minor (u16)
  • client_caps (u64)
  • app_name (string)
  • client_build (optional string)

HELLO_ACK (server -> client):

  • selected protocol version
  • server capabilities (u64)
  • limits (max payload size, max callbacks, etc.)

AUTH (client -> server; WSS)

Purpose: establish a user/player identity (or a session).

Payload options (implementation-dependent):

  • bearer token (JWT or opaque)
  • platform ticket (Steam/EOS/etc)
  • guest/anonymous (dev)

AUTH_OK (server -> client):

  • session_id (u64 or bytes)
  • user_id (string or u64)
  • expires_at (u64 epoch ms)
  • optional claims / roles

AUTH_FAIL:

  • error_code (u16)
  • message (string)

JOIN_ROOM_REQUEST / JOIN_ROOM_RESPONSE (WSS)

JOIN_ROOM_RESPONSE MUST include:

  • room endpoint (host:port)
  • join_ticket (opaque bytes)
  • ttl_ms
  • room_id

9) JoinTicket (WSS -> RUDP bridge)

9.1 Requirements

A JoinTicket MUST be:

  • minted on the control plane over TLS
  • short-lived (10-30 seconds recommended)
  • one-time-use (recommended)

9.2 RUDP join handshake

RUDP_JOIN (client -> room server; first application-level message)

Payload (recommended):

  • join_ticket (bytes)
  • client_nonce (u64 or 16 bytes)

RUDP_JOIN_OK (room server -> client)

Payload (recommended):

  • peer_id (u32)
  • room_session_id (u64)
  • server_nonce (u64 or 16 bytes)
  • optional tick_rate, channel layout, compression policy

RUDP_JOIN_FAIL

Payload:

  • error_code (u16)
  • message (string)
  • Ticket is consumed on first success.
  • Room server rejects already-consumed tickets.

10) Error model (core)

FigNet SHOULD define a core error message to standardize failures across implementations.

Recommended fields:

  • error_code (u16)
  • message (string)
  • retryable (u8 bool)
  • details (optional bytes)

When responding to a request, the server SHOULD echo the request's callback_id.


11) Message ID registry

  • 60000-60099: FigNet Core (reserved)
  • 60100-60299: FigNet Core Extensions (auth/session/tickets/errors/versioning)
  • 60300-60599: Entangle module (current)
  • 60600-60999: Voice/other official modules
  • 0-59999: user/application-defined

11.2 Existing reserved message IDs (from current codebase)

  • 60000 RegisterIdentity (legacy; deprecate in favor of HELLO/AUTH)
  • 60001 ValidateIdentity (legacy/incomplete; reserved)
  • 60002 Ping

12) Examples

12.1 Envelope example (magic enabled, unencrypted, no callback)

  • magic = 0xC0DE -> DE C0
  • msg_id = 0x1234 -> 34 12
  • flags = 0x00
  • payload = AA BB CC

Hex:

DE C0 34 12 00 AA BB CC

12.2 Envelope example (encrypted datagram, callback_id=7)

  • msg_id = 0x1234 -> 34 12
  • flags bit7 set + callback 7 -> 0b1000_0111 = 0x87
  • sequence = 01 00 00 00

Hex:

34 12 87 01 00 00 00 <enc_payload...> <16-byte-tag>

13) Entangle Module (overview)

Entangle defines a set of message IDs ("operation codes") in the 60300+ range and uses FigNet's envelope for transport.

13.1 Entangle message IDs

These are the canonical IDs that must be shared across ports:

Server registry / stats

  • 60050 RegisterServer
  • 60051 UpdateServerStats
  • 60052 GetServerStatsList
  • 60053 GetServerToJoinEndPoint
  • 60054 RegisterRoom
  • 60055 UnRegisterRoom

Lobby / room management

  • 60300 CreateRoom
  • 60301 JoinRoom
  • 60302 GetRoomList
  • 60303 LeaveRoom
  • 60304 AppKey
  • 60305 ReJoinRoom
  • 60306 ForcedSyncRoomState

Room events / replication

  • 60400 OnPlayerJoinRoom
  • 60401 OnPlayerLeftRoom
  • 60402 OnEntityState
  • 60403 OnMasterClientChange
  • 60404 InstantiateEntity
  • 60405 DeleteEntity
  • 60406 RoomEvent
  • 60407 RequestOwnerShip
  • 60408 ClearOwnerShip
  • 60409 OnAgentOwnerShipChange
  • 60410 RoomStateChange
  • 60411 PreRoomStateReceived
  • 60412 PostRoomStateReceived
  • 60413 EntityUpdate
  • 60414 PresenterMode
  • 60415 OnCachedEventRemoved
  • 60416 CustomGroupOperation
  • 60417 Heartbeat
  • 60418 UpdateSeat

Game

  • 60500 GameMessage
  • 60501 GameState
  • WSS/TLS (control plane):
    • GetRoomList, CreateRoom, JoinRoom, LeaveRoom, AppKey
    • issuance of JoinTicket (returned inside JoinRoom response)
  • RUDP (data plane):
    • EntityUpdate, RoomEvent, Heartbeat, continuous replication
sequenceDiagram
autonumber
participant C as Client
participant L as Entangle Lobby (WSS/TLS)
participant R as Room Server (RUDP)

C->>L: GetRoomList (60302)
L-->>C: RoomList

C->>L: CreateRoom (60300) [callback_id=x]
L-->>C: CreateRoomResponse [callback_id=x]

C->>L: JoinRoom (60301) [callback_id=y]
L-->>C: JoinRoomResponse (roomEndpoint, joinTicket) [callback_id=y]

C->>R: RUDP_JOIN (joinTicket)
R-->>C: RUDP_JOIN_OK

Note over C,R: EntityUpdate (60413), RoomEvent (60406), Heartbeat (60417) on RUDP