bw logo

Chapter 30. Mercury Packet Structure

The API for network communications that Mercury exposes is based on a message/bundle paradigm, which masks the true nature of the actual UDP packets being sent and received.

From the programmer's point of view, messages are created and streamed onto Bundles (see src/lib/network/bundle.hpp). At some point a Bundle will be sent, at which point Mercury will convert the messages on the Bundle into one or more contiguous sequences of bytes, and send them as a regular UDP packets. This section details the format of those packets.

Broadly speaking, the structure of a Mercury packet is composed of the following:

  • A single-byte header.

  • Zero or more messages.

  • The footers specified by the flags in the header.

Broad structure

Message structure

Specific message types

Footers

How first request offset/reply IDs work

30.1. Header

The first byte of a Mercury packet is always a header, which essentially details which footers should be expected on the packet.

It is a bitwise combination of the FLAG* constants defined inside Bundle in src/lib/network/packet.hpp.

30.2. Messages

The first byte of a message is always its ID.

Looking up that message ID in whichever Mercury interface is currently in effect will reveal what type of message it is and how the subsequent bytes on the packet should be interpreted. For example, LoginApp processes all packets on its external interface according to LoginInterface, defined in bigworld/src/common/login_interface.hpp.

The ID of a message is the index of its MERCURY_*_MESSAGE declaration in the BEGIN_MERCURY_INTERFACE() / END_MERCURY_INTERFACE() block for that Mercury interface. For example, in bigworld/src/common/login_interface.hpp, a login message has ID 0, because it is the first message type declared in the MERCURY_INTERFACE block.

There are three basic types of Mercury messages:

  • Fixed-length messages

  • Variable-length messages

  • Multiple-length messages

These types are described in the sub-sections below.

30.2.1. Fixed-Length Messages

A fixed-length message has its precise length determined at compile time. Specifically, it is the second argument to the MERCURY_FIXED_MESSAGE macro that declares it.

Therefore, the header of a fixed-length message is simply its message ID. The message payload immediately follows the header, and must have the exact length given in the interface definition.

Although all fixed-length messages are treated equally during low-level Mercury processing, there are different macros that can declare them, which result in different handling in the callbacks that receive the unpacked messages.

The most common example of this is the MERCURY_STRUCT_MESSAGE declaration. A message type declared in this way is simply a fixed-length message whose length is equal to the sum of the sizes of the fields of the struct.

The advantage of using a struct message instead of a vanilla fixed-length one is that the object passed to the callback registered for this message type will be an object whose fields match those of the struct. The callback can therefore access them by name, and with the correct types, instead of having to extract bytes at particular offsets and lengths inside the binary data blob, and then cast them to the desired types.

30.2.2. Variable-Length Messages

The header for a variable-length message is the message ID, followed by a short field that specifies the exact size of the message payload.

The length of the field specifying the payload size is the second argument to the MERCURY_VARIABLE_MESSAGE macro that declares the message type.

For example, from bigworld/src/common/login_interface.hpp:

MERCURY_VARIABLE_MESSAGE( login, 2, &gLoginHandler )

The length field in a login message header will therefore be 2 bytes long.

30.3. Footers

The footers that are present on Mercury packet are specified by the field header, which is the first byte of the packet, and must appear in a specific order.

They are listed in the following sub-sections in reverse order, i.e., from the end of the packet towards the messages.

30.3.1. Fragment Numbers

If a packet has the header FLAG_IS_FRAGMENT, then it is part of a chain of packets where messages span the packet boundaries.

Typically, this will occur when trying to send messages larger than the maximum allowable UDP packet size (e.g., large writes to the database).

The sequence numbers for the first and last packet in the sequence of fragments are written to the footer of every packet in the sequence. Therefore, the footer consists of two 4-byte integers specifying the relevant sequence numbers.

30.3.2. Sequence Number

Given by FLAG_HAS_SEQUENCE_NUMBER, this is a single 4-byte sequence number that is streamed onto reliable packets so that they can be acknowledged.

30.3.3. ACKs

Given by FLAG_HAS_ACKS, these are acknowledgement messages corresponding to sequence numbers from previous outgoing packets.

This is a sequence of 4-byte sequence numbers, followed by a single byte indicating the number of ACKs on the packet.

30.3.4. Indexed Channel ID

Given by FLAG_INDEXED_CHANNEL, this is the channel ID of a packet on an indexed channel, as opposed to a normal channel that is identified by the source address of the packet.

30.3.5. First Request Offset and replyID

When a request is sent to a server component (for example, the LoginInterface 'probe' message, which the server replies to with information about itself) the requester will attach a replyID to the message. The server will attach the same ID to the reply so that the requester can correlate the reply to its original request.

If a message is a request, then an extra field will be added to the packet between the header and the message payload. This extra field is not accounted for by any variable-length fields. The field will contain the 4-byte replyID, followed by the 2-byte offset (from the start of the packet) of the next replyID in the same packet. The reason that the address of the next replyID needs to be specified is so that the replyIDs can be distinguished from regular message payloads and parsed correctly.

If any of the messages on a packet have replyIDs, the header will have FLAG_HAS_REQUESTS set, and the footer will contain the 2-byte offset of the first replyID on the packet. The last request on a packet will have a next-request-offset of 0.