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:
u16identifier 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; bytesDE 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
B) Magic enabled (recommended)
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 == 0means no correlation.callback_id != 0means a correlated response is expected.- Responders MUST echo the request's
callback_idin the response.
6) Callback / RPC semantics
6.1 Correlation rules
- Callback IDs are scoped to a connection.
- Callback IDs MUST be in range
1..127for 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) Control-plane handshake (recommended v1)
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_msroom_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)
9.3 Replay protection (recommended)
- 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
11.1 Recommended ranges
- 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)
60000RegisterIdentity (legacy; deprecate in favor of HELLO/AUTH)60001ValidateIdentity (legacy/incomplete; reserved)60002Ping
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
60050RegisterServer60051UpdateServerStats60052GetServerStatsList60053GetServerToJoinEndPoint60054RegisterRoom60055UnRegisterRoom
Lobby / room management
60300CreateRoom60301JoinRoom60302GetRoomList60303LeaveRoom60304AppKey60305ReJoinRoom60306ForcedSyncRoomState
Room events / replication
60400OnPlayerJoinRoom60401OnPlayerLeftRoom60402OnEntityState60403OnMasterClientChange60404InstantiateEntity60405DeleteEntity60406RoomEvent60407RequestOwnerShip60408ClearOwnerShip60409OnAgentOwnerShipChange60410RoomStateChange60411PreRoomStateReceived60412PostRoomStateReceived60413EntityUpdate60414PresenterMode60415OnCachedEventRemoved60416CustomGroupOperation60417Heartbeat60418UpdateSeat
Game
60500GameMessage60501GameState
13.2 Entangle across control/data planes (recommended)
- WSS/TLS (control plane):
GetRoomList,CreateRoom,JoinRoom,LeaveRoom,AppKey- issuance of JoinTicket (returned inside
JoinRoomresponse)
- RUDP (data plane):
EntityUpdate,RoomEvent,Heartbeat, continuous replication
13.3 Entangle handshake sequence (recommended)
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