Transport
A server and a client exchange messages over UDP, carried by ENet 1.2.5 with the header each range’s definitions give.
Layering
Four layers carry a message to a socket.
- UDP moves datagrams.
- ENet turns datagrams into ordered, acknowledged deliveries on numbered channels.
- A cipher covers each channel payload.
- A message occupies the deciphered payload, opening with the header its family carries, as Messages defines.
This chapter covers layers 1 to 3.
Stock ENet
This document does not restate ENet. Connection setup, acknowledgement, sequence numbers, retransmission, throttling, and fragmentation behave as ENet 1.2.5 defines them.
The rest of this chapter says what a client does not accept from stock ENet. A server built on an unmodified ENet 1.2.5 never completes a connection.
The packet header
Each range has its own header, below with the constants it is read under. A server MUST use the layout of the version it serves, because a client establishes no connection under any other layout.
Packet headers and constants
In 4.1–4.19
ENet 1.2.5 beneath; little-endian fields. Blowfish in ECB mode over 8-byte blocks, each read big-endian, under a 16-byte key given in base64. Neither end pads.
| Field | Type | Present when | What it is |
|---|---|---|---|
| sessionId | u8 | The value the client supplied when it connected, carried on every packet. | |
| peer | 1-byte bits | ||
| peer.peerId | bit 0 to 6 | 127 marks an unassigned peer, which is what a connecting client sends. | |
| peer.sentTimeFlag | bit 7 | Set when the header carries a sent time. | |
| sentTime | u16, big-endian | peer.sentTimeFlag set | Milliseconds since the connection opened. |
| Constant | Value | Stock ENet | What it is |
|---|---|---|---|
| maximumPeerId | 127 | 32767 | Follows from the peer id occupying seven bits. |
| maximumTransmissionUnit | 996 | 4096 | A client asks for 1400 in its connect command, and takes no datagram larger than 996 bytes. |
| minimumDatagram | 4 | 2 | A client discards any datagram shorter than this many bytes. |
In 4.20
ENet 1.2.5 beneath; little-endian fields. Blowfish in ECB mode over 8-byte blocks, each read big-endian, under a 16-byte key given in base64. Neither end pads.
| Field | Type | Present when | What it is |
|---|---|---|---|
| addressCheck | bytes, 4 | A CRC-32 over the address the datagram is sent to, written big-endian. The input is ten bytes: the IPv4 address in network order, the UDP port as a little-endian u16, and 98 2e 3f 3c. The register starts at all ones; for each input byte it is shifted left eight bits with the byte placed in the low eight, then XORed with the table entry for the eight bits shifted out; the entry for a value v is v shifted left 24 bits, then eight steps of the polynomial 04c11db7, most significant bit first, with no reflection; at the end the register is inverted. A client sending to 127.0.0.1 port 5000 writes 28 68 bd eb, and to port 5001 fa 71 7c 37. A client does not check the field on what it receives, so a server may write any four bytes; a client does not connect without the field. | |
| sessionId | u8 | The value the client supplied when it connected, carried on every packet. A connection whose packets carry any other value times out. | |
| peer | 1-byte bits | ||
| peer.peerId | bit 0 to 6 | 127 marks an unassigned peer, which is what a connecting client sends. | |
| peer.sentTimeFlag | bit 7 | Set when the header carries a sent time. | |
| sentTime | u16, big-endian | peer.sentTimeFlag set | Milliseconds since the connection opened. |
| Constant | Value | Stock ENet | What it is |
|---|---|---|---|
| maximumPeerId | 127 | 32767 | Follows from the peer id occupying seven bits. |
| maximumTransmissionUnit | 996 | 4096 | A client asks for 1400 in its connect command, and takes no datagram larger than 996 bytes. |
| minimumDatagram | 8 | 2 | A client discards any datagram shorter than this many bytes. |
A header built from the schema is necessary to reach a client, and not sufficient on its own. Where a field’s content is free rather than fixed, its note in the schema says so.
conformance/vectors/transport holds a header with a sent time and one
without.
A client discards any datagram shorter than the minimumDatagram constant of
its range. A server MUST NOT send a shorter datagram.
The connect command
A server’s verify connect MUST state the channel count, and the throttle interval, acceleration and deceleration, the connect command carried: a client abandons a connection whose verify connect states anything else.
A client’s connect command takes ENet’s layout, except that two of its fields carry one byte of meaning each. The peer id is the first of the two bytes ENet gives it, and the session id is the first of the four. The bytes after each carry nothing a server reads, and a client fills them with anything.
A server MUST take the peer id and the session id from those first bytes. The session id is then the byte every header carries, and a server that reads either field at ENet’s width answers a client that never hears it.
A client asks for 8 channels.
Maximum transmission unit
A client asks for a transmission unit of 1400 bytes, and discards any datagram
whose UDP payload, packet header included, exceeds the
maximumTransmissionUnit constant of its range. A server MUST NOT send a larger
datagram.
That constant is below what a client asks for. A server MUST accept a connection at a transmission unit no greater than the constant, by clamping the value the client asks for. Capping its own sending without clamping the negotiated value is not sufficient, because ENet derives fragment sizes from the negotiated value and still emits datagrams above the limit.
The cipher
Whether a payload is enciphered follows from the state of the connection rather than from the channel it travels on. A connection begins with no cipher state, and the request that establishes it travels in the clear apart from one field it carries enciphered as proof. Everything after that is enciphered, on every channel and in both directions, the answer to that request included.
schema/protocol.json records which cipher, in which mode, and the shape of a
block. A server MUST use that cipher; a client accepts no other. A standard
implementation of Blowfish interoperates with a client.
What a server sends carries no padding. Trailing bytes that do not fill a block travel unchanged, so a payload shorter than one block travels in the clear.
What a client sends carries no padding either.
conformance/vectors/cipher covers a whole block, a payload with a tail too
short to fill one, and a payload shorter than a block.
The key
Both ends hold the key before a connection opens. Nothing on the wire negotiates it, and no exchange derives it. A client receives it as a launch argument.
The argument holds the key in base64.
Registration associates the key with a connection. Register travels in the
clear apart from one field enciphered under the key, and a server MUST refuse a
connection whose deciphered field is not what it expects, since that field is
all that shows the client holds the key. schema/messages
records the exchange, as Register and Registered.
Channels
In every range of this book
| Channel | Name | Family |
|---|---|---|
| 0 | registration | registration |
| 1 | requests | game |
| 2 | clock | game |
| 3 | events | game |
| 4 | transient | game |
| 5 | chat | session |
| 7 | roster | session |