QuicClientConnection
struct QuicClientConnectionA single client-role QUIC connection over a connected UDP flow.
Owns the UDP socket, the rustls client session, and the
sans-I/O :class:flare.quic.state.Connection. Construct via
:meth:start (non-blocking: sends the first Initial) or
:meth:connect (blocks until the handshake completes). After
the handshake the H3 layer drives streams via
:meth:open_bidi_stream, :meth:send_stream, and :meth:poll.
Implements Movable
- Fields
conn: Connection: Sans-I/O connection state: per-stream + flow-control bookkeeping, reused unchanged from the server path.session: RustlsQuicSession: The rustls client session: drives the TLS 1.3 handshake inside CRYPTO frames and holds the Handshake / 1-RTT AEAD keys.sock: UdpSocketpeer: SocketAddrinitial_dcid: ConnectionId: Client-chosen Destination CID placed on the first Initial. Both ends derive Initial secrets from this value (RFC 9001 §5.2); it is the AEADdcidfor every Initial packet regardless of any later CID change.first_dcid: ConnectionId: The DCID of our very first Initial, before any Retry. The server must echo it asoriginal_destination_connection_id.retry_scid: List[UInt8]: Source CID of the Retry we followed, empty if none.server_scid: List[UInt8]: Source CID of the server's first Initial; the server must echo it asinitial_source_connection_id.dcid: ConnectionId: Outbound Destination CID for routing. Starts equal to :attr:initial_dcid; switches to the server's Source CID once the first server long-header packet is parsed (RFC 9000 §7.2).scid: ConnectionId: Client Source CID -- our own identity. Its length is the pinned DCID length for parsing inbound short-header 1-RTT packets (RFC 9000 §17.3).aead_choice: Intmax_udp_payload_size: Inttx_initial_pn: UInt64tx_initial_offset: UInt64tx_handshake_pn: UInt64tx_handshake_offset: UInt64tx_1rtt_pn: UInt64key_phase: Bool: 1-RTT key phase bit we send; flips on every key update (RFC 9001 sec 6).tx_initial_crypto: List[UInt8]: Outbound Initial-level CRYPTO awaiting egress (the ClientHello before it is first sent; empty afterwards).tx_handshake_crypto: List[UInt8]: Outbound Handshake-level CRYPTO (the client Finished).tx_1rtt_crypto: List[UInt8]: Outbound 1-RTT-level CRYPTO (rare client post-handshake messages).rx_initial_ranges: List[UInt64]rx_handshake_ranges: List[UInt64]rx_1rtt_ranges: List[UInt64]rx_initial_ack_pending: Boolrx_handshake_ack_pending: Boolrx_1rtt_ack_pending: Boolrx_initial_largest: UInt64rx_handshake_largest: UInt64rx_1rtt_largest: UInt64reasm: _CryptoReasmhave_hs_keys: Boolhave_1rtt_keys: Boolgot_server_cid: Boolestablished: Boolretry_token: List[UInt8]: Address-validation token from an inbound Retry (RFC 9000 sec 8.1), echoed on every subsequent Initial. Empty until a Retry arrives.retried: Bool: True once a Retry has been consumed; further Retries are ignored (RFC 9000 sec 17.2.5.2).first_initial_crypto: List[UInt8]: Cached ClientHello CRYPTO so the Initial can be re-sent verbatim (with the token + new Initial keys) after a Retry.next_bidi_stream: UInt64: Next client-initiated bidirectional stream id to hand out (RFC 9000 §2.1: client bidi ids are 0, 4, 8, ...).next_uni_stream: UInt64: Next client-initiated unidirectional stream id to hand out (RFC 9000 §2.1: client uni ids are 2, 6, 10, ...). H3 opens three of these per connection: control + QPACK encoder/decoder.send_offsets: Dict[UInt64, UInt64]: Per-stream cumulative send offset for outbound STREAM frames.enable_0rtt: Bool: Opt-in TLS session resumption / 0-RTT (RFC 9001 §4.6). Off by default so the standard path is byte-for-byte unchanged. When the same :class:RustlsQuicConnectoris reused for a later connection to a server that issued a 0-RTT-capable ticket, rustls resumes the session and (in-process only -- rustls 0.23 has no cross-process ticket export) exposes early keys.ecn_rx: List[UInt64]: ECT(0), ECT(1) and CE counts of 1-RTT packets received, reported in ACK_ECN frames (RFC 9000 sec 13.4.1).
__init__
def __init__(out self, var conn: Connection, var session: RustlsQuicSession, var sock: UdpSocket, peer: SocketAddr, var initial_dcid: ConnectionId, var scid: ConnectionId, aead_choice: Int, max_udp_payload_size: Int) start
def start(peer: SocketAddr, connector: RustlsQuicConnector, server_name: String, max_idle_timeout_ms: UInt64 = UInt64(30000), initial_max_data: UInt64 = UInt64(1048576), max_udp_payload_size: Int = Int(1452), aead_choice: Int = Int(0), enable_0rtt: Bool = False, max_datagram_frame_size: UInt64 = UInt64(0), token: List[UInt8] = List(), ecn: Bool = True) -> Self Open a client connection and emit the first Initial (ClientHello) packet without blocking on the response.
Picks the Initial DCID + client SCID, builds the rustls
client session with the encoded client transport
parameters, drains the ClientHello, and ships the padded
Initial datagram. Drive the handshake to completion with
:meth:poll (or use :meth:connect for the blocking
convenience).
max_datagram_frame_size > 0 accepts DATAGRAM frames up to
that size (RFC 9221); they arrive in poll's
events.datagrams. token is an earlier connection's
:meth:address_token for the same server, which spares a Retry
round trip (RFC 9000 sec 8.1.3). ecn marks packets ECT(0) and
reports the server's marks (RFC 9000 sec 13.4; Linux).
connect
def connect(peer: SocketAddr, connector: RustlsQuicConnector, server_name: String, handshake_timeout_ms: Int = Int(5000), max_idle_timeout_ms: UInt64 = UInt64(30000), initial_max_data: UInt64 = UInt64(1048576), max_udp_payload_size: Int = Int(1452), aead_choice: Int = Int(0), enable_0rtt: Bool = False, max_datagram_frame_size: UInt64 = UInt64(0), token: List[UInt8] = List(), ecn: Bool = True) -> Self Open a client connection and block until the QUIC + TLS handshake completes (or handshake_timeout_ms elapses).
Raises on timeout. On return the connection is
ESTABLISHED and ready for :meth:send_stream.
enable_0rtt opts into session resumption (RFC 9001 §4.6):
reuse the same :class:RustlsQuicConnector across connections
so a later one resumes the cached session. Off by default.
poll
def poll(mut self, timeout_ms: Int = Int(100)) -> ConnectionEvents Receive a burst of inbound datagrams, advance the handshake / stream state, flush pending ACKs + CRYPTO, and return the per-call :class:ConnectionEvents.
Blocks up to timeout_ms for the first datagram, then
drains the rest of the socket queue non-blocking (up to
:data:_POLL_BATCH) so one poll consumes a whole server
flight. Returns empty events on a clean timeout.
open_bidi_stream
def open_bidi_stream(mut self) -> UInt64 Allocate the next client-initiated bidirectional stream id (RFC 9000 §2.1: 0, 4, 8, ...). The H3 request layer opens one bidi stream per request.
open_uni_stream
def open_uni_stream(mut self) -> UInt64 Allocate the next client-initiated unidirectional stream id (RFC 9000 §2.1: 2, 6, 10, ...). H3 opens three of these per connection (control + QPACK encoder/decoder).
send_stream
def send_stream(mut self, stream_id: UInt64, data: List[UInt8], fin: Bool) Send data (with optional FIN) on stream_id, fragmenting across as many 1-RTT STREAM frames / packets as the path MTU requires and advancing the per-stream offset. An empty FIN-only send emits a single zero-length frame so a body-less request still closes its stream.
send_datagram
def send_datagram(mut self, payload: Span[UInt8]) -> Bool Send payload now as an unreliable DATAGRAM (RFC 9221) in a packet of its own, never retransmitted (sec 5.2). False, and nothing sent, before the server's transport parameters are known, when it takes no DATAGRAMs or the frame exceeds its max_datagram_frame_size or one packet, or when the congestion window is full.
cancel_stream
def cancel_stream(mut self, stream_id: UInt64) Cancel both directions of an HTTP/3 request (H3_REQUEST_CANCELLED).
send_capacity
def send_capacity(self, stream_id: UInt64) -> Int Current flow-control credit for a client-initiated stream.
stream_reset
def stream_reset(self, stream_id: UInt64) -> Bool release_stream_credit
def release_stream_credit(mut self, stream_id: UInt64, consumed: UInt64) Replenish receive credit for newly consumed, ordered stream bytes.
send_stream_early
def send_stream_early(mut self, stream_id: UInt64, data: List[UInt8], fin: Bool) Send data (with optional FIN) on stream_id at 0-RTT (EarlyData), before the handshake completes.
The 0-RTT counterpart of :meth:send_stream: same
MTU-bounded STREAM-frame fragmentation and per-stream offset
bookkeeping (it shares :attr:send_offsets), but each frame
rides a :meth:_build_0rtt packet protected with the resumed
connection's EarlyData keys. Requires
:attr:_early_keys_ready (a resumed connection that installed
0-RTT keys -- see :meth:early_data_ready).
The full send is recorded in :attr:_early_flight so
:meth:finish_early_data can replay it at 1-RTT if the server
rejects early data. The emitted 0-RTT packets are registered with
the loss-recovery tracker (see :meth:_build_0rtt), so a lost
0-RTT packet is retransmitted as 1-RTT once keys are up (the
shared packet-number space makes this transparent).
On the reject path the registered 0-RTT packets are
never acked (the server discarded them), so the PTO eventually
re-sends their frames in addition to :meth:finish_early_data's
explicit 1-RTT replay. Both carry identical stream offsets, so
the receiver dedupes them -- the only cost is a few redundant
packets on rejection. Upgrade path: a drop-by-pn on the loss
tracker invoked from the reject branch.
finish_early_data
def finish_early_data(mut self) -> Bool Resolve the 0-RTT flight after the handshake completes.
Call once the connection is ESTABLISHED. Returns True when
the server accepted early data (the 0-RTT flight stands, nothing
more to send) and False when it rejected it (RFC 9001 sec
4.6) -- in which case the recorded flight
(:attr:_early_flight) is replayed at 1-RTT so the request
still completes, transparently to the caller.
A rejected 0-RTT flight leaves no stream state on the server
(rustls drops the early data), so the replay resends the
identical STREAM frames on the same stream ids from offset
0: each distinct early stream's offset is reset, then every
recorded send is re-emitted in order via :meth:send_stream
(1-RTT). The H3 response reader keys on the stream id, so it is
oblivious to which flight delivered the request.
keepalive
def keepalive(mut self) Send a 1-RTT PING to keep a pooled-idle connection alive and elicit a server ACK (RFC 9000 §10.1.2). A no-op until 1-RTT keys are installed.
migrate
def migrate(mut self, rebind: Bool = True) -> Bool Migrate the connection to a fresh network path.
Switches the active Destination CID to a spare the server
granted via NEW_CONNECTION_ID (RFC 9000 §9.5 requires an
unused CID per path so the two paths cannot be linked),
optionally rebinds the local UDP socket to a new ephemeral
port (the address change that defines a migration), and
probes the new path with a PATH_CHALLENGE (§8.2). The peer
echoes a PATH_RESPONSE which :meth:poll matches to mark
:attr:path_validated. The old CID is retired.
Returns False if the connection is not established or the
server has not granted a spare CID yet (no migration is
possible without one). Use :meth:migrate_and_validate for
the blocking convenience that polls until the new path is
confirmed.
migrate_and_validate
def migrate_and_validate(mut self, rebind: Bool = True, timeout_ms: Int = Int(2000)) -> Bool Migrate (see :meth:migrate) then block until the new path is validated by a PATH_RESPONSE or timeout_ms elapses. Returns True on a validated path.
initiate_key_update
def initiate_key_update(mut self) Start a 1-RTT key update (RFC 9001 sec 6.1): later packets go out under the next key generation and the other phase bit.
address_token
def address_token(self) -> List[UInt8] The server's latest NEW_TOKEN token (empty if none), to pass as token when connecting to it again (RFC 9000 sec 8.1.3).
is_established
def is_established(self) -> Bool Whether the QUIC + TLS handshake has completed and 1-RTT keys are installed.
early_data_ready
def early_data_ready(self) -> Bool Whether this connection resumed a session and rustls installed 0-RTT (EarlyData) keys (RFC 9001 §4.6). True only when :attr:enable_0rtt was set, the same connector was reused, and the server's prior ticket allowed early data.
When True, :meth:send_stream_early emits application STREAM
frames in 0-RTT (EarlyData) packets and
:meth:finish_early_data resolves acceptance after the
handshake (replaying at 1-RTT on rejection).
early_data_accepted
def early_data_accepted(self) -> Bool Whether the server accepted early data for this resumed connection (RFC 8446 §4.2.10; meaningful after the handshake completes). False on a fresh connection or when 0-RTT was not enabled. A client that sent 0-RTT data must replay it at 1-RTT when this is False.
path_validated
def path_validated(self) -> Bool Whether the most recent :meth:migrate probe has been confirmed by a matching PATH_RESPONSE (RFC 9000 §8.2).
active_dcid_seq
def active_dcid_seq(self) -> UInt64 Sequence number of the peer Connection ID currently used as the active Destination CID (0 is the handshake CID).
peer_cid_count
def peer_cid_count(self) -> Int Number of spare peer Connection IDs learned via NEW_CONNECTION_ID. A migration needs at least one.
alpn
def alpn(self) -> String Negotiated ALPN identifier (e.g. "h3"). Meaningful only after the handshake completes.
local_addr
def local_addr(self) -> SocketAddr The ephemeral local UDP address the client bound to.
shutdown
def shutdown(mut self) Gracefully close: send a 1-RTT application-level CONNECTION_CLOSE (RFC 9000 sec 10.2, error 0) so the peer tears the connection down immediately instead of waiting out its idle timeout, then close the socket. A no-op send until 1-RTT keys are installed.
close
def close(mut self) Close the underlying UDP socket. Idempotent.