Skip to main content

QUIC frames — RFC 9000 §19

Every QUIC packet carries a sequence of frames. The frame layer lives in core.net.quic.frame and exposes a single QuicFrame sum type with 24 variants (core/net/quic/frame.vr:169). The count is not the number of RFC type codes: several codes share one variant (AckFrame covers 0x02/0x03) and several variants split one concept (MaxStreamsBidi / MaxStreamsUni, ConnectionCloseTransport / ConnectionCloseApplication). The last is Datagram, from RFC 9221. Each frame is serialised as:

Type (QUIC varint) || [frame-specific fields]

Frame catalogue​

TypeNameAck-elicitingProbingModuleKAT
0x00PADDING—✓§19.1misc_frames_kat
0x01PING✓—§19.2misc_frames_kat
0x02/03ACK / ACK_ECN——§19.3ack_frame_encode_kat, ack_frame_large_values_kat
0x04RESET_STREAM✓—§19.4misc_frames_kat
0x05STOP_SENDING✓—§19.5misc_frames_kat
0x06CRYPTO✓—§19.6crypto_stream_frame_encode_kat
0x07NEW_TOKEN✓—§19.7misc_frames_kat, new_token_datagram_surface
0x08–0x0FSTREAM (8 flag combinations)✓—§19.8crypto_stream_frame_encode_kat, stream_frame_large_varint_kat
0x10MAX_DATA✓—§19.9flow_control_frames_kat
0x11MAX_STREAM_DATA✓—§19.10flow_control_frames_kat
0x12/13MAX_STREAMS (bidi/uni)✓—§19.11flow_control_frames_kat
0x14DATA_BLOCKED✓—§19.12flow_control_frames_kat
0x15STREAM_DATA_BLOCKED✓—§19.13flow_control_frames_kat
0x16/17STREAMS_BLOCKED (bidi/uni)✓—§19.14flow_control_frames_kat
0x18NEW_CONNECTION_ID✓✓§19.15new_retire_cid_frame_kat
0x19RETIRE_CONNECTION_ID✓—§19.16new_retire_cid_frame_kat
0x1APATH_CHALLENGE✓✓§19.17misc_frames_kat
0x1BPATH_RESPONSE✓✓§19.18misc_frames_kat
0x1CCONNECTION_CLOSE (transport)——§19.19connection_close_datagram_kat
0x1DCONNECTION_CLOSE (app)——§19.20connection_close_datagram_kat
0x1EHANDSHAKE_DONE✓—§19.21misc_frames_kat
0x30/31DATAGRAM (no-len / with-len)✓—RFC 9221connection_close_datagram_kat

The "Ack-eliciting" column drives whether the peer must send an ACK within max_ack_delay. "Probing" marks frames that MAY travel on a path that hasn't yet been validated (RFC 9000 §9.1). Both attributes are public predicates on QuicFrame: QuicFrame.is_ack_eliciting() and QuicFrame.is_probing() (core/net/quic/frame.vr:205, :216).

Variable-length integer encoding​

Every frame field tagged (i) in RFC 9000 is a QUIC varint (§16). The top 2 bits of the first byte encode the total length:

PrefixLengthValue range
001 byte0 ..= 63
012 bytes0 ..= 16,383
104 bytes0 ..= 2³⁰ − 1
118 bytes0 ..= 2⁶² − 1

The primitive lives in core.encoding.varint as quic_encode / quic_decode. Every RFC 9000 §A.1 worked example is pinned byte-exact in varint_examples_errors_kat:

mount core.encoding.varint.{quic_decode};

// RFC 9000 §A.1: 0x25 → 37
let (v, _) = quic_decode(&[0x25_u8], 0).unwrap();
assert(v == 37_u64);

// 0x7b 0xbd → 15293
let (v, _) = quic_decode(&[0x7B_u8, 0xBD_u8], 0).unwrap();
assert(v == 15_293_u64);

