quic_connection (quic v3.0.0)

View Source

QUIC connection state machine implemented as gen_statem.

This module manages the lifecycle of a QUIC connection, handling: - TLS 1.3 handshake via CRYPTO frames - Packet encryption/decryption at each level - Stream management - Flow control - Timer management

Connection States

idle -> handshaking -> connected -> draining -> closed

Messages to Owner

{quic, Conn, {connected, Info}} where Conn is the connection pid {quic, Conn, {stream_data, StreamId, Data, Fin}} {quic, Conn, {stream_opened, StreamId}} {quic, Conn, {closed, Reason}}

Layout

This module is large. Navigate by section banner, in this order:

- API, gen_statem callbacks, State Functions, Common Event Handling - TLS handshake; PSK validation and the session ticket store; TLS handshake continued (server flight, tickets) - Packet send layer: frames and payloads become packets, all levels - Frame classification; Packet Processing (decrypt, parse, batched receive); TLS message driver, which is the handshake's second half and is reached from packet processing - Stream Processing; Socket I/O; ACK emission and decimation; Delivery to the owner; Frame classification for ACK policy - Reclaimed-stream tracking; Send path; Send queue and priority queue - Retransmission; the timer sections (PTO, idle, keep-alive, pacing) - Key update; migration and path validation; PMTU discovery

Two walkthroughs trace the hot paths end to end, naming functions rather than line numbers: docs/SEND_PATH.md and docs/RECV_PATH.md.

#state{} lives in quic_connection_state.hrl and has 194 fields. When changing one, grep for the field name: most are touched in several regions.

Summary

Functions

Cancel a stream deadline.

Whether the cert and key in Opts can authenticate as Role, the error naming the side whose key is at fault. Opts without both have nothing to check here.

Whether Key can authenticate as Cert: sign a CertificateVerify as a handshake does and check the certificate's public key accepts the signature. This catches a key in a form the signer does not take and a key that belongs to another certificate, both of which would otherwise fail every handshake that presents the certificate.

Close the connection.

Close a specific stream.

Initiate a connection to a QUIC server. This is a convenience wrapper that starts the process and initiates handshake.

Largest datagram payload send_datagram/2 accepts now. Returns 0 if peer doesn't support datagrams.

Returns whether the server accepted early data, or unknown if the handshake has not yet completed. Useful only after the connection enters the connected state to confirm that 0-RTT was actually used.

Get the current MTU for the connection. Returns the effective MTU discovered via DPLPMTUD (RFC 8899).

Path metrics snapshot. See quic:get_path_stats/1.

Get peer's transport parameters. Returns the transport parameters received from the peer during handshake. Useful for verifying peer capabilities (e.g., WebTransport support).

Get send queue status for backpressure decisions. Returns information about the current send queue state including whether the connection is congested and should apply backpressure.

Get current connection state (for debugging).

Get connection statistics for liveness detection. Returns packet counts that can be used by net_kernel for tick checking. Any QUIC packet (ACK, PING, data) counts as proof of peer liveness.

Get remaining time for a stream deadline.

Get stream priority (RFC 9218). Returns {ok, {Urgency, Incremental}} or {error, not_found}.

Handle a timeout event.

Handle a timeout event with timestamp. The NowMs parameter is currently unused as the connection manages its own timing internally.

Returns whether the connection has derived early keys (RFC 9001 §4.6). True when a session ticket was supplied at connect time and send_client_hello/1 derived 0-RTT traffic keys; the H3 layer uses this to choose between the fresh-handshake and 0-RTT bootstraps.

Issue new connection IDs to the peer. RFC 9000 Section 5.1.1: Generates new CIDs with stateless reset tokens.

Initiate a key update (RFC 9001 Section 6). This triggers a key update cycle, deriving new encryption keys. Only valid when connection is in connected state.

Initiate connection migration. This triggers path validation by sending PATH_CHALLENGE on a new path. Simulates network change by rebinding the socket.

Trigger connection migration with timeout option. Simulates network change by rebinding the socket.

Open a new bidirectional stream.

Open a new unidirectional stream.

Get peer certificate (DER-encoded).

Get remote address.

Process pending events (called when socket is ready).

Reset a stream with reliable delivery up to ReliableSize. Data up to ReliableSize will be delivered before the reset takes effect. Requires peer support via the reset_stream_at transport parameter.

Send data on a stream.

Like send_data/4, giving up with {error, timeout} after Timeout.

Send data on a stream asynchronously. This is faster than send_data/4 because it uses cast instead of call, avoiding the round-trip latency. However, errors are silently dropped. Use this for high-throughput scenarios where occasional dropped data is acceptable.

Send a datagram.

Send a PING frame (RFC 9000), useful for liveness checks. It waits for the congestion window like any packet in flight. The PING elicits an ACK from the peer, confirming the connection is alive.

Set the congestion control algorithm for a connection. Algorithm: newreno | bbr | cubic

Set new owner process (async).

Set new owner process (synchronous). Use this when you need to ensure ownership is transferred before continuing. A connection that is already draining or closed refuses the swap with {error, {invalid_state, StateName}}.

Set a deadline for a stream. TimeoutMs is milliseconds from now until expiry. Options: action => reset | notify | both, error_code => non_neg_integer()

Set stream priority (RFC 9218). Urgency: 0-7 (lower = more urgent, default 3) Incremental: boolean (data can be processed incrementally)

See quic:set_stream_reliable_size/3.

Set connection options.

Get local address.

Start a QUIC connection process.

Start a QUIC connection with optional pre-opened socket.

Start a server-side QUIC connection. Called by quic_listener when a new connection is accepted.

Request peer to stop sending on a stream. Sends a STOP_SENDING frame (RFC 9000 Section 19.5).

Functions

callback_mode()

cancel_stream_deadline(Conn, StreamId)

-spec cancel_stream_deadline(pid(), non_neg_integer()) -> ok | {error, term()}.

Cancel a stream deadline.

check_credentials(Role, Opts)

-spec check_credentials(server | client, map()) -> ok | {error, term()}.

Whether the cert and key in Opts can authenticate as Role, the error naming the side whose key is at fault. Opts without both have nothing to check here.

check_signing_key(Cert, Key)

-spec check_signing_key(binary(), term()) -> ok | {error, term()}.

Whether Key can authenticate as Cert: sign a CertificateVerify as a handshake does and check the certificate's public key accepts the signature. This catches a key in a form the signer does not take and a key that belongs to another certificate, both of which would otherwise fail every handshake that presents the certificate.

close(Conn, Reason)

-spec close(pid(), term()) -> ok.

Close the connection.

close_stream(Conn, StreamId, ErrorCode)

-spec close_stream(pid(), non_neg_integer(), non_neg_integer()) -> ok | {error, term()}.

Close a specific stream.

closed(EventType, OldState, State)

code_change(OldVsn, StateName, State, Extra)

connect(Host, Port, Opts, Owner)

-spec connect(binary() | inet:hostname() | inet:ip_address(), inet:port_number(), map(), pid()) ->
                 {ok, reference(), pid()} | {error, term()}.

Initiate a connection to a QUIC server. This is a convenience wrapper that starts the process and initiates handshake.

connected(EventType, OldState, State)

datagram_max_size(Conn)

-spec datagram_max_size(pid()) -> non_neg_integer() | {error, term()}.

Largest datagram payload send_datagram/2 accepts now. Returns 0 if peer doesn't support datagrams.

datagram_stats(Conn)

-spec datagram_stats(pid()) ->
                        #{delivered := non_neg_integer(),
                          dropped_recv := non_neg_integer(),
                          sent := non_neg_integer(),
                          dropped_send := non_neg_integer()}.

draining(EventType, OldState, State)

early_data_accepted(Conn)

-spec early_data_accepted(pid()) -> boolean() | unknown.

Returns whether the server accepted early data, or unknown if the handshake has not yet completed. Useful only after the connection enters the connected state to confirm that 0-RTT was actually used.

format_status(Status)

-spec format_status(gen_statem:format_status()) -> gen_statem:format_status().

get_mtu(Conn)

-spec get_mtu(pid()) -> {ok, pos_integer()} | {error, term()}.

Get the current MTU for the connection. Returns the effective MTU discovered via DPLPMTUD (RFC 8899).

get_path_stats(Conn)

-spec get_path_stats(pid()) -> {ok, quic:path_stats()} | {error, term()}.

Path metrics snapshot. See quic:get_path_stats/1.

get_peer_transport_params(Conn)

-spec get_peer_transport_params(pid()) -> {ok, map()} | {error, term()}.

Get peer's transport parameters. Returns the transport parameters received from the peer during handshake. Useful for verifying peer capabilities (e.g., WebTransport support).

get_send_queue_info(Conn)

-spec get_send_queue_info(pid()) -> {ok, quic:send_queue_info()} | {error, term()}.

