Packet layout 0.7
This page describes which format the full udp packets have that
are sent by client and server.
There are three types of messages:
system messages,
game messages and
control messages
System and game messages have the same layout.
There can be multiple game and system messages in one packet payload.
And they can also be mixed. Every message has its own chunk header.
So "message" and "chunk" can be used as synonyms here.
Control messages on the other hand always fill up the whole packet payload.
So there can only be one control message per udp packet. And it does not have a chunk header.
Packet header
The payload of every teeworlds udp packet starts with the teeworlds packet header. In version 0.7 it is 7 bytes at all times. It has the following layout:
# Packet header
+---------+---------+------------------------------+------------------+----------------+
| Unused | Flags | Acknowledged sequence number | Number of chunks | Security token |
| 2 bits | 4 bits | 10 bits | 1 byte | 4 bytes |
+---------+---------+------------------------------+------------------+----------------+
Field | Description |
---|---|
Unused | |
Flags |
4 bits in the following order
0100
It means it is a compressed packet. Which was not resend and so on.
|
Acknowledged sequence number | Telling the receiver how many vital messages (chunks) were successfully received. |
Number of chunks | Number of game and system message chunks. If it is a control packet this value is 0 at all times. |
Security token | 4 byte random security token to avoid spoofing. |
Chunk headers
The chunk header has a optional sequence number field that is only set if the vital flag is set to true. The vital flag indicates that the following chunk is important. So it includes a sequence number that has to be acknowledged in the packet header by the receiving party if that does not happen the message will be resend.
# Chunk header (not vital)
+---------+---------+
| Flags | Size |
| 2 bits | 12 bits |
+---------+---------+
# Chunk header (vital)
+---------+---------+-----------------+
| Flags | Size | Sequence number |
| 2 bits | 12 bits | 10 bits |
+---------+---------+-----------------+
Unfortunately the above structure is not 100% accurate how the bits are sent over the network.
The fields do overlap into each other like this
# Chunk header (not vital)
+---------+---------+--------+--------+
| Flags | Size | Unused | Size |
| 2 bits | 6 bits | 2 bits | 6 bits |
+---------+---------+--------+--------+
# Chunk header (vital)
+---------+---------+-----------------+--------+-----------------+
| Flags | Size | Sequence number | Size | Sequence number |
| 2 bits | 6 bits | 2 bits | 6 bits | 8 bits |
+---------+---------+-----------------+--------+-----------------+
Field | Description |
---|---|
Flags |
2 bits in the following order
01
It means it is a vital chunk that is sent for the first time.
|
Size |
Chunk payload size in bytes. Including the message id. The chunk header it self might be 2 or 3 bytes depending if it is vital or not.
These bytes are not counted in the size. Only the message id that follows the chunk header and the payload is included in the size.
|
Sequence number | The amount of vital chunks already sent. It also counts it self. So if the first chunk that is sent is not vital the sequence number is 0. If the first chunk that is sent is vital the sequence number is 1. Then it will increment by 1 for every vital chunk being sent. |
Game/system message with header and payload
Game messages and system messages share the exact same chunk header format. And also message payload structure. The only thing that differentiates a game message and a system message. Is the last bit of the unpacked message id.
# Game or system message layout with header
+--------------+------------+----------------------------+
| Chunk header | Message ID | Message payload fields ... |
| 2-3 bytes | 1-2 bytes | |
+--------------+------------+----------------------------+
The Message ID field is a bit special. It uses the regular int packing
compression from the teeworlds code. But also the very last bit of the unpacked id is a system flag.
If the system flag bit is set to 1 it is a system message. Otherwise it is a game message.
The message id field needs two bytes as soon as the message id is higher than 31.
For system messages the message id field is always 1 byte because there are only 30.
But there are 39 game messages in the 0.7.5 protocol version. So these messages all have a 2 byte message ID field.
NETMSGTYPE_CL_CALLVOTE (32)
NETMSGTYPE_SV_SKINCHANGE (33)
NETMSGTYPE_CL_SKINCHANGE (34)
NETMSGTYPE_SV_RACEFINISH (35)
NETMSGTYPE_SV_CHECKPOINT (36)
NETMSGTYPE_SV_COMMANDINFO (37)
NETMSGTYPE_SV_COMMANDINFOREMOVE (38)
NETMSGTYPE_CL_COMMAND (39)
You might be wondering why 1 byte can hold at most 31 message ids. Because usually one byte can hold numbers from 0-255. But this byte has 2 bits already in use for the int packer The extension bit and the sign bit. And then the system flag takes another bit. So you are left with 5 (8 - 3) bits. The maximum number you can build with 5 bits is
111111
which is 31 in decimal.
Lets look at the example of NETMSGTYPE_CL_SKINCHANGE (34) These are the bytes sent over the network as hexadecimal.
41 0c 0e 84 01 67 72 65 ...
\______/ \___/ \__________ ...
| | |
Chunk Message Cut off payload
header ID
And here represented as binary
01000001 00001100 00001110 10000100 00000001 01100111 01110010 01100101 ...
\________________________/ \_______________/ \_________________________ ...
| | |
Chunk header Message ID Cut off payload
|
gets int unpacked to
|
v
decimal 68
binary 1000100
\____/|
| |
| System flag not set => Game message
|
34
NETMSGTYPE_CL_SKINCHANGE
Full example packet (system & game)
+-------------------------+----------------------+-------------+----------------------+------------------------+----------------------+--------------------+
| teeworlds packet header | chunk header (vital) | motd (game) | chunk header (vital) | server settings (game) | chunk header (vital) | con ready (system) |
| 7 bytes | 3 bytes | | 3 bytes | | 3 bytes | |
+-------------------------+----------------------+-------------+----------------------+------------------------+----------------------+--------------------+
Control messages
Control messages hijack the whole packet. So in the packet header the control flag is set. And the full payload is only the protocol message. The control message layout always starts with 1 byte that is the message id. Theoretically the regular int packer is used to pack that byte. So if there were more than 63 control messages this message id field could be multiple bytes but since there are only 5 control messages in the protocol version 0.7. It can safely be assumed that the message id field is 1 byte at all times. Then it is followed by an optional payload. Every control message id has a fixed payload structure defined. The messages NET_CTRLMSG_KEEPALIVE and NET_CTRLMSG_ACCEPT Do not have any payload. So the full packet has a size of 8 bytes at all times. And the message NET_CTRLMSG_CLOSE has a optional payload. Meaning it could also be 8 bytes. But if some more bytes are sent they are supposed to be consumed a as a reason string.Control messages without any payload look like this:
+-------------------------+--------------------+
| teeworlds packet header | Control message id |
| has control flag set | |
| 7 bytes | 1 byte |
+-------------------------+--------------------+
To give an example of a control message with payload.
This is how NET_CTRLMSG_CONNECT
is layed out:
+-------------------------+--------------------+----------------+-----------------------------------+
| teeworlds packet header | Control message id | Response token | Anti reflection attack null bytes |
| has control flag set | | | |
| 7 bytes | 1 byte | 4 bytes | 508 bytes |
+-------------------------+--------------------+----------------+-----------------------------------+