flare_mlake · API · quic

flare.quic.packet

QUIC v1 packet header codec (RFC 9000 §17).

QUIC packets come in two flavours:

  • Long header (§17.2) — used during the handshake (Initial, 0-RTT, Handshake, Retry). Carries the QUIC version and both Destination + Source Connection IDs explicitly.
  • Short header (§17.3) — used after the handshake (1-RTT). Carries only the Destination Connection ID; the receiver already knows the SCID by virtue of the prior handshake.

This module parses the public (unprotected) parts of these headers: the first byte's flag bits, the version, and the connection IDs. The packet number and the length-prefixed payload live inside header-protection-protected bytes that require key material to access; that decryption layer is the job of a later cycle's TLS adapter.

Connection IDs are 0..20 octet identifiers (§5.1.1). The long header explicitly carries a length byte for each CID; the short header relies on the receiver knowing the DCID length out of band (typically pinned at handshake time).

References:

  • RFC 9000 §17 "Packet Formats".
  • RFC 9000 §5.1 "Connection IDs".
  • aioquic packet.PullQuicHeader / packet.encode_quic_header.

Structs

ConnectionId

struct ConnectionId

A QUIC connection identifier (0..20 octets per §5.1.1).

Implements Copyable, Movable

Fields
bytes: List[UInt8]

length

def length(self) -> Int

copy

def copy(self) -> Self

Return a deep copy of this CID (so callers can move the original into a parent struct without invalidating downstream references).

LongHeader

struct LongHeader

Parsed long-header fields.

The payload_offset is the byte index inside the original buffer where the (still encrypted) packet payload begins. Callers that decrypt header protection use this offset as the starting point. For version negotiation packets the packet_type field is irrelevant and the payload is the list of supported versions; the parser leaves it on the caller to inspect version == QUIC_VERSION_NEGOTIATION.

Implements Copyable, Movable

Fields
packet_type: Int
version: UInt32
dcid: ConnectionId
scid: ConnectionId
payload_offset: Int

InitialExtras

struct InitialExtras

Initial-specific extras that follow the long-header common prefix (§17.2.2): a length-prefixed token, then a varint declaring the protected payload length.

Implements Copyable, Movable

Fields
token: List[UInt8]
payload_length: UInt64
consumed: Int

ShortHeader

struct ShortHeader

Parsed short-header (1-RTT) public fields.

The DCID length is not on the wire; the caller passes it in (typically pinned at handshake time). The key_phase bit is exposed because callers track key updates per connection.

Implements Copyable, Movable

Fields
spin_bit: Bool
key_phase: Bool
dcid: ConnectionId
payload_offset: Int

Functions

parse_long_header

def parse_long_header(buf: Span[UInt8]) -> LongHeader

Parse a long-header packet's public fields.

The first byte's high two bits must be 0b11 (long header indicator). The next two bits carry the packet type. Bits 0-3 are version-specific (reserved + packet-number length in QUIC v1, but those are header-protected and not parsed here).

Then a 32-bit version, length-prefixed DCID, length-prefixed SCID. Initial-specific token + length parsing is split into :func:parse_initial_extras because Retry/Handshake/0-RTT packets share the same prefix but diverge from here.

parse_initial_extras

def parse_initial_extras(buf: Span[UInt8], offset: Int) -> InitialExtras

Parse the Initial-specific token + length fields starting at offset (typically the payload_offset returned by :func:parse_long_header for a packet whose type is Initial).

parse_short_header

def parse_short_header(buf: Span[UInt8], dcid_length: Int) -> ShortHeader

Parse a short-header (1-RTT) packet's public fields.

The first byte's high two bits must be 0b01 (short header indicator + fixed bit). Bit 5 is the spin bit (§17.4); bits 3-4 are reserved + key-phase, but the key-phase bit (bit 2) is header-protected, so callers that need it must apply header protection first. The parser exposes key_phase as a raw bit read for callers that have already deprotected.

packet_number_length

def packet_number_length(pn: UInt64, largest_acked: UInt64, any_acked: Bool) -> Int

Bytes needed to encode pn so the peer can recover it (RFC 9000 sec 17.1, appendix A.2): enough bits for twice the distance from the largest acknowledged packet, at least 2 bytes, at most 4.

encode_long_header

def encode_long_header(packet_type: Int, version: UInt32, dcid: ConnectionId, scid: ConnectionId, type_specific_bits: Int = Int(0)) -> List[UInt8]

Encode the long-header public prefix.

type_specific_bits populates the low 4 bits of the first byte. For QUIC v1 those bits are reserved bits 2-3 + the encoded packet-number length in bits 0-1 (a 2-bit value, one less than the actual length). The caller computes these bits; the encoder just writes them.

encode_short_header

def encode_short_header(dcid: ConnectionId, spin_bit: Bool, key_phase: Bool, pn_length: Int = Int(1)) -> List[UInt8]

Encode the short-header public prefix.

pn_length is the encoded packet-number length (1..4); this is the value the receiver would observe after header protection is removed. The encoder writes it into bits 0-1 (value minus one). Header protection itself is the caller's job after this prefix is emitted.

encode_short_header_into

def encode_short_header_into(dcid: ConnectionId, spin_bit: Bool, key_phase: Bool, pn_length: Int, mut out: List[UInt8])

Append the short-header public prefix to out; see :func:encode_short_header.

Aliases

comptime QUIC_VERSION_1 = UInt32(1)

RFC 9000's "version 1" wire constant. Returned by the parser when the long header carries the standard QUIC v1 version.

comptime QUIC_VERSION_NEGOTIATION = UInt32(0)

Version negotiation packets (RFC 9000 §17.2.1) carry version=0 in the long header. The parser exposes this constant so callers can detect VN packets cleanly without magic numbers.

comptime PACKET_TYPE_INITIAL = Int(0)
comptime PACKET_TYPE_ZERO_RTT = Int(1)
comptime PACKET_TYPE_HANDSHAKE = Int(2)
comptime PACKET_TYPE_RETRY = Int(3)

Long-header packet types (§17.2). The two-bit T T field in bits 4-5 of the first byte selects one of these four values.

comptime MAX_CID_LENGTH = Int(20)

Maximum Connection ID length (§5.1.1). Receivers that observe a CID length greater than 20 octets must reject the packet at the codec boundary.