Get send queue status for backpressure decisions. Returns information about the current send queue state including whether the connection is congested and should apply backpressure.

get_state(Conn)

-spec get_state(pid()) -> {atom(), map()}.

Get current connection state (for debugging).

get_stats(Conn)

-spec get_stats(pid()) -> {ok, map()} | {error, term()}.

Get connection statistics for liveness detection. Returns packet counts that can be used by net_kernel for tick checking. Any QUIC packet (ACK, PING, data) counts as proof of peer liveness.

get_stream_deadline(Conn, StreamId)

-spec get_stream_deadline(pid(), non_neg_integer()) ->
                             {ok, {non_neg_integer() | infinity, reset | notify | both}} |
                             {error, term()}.

Get remaining time for a stream deadline.

get_stream_priority(Conn, StreamId)

-spec get_stream_priority(pid(), non_neg_integer()) -> {ok, {0..7, boolean()}} | {error, term()}.

Get stream priority (RFC 9218). Returns {ok, {Urgency, Incremental}} or {error, not_found}.

handle_timeout(Conn)

-spec handle_timeout(pid()) -> ok.

Handle a timeout event.

handle_timeout(Conn, NowMs)

-spec handle_timeout(pid(), non_neg_integer()) -> non_neg_integer() | infinity.

Handle a timeout event with timestamp. The NowMs parameter is currently unused as the connection manages its own timing internally.

handshaking(EventType, EventContent, State)

has_early_keys(Conn)

-spec has_early_keys(pid()) -> boolean().

Returns whether the connection has derived early keys (RFC 9001 §4.6). True when a session ticket was supplied at connect time and send_client_hello/1 derived 0-RTT traffic keys; the H3 layer uses this to choose between the fresh-handshake and 0-RTT bootstraps.

idle(EventType, OldState, State)

init(_)

issue_new_connection_ids(State)

