QUIC headers & frames // RFC 9000
QUIC runs over UDP but its wire format is a stack of varints, protected headers and typed frames. This page separates the parts you can see on the wire from the parts that only exist after header protection and packet decryption.
01Packet headers
first byte · version · connection IDsThe first byte's top bit selects a long header (1) or a short header (0). Long headers carry a version and both connection IDs; short headers carry only the destination connection ID. The bottom bits are partly protected by header protection, so a passive observer cannot trust them naively.
Long header · first byte
Short header · first byte
Long packet types in QUIC v1: 00 Initial, 01 0-RTT,
10 Handshake, 11 Retry. Version 0x00000000 is a
Version Negotiation packet; Retry packets have no packet number and end with a 16-byte
Retry Integrity Tag.
Short header
Header protection masks the low bits of the first byte and the packet number field with a header-protection key derived from the same traffic secret. The first byte shown here is the logical, unprotected value. On the wire, the reserved bits, key phase and packet-number length may appear different until you remove protection (RFC 9001 §5.4).
02Variable-length integers
2-bit prefix · 1, 2, 4 or 8 bytesQUIC varints use the top two bits of the first byte as a length selector. The remaining 6, 14, 30 or 62 bits hold the value. Encoding is big-endian, and non-minimal encodings are legal although senders usually choose the shortest form.
| Prefix | Length | Value bits | Maximum value | Example |
|---|---|---|---|---|
00 | 1 B | 6 | 63 · 0x3f | 0x25 → 37 |
01 | 2 B | 14 | 16,383 · 0x3fff | 0x7b bd → 15293 |
10 | 4 B | 30 | 1,073,741,823 · 0x3fffffff | 0x9d 7f 3e 7d → 494878333 |
11 | 8 B | 62 | 2⁶² − 1 · 0x3fffffffffffffff | 0xc2 19 7c 5e ff 14 e8 8c → 151288809941952652 |
varint playground
0 to 2⁶²−1no result yet
why not protobuf varints?
different trade-off- QUIC's prefix is at the front, so the decoder knows the length before reading the value.
- The length is explicit in the top two bits, not discovered by continuation bits.
- A varint may use more bytes than necessary; the receiver accepts non-minimal forms.
- Frame types are varints too, but all standard v1 types fit in one byte.
Packet number length is 1–4 bytes and is encoded in the first byte (type-specific bits in a long header, low bits in a short header), not as a varint. The length field of a long header is a varint.
03Frame formats
frames live inside encrypted packetsAfter packet protection is removed, the payload is a sequence of frames. A single packet can carry many frames; a UDP datagram can carry several coalesced packets. Frame types are varints, and most frame fields are varints or length-prefixed bytes.
STREAM type octet
The STREAM frame type spans 0x08–0x0f; three low bits switch optional fields on or off.
| Type | Frame | Payload fields | RFC | Notes |
|---|---|---|---|---|
0x00 | PADDING | zero bytes | 9000 §19.1 | May repeat; used for size and amplification limits. |
0x01 | PING | — | 9000 §19.2 | Acknowledgement of liveness; no payload. |
0x02/03 | ACK | largest, delay, range count, first range, ranges, [ECN] | 9000 §19.3 | 0x03 adds ECT0/ECT1/CE counts. |
0x04 | RESET_STREAM | stream id, error code, final size | 9000 §19.4 | Abruptly terminates the sending side. |
0x05 | STOP_SENDING | stream id, error code | 9000 §19.5 | Requests that the peer stop sending. |
0x06 | CRYPTO | offset, length, data | 9000 §19.6 | Carries TLS handshake bytes. |
0x07 | NEW_TOKEN | token length, token | 9000 §19.7 | Address validation token for a future connection. |
0x08–0f | STREAM | stream id, [offset], [length], data | 9000 §19.8 | OFF 0x04, LEN 0x02, FIN 0x01. |
0x10 | MAX_DATA | maximum data | 9000 §19.9 | Connection-level flow control. |
0x11 | MAX_STREAM_DATA | stream id, maximum | 9000 §19.10 | Stream-level flow control. |
0x12/13 | MAX_STREAMS | maximum streams | 9000 §19.11 | 0x12 bidirectional, 0x13 unidirectional. |
0x14 | DATA_BLOCKED | maximum data | 9000 §19.12 | Sender is blocked at the connection limit. |
0x15 | STREAM_DATA_BLOCKED | stream id, maximum | 9000 §19.13 | Sender is blocked at the stream limit. |
0x16/17 | STREAMS_BLOCKED | maximum streams | 9000 §19.14 | 0x16 bidi, 0x17 uni. |
0x18 | NEW_CONNECTION_ID | sequence, retire prior to, length, CID, reset token | 9000 §19.15 | Provides an alternative connection ID. |
0x19 | RETIRE_CONNECTION_ID | sequence number | 9000 §19.16 | Retires a previously issued CID. |
0x1a/1b | PATH_CHALLENGE / RESPONSE | 8-byte data | 9000 §19.17–18 | Path validation; response echoes the challenge. |
0x1c/1d | CONNECTION_CLOSE | error code, [frame type], reason length, reason | 9000 §19.19 | 0x1d is application-level; 0x1c transport-level. |
0x1e | HANDSHAKE_DONE | — | 9000 §19.20 | Server confirms the handshake. |
0x30/31 | DATAGRAM | [length], data | RFC 9221 | Unreliable application datagram. |
04Frame builder
one frame at a timeBuild a frame and inspect its bytes. Frames are not independently protected; they are plaintext inside the encrypted packet payload. This builder constructs the frame body, not the enclosing QUIC packet header.
frame parameters
varint-awareValues are encoded as QUIC varints. Hex fields accept spaces, colons or 0x prefixes. Length prefixes are added automatically.
frame bytes
waitingno bytes built yet
The decoder accepts a sequence of frames, so you can paste this output and see the parsed fields. A frame is only valid inside the packet number space and encryption level where it was sent.
05Frame decoder
paste one or more framesPaste the plaintext payload of a QUIC packet: a sequence of frames, not the encrypted packet itself. The decoder walks frame types and lengths, consumes varints, and reports the bytes each frame occupies. Unknown or malformed frames stop the walk with an offset.
parse frames
frame type = varint06Wire notes
before you trust a frame dumpThe long/short headers and frame list are logical views. On the wire, header protection masks bits and the payload is AEAD-protected. A “QUIC parser” that ignores keys can only decode the invariant portion of the connection ID and version. Wireshark shows decrypted frames only when you supply the TLS secrets.
- Version Negotiation has version 0. Its long header has no length field and the connection IDs are followed by a list of supported versions.
- Retry has no packet number. It ends with a 16-byte Retry Integrity Tag, not a payload.
- Packet numbers are not frame fields. They live in the packet header and are used in the AEAD nonce; frame types and stream offsets are separate.
- STREAM data without LEN runs to the end of the packet. A parser must not assume another frame follows; the remaining bytes belong to that frame.
- Coalescing is normal. Multiple QUIC packets can share one UDP datagram. A frame parser sees only the decrypted payload of one packet.
- PADDING is a frame, not a byte-stuffing afterthought. It inflates packets for amplification limits and is protected like everything else.