// 0xc2 0x19 0x7c 0x5e 0xff 0x14 0xe8 0x8c → 151_288_809_941_952_652
let wire: List<Byte> = [
0xC2_u8, 0x19_u8, 0x7C_u8, 0x5E_u8,
0xFF_u8, 0x14_u8, 0xE8_u8, 0x8C_u8,
];
let (v, _) = quic_decode(wire.as_slice(), 0).unwrap();
assert(v == 151_288_809_941_952_652_u64);

STREAM frame type byte​

STREAM (§19.8) encodes three flag bits in the type byte itself:

Type = 0b00001OLF where
O = OFF bit (offset field present)
L = LEN bit (length field present)
F = FIN bit (final frame on this stream)

core.net.quic.frame.encode_frame always sets L, sets O iff offset > 0, and sets F per the frame's fin field. The resulting type byte values are:

TypeCondition
0x0Aoffset = 0, fin = false
0x0Boffset = 0, fin = true
0x0Eoffset > 0, fin = false
0x0Foffset > 0, fin = true

Values 0x08, 0x09, 0x0C, 0x0D are valid on the wire (LEN bit clear — length derived from the enclosing packet). warp emits only the L-set forms because it coalesces multiple frames per packet.

ACK frame layout​

ACK Frame {
Type (i) = 0x02..0x03,
Largest Acknowledged (i),
ACK Delay (i),
ACK Range Count (i),
First ACK Range (i), ← length of first range
ACK Range (..) ... , ← (gap, range_length) pairs
[ECN Counts (..)], ← three varints when type = 0x03
}

Ranges are stored in descending PN order. The gap between range i and range i+1 satisfies prev.smallest = curr.largest + gap + 2 (§19.3.1). The ECN Counts trailer is present iff Type = 0x03 and carries three varints for ECT(0), ECT(1), and CE counters.

AckRanges is a refinement-typed List<AckRange> where every sequence of insert_pn calls is proven by the SMT layer to preserve non-overlapping + strictly-descending + gap ≥ 2 (V3 theorem).

CONNECTION_CLOSE​

Two distinct type bytes encode transport-level vs application-level errors (§19.19 / §19.20):

Transport (0x1C): type(i) | error_code(i) | frame_type(i) | reason_len(i) | reason_bytes
Application (0x1D): type(i) | error_code(i) | reason_len(i) | reason_bytes

The frame_type field on transport variants identifies the source frame that triggered the error (0 = generic). Neither variant is ack-eliciting (§13.2.1) — the peer MUST NOT ACK a CONNECTION_CLOSE. core.net.quic.error.TransportErrorCode catalogues the 17 registered transport errors plus the CRYPTO_ERROR_BASE = 0x0100 range used for TLS Alert relay.

DATAGRAM (RFC 9221)​

Two type bytes distinguish length-less vs length-prefixed datagrams:

0x30: type | data (consumes rest of packet)
0x31: type | length(i) | data

Use the with-length form when the datagram is not the last frame in the packet; the no-length form is lighter but must be terminal. max_datagram_frame_size (transport parameter 0x20) advertises peer support.

References​

KAT files, all typecheck-pass:

  • vcs/specs/L2-standard/net/quic/ack_frame_encode_kat.vr
  • vcs/specs/L2-standard/net/quic/ack_frame_large_values_kat.vr
  • vcs/specs/L2-standard/net/quic/crypto_stream_frame_encode_kat.vr
  • vcs/specs/L2-standard/net/quic/stream_frame_large_varint_kat.vr
  • vcs/specs/L2-standard/net/quic/flow_control_frames_kat.vr
  • vcs/specs/L2-standard/net/quic/misc_frames_kat.vr
  • vcs/specs/L2-standard/net/quic/new_retire_cid_frame_kat.vr
  • vcs/specs/L2-standard/net/quic/connection_close_datagram_kat.vr
  • vcs/specs/L2-standard/net/quic/new_token_datagram_surface.vr
  • vcs/specs/L2-standard/net/quic/varint_kat.vr
  • vcs/specs/L2-standard/net/quic/varint_examples_errors_kat.vr
  • vcs/specs/L2-standard/net/quic/frame_type_codepoints.vr