-spec issue_new_connection_ids(#state{dcid :: binary(),
                                      version :: non_neg_integer(),
                                      socket :: gen_udp:socket() | socket:socket() | undefined,
                                      socket_state :: quic_socket:socket_state() | undefined,
                                      client_receiver :: pid() | undefined,
                                      remote_addr :: {inet:ip_address(), inet:port_number()},
                                      owner :: pid(),
                                      owner_mon :: reference() | {linked, pid()} | opted_out | undefined,
                                      app_keys ::
                                          {#crypto_keys{key :: binary(),
                                                        iv :: binary(),
                                                        hp :: binary(),
                                                        cipher ::
                                                            aes_128_gcm | aes_256_gcm |
                                                            chacha20_poly1305},
                                           #crypto_keys{key :: binary(),
                                                        iv :: binary(),
                                                        hp :: binary(),
                                                        cipher ::
                                                            aes_128_gcm | aes_256_gcm |
                                                            chacha20_poly1305}} |
                                          undefined,
                                      key_state ::
                                          #key_update_state{current_phase :: 0 | 1,
                                                            current_keys ::
                                                                {#crypto_keys{key :: binary(),
                                                                              iv :: binary(),
                                                                              hp :: binary(),
                                                                              cipher ::
                                                                                  aes_128_gcm |
                                                                                  aes_256_gcm |
                                                                                  chacha20_poly1305},
                                                                 #crypto_keys{key :: binary(),
                                                                              iv :: binary(),
                                                                              hp :: binary(),
                                                                              cipher ::
                                                                                  aes_128_gcm |
                                                                                  aes_256_gcm |
                                                                                  chacha20_poly1305}} |
                                                                undefined,
                                                            prev_keys ::
                                                                {#crypto_keys{key :: binary(),
                                                                              iv :: binary(),
                                                                              hp :: binary(),
                                                                              cipher ::
                                                                                  aes_128_gcm |
                                                                                  aes_256_gcm |
                                                                                  chacha20_poly1305},
                                                                 #crypto_keys{key :: binary(),
                                                                              iv :: binary(),
                                                                              hp :: binary(),
                                                                              cipher ::
                                                                                  aes_128_gcm |
                                                                                  aes_256_gcm |
                                                                                  chacha20_poly1305}} |
                                                                undefined,
                                                            client_app_secret :: binary() | undefined,
                                                            server_app_secret :: binary() | undefined,
                                                            update_state ::
                                                                idle | initiated | responding,
                                                            send_count :: non_neg_integer(),
                                                            recv_first_pn ::
                                                                non_neg_integer() | undefined} |
                                          undefined,
                                      alpn :: binary() | undefined,
                                      pn_app ::
                                          #pn_space{next_pn :: non_neg_integer(),
                                                    largest_recv :: non_neg_integer() | undefined,
                                                    recv_time :: non_neg_integer() | undefined,
                                                    ack_ranges ::
                                                        [{non_neg_integer(), non_neg_integer()}],
                                                    recv_floor :: non_neg_integer(),
                                                    ack_eliciting_in_flight :: non_neg_integer(),
                                                    ecn_ce_count :: non_neg_integer()},
                                      max_data_local :: non_neg_integer(),
                                      max_data_remote :: non_neg_integer(),
                                      data_sent :: non_neg_integer(),
                                      data_received :: non_neg_integer(),
                                      fc_last_stream_update :: integer() | undefined,
                                      fc_last_conn_update :: integer() | undefined,
                                      credit_timer :: reference() | undefined,
                                      credit_withheld :: sets:set(non_neg_integer()),
                                      fc_max_stream_recv_window :: non_neg_integer(),
                                      streams ::
                                          #{non_neg_integer() =>
                                                #stream_state{id :: non_neg_integer(),
                                                              state ::
                                                                  idle | open | half_closed_local |
                                                                  half_closed_remote | closed | reset |
                                                                  stopped,
                                                              send_offset :: non_neg_integer(),
                                                              send_max_data :: non_neg_integer(),
                                                              send_fin :: boolean(),
                                                              send_buffer :: iolist(),
                                                              send_done :: boolean(),
                                                              send_highest :: non_neg_integer(),
                                                              recv_offset :: non_neg_integer(),
                                                              recv_max_data :: non_neg_integer(),
                                                              recv_fin :: boolean(),
                                                              recv_buffer ::
                                                                  gb_trees:tree(non_neg_integer(),
                                                                                binary()),
                                                              recv_buffered :: non_neg_integer(),
                                                              recv_highest :: non_neg_integer(),
                                                              recv_flow :: auto | manual,
                                                              recv_consumed :: non_neg_integer(),
                                                              recv_done :: boolean(),
                                                              final_size ::
                                                                  non_neg_integer() | undefined,
                                                              urgency :: 0..7,
                                                              incremental :: boolean(),
                                                              deadline ::
                                                                  non_neg_integer() |
                                                                  infinity | undefined,
                                                              deadline_timer :: reference() | undefined,
                                                              deadline_action :: reset | notify | both,
                                                              deadline_error_code :: non_neg_integer(),
                                                              send_reset_at ::
                                                                  non_neg_integer() | undefined,
                                                              recv_reset_at ::
                                                                  non_neg_integer() | undefined,
                                                              send_reset_error ::
                                                                  non_neg_integer() | undefined,
                                                              recv_reset_error ::
                                                                  non_neg_integer() | undefined,
                                                              reliable_size ::
                                                                  non_neg_integer() | undefined}},
                                      next_stream_id_bidi :: non_neg_integer(),
                                      next_stream_id_uni :: non_neg_integer(),
                                      max_streams_bidi_local :: non_neg_integer(),
                                      max_streams_bidi_remote :: non_neg_integer(),
                                      max_streams_uni_local :: non_neg_integer(),
                                      max_streams_uni_remote :: non_neg_integer(),
                                      streams_reclaimed :: {non_neg_integer(), non_neg_integer()},
                                      streams_credit_step :: {pos_integer(), pos_integer()},
                                      reclaimed_ranges_bidi ::
                                          #{local | peer => [{non_neg_integer(), non_neg_integer()}]},
                                      reclaimed_ranges_uni ::
                                          #{local | peer => [{non_neg_integer(), non_neg_integer()}]},
                                      pending_send_reset_at :: #{non_neg_integer() => non_neg_integer()},
                                      pending_fin_reclaim :: #{non_neg_integer() => non_neg_integer()},
                                      ctrl_queue :: {non_neg_integer(), queue:queue(term())},
                                      max_datagram_frame_size_remote :: non_neg_integer(),
                                      datagram_recv_queue :: queue:queue(binary()),
                                      spin_outgoing :: 0 | 1,
                                      spin_recv :: 0 | 1,
                                      spin_recv_largest_pn :: integer(),
                                      transport_params :: map(),
                                      last_activity :: non_neg_integer(),
                                      ack_eliciting_since_recv :: boolean(),
                                      timer_ref :: reference() | undefined,
                                      cc_state :: quic_cc:cc_state() | undefined,
                                      loss_state :: quic_loss:loss_state() | undefined,
                                      pto_timer :: reference() | undefined,
                                      pto_scheduled_at :: integer() | undefined,
                                      pto_armed_at :: integer() | undefined,
                                      pto_space :: quic_loss:space(),
                                      pto_kind :: loss | pto,
                                      idle_timer :: reference() | undefined,
                                      keep_alive_timer :: reference() | undefined,
                                      disconnect_timer :: reference() | {final, reference()} | undefined,
                                      pacing_timer :: reference() | undefined,
                                      send_queue :: tuple(),
                                      send_parked :: #{non_neg_integer() => queue:queue()},
                                      pending_data :: [{non_neg_integer(), iodata(), boolean()}],
                                      send_queue_bytes :: non_neg_integer(),
                                      manual_recv_streams :: sets:set(non_neg_integer()),
                                      hold_peer_bidi :: boolean(),
                                      send_ready_wanted :: boolean(),
                                      send_queue_count :: non_neg_integer(),
                                      send_queue_version :: non_neg_integer(),
                                      recv_buffer_bytes :: non_neg_integer(),
                                      close_reason :: term(),
                                      closing_frame ::
                                          {app | handshake | initial | none, tuple()} | undefined,
                                      closing_recv :: non_neg_integer(),
                                      auth_failures :: non_neg_integer(),
                                      current_path ::
                                          #path_state{remote_addr ::
                                                          {inet:ip_address(), inet:port_number()},
                                                      status ::
                                                          unknown | validating | validated | failed,
                                                      challenge_data :: binary() | undefined,
                                                      challenge_count :: non_neg_integer(),
                                                      bytes_sent :: non_neg_integer(),
                                                      bytes_received :: non_neg_integer(),
                                                      rtt :: non_neg_integer() | undefined,
                                                      dcid :: binary() | undefined,
                                                      dcid_seq :: non_neg_integer() | undefined,
                                                      is_nat_rebinding :: boolean(),
                                                      via :: term(),
                                                      old_challenges :: [binary()],
                                                      deadline :: integer() | undefined,
                                                      short_challenges :: [binary()],
                                                      address_validated :: boolean()} |
                                          undefined,
                                      current_packet_source ::
                                          {inet:ip_address(), inet:port_number()} | undefined,
                                      current_packet_via :: term(),
                                      has_non_probing_frame :: boolean(),
                                      cid_pool_state :: quic_cid:pool(),
                                      pmtu_state ::
                                          #pmtu_state{state ::
                                                          disabled | base | searching |
                                                          search_complete | error,
                                                      base_mtu :: pos_integer(),
                                                      current_mtu :: pos_integer(),
                                                      max_mtu :: pos_integer(),
                                                      search_min :: pos_integer(),
                                                      lost :: [pos_integer() | undefined],
                                                      last_probe_lost :: boolean(),
                                                      probe_size :: non_neg_integer(),
                                                      probe_pn :: non_neg_integer() | undefined,
                                                      generation :: non_neg_integer(),
                                                      probe_timer :: reference() | undefined,
                                                      raise_timer :: reference() | undefined,
                                                      black_hole_count :: non_neg_integer(),
                                                      black_hole_threshold :: pos_integer()} |
                                          undefined,
                                      pmtu_probe_timer :: reference() | undefined,
                                      pmtu_raise_timer :: reference() | undefined,
                                      pto_dirty :: boolean(),
                                      burst_sent :: non_neg_integer(),
                                      recv_pass :: boolean(),
                                      hold_pass_frames :: boolean(),
                                      pass_frames :: [term()],
                                      pend_deliver :: none | {non_neg_integer(), [binary()], boolean()},
                                      ack_elicited_count :: non_neg_integer(),
                                      ack_timer :: reference() | undefined,
                                      last_recv_trigger :: sequential | reordered,
                                      peer_ack_freq ::
                                          #peer_ack_freq{seq :: non_neg_integer(),
                                                         tolerance :: pos_integer(),
                                                         max_ack_delay :: pos_integer(),
                                                         reordering :: non_neg_integer(),
                                                         largest_acked :: integer()} |
                                          undefined,
                                      ack_freq_req ::
                                          #ack_freq_req{seq :: non_neg_integer(),
                                                        threshold :: non_neg_integer(),
                                                        max_ack_delay :: pos_integer(),
                                                        reordering :: non_neg_integer(),
                                                        sent_at :: integer(),
                                                        pending_pto_delay :: pos_integer() | undefined} |
                                          undefined,
                                      coalesce :: boolean(),
                                      pend_payload :: [iodata()],
                                      pend_frames :: [tuple()],
                                      pend_size :: non_neg_integer(),
                                      streams_blocked :: [bidi | uni],
                                      cfg ::
                                          #conn_cfg{cc_opts :: quic_cc:cc_opts(),
                                                    scid :: binary(),
                                                    original_dcid :: binary(),
                                                    retry_scid_for_tp :: binary() | undefined,
                                                    role :: client | server,
                                                    supported_versions :: [non_neg_integer()],
                                                    send_socket :: gen_udp:socket() | undefined,
                                                    client_socket_backend :: gen_udp | socket | adapter,
                                                    local_addr ::
                                                        {inet:ip_address(), inet:port_number()} |
                                                        undefined,
                                                    conn_ref :: reference(),
                                                    verify :: boolean(),
                                                    cacerts :: [binary()] | undefined,
                                                    require_client_cert :: boolean(),
                                                    stream_reset_final_size :: boolean(),
                                                    notify_streams_available :: boolean(),
                                                    tls_groups :: [atom()],
                                                    tls_sig_algs :: [atom()] | undefined,
                                                    cipher_preference :: [atom()],
                                                    alpn_list :: [binary()],
                                                    max_stream_data_bidi_local :: non_neg_integer(),
                                                    max_stream_data_bidi_remote :: non_neg_integer(),
                                                    max_stream_data_uni :: non_neg_integer(),
                                                    fc_max_receive_window :: non_neg_integer(),
                                                    max_owner_queue :: pos_integer() | infinity,
                                                    max_udp_payload_size_local ::
                                                        pos_integer() | undefined,
                                                    max_datagram_frame_size_local :: non_neg_integer(),
                                                    datagram_recv_queue_len ::
                                                        non_neg_integer() | infinity,
                                                    spin_bit_enabled :: boolean(),
                                                    stateless_reset_secret :: binary() | undefined,
                                                    reset_stream_at_enabled :: boolean(),
                                                    idle_timeout :: non_neg_integer(),
                                                    keep_alive_interval ::
                                                        non_neg_integer() | disabled | auto,
                                                    disconnect_timeout :: pos_integer() | infinity,
                                                    pacing_enabled :: boolean(),
                                                    listener :: pid() | undefined,
                                                    sni_callback ::
                                                        fun((binary() | undefined) ->
                                                                {ok, map()} | {error, term()}) |
                                                        undefined,
                                                    alpn_handler ::
                                                        fun((pid(), binary()) ->
                                                                {ok, pid()} | {error, term()}) |
                                                        fun((pid(), binary(), pid()) ->
                                                                {ok, pid()} | {error, term()}) |
                                                        undefined,
                                                    server_preferred_address ::
                                                        #preferred_address{ipv4_addr ::
                                                                               inet:ip4_address() |
                                                                               undefined,
                                                                           ipv4_port ::
                                                                               inet:port_number() |
                                                                               undefined,
                                                                           ipv6_addr ::
                                                                               inet:ip6_address() |
                                                                               undefined,
                                                                           ipv6_port ::
                                                                               inet:port_number() |
                                                                               undefined,
                                                                           cid :: binary(),
                                                                           stateless_reset_token ::
                                                                               binary()} |
                                                        undefined,
                                                    client_cert :: binary() | undefined,
                                                    client_cert_chain :: [binary()],
                                                    client_private_key :: term() | undefined,
                                                    ticket_table :: ets:table() | undefined,
                                                    external_psk ::
                                                        {binary(), binary()} |
                                                        {binary(), binary(), [psk_dhe_ke | psk_ke]} |
                                                        undefined,
                                                    psk_config ::
                                                        #{psk_callback =>
                                                              fun((binary()) ->
                                                                      {ok, binary()} | not_found) |
                                                              undefined,
                                                          psks => #{binary() => binary()} | undefined} |
                                                        undefined,
                                                    cid_config ::
                                                        #cid_config{lb_config ::
                                                                        #lb_config{config_rotation ::
                                                                                       0..6,
                                                                                   algorithm ::
                                                                                       plaintext |
                                                                                       encrypted,
                                                                                   server_id :: binary(),
                                                                                   server_id_len ::
                                                                                       1..15,
                                                                                   nonce_len :: 4..18,
                                                                                   key ::
                                                                                       binary() |
                                                                                       undefined} |
                                                                        undefined,
                                                                    cid_len :: 1..20,
                                                                    reset_secret :: binary() | undefined} |
                                                        undefined,
                                                    congestion_threshold :: pos_integer(),
                                                    counters :: counters:counters_ref(),
                                                    active_n :: pos_integer(),
                                                    pmtu_raise_interval :: pos_integer(),
                                                    burst_budget :: pos_integer(),
                                                    delivery_coalescing :: boolean(),
                                                    ack_packet_tolerance :: pos_integer(),
                                                    qlog_ctx ::
                                                        #qlog_ctx{enabled :: boolean(),
                                                                  writer :: pid() | undefined,
                                                                  odcid :: binary() | undefined,
                                                                  reference_time ::
                                                                      integer() | undefined,
                                                                  vantage_point ::
                                                                      client | server | undefined,
                                                                  events :: all | [atom()],
                                                                  dir :: file:filename()} |
                                                        undefined},
                                      hs ::
                                          #hs{retry_token :: binary(),
                                              retry_received :: boolean(),
                                              address_validated :: boolean(),
                                              amp_rx :: non_neg_integer(),
                                              amp_tx :: non_neg_integer(),
                                              retry_scid :: binary() | undefined,
                                              server_name :: binary() | undefined,
                                              initial_keys ::
                                                  {#crypto_keys{key :: binary(),
                                                                iv :: binary(),
                                                                hp :: binary(),
                                                                cipher ::
                                                                    aes_128_gcm | aes_256_gcm |
                                                                    chacha20_poly1305},
                                                   #crypto_keys{key :: binary(),
                                                                iv :: binary(),
                                                                hp :: binary(),
                                                                cipher ::
                                                                    aes_128_gcm | aes_256_gcm |
                                                                    chacha20_poly1305}} |
                                                  undefined,
                                              initial_keys_alt ::
                                                  {non_neg_integer(),
                                                   {#crypto_keys{key :: binary(),
                                                                 iv :: binary(),
                                                                 hp :: binary(),
                                                                 cipher ::
                                                                     aes_128_gcm | aes_256_gcm |
                                                                     chacha20_poly1305},
                                                    #crypto_keys{key :: binary(),
                                                                 iv :: binary(),
                                                                 hp :: binary(),
                                                                 cipher ::
                                                                     aes_128_gcm | aes_256_gcm |
                                                                     chacha20_poly1305}}} |
                                                  undefined,
                                              handshake_keys ::
                                                  {#crypto_keys{key :: binary(),
                                                                iv :: binary(),
                                                                hp :: binary(),
                                                                cipher ::
                                                                    aes_128_gcm | aes_256_gcm |
                                                                    chacha20_poly1305},
                                                   #crypto_keys{key :: binary(),
                                                                iv :: binary(),
                                                                hp :: binary(),
                                                                cipher ::
                                                                    aes_128_gcm | aes_256_gcm |
                                                                    chacha20_poly1305}} |
                                                  undefined,
                                              tls_state :: atom(),
                                              tls_private_key :: quic_crypto:kex_private() | undefined,
                                              tls_transcript :: binary(),
                                              handshake_secret :: binary() | undefined,
                                              master_secret :: binary() | undefined,
                                              server_hs_secret :: binary() | undefined,
                                              client_hs_secret :: binary() | undefined,
                                              peer_sig_algs :: [non_neg_integer()],
                                              peer_psk_dhe_ke :: boolean(),
                                              cert_verify_code :: non_neg_integer() | undefined,
                                              tls_group :: atom(),
                                              hrr_sent :: boolean(),
                                              hrr_group :: atom() | undefined,
                                              hrr_cipher :: atom() | undefined,
                                              initial_tx_off :: non_neg_integer(),
                                              initial_ack_sent ::
                                                  [{non_neg_integer(), non_neg_integer()}],
                                              tls_ch1_random :: binary() | undefined,
                                              client_hs_flight :: undefined | binary(),
                                              tls_ch1_opts :: map() | undefined,
                                              negotiated_group :: atom() | undefined,
                                              negotiated_scheme :: atom() | undefined,
                                              crypto_buffer :: map(),
                                              crypto_offset :: map(),
                                              tls_buffer :: map(),
                                              pn_initial ::
                                                  #pn_space{next_pn :: non_neg_integer(),
                                                            largest_recv ::
                                                                non_neg_integer() | undefined,
                                                            recv_time :: non_neg_integer() | undefined,
                                                            ack_ranges ::
                                                                [{non_neg_integer(), non_neg_integer()}],
                                                            recv_floor :: non_neg_integer(),
                                                            ack_eliciting_in_flight :: non_neg_integer(),
                                                            ecn_ce_count :: non_neg_integer()},
                                              pn_handshake ::
                                                  #pn_space{next_pn :: non_neg_integer(),
                                                            largest_recv ::
                                                                non_neg_integer() | undefined,
                                                            recv_time :: non_neg_integer() | undefined,
                                                            ack_ranges ::
                                                                [{non_neg_integer(), non_neg_integer()}],
                                                            recv_floor :: non_neg_integer(),
                                                            ack_eliciting_in_flight :: non_neg_integer(),
                                                            ecn_ce_count :: non_neg_integer()},
                                              pending_hs ::
                                                  #{quic_loss:space() =>
                                                        queue:queue({iodata(), [term()]})},
                                              pending_hs_bytes ::
                                                  #{quic_loss:space() => non_neg_integer()},
                                              hs_probe :: boolean(),
                                              early_packets :: [{handshake | app, binary(), term()}],
                                              amp_probe_blocked :: boolean(),
                                              dgram ::
                                                  undefined |
                                                  {[binary()], [binary()], non_neg_integer(), boolean()},
                                              handshake_ack_received :: boolean(),
                                              peer_cert :: binary() | undefined,
                                              peer_cert_chain :: [binary()],
                                              server_cert :: binary() | undefined,
                                              server_cert_chain :: [binary()],
                                              server_private_key :: term() | undefined,
                                              cert_request_received :: boolean(),
                                              resumption_secret :: binary() | undefined,
                                              max_early_data :: non_neg_integer(),
                                              ticket_store :: quic_ticket:ticket_store(),
                                              selected_psk ::
                                                  undefined |
                                                  #{identity => binary(),
                                                    identity_idx => non_neg_integer(),
                                                    secret => binary(),
                                                    mode => psk_dhe_ke | psk_ke,
                                                    source => external | ticket},
                                              early_keys ::
                                                  {#crypto_keys{key :: binary(),
                                                                iv :: binary(),
                                                                hp :: binary(),
                                                                cipher ::
                                                                    aes_128_gcm | aes_256_gcm |
                                                                    chacha20_poly1305},
                                                   binary()} |
                                                  undefined,
                                              early_data_sent :: non_neg_integer(),
                                              early_deferred :: boolean(),
                                              early_data_accepted :: boolean(),
                                              zero_rtt_stream_ids :: sets:set(non_neg_integer())},
                                      mig ::
                                          #mig{alt_paths ::
                                                   [#path_state{remote_addr ::
                                                                    {inet:ip_address(),
                                                                     inet:port_number()},
                                                                status ::
                                                                    unknown | validating | validated |
                                                                    failed,
                                                                challenge_data :: binary() | undefined,
                                                                challenge_count :: non_neg_integer(),
                                                                bytes_sent :: non_neg_integer(),
                                                                bytes_received :: non_neg_integer(),
                                                                rtt :: non_neg_integer() | undefined,
                                                                dcid :: binary() | undefined,
                                                                dcid_seq ::
                                                                    non_neg_integer() | undefined,
                                                                is_nat_rebinding :: boolean(),
                                                                via :: term(),
                                                                old_challenges :: [binary()],
                                                                deadline :: integer() | undefined,
                                                                short_challenges :: [binary()],
                                                                address_validated :: boolean()}],
                                               preferred_address ::
                                                   #preferred_address{ipv4_addr ::
                                                                          inet:ip4_address() | undefined,
                                                                      ipv4_port ::
                                                                          inet:port_number() | undefined,
                                                                      ipv6_addr ::
                                                                          inet:ip6_address() | undefined,
                                                                      ipv6_port ::
                                                                          inet:port_number() | undefined,
                                                                      cid :: binary(),
                                                                      stateless_reset_token :: binary()} |
                                                   undefined,
                                               migration_state :: idle | validating_peer,
                                               pending_peer_validation ::
                                                   #path_state{remote_addr ::
                                                                   {inet:ip_address(),
                                                                    inet:port_number()},
                                                               status ::
                                                                   unknown | validating | validated |
                                                                   failed,
                                                               challenge_data :: binary() | undefined,
                                                               challenge_count :: non_neg_integer(),
                                                               bytes_sent :: non_neg_integer(),
                                                               bytes_received :: non_neg_integer(),
                                                               rtt :: non_neg_integer() | undefined,
                                                               dcid :: binary() | undefined,
                                                               dcid_seq :: non_neg_integer() | undefined,
                                                               is_nat_rebinding :: boolean(),
                                                               via :: term(),
                                                               old_challenges :: [binary()],
                                                               deadline :: integer() | undefined,
                                                               short_challenges :: [binary()],
                                                               address_validated :: boolean()} |
                                                   undefined,
                                               old_path_validation ::
                                                   #path_state{remote_addr ::
                                                                   {inet:ip_address(),
                                                                    inet:port_number()},
                                                               status ::
                                                                   unknown | validating | validated |
                                                                   failed,
                                                               challenge_data :: binary() | undefined,
                                                               challenge_count :: non_neg_integer(),
                                                               bytes_sent :: non_neg_integer(),
                                                               bytes_received :: non_neg_integer(),
                                                               rtt :: non_neg_integer() | undefined,
                                                               dcid :: binary() | undefined,
                                                               dcid_seq :: non_neg_integer() | undefined,
                                                               is_nat_rebinding :: boolean(),
                                                               via :: term(),
                                                               old_challenges :: [binary()],
                                                               deadline :: integer() | undefined,
                                                               short_challenges :: [binary()],
                                                               address_validated :: boolean()} |
                                                   undefined,
                                               validated_addr ::
                                                   {inet:ip_address(), inet:port_number()} | undefined,
                                               validated_dcid :: binary() | undefined,
                                               held :: [binary()],
                                               path_validation_timer :: reference() | undefined,
                                               path_validation_token :: reference() | undefined,
                                               peer_disable_migration :: boolean(),
                                               peer_dcid :: binary() | undefined,
                                               pass_dcid :: binary() | undefined,
                                               pass_rx :: non_neg_integer(),
                                               main_sock :: {term(), term()} | undefined}}) ->
                                  #state{dcid :: binary(),
                                         version :: non_neg_integer(),
                                         socket :: gen_udp:socket() | socket:socket() | undefined,
                                         socket_state :: quic_socket:socket_state() | undefined,
                                         client_receiver :: pid() | undefined,
                                         remote_addr :: {inet:ip_address(), inet:port_number()},
                                         owner :: pid(),
                                         owner_mon ::
                                             reference() | {linked, pid()} | opted_out | undefined,
                                         app_keys ::
                                             {#crypto_keys{key :: binary(),
                                                           iv :: binary(),
                                                           hp :: binary(),
                                                           cipher ::
                                                               aes_128_gcm | aes_256_gcm |
                                                               chacha20_poly1305},
                                              #crypto_keys{key :: binary(),
                                                           iv :: binary(),
                                                           hp :: binary(),
                                                           cipher ::
                                                               aes_128_gcm | aes_256_gcm |
                                                               chacha20_poly1305}} |
                                             undefined,
                                         key_state ::
                                             #key_update_state{current_phase :: 0 | 1,
                                                               current_keys ::
                                                                   {#crypto_keys{key :: binary(),
                                                                                 iv :: binary(),
                                                                                 hp :: binary(),
                                                                                 cipher ::
                                                                                     aes_128_gcm |
                                                                                     aes_256_gcm |
                                                                                     chacha20_poly1305},
                                                                    #crypto_keys{key :: binary(),
                                                                                 iv :: binary(),
                                                                                 hp :: binary(),
                                                                                 cipher ::
                                                                                     aes_128_gcm |
                                                                                     aes_256_gcm |
                                                                                     chacha20_poly1305}} |
                                                                   undefined,
                                                               prev_keys ::
                                                                   {#crypto_keys{key :: binary(),
                                                                                 iv :: binary(),
                                                                                 hp :: binary(),
                                                                                 cipher ::
                                                                                     aes_128_gcm |
                                                                                     aes_256_gcm |
                                                                                     chacha20_poly1305},
                                                                    #crypto_keys{key :: binary(),
                                                                                 iv :: binary(),
                                                                                 hp :: binary(),
                                                                                 cipher ::
                                                                                     aes_128_gcm |
                                                                                     aes_256_gcm |
                                                                                     chacha20_poly1305}} |
                                                                   undefined,
                                                               client_app_secret :: binary() | undefined,
                                                               server_app_secret :: binary() | undefined,
                                                               update_state ::
                                                                   idle | initiated | responding,
                                                               send_count :: non_neg_integer(),
                                                               recv_first_pn ::
                                                                   non_neg_integer() | undefined} |
                                             undefined,
                                         alpn :: binary() | undefined,
                                         pn_app ::
                                             #pn_space{next_pn :: non_neg_integer(),
                                                       largest_recv :: non_neg_integer() | undefined,
                                                       recv_time :: non_neg_integer() | undefined,
                                                       ack_ranges ::
                                                           [{non_neg_integer(), non_neg_integer()}],
                                                       recv_floor :: non_neg_integer(),
                                                       ack_eliciting_in_flight :: non_neg_integer(),
                                                       ecn_ce_count :: non_neg_integer()},
                                         max_data_local :: non_neg_integer(),
                                         max_data_remote :: non_neg_integer(),
                                         data_sent :: non_neg_integer(),
                                         data_received :: non_neg_integer(),
                                         fc_last_stream_update :: integer() | undefined,
                                         fc_last_conn_update :: integer() | undefined,
                                         credit_timer :: reference() | undefined,
                                         credit_withheld :: sets:set(non_neg_integer()),
                                         fc_max_stream_recv_window :: non_neg_integer(),
                                         streams ::
                                             #{non_neg_integer() =>
                                                   #stream_state{id :: non_neg_integer(),
                                                                 state ::
                                                                     idle | open | half_closed_local |
                                                                     half_closed_remote | closed |
                                                                     reset | stopped,
                                                                 send_offset :: non_neg_integer(),
                                                                 send_max_data :: non_neg_integer(),
                                                                 send_fin :: boolean(),
                                                                 send_buffer :: iolist(),
                                                                 send_done :: boolean(),
                                                                 send_highest :: non_neg_integer(),
                                                                 recv_offset :: non_neg_integer(),
                                                                 recv_max_data :: non_neg_integer(),
                                                                 recv_fin :: boolean(),
                                                                 recv_buffer ::
                                                                     gb_trees:tree(non_neg_integer(),
                                                                                   binary()),
                                                                 recv_buffered :: non_neg_integer(),
                                                                 recv_highest :: non_neg_integer(),
                                                                 recv_flow :: auto | manual,
                                                                 recv_consumed :: non_neg_integer(),
                                                                 recv_done :: boolean(),
                                                                 final_size ::
                                                                     non_neg_integer() | undefined,
                                                                 urgency :: 0..7,
                                                                 incremental :: boolean(),
                                                                 deadline ::
                                                                     non_neg_integer() |
                                                                     infinity | undefined,
                                                                 deadline_timer ::
                                                                     reference() | undefined,
                                                                 deadline_action ::
                                                                     reset | notify | both,
                                                                 deadline_error_code ::
                                                                     non_neg_integer(),
                                                                 send_reset_at ::
                                                                     non_neg_integer() | undefined,
                                                                 recv_reset_at ::
                                                                     non_neg_integer() | undefined,
                                                                 send_reset_error ::
                                                                     non_neg_integer() | undefined,
                                                                 recv_reset_error ::
                                                                     non_neg_integer() | undefined,
                                                                 reliable_size ::
                                                                     non_neg_integer() | undefined}},
                                         next_stream_id_bidi :: non_neg_integer(),
                                         next_stream_id_uni :: non_neg_integer(),
                                         max_streams_bidi_local :: non_neg_integer(),
                                         max_streams_bidi_remote :: non_neg_integer(),
                                         max_streams_uni_local :: non_neg_integer(),
                                         max_streams_uni_remote :: non_neg_integer(),
                                         streams_reclaimed :: {non_neg_integer(), non_neg_integer()},
                                         streams_credit_step :: {pos_integer(), pos_integer()},
                                         reclaimed_ranges_bidi ::
                                             #{local | peer => [{non_neg_integer(), non_neg_integer()}]},
                                         reclaimed_ranges_uni ::
                                             #{local | peer => [{non_neg_integer(), non_neg_integer()}]},
                                         pending_send_reset_at ::
                                             #{non_neg_integer() => non_neg_integer()},
                                         pending_fin_reclaim ::
                                             #{non_neg_integer() => non_neg_integer()},
                                         ctrl_queue :: {non_neg_integer(), queue:queue(term())},
                                         max_datagram_frame_size_remote :: non_neg_integer(),
                                         datagram_recv_queue :: queue:queue(binary()),
                                         spin_outgoing :: 0 | 1,
                                         spin_recv :: 0 | 1,
                                         spin_recv_largest_pn :: integer(),
                                         transport_params :: map(),
                                         last_activity :: non_neg_integer(),
                                         ack_eliciting_since_recv :: boolean(),
                                         timer_ref :: reference() | undefined,
                                         cc_state :: quic_cc:cc_state() | undefined,
                                         loss_state :: quic_loss:loss_state() | undefined,
                                         pto_timer :: reference() | undefined,
                                         pto_scheduled_at :: integer() | undefined,
                                         pto_armed_at :: integer() | undefined,
                                         pto_space :: quic_loss:space(),
                                         pto_kind :: loss | pto,
                                         idle_timer :: reference() | undefined,
                                         keep_alive_timer :: reference() | undefined,
                                         disconnect_timer ::
                                             reference() | {final, reference()} | undefined,
                                         pacing_timer :: reference() | undefined,
                                         send_queue :: tuple(),
                                         send_parked :: #{non_neg_integer() => queue:queue()},
                                         pending_data :: [{non_neg_integer(), iodata(), boolean()}],
                                         send_queue_bytes :: non_neg_integer(),
                                         manual_recv_streams :: sets:set(non_neg_integer()),
                                         hold_peer_bidi :: boolean(),
                                         send_ready_wanted :: boolean(),
                                         send_queue_count :: non_neg_integer(),
                                         send_queue_version :: non_neg_integer(),
                                         recv_buffer_bytes :: non_neg_integer(),
                                         close_reason :: term(),
                                         closing_frame ::
                                             {app | handshake | initial | none, tuple()} | undefined,
                                         closing_recv :: non_neg_integer(),
                                         auth_failures :: non_neg_integer(),
                                         current_path ::
                                             #path_state{remote_addr ::
                                                             {inet:ip_address(), inet:port_number()},
                                                         status ::
                                                             unknown | validating | validated | failed,
                                                         challenge_data :: binary() | undefined,
                                                         challenge_count :: non_neg_integer(),
                                                         bytes_sent :: non_neg_integer(),
                                                         bytes_received :: non_neg_integer(),
                                                         rtt :: non_neg_integer() | undefined,
                                                         dcid :: binary() | undefined,
                                                         dcid_seq :: non_neg_integer() | undefined,
                                                         is_nat_rebinding :: boolean(),
                                                         via :: term(),
                                                         old_challenges :: [binary()],
                                                         deadline :: integer() | undefined,
                                                         short_challenges :: [binary()],
                                                         address_validated :: boolean()} |
                                             undefined,
                                         current_packet_source ::
                                             {inet:ip_address(), inet:port_number()} | undefined,
                                         current_packet_via :: term(),
                                         has_non_probing_frame :: boolean(),
                                         cid_pool_state :: quic_cid:pool(),
                                         pmtu_state ::
                                             #pmtu_state{state ::
                                                             disabled | base | searching |
                                                             search_complete | error,
                                                         base_mtu :: pos_integer(),
                                                         current_mtu :: pos_integer(),
                                                         max_mtu :: pos_integer(),
                                                         search_min :: pos_integer(),
                                                         lost :: [pos_integer() | undefined],
                                                         last_probe_lost :: boolean(),
                                                         probe_size :: non_neg_integer(),
                                                         probe_pn :: non_neg_integer() | undefined,
                                                         generation :: non_neg_integer(),
                                                         probe_timer :: reference() | undefined,
                                                         raise_timer :: reference() | undefined,
                                                         black_hole_count :: non_neg_integer(),
                                                         black_hole_threshold :: pos_integer()} |
                                             undefined,
                                         pmtu_probe_timer :: reference() | undefined,
                                         pmtu_raise_timer :: reference() | undefined,
                                         pto_dirty :: boolean(),
                                         burst_sent :: non_neg_integer(),
                                         recv_pass :: boolean(),
                                         hold_pass_frames :: boolean(),
                                         pass_frames :: [term()],
                                         pend_deliver ::
                                             none | {non_neg_integer(), [binary()], boolean()},
                                         ack_elicited_count :: non_neg_integer(),
                                         ack_timer :: reference() | undefined,
                                         last_recv_trigger :: sequential | reordered,
                                         peer_ack_freq ::
                                             #peer_ack_freq{seq :: non_neg_integer(),
                                                            tolerance :: pos_integer(),
                                                            max_ack_delay :: pos_integer(),
                                                            reordering :: non_neg_integer(),
                                                            largest_acked :: integer()} |
                                             undefined,
                                         ack_freq_req ::
                                             #ack_freq_req{seq :: non_neg_integer(),
                                                           threshold :: non_neg_integer(),
                                                           max_ack_delay :: pos_integer(),
                                                           reordering :: non_neg_integer(),
                                                           sent_at :: integer(),
                                                           pending_pto_delay ::
                                                               pos_integer() | undefined} |
                                             undefined,
                                         coalesce :: boolean(),
                                         pend_payload :: [iodata()],
                                         pend_frames :: [tuple()],
                                         pend_size :: non_neg_integer(),
                                         streams_blocked :: [bidi | uni],
                                         cfg ::
                                             #conn_cfg{cc_opts :: quic_cc:cc_opts(),
                                                       scid :: binary(),
                                                       original_dcid :: binary(),
                                                       retry_scid_for_tp :: binary() | undefined,
                                                       role :: client | server,
                                                       supported_versions :: [non_neg_integer()],
                                                       send_socket :: gen_udp:socket() | undefined,
                                                       client_socket_backend ::
                                                           gen_udp | socket | adapter,
                                                       local_addr ::
                                                           {inet:ip_address(), inet:port_number()} |
                                                           undefined,
                                                       conn_ref :: reference(),
                                                       verify :: boolean(),
                                                       cacerts :: [binary()] | undefined,
                                                       require_client_cert :: boolean(),
                                                       stream_reset_final_size :: boolean(),
                                                       notify_streams_available :: boolean(),
                                                       tls_groups :: [atom()],
                                                       tls_sig_algs :: [atom()] | undefined,
                                                       cipher_preference :: [atom()],
                                                       alpn_list :: [binary()],
                                                       max_stream_data_bidi_local :: non_neg_integer(),
                                                       max_stream_data_bidi_remote :: non_neg_integer(),
                                                       max_stream_data_uni :: non_neg_integer(),
                                                       fc_max_receive_window :: non_neg_integer(),
                                                       max_owner_queue :: pos_integer() | infinity,
                                                       max_udp_payload_size_local ::
                                                           pos_integer() | undefined,
                                                       max_datagram_frame_size_local ::
                                                           non_neg_integer(),
                                                       datagram_recv_queue_len ::
                                                           non_neg_integer() | infinity,
                                                       spin_bit_enabled :: boolean(),
                                                       stateless_reset_secret :: binary() | undefined,
                                                       reset_stream_at_enabled :: boolean(),
                                                       idle_timeout :: non_neg_integer(),
                                                       keep_alive_interval ::
                                                           non_neg_integer() | disabled | auto,
                                                       disconnect_timeout :: pos_integer() | infinity,
                                                       pacing_enabled :: boolean(),
                                                       listener :: pid() | undefined,
                                                       sni_callback ::
                                                           fun((binary() | undefined) ->
                                                                   {ok, map()} | {error, term()}) |
                                                           undefined,
                                                       alpn_handler ::
                                                           fun((pid(), binary()) ->
                                                                   {ok, pid()} | {error, term()}) |
                                                           fun((pid(), binary(), pid()) ->
                                                                   {ok, pid()} | {error, term()}) |
                                                           undefined,
                                                       server_preferred_address ::
                                                           #preferred_address{ipv4_addr ::
                                                                                  inet:ip4_address() |
                                                                                  undefined,
                                                                              ipv4_port ::
                                                                                  inet:port_number() |
                                                                                  undefined,
                                                                              ipv6_addr ::
                                                                                  inet:ip6_address() |
                                                                                  undefined,
                                                                              ipv6_port ::
                                                                                  inet:port_number() |
                                                                                  undefined,
                                                                              cid :: binary(),
                                                                              stateless_reset_token ::
                                                                                  binary()} |
                                                           undefined,
                                                       client_cert :: binary() | undefined,
                                                       client_cert_chain :: [binary()],
                                                       client_private_key :: term() | undefined,
                                                       ticket_table :: ets:table() | undefined,
                                                       external_psk ::
                                                           {binary(), binary()} |
                                                           {binary(), binary(), [psk_dhe_ke | psk_ke]} |
                                                           undefined,
                                                       psk_config ::
                                                           #{psk_callback =>
                                                                 fun((binary()) ->
                                                                         {ok, binary()} | not_found) |
                                                                 undefined,
                                                             psks => #{binary() => binary()} | undefined} |
                                                           undefined,
                                                       cid_config ::
                                                           #cid_config{lb_config ::
                                                                           #lb_config{config_rotation ::
                                                                                          0..6,
                                                                                      algorithm ::
                                                                                          plaintext |
                                                                                          encrypted,
                                                                                      server_id ::
                                                                                          binary(),
                                                                                      server_id_len ::
                                                                                          1..15,
                                                                                      nonce_len :: 4..18,
                                                                                      key ::
                                                                                          binary() |
                                                                                          undefined} |
                                                                           undefined,
                                                                       cid_len :: 1..20,
                                                                       reset_secret ::
                                                                           binary() | undefined} |
                                                           undefined,
                                                       congestion_threshold :: pos_integer(),
                                                       counters :: counters:counters_ref(),
                                                       active_n :: pos_integer(),
                                                       pmtu_raise_interval :: pos_integer(),
                                                       burst_budget :: pos_integer(),
                                                       delivery_coalescing :: boolean(),
                                                       ack_packet_tolerance :: pos_integer(),
                                                       qlog_ctx ::
                                                           #qlog_ctx{enabled :: boolean(),
                                                                     writer :: pid() | undefined,
                                                                     odcid :: binary() | undefined,
                                                                     reference_time ::
                                                                         integer() | undefined,
                                                                     vantage_point ::
                                                                         client | server | undefined,
                                                                     events :: all | [atom()],
                                                                     dir :: file:filename()} |
                                                           undefined},
                                         hs ::
                                             #hs{retry_token :: binary(),
                                                 retry_received :: boolean(),
                                                 address_validated :: boolean(),
                                                 amp_rx :: non_neg_integer(),
                                                 amp_tx :: non_neg_integer(),
                                                 retry_scid :: binary() | undefined,
                                                 server_name :: binary() | undefined,
                                                 initial_keys ::
                                                     {#crypto_keys{key :: binary(),
                                                                   iv :: binary(),
                                                                   hp :: binary(),
                                                                   cipher ::
                                                                       aes_128_gcm | aes_256_gcm |
                                                                       chacha20_poly1305},
                                                      #crypto_keys{key :: binary(),
                                                                   iv :: binary(),
                                                                   hp :: binary(),
                                                                   cipher ::
                                                                       aes_128_gcm | aes_256_gcm |
                                                                       chacha20_poly1305}} |
                                                     undefined,
                                                 initial_keys_alt ::
                                                     {non_neg_integer(),
                                                      {#crypto_keys{key :: binary(),
                                                                    iv :: binary(),
                                                                    hp :: binary(),
                                                                    cipher ::
                                                                        aes_128_gcm | aes_256_gcm |
                                                                        chacha20_poly1305},
                                                       #crypto_keys{key :: binary(),
                                                                    iv :: binary(),
                                                                    hp :: binary(),
                                                                    cipher ::
                                                                        aes_128_gcm | aes_256_gcm |
                                                                        chacha20_poly1305}}} |
                                                     undefined,
                                                 handshake_keys ::
                                                     {#crypto_keys{key :: binary(),
                                                                   iv :: binary(),
                                                                   hp :: binary(),
                                                                   cipher ::
                                                                       aes_128_gcm | aes_256_gcm |
                                                                       chacha20_poly1305},
                                                      #crypto_keys{key :: binary(),
                                                                   iv :: binary(),
                                                                   hp :: binary(),
                                                                   cipher ::
                                                                       aes_128_gcm | aes_256_gcm |
                                                                       chacha20_poly1305}} |
                                                     undefined,
                                                 tls_state :: atom(),
                                                 tls_private_key ::
                                                     quic_crypto:kex_private() | undefined,
                                                 tls_transcript :: binary(),
                                                 handshake_secret :: binary() | undefined,
                                                 master_secret :: binary() | undefined,
                                                 server_hs_secret :: binary() | undefined,
                                                 client_hs_secret :: binary() | undefined,
                                                 peer_sig_algs :: [non_neg_integer()],
                                                 peer_psk_dhe_ke :: boolean(),
                                                 cert_verify_code :: non_neg_integer() | undefined,
                                                 tls_group :: atom(),
                                                 hrr_sent :: boolean(),
                                                 hrr_group :: atom() | undefined,
                                                 hrr_cipher :: atom() | undefined,
                                                 initial_tx_off :: non_neg_integer(),
                                                 initial_ack_sent ::
                                                     [{non_neg_integer(), non_neg_integer()}],
                                                 tls_ch1_random :: binary() | undefined,
                                                 client_hs_flight :: undefined | binary(),
                                                 tls_ch1_opts :: map() | undefined,
                                                 negotiated_group :: atom() | undefined,
                                                 negotiated_scheme :: atom() | undefined,
                                                 crypto_buffer :: map(),
                                                 crypto_offset :: map(),
                                                 tls_buffer :: map(),
                                                 pn_initial ::
                                                     #pn_space{next_pn :: non_neg_integer(),
                                                               largest_recv ::
                                                                   non_neg_integer() | undefined,
                                                               recv_time ::
                                                                   non_neg_integer() | undefined,
                                                               ack_ranges ::
                                                                   [{non_neg_integer(),
                                                                     non_neg_integer()}],
                                                               recv_floor :: non_neg_integer(),
                                                               ack_eliciting_in_flight ::
                                                                   non_neg_integer(),
                                                               ecn_ce_count :: non_neg_integer()},
                                                 pn_handshake ::
                                                     #pn_space{next_pn :: non_neg_integer(),
                                                               largest_recv ::
                                                                   non_neg_integer() | undefined,
                                                               recv_time ::
                                                                   non_neg_integer() | undefined,
                                                               ack_ranges ::
                                                                   [{non_neg_integer(),
                                                                     non_neg_integer()}],
                                                               recv_floor :: non_neg_integer(),
                                                               ack_eliciting_in_flight ::
                                                                   non_neg_integer(),
                                                               ecn_ce_count :: non_neg_integer()},
                                                 pending_hs ::
                                                     #{quic_loss:space() =>
                                                           queue:queue({iodata(), [term()]})},
                                                 pending_hs_bytes ::
                                                     #{quic_loss:space() => non_neg_integer()},
                                                 hs_probe :: boolean(),
                                                 early_packets :: [{handshake | app, binary(), term()}],
                                                 amp_probe_blocked :: boolean(),
                                                 dgram ::
                                                     undefined |
                                                     {[binary()],
                                                      [binary()],
                                                      non_neg_integer(),
                                                      boolean()},
                                                 handshake_ack_received :: boolean(),
                                                 peer_cert :: binary() | undefined,
                                                 peer_cert_chain :: [binary()],
                                                 server_cert :: binary() | undefined,
                                                 server_cert_chain :: [binary()],
                                                 server_private_key :: term() | undefined,
                                                 cert_request_received :: boolean(),
                                                 resumption_secret :: binary() | undefined,
                                                 max_early_data :: non_neg_integer(),
                                                 ticket_store :: quic_ticket:ticket_store(),
                                                 selected_psk ::
                                                     undefined |
                                                     #{identity => binary(),
                                                       identity_idx => non_neg_integer(),
                                                       secret => binary(),
                                                       mode => psk_dhe_ke | psk_ke,
                                                       source => external | ticket},
                                                 early_keys ::
                                                     {#crypto_keys{key :: binary(),
                                                                   iv :: binary(),
                                                                   hp :: binary(),
                                                                   cipher ::
                                                                       aes_128_gcm | aes_256_gcm |
                                                                       chacha20_poly1305},
                                                      binary()} |
                                                     undefined,
                                                 early_data_sent :: non_neg_integer(),
                                                 early_deferred :: boolean(),
                                                 early_data_accepted :: boolean(),
                                                 zero_rtt_stream_ids :: sets:set(non_neg_integer())},
                                         mig ::
                                             #mig{alt_paths ::
                                                      [#path_state{remote_addr ::
                                                                       {inet:ip_address(),
                                                                        inet:port_number()},
                                                                   status ::
                                                                       unknown | validating |
                                                                       validated | failed,
                                                                   challenge_data ::
                                                                       binary() | undefined,
                                                                   challenge_count :: non_neg_integer(),
                                                                   bytes_sent :: non_neg_integer(),
                                                                   bytes_received :: non_neg_integer(),
                                                                   rtt :: non_neg_integer() | undefined,
                                                                   dcid :: binary() | undefined,
                                                                   dcid_seq ::
                                                                       non_neg_integer() | undefined,
                                                                   is_nat_rebinding :: boolean(),
                                                                   via :: term(),
                                                                   old_challenges :: [binary()],
                                                                   deadline :: integer() | undefined,
                                                                   short_challenges :: [binary()],
                                                                   address_validated :: boolean()}],
                                                  preferred_address ::
                                                      #preferred_address{ipv4_addr ::
                                                                             inet:ip4_address() |
                                                                             undefined,
                                                                         ipv4_port ::
                                                                             inet:port_number() |
                                                                             undefined,
                                                                         ipv6_addr ::
                                                                             inet:ip6_address() |
                                                                             undefined,
                                                                         ipv6_port ::
                                                                             inet:port_number() |
                                                                             undefined,
                                                                         cid :: binary(),
                                                                         stateless_reset_token ::
                                                                             binary()} |
                                                      undefined,
                                                  migration_state :: idle | validating_peer,
                                                  pending_peer_validation ::
                                                      #path_state{remote_addr ::
                                                                      {inet:ip_address(),
                                                                       inet:port_number()},
                                                                  status ::
                                                                      unknown | validating | validated |
                                                                      failed,
                                                                  challenge_data :: binary() | undefined,
                                                                  challenge_count :: non_neg_integer(),
                                                                  bytes_sent :: non_neg_integer(),
                                                                  bytes_received :: non_neg_integer(),
                                                                  rtt :: non_neg_integer() | undefined,
                                                                  dcid :: binary() | undefined,
                                                                  dcid_seq ::
                                                                      non_neg_integer() | undefined,
                                                                  is_nat_rebinding :: boolean(),
                                                                  via :: term(),
                                                                  old_challenges :: [binary()],
                                                                  deadline :: integer() | undefined,
                                                                  short_challenges :: [binary()],
                                                                  address_validated :: boolean()} |
                                                      undefined,
                                                  old_path_validation ::
                                                      #path_state{remote_addr ::
                                                                      {inet:ip_address(),
                                                                       inet:port_number()},
                                                                  status ::
                                                                      unknown | validating | validated |
                                                                      failed,
                                                                  challenge_data :: binary() | undefined,
                                                                  challenge_count :: non_neg_integer(),
                                                                  bytes_sent :: non_neg_integer(),
                                                                  bytes_received :: non_neg_integer(),
                                                                  rtt :: non_neg_integer() | undefined,
                                                                  dcid :: binary() | undefined,
                                                                  dcid_seq ::
                                                                      non_neg_integer() | undefined,
                                                                  is_nat_rebinding :: boolean(),
                                                                  via :: term(),
                                                                  old_challenges :: [binary()],
                                                                  deadline :: integer() | undefined,
                                                                  short_challenges :: [binary()],
                                                                  address_validated :: boolean()} |
                                                      undefined,
                                                  validated_addr ::
                                                      {inet:ip_address(), inet:port_number()} |
                                                      undefined,
                                                  validated_dcid :: binary() | undefined,
                                                  held :: [binary()],
                                                  path_validation_timer :: reference() | undefined,
                                                  path_validation_token :: reference() | undefined,
                                                  peer_disable_migration :: boolean(),
                                                  peer_dcid :: binary() | undefined,
                                                  pass_dcid :: binary() | undefined,
                                                  pass_rx :: non_neg_integer(),
                                                  main_sock :: {term(), term()} | undefined}}.

