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.

RFC 9000 · QUIC v1 RFC 9001 · TLS RFC 9221 · DATAGRAM RFC 9369 · v2 UDP varint
layer transport · TLS header long or short lengths varint-encoded byte order big-endian

01Packet headers

first byte · version · connection IDs

The 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 is not optional

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 bytes

QUIC 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.

PrefixLengthValue bitsMaximum valueExample
001 B663 · 0x3f0x25 → 37
012 B1416,383 · 0x3fff0x7b bd → 15293
104 B301,073,741,823 · 0x3fffffff0x9d 7f 3e 7d → 494878333
118 B622⁶² − 1 · 0x3fffffffffffffff0xc2 19 7c 5e ff 14 e8 8c → 151288809941952652

varint playground

0 to 2⁶²−1
Try a decimal value or a hex byte string.
result
no 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.
offsets are not varints

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 packets

After 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.

RFC 9000

STREAM type octet

The STREAM frame type spans 0x08–0x0f; three low bits switch optional fields on or off.

TypeFramePayload fieldsRFCNotes
0x00PADDINGzero bytes9000 §19.1May repeat; used for size and amplification limits.
0x01PING—9000 §19.2Acknowledgement of liveness; no payload.
0x02/03ACKlargest, delay, range count, first range, ranges, [ECN]9000 §19.30x03 adds ECT0/ECT1/CE counts.
0x04RESET_STREAMstream id, error code, final size9000 §19.4Abruptly terminates the sending side.
0x05STOP_SENDINGstream id, error code9000 §19.5Requests that the peer stop sending.
0x06CRYPTOoffset, length, data9000 §19.6Carries TLS handshake bytes.
0x07NEW_TOKENtoken length, token9000 §19.7Address validation token for a future connection.
0x08–0fSTREAMstream id, [offset], [length], data9000 §19.8OFF 0x04, LEN 0x02, FIN 0x01.
0x10MAX_DATAmaximum data9000 §19.9Connection-level flow control.
0x11MAX_STREAM_DATAstream id, maximum9000 §19.10Stream-level flow control.
0x12/13MAX_STREAMSmaximum streams9000 §19.110x12 bidirectional, 0x13 unidirectional.
0x14DATA_BLOCKEDmaximum data9000 §19.12Sender is blocked at the connection limit.
0x15STREAM_DATA_BLOCKEDstream id, maximum9000 §19.13Sender is blocked at the stream limit.
0x16/17STREAMS_BLOCKEDmaximum streams9000 §19.140x16 bidi, 0x17 uni.
0x18NEW_CONNECTION_IDsequence, retire prior to, length, CID, reset token9000 §19.15Provides an alternative connection ID.
0x19RETIRE_CONNECTION_IDsequence number9000 §19.16Retires a previously issued CID.
0x1a/1bPATH_CHALLENGE / RESPONSE8-byte data9000 §19.17–18Path validation; response echoes the challenge.
0x1c/1dCONNECTION_CLOSEerror code, [frame type], reason length, reason9000 §19.190x1d is application-level; 0x1c transport-level.
0x1eHANDSHAKE_DONE—9000 §19.20Server confirms the handshake.
0x30/31DATAGRAM[length], dataRFC 9221Unreliable application datagram.

04Frame builder

one frame at a time

Build 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-aware

Values are encoded as QUIC varints. Hex fields accept spaces, colons or 0x prefixes. Length prefixes are added automatically.

frame bytes

waiting
Choose a frame and build it.
frame hex
no bytes built yet
decode it below

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 frames

Paste 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 = varint
No frames decoded yet.

06Wire notes

before you trust a frame dump
you cannot parse a protected packet by hand

The 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.

References

next protocol BGP path attributes ->