2. UDP packet header¶
Every UDP datagram, in both directions, MUST start with this 18-byte header. The payload of its type follows immediately. Encoding rules are in section 1.
2.1 Layout¶
0 4 5 6 8 10 14 18
+---------------+---+---+-------+-------+---------------+---------------+
| magic |ver|typ| seq | ack | ack_bits | session |
+---------------+---+---+-------+-------+---------------+---------------+
| Offset | Size | Field | Type | Content |
|---|---|---|---|---|
| 0 | 4 | magic |
u32 |
0x50595452, sent as the bytes 52 54 59 50 ("RTYP" in ASCII) |
| 4 | 1 | version |
u8 |
1 |
| 5 | 1 | type |
u8 |
Packet type (2.3) in bits 0 to 6; bit 7 set when the payload is compressed (2.6) |
| 6 | 2 | sequence |
u16 |
Number of this datagram (2.2) |
| 8 | 2 | ack |
u16 |
Most recent sequence received from the peer (2.2) |
| 10 | 4 | ack_bits |
u32 |
The 32 sequences before ack that were received (2.2) |
| 14 | 4 | session |
u32 |
Session token (2.2) |
A datagram therefore carries at most 1200 − 18 = 1182 bytes of payload.
2.2 Fields¶
magicidentifies the protocol. A receiver MUST drop a datagram whose first four bytes are not52 54 59 50.versionis the protocol version,1for this document. A receiver MUST drop a datagram of another version.typesays what the payload is (2.3) in its 7 low bits. Its high bit (0x80) says that the payload is compressed (2.6): a compressedSnapshothas the type byte0x84. A receiver MUST drop a datagram of an unknown type, with or without that bit.sequencenumbers the datagrams of one sender in a session: the sender MUST add 1 for each datagram it sends, whatever its type, going from 65535 back to 0. Because of this wrap, sequences MUST be compared as in RFC 1982 (serial number arithmetic): sequence a is more recent than b when a is 1 to 32767 steps ahead of b, counting modulo 65536, that is when a > b and a − b < 32768, or a < b and b − a > 32768. So0is more recent than65535, and10than40000. Two sequences exactly 32768 apart are not ordered: a receiver MUST NOT treat either as the more recent one.ackis the most recent sequence received from the peer, and bit n ofack_bits(bit 0 being the least significant) is set when sequenceack − 1 − nwas received too. Every datagram thus acknowledges the last 33 datagrams of the peer, without dedicated acknowledgement packets. The rules that use them, including their values before anything was received, are in section 5.sessionis the token the server issued at login (section 3);0is never issued. The server MUST drop a datagram whose token does not match the session bound to its source endpoint, except aConnectdatagram, which binds them (section 3.4).
2.3 Packet types¶
| Value | Name | Direction | Purpose |
|---|---|---|---|
| 1 | Connect |
client → server | Binds the client's UDP endpoint to its session |
| 2 | Accept |
server → client | Confirms the binding: gameplay traffic may start |
| 3 | Input |
client → server | The player's input state for one tick |
| 4 | Snapshot |
server → client | The state of the world |
| 5 | Event |
both | Gameplay events that must arrive (spawns, deaths...), resent until acknowledged |
| 6 | Heartbeat |
both | Keeps the session alive and carries acknowledgements when nothing else is sent |
| 7 | Disconnect |
both | The sender leaves the session |
Values 0 and 8 to 127 are reserved, with or without the compressed bit. The payloads are specified in
section 3.4 (Connect, Accept),
section 4 (Input, Snapshot, Event) and
section 5.5 (Heartbeat, Disconnect), and the
reliability of Event in section 5.
2.4 Processing¶
A receiver MUST check, in this order:
- that the datagram holds at least 18 bytes;
- the magic;
- the version;
- the type.
At the first failed check, the datagram MUST be dropped without reading further. A server SHOULD count dropped datagrams per source, to detect floods and broken clients. The checks that follow the header are in section 6.3.
2.5 Example¶
A Snapshot with sequence 0x1234 (4660), acknowledging the peer's sequence 0xABCD
(43981) and the ones at distance 1 and 32 before it, in session 0xDEADBEEF:
52 54 59 50 01 04 34 12 CD AB 01 00 00 80 EF BE AD DE
magic ver typ seq ack ack_bits session
The Aegis2D implementation is net::PacketHeader, net::write_header() and
net::read_header() (aegis2d/net/packet.hpp); its unit tests check these exact bytes.
Sequences are compared with net::sequence_greater_than() and net::sequence_less_than()
(aegis2d/net/sequence.hpp).
2.6 Compressed payloads¶
A sender MAY compress the payload of a datagram with LZ4
(its block format) and set bit 7 of type. The compressed payload is:
| Field | Type | Content |
|---|---|---|
size |
u16 |
Size of the original payload, 1 to 1182 |
block |
bytes | The original payload as one LZ4 block, up to the end of the datagram |
- A sender SHOULD compress only payloads that come out smaller, and only from a size where
that is likely: Aegis2D tries from 128 bytes (
net::default_compression_threshold) and sends the payload as it is when it does not shrink. A peer that never compresses thus still reads every datagram of one that does. - Compression is optional per datagram: the same session MAY mix both. The original payload follows the rules of its type, as if it had been sent as it is; the acknowledgements and the sequence of the header do not change.
- A receiver MUST decompress the payload before checking it, and MUST drop the datagram,
without acknowledging it, when
sizeis0or above1182, when the block is invalid, or when it does not give exactlysizebytes (section 6.3). The LZ4 decoder used MUST never read or write past its buffers, whatever the block (LZ4_decompress_safe()).
For example, the Event datagram of the fuzz corpus tests/fuzz/corpus/packet/event-compressed
carries one event of 300 bytes that repeat: its 305-byte payload is sent as 135 bytes (size
then a 133-byte block), with the type byte 0x85, so the datagram takes 153 bytes instead
of 323.
The Aegis2D implementation is aegis2d/net/compression.hpp: compress_payload(),
decompress_payload(), write_datagram(), which compresses from a threshold when that
pays, and read_payload(), which decompresses when the header says so.