Wire Format and Framing
Table of Contents
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)|
| Field | Type | Description |
|---|---|---|
type | uint8 | Frame type (1=method, 2=content header, 3=content body, 8=heartbeat) |
channel | uint16 | Channel number (0 for connection-level) |
size | uint32 | Byte length of payload |
payload | bytes | Frame content (type-specific) |
frame-end | uint8 | Must 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 code | Name | Purpose |
|---|---|---|
| 1 | METHOD | Carries a protocol method (exchange.declare, basic.publish, etc.) |
| 2 | HEADER | Carries message properties and body size |
| 3 | BODY | Carries a chunk of message body |
| 4 | OOB_METHOD | Out-of-band method (rarely used) |
| 5 | OOB_HEADER | Out-of-band header (rarely used) |
| 6 | OOB_BODY | Out-of-band body (rarely used) |
| 7 | TRACE | Trace frame (not in 0-9-1 final spec) |
| 8 | HEARTBEAT | Keepalive, 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 ID | Class name | Example methods |
|---|---|---|
| 10 | connection | start, start-ok, tune, tune-ok, open, open-ok, close, close-ok |
| 20 | channel | open, open-ok, flow, flow-ok, close, close-ok |
| 30 | access | (deprecated) |
| 40 | exchange | declare, declare-ok, delete, delete-ok, bind, bind-ok, unbind, unbind-ok |
| 50 | queue | declare, declare-ok, bind, bind-ok, unbind, unbind-ok, purge, purge-ok, delete, delete-ok |
| 60 | basic | qos, qos-ok, consume, consume-ok, cancel, cancel-ok, publish, return, deliver, get, get-ok, get-empty, ack, reject, recover-async, recover, recover-ok, nack |
| 85 | confirm | select, select-ok |
| 90 | tx | select, select-ok, commit, commit-ok, rollback, rollback-ok |
Method arguments are encoded using AMQP’s type system:
| AMQP type | Size | Example |
|---|---|---|
| bit | 1 bit (packed) | durable, exclusive flags |
| octet | 1 byte | delivery-mode, priority |
| short | 2 bytes unsigned | channel-max, reply-code |
| long | 4 bytes unsigned | frame-max, message-count |
| longlong | 8 bytes unsigned | delivery-tag, timestamp |
| shortstr | 1-byte length + N bytes | reply-text, routing-key (max 255 bytes) |
| longstr | 4-byte length + N bytes | sasl-response, large strings |
| table | 4-byte length + field pairs | headers, server-properties |
| timestamp | 8 bytes (longlong) | timestamp property |
| decimal | 1-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:
| Byte | Type | Size |
|---|---|---|
t (0x74) | boolean | 1 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) | float | 4 bytes |
d (0x64) | double | 8 bytes |
D (0x44) | decimal | 5 bytes |
S (0x53) | long string | 4-byte length + N bytes |
A (0x41) | array | 4-byte length + field-values |
T (0x54) | timestamp | 8 bytes |
F (0x46) | nested table | table encoding |
V (0x56) | void | 0 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.