Issue new connection IDs to the peer. RFC 9000 Section 5.1.1: Generates new CIDs with stateless reset tokens.

key_update(Conn)

-spec key_update(pid()) -> ok | {error, term()}.

Initiate a key update (RFC 9001 Section 6). This triggers a key update cycle, deriving new encryption keys. Only valid when connection is in connected state.

migrate(Conn)

-spec migrate(pid()) -> ok | {error, term()}.

Initiate connection migration. This triggers path validation by sending PATH_CHALLENGE on a new path. Simulates network change by rebinding the socket.

migrate(Conn, Timeout)

-spec migrate(pid(), timeout()) -> ok | {error, term()}.

Trigger connection migration with timeout option. Simulates network change by rebinding the socket.

open_stream(Conn)

-spec open_stream(pid()) -> {ok, non_neg_integer()} | {error, term()}.

Open a new bidirectional stream.

open_unidirectional_stream(Conn)

-spec open_unidirectional_stream(pid()) -> {ok, non_neg_integer()} | {error, term()}.

Open a new unidirectional stream.

peercert(Conn)

-spec peercert(pid()) -> {ok, binary()} | {error, term()}.

Get peer certificate (DER-encoded).

peername(Conn)

-spec peername(pid()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, term()}.

Get remote address.

process(Conn)

