Skip to main content
  1. AMQP 0-9-1: The Complete Protocol/

Wire Format and Framing

Wire Format and Framing #

AMQP 0-9-1 transmits data as a sequence of frames over a TCP connection. Every method call, every message, every acknowledgment is encoded as one or more frames. Understanding the frame format is essential for implementing clients, debugging protocol issues, and understanding why certain AMQP behaviors work the way they do.

The Frame Structure #

Every AMQP frame has the same outer structure:

+--------+----------+-----------+----------+--------+
| type   | channel  | size      | payload  | frame- |
| (1 byte)| (2 bytes)| (4 bytes) | (n bytes)| end    |
+--------+----------+-----------+----------+ (1 byte)|
FieldTypeDescription
typeuint8Frame type (1=method, 2=content header, 3=content body, 8=heartbeat)
channeluint16Channel number (0 for connection-level)
sizeuint32Byte length of payload
payloadbytesFrame content (type-specific)
frame-enduint8Must be 0xCE — frame boundary marker

The frame-end sentinel (0xCE) detects frame corruption. If the byte at position size + 7 is not 0xCE, the frame is malformed and the broker closes the connection with FRAME_ERROR.

Total minimum frame size: 7 bytes (header) + 0 bytes (empty payload) + 1 byte (frame-end) = 8 bytes. A heartbeat frame is exactly 8 bytes.

Maximum frame size: negotiated in connection.tune as frame-max. The payload cannot exceed frame-max bytes. Large message bodies are split across multiple content body frames.

Frame Types #

Type codeNamePurpose
1METHODCarries a protocol method (exchange.declare, basic.publish, etc.)
2HEADERCarries message properties and body size
3BODYCarries a chunk of message body
4OOB_METHODOut-of-band method (rarely used)
5OOB_HEADEROut-of-band header (rarely used)
6OOB_BODYOut-of-band body (rarely used)
7TRACETrace frame (not in 0-9-1 final spec)
8HEARTBEATKeepalive, no payload

In practice, only types 1, 2, 3, and 8 are used in AMQP 0-9-1 implementations.

Method Frame (Type 1) #

A method frame encodes an AMQP method call. The payload has a fixed structure:

Method frame payload:
+------------+----------+-------------------+
| class-id   | method-id| method arguments  |
| (2 bytes)  | (2 bytes)| (variable)        |
+------------+----------+-------------------+

Class ID and Method ID together identify the method. AMQP organizes methods into classes:

Class IDClass nameExample methods
10connectionstart, start-ok, tune, tune-ok, open, open-ok, close, close-ok
20channelopen, open-ok, flow, flow-ok, close, close-ok
30access(deprecated)
40exchangedeclare, declare-ok, delete, delete-ok, bind, bind-ok, unbind, unbind-ok
50queuedeclare, declare-ok, bind, bind-ok, unbind, unbind-ok, purge, purge-ok, delete, delete-ok
60basicqos, qos-ok, consume, consume-ok, cancel, cancel-ok, publish, return, deliver, get, get-ok, get-empty, ack, reject, recover-async, recover, recover-ok, nack
85confirmselect, select-ok
90txselect, select-ok, commit, commit-ok, rollback, rollback-ok

Method arguments are encoded using AMQP’s type system:

AMQP typeSizeExample
bit1 bit (packed)durable, exclusive flags
octet1 bytedelivery-mode, priority
short2 bytes unsignedchannel-max, reply-code
long4 bytes unsignedframe-max, message-count
longlong8 bytes unsigneddelivery-tag, timestamp
shortstr1-byte length + N bytesreply-text, routing-key (max 255 bytes)
longstr4-byte length + N bytessasl-response, large strings
table4-byte length + field pairsheaders, server-properties
timestamp8 bytes (longlong)timestamp property
decimal1-byte scale + 4-byte value(rare)

Bit packing: consecutive bit arguments in a method are packed into octets, MSB to LSB. For example, queue.declare has arguments passive, durable, exclusive, auto-delete, no-wait — five bits, packed into one octet:

bits: passive(1) durable(1) exclusive(1) auto-delete(1) no-wait(1) → 0b00011111

Example: basic.publish Method Frame #

Publishing a message to exchange events with routing key order.created, mandatory=False, immediate=False:

Frame type:  0x01 (METHOD)
Channel:     0x00 0x01 (channel 1)
Size:        0x00 0x00 0x00 0x1C (28 bytes)
Payload:
  Class ID:  0x00 0x3C (60 = basic)
  Method ID: 0x00 0x28 (40 = publish)
  reserved:  0x00 0x00 (short, always 0)
  exchange:  0x06 'events' (shortstr: 1-byte length + 6 bytes)
  routing-key: 0x0D 'order.created' (shortstr: 1-byte length + 13 bytes)
  bits:      0x00 (mandatory=0, immediate=0)
Frame-end:   0xCE

Total frame: 7 (header) + 28 (payload) + 1 (frame-end) = 36 bytes.

Content Header Frame (Type 2) #

After the basic.publish method frame, the producer sends a content header frame describing the message:

Content header payload:
+----------+----------+-----------+--------------------+
| class-id | weight   | body-size | property flags     | property list...
| (2 bytes)| (2 bytes)| (8 bytes) | (2+ bytes)         |
+----------+----------+-----------+--------------------+
  • class-id: always 60 (basic) for messages.
  • weight: always 0 (reserved, not used).
  • body-size: total body length in bytes (across all body frames).
  • property flags: a bitmask indicating which of the 14 properties are present.
  • property list: the non-null property values, encoded in order.

Property flags bitmask (2 bytes, potentially extended):

Bit 15 (MSB): content-type present?
Bit 14: content-encoding present?
Bit 13: headers present?
Bit 12: delivery-mode present?
Bit 11: priority present?
Bit 10: correlation-id present?
Bit  9: reply-to present?
Bit  8: expiration present?
Bit  7: message-id present?
Bit  6: timestamp present?
Bit  5: type present?
Bit  4: user-id present?
Bit  3: app-id present?
Bit  2: reserved (cluster-id, ignored)
Bit  1: continuation flag (another 2-byte flags word follows if set)

Only properties with their flag bit set are included in the property list. A message with only delivery-mode=2 and content-type='application/json' has a 2-byte flags word with bits 15 and 12 set:

Flags: 0b1001000000000000 = 0x9000
Property list:
  content-type: 0x10 'application/json' (shortstr)
  delivery-mode: 0x02 (octet)

Total content header payload: 2+2+8+2+17+1 = 32 bytes.

Content Body Frame (Type 3) #

The message body is split into one or more body frames, each at most frame-max - 8 bytes:

Body frame payload: raw body bytes

For a 1024-byte body with frame-max=131072:

  • One body frame, payload = 1024 bytes.
  • Total frame size: 7 + 1024 + 1 = 1032 bytes.

For a 500,000-byte body with frame-max=131072:

  • Body frame 1: 131064 bytes of body (131072 - 8)
  • Body frame 2: 131064 bytes of body
  • Body frame 3: 131064 bytes of body
  • Body frame 4: remaining 106808 bytes of body
  • Consumer receives all body bytes concatenated = 500000 bytes.

Complete Message Publish Sequence #

Publishing a 24-byte JSON message, delivery-mode=2, content-type='application/json':

Frame 1 — METHOD (basic.publish):
  type=1, channel=1, size=X
  class=60, method=40
  exchange='', routing_key='orders', mandatory=0, immediate=0

Frame 2 — HEADER:
  type=2, channel=1, size=32
  class=60, weight=0, body_size=24
  flags=0x9000 (content-type + delivery-mode)
  content-type='application/json'
  delivery-mode=2

Frame 3 — BODY:
  type=3, channel=1, size=24
  payload: {"order_id":"123"}

When consumed, the broker sends the same three-frame sequence but with a basic.deliver method frame instead of basic.publish.

Heartbeat Frame (Type 8) #

The heartbeat frame has an empty payload:

type=8, channel=0, size=0, frame-end=0xCE
Total: 8 bytes

Both client and broker send heartbeat frames on channel 0 when no other frames have been sent within the heartbeat interval. The heartbeat frame is the only frame with a zero-size payload.

Table Encoding #

The table type (used for headers, server-properties, binding arguments) encodes key-value pairs:

table := 4-byte-length field-pair*
field-pair := shortstr-key field-value
field-value := type-indicator-byte value

Type indicator bytes:

ByteTypeSize
t (0x74)boolean1 byte
b (0x62)short-short (signed byte)1 byte
B (0x42)short-short (unsigned byte)1 byte
U (0x55)short int (signed)2 bytes
u (0x75)short int (unsigned)2 bytes
I (0x49)long int (signed)4 bytes
i (0x69)long int (unsigned)4 bytes
L (0x4C)long long (signed)8 bytes
l (0x6C)long long (unsigned)8 bytes
f (0x66)float4 bytes
d (0x64)double8 bytes
D (0x44)decimal5 bytes
S (0x53)long string4-byte length + N bytes
A (0x41)array4-byte length + field-values
T (0x54)timestamp8 bytes
F (0x46)nested tabletable encoding
V (0x56)void0 bytes
x (0x78)byte array (RabbitMQ extension)4-byte length + N bytes

String encoding: AMQP uses shortstr (max 255 bytes, 1-byte length prefix) for method arguments and longstr (max ~4GB, 4-byte length prefix) for table values and larger fields. The distinction matters when encoding headers — header values use S type (longstr), not shortstr.

Debugging with Wireshark #

Wireshark has a built-in AMQP 0-9-1 dissector. Filter: amqp. The dissector decodes all frame types, shows the method class/ID, and displays properties. Useful for:

  • Verifying client library behavior.
  • Debugging unexpected channel closes.
  • Measuring frame sizes and connection behavior.

For TLS connections, configure Wireshark with the server’s private key to decrypt.

Summary #

AMQP 0-9-1 frames are the atomic unit of protocol exchange: a 7-byte header (type, channel, size), payload, and 0xCE sentinel. Method frames carry encoded method calls (class+method ID + typed arguments). Content header frames carry the 14 standard properties via a bitmask + property list. Content body frames carry raw payload bytes, split to fit within frame-max. Heartbeat frames are 8 bytes, channel 0 only. Table encoding uses type-indicator prefixed values and supports nesting. The property flags bitmask means only set properties travel the wire — sparse property sets are compact.