SimTX API/Framing
This page describes how messages of the SimTX API game protocol, version 25, are laid out on the TLS byte stream: the preface, the length-delimited frames, the limits a frame must respect, and how a reader skips what it does not understand.
Preface[edit | edit source]
The client opens the stream with exactly 8 bytes:
53 49 4D 54 magic 0x53494D54, "SIMT", big-endian int32
00 00 00 19 version 25, big-endian int32
The server sends no preface. The preface is the only part of the protocol outside protobuf, and the only place the protocol version appears. A server that does not speak the requested version answers with a ServerHello whose status is HELLO_STATUS_VERSION_UNSUPPORTED.
Frames[edit | edit source]
After the preface, each direction is a sequence of frames:
varint length number of bytes that follow
bytes message one ClientFrame (client to server) or ServerFrame (server to client)
This is protobuf's standard length-delimited encoding of a message, as written by writeDelimitedTo and read by parseDelimitedFrom, or the equivalent helpers of other protobuf libraries. The varint is the usual base-128 encoding, least significant group first.
ClientFrame and ServerFrame each consist of one oneof msg. Exactly one member is set in every frame. The member number says what the frame is, and the member's value is the message:
message ClientFrame {
oneof msg {
ClientHello hello = 1;
Ping ping = 2;
...
ReceiverStart receiver_start = 10;
...
}
}
So a frame body always starts with the tag of its member (field number × 8 + 2), then the member's length, then the member's message. The complete lists are on SimTX API/Packet list.
Member numbers[edit | edit source]
Member numbers are allocated in blocks, the same in both directions:
| Numbers | Area |
|---|---|
| 1 to 9 | Connection: hello, ping, errors, banners |
| 10 to 29 | Receiver and transmitter sessions |
| 30 to 59 | Control sessions |
| 60 to 79 | Moderation sessions |
| 80 to 99 | Reserved for the next area |
Numbers 1 to 15 encode the tag in one byte. ServerFrame numbers 15, 16 and 62 are reserved; do not use them.
Size and shape limits[edit | edit source]
| Limit | Value |
|---|---|
| Frame length | At most 16 MiB (16,777,216 bytes), both directions |
| Hello frame | At most 64 KiB |
| Frames before the hello | At most 16 unknown or empty frames |
| Elements per frame | At most 16,384 in client frames and 2,097,152 in server frames, counting nested messages, elements of repeated string, bytes and message fields, and unknown fields |
| Nesting depth | At most 32 levels of nested messages |
| Group encoding | Not allowed (wire types 3 and 4) |
A client frame over a limit is a protocol violation; the server answers Error INVALID_REQUEST where it can and closes the connection. Clients should apply the same limits to server frames.
A reader should not allocate the announced length blindly: check it against 16 MiB first, and for large frames let the buffer grow as bytes arrive.
Unknown frames and fields[edit | edit source]
A reader must:
- Skip unknown frames. A frame whose member number it does not know parses as a frame with no member set. Ignore it and read the next frame. Never treat it as fatal. An empty frame is skipped the same way.
- Skip unknown fields of every wire type: varint (0), 64-bit (1), length-delimited (2) and 32-bit (5). Generated protobuf code does this already; hand-written decoders must do it explicitly.
- Keep unknown enum values. Treat them as "unknown" in the user interface rather than failing. Each enum documents how its 0 value and unknown values are read; see Enumerations.
Within a session, frames that are known but not allowed (for example a TxUpdate on a receiver connection) are a protocol violation.
Order[edit | edit source]
Frames are processed in order on each connection. Responses to control requests arrive in the order of the requests, but other frames can arrive between any two of them: Pong, Banner, MonitorBlock, SessionStatus updates and Error. Read each connection with a loop that dispatches on the member number, rather than expecting a particular frame next.
Reading loop[edit | edit source]
send preface, then the hello frame
loop:
length = read varint (at most 5 bytes for a valid length)
if length > 16 MiB: protocol error, close
body = read exactly length bytes
frame = parse ServerFrame from body (unknown fields skipped)
switch on the member that is set:
known member -> handle it
nothing set -> ignore (newer message or empty frame)
Example[edit | edit source]
A ping and its pong:
ping (client to server, 12 bytes):
0B frame length 11
12 09 ping (Ping), 9 bytes
08 63 nonce = 99
10 80 94 83 B9 97 34 client_time_ms = 1793000000000
pong (server to client, 19 bytes):
12 frame length 18
12 10 pong (Pong), 16 bytes
08 63 nonce = 99
10 80 94 83 B9 97 34 client_time_ms = 1793000000000
18 8C 94 83 B9 97 34 server_time_ms = 1793000000012