-spec process(pid()) -> ok.

Process pending events (called when socket is ready).

reset_stream(Conn, StreamId, ErrorCode)

-spec reset_stream(pid(), non_neg_integer(), non_neg_integer()) -> ok | {error, term()}.

Reset a stream.

reset_stream_at(Conn, StreamId, ErrorCode, ReliableSize)

-spec reset_stream_at(pid(), non_neg_integer(), non_neg_integer(), non_neg_integer()) ->
                         ok | {error, term()}.

Reset a stream with reliable delivery up to ReliableSize. Data up to ReliableSize will be delivered before the reset takes effect. Requires peer support via the reset_stream_at transport parameter.

send_data(Conn, StreamId, Data, Fin)

-spec send_data(pid(), non_neg_integer(), iodata(), boolean()) -> ok | {error, term()}.

Send data on a stream.

send_data(Conn, StreamId, Data, Fin, Timeout)

-spec send_data(pid(), non_neg_integer(), iodata(), boolean(), timeout()) -> ok | {error, term()}.

Like send_data/4, giving up with {error, timeout} after Timeout.

send_data_async(Conn, StreamId, Data, Fin)

-spec send_data_async(pid(), non_neg_integer(), iodata(), boolean()) -> ok | {error, badarg}.

Send data on a stream asynchronously. This is faster than send_data/4 because it uses cast instead of call, avoiding the round-trip latency. However, errors are silently dropped. Use this for high-throughput scenarios where occasional dropped data is acceptable.

