Teeworlds 0.7

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
  • connless - 1=connection less 0=connection oriented
  • compression - 1=compressed 0=not compressed
  • resend - 1=resend 0=not resend
  • control - 1=control packet 0=system/game message packet
For example if the binary value of flags is 0100 It means it is a compressed packet. Which was not resend and so on.
Acknowledged sequence number Telling the receiver how many vital packets 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
  • resend - 1=resend 0=not resend
  • vital - 1=vital 0=not vital
For example if the binary value of flags is 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.
                            
    # Game or system message layout with header
    +--------------+------------+----------------------------+
    | Chunk header | Message ID | Message payload fields ... |
    | 2-3 bytes    | 1-2 bytes  |                            |
    +--------------+------------+----------------------------+
                    <------------ size in bytes ------------>
                            
                        
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                         |
    +-------------------------+--------------------+----------------+-----------------------------------+