send_datagram(Conn, Data)

-spec send_datagram(pid(), iodata()) -> ok | {error, term()}.

Send a datagram.

send_ping(Conn)

-spec send_ping(pid()) -> ok | {error, term()}.

Send a PING frame (RFC 9000), useful for liveness checks. It waits for the congestion window like any packet in flight. The PING elicits an ACK from the peer, confirming the connection is alive.

set_congestion_control(Conn, Algorithm)

-spec set_congestion_control(pid(), quic_cc:cc_algorithm()) -> ok | {error, term()}.

Set the congestion control algorithm for a connection. Algorithm: newreno | bbr | cubic

set_owner(Conn, NewOwner)

-spec set_owner(pid(), pid()) -> ok | {error, badarg}.

Set new owner process (async).

set_owner_sync(Conn, NewOwner)

-spec set_owner_sync(pid(), pid()) -> ok | {error, term()}.

Set new owner process (synchronous). Use this when you need to ensure ownership is transferred before continuing. A connection that is already draining or closed refuses the swap with {error, {invalid_state, StateName}}.

set_stream_deadline(Conn, StreamId, TimeoutMs, Opts)

-spec set_stream_deadline(pid(), non_neg_integer(), pos_integer(), map()) -> ok | {error, term()}.

Set a deadline for a stream. TimeoutMs is milliseconds from now until expiry. Options: action => reset | notify | both, error_code => non_neg_integer()

set_stream_priority(Conn, StreamId, Urgency, Incremental)

-spec set_stream_priority(pid(), non_neg_integer(), 0..7, boolean()) -> ok | {error, term()}.

Set stream priority (RFC 9218). Urgency: 0-7 (lower = more urgent, default 3) Incremental: boolean (data can be processed incrementally)

set_stream_reliable_size(Conn, StreamId, Size)

-spec set_stream_reliable_size(pid(), non_neg_integer(), non_neg_integer()) -> ok | {error, term()}.

See quic:set_stream_reliable_size/3.

setopts(Conn, Opts)

-spec setopts(pid(), [{atom(), term()}]) -> ok | {error, term()}.

Set connection options.

sockname(Conn)

-spec sockname(pid()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, term()}.

Get local address.

start_link(Host, Port, Opts, Owner)

-spec start_link(binary() | inet:hostname() | inet:ip_address(), inet:port_number(), map(), pid()) ->
                    {ok, pid()} | {error, term()}.

Start a QUIC connection process.

start_link(Host, Port, Opts, Owner, Socket)

-spec start_link(binary() | inet:hostname() | inet:ip_address(),
                 inet:port_number(),
                 map(),
                 pid(),
                 gen_udp:socket() | undefined) ->
                    {ok, pid()} | {error, term()}.

Start a QUIC connection with optional pre-opened socket.

start_server(Opts)

-spec start_server(map()) -> {ok, pid()} | {error, term()}.

Start a server-side QUIC connection. Called by quic_listener when a new connection is accepted.

stop_sending(Conn, StreamId, ErrorCode)

-spec stop_sending(pid(), non_neg_integer(), non_neg_integer()) -> ok | {error, term()}.

Request peer to stop sending on a stream. Sends a STOP_SENDING frame (RFC 9000 Section 19.5).

terminate(Reason, StateName, State)