quic_connection (quic v1.3.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}}

Summary

Functions

Cancel a stream deadline.

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.

Get maximum datagram payload size. Returns 0 if peer doesn't support datagrams.

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

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.

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.

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). PING frames bypass congestion control and are useful for liveness checks. 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.

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)

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.

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().

Get maximum datagram payload size. 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)

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_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)

idle(EventType, OldState, State)

init(_)

issue_new_connection_ids(State)

-spec issue_new_connection_ids(#state{scid :: binary(),
                                      dcid :: binary(),
                                      original_dcid :: binary(),
                                      retry_token :: binary(),
                                      retry_received :: boolean(),
                                      address_validated :: boolean(),
                                      retry_scid_for_tp :: binary() | undefined,
                                      retry_scid :: binary() | undefined,
                                      role :: client | server,
                                      version :: non_neg_integer(),
                                      socket :: gen_udp:socket() | socket:socket() | undefined,
                                      send_socket :: gen_udp:socket() | undefined,
                                      socket_state :: quic_socket:socket_state() | undefined,
                                      client_socket_backend :: gen_udp | socket,
                                      client_receiver :: pid() | undefined,
                                      remote_addr :: {inet:ip_address(), inet:port_number()},
                                      local_addr :: {inet:ip_address(), inet:port_number()} | undefined,
                                      owner :: pid(),
                                      conn_ref :: reference(),
                                      server_name :: binary() | undefined,
                                      verify :: boolean(),
                                      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,
                                      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,
                                      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} |
                                          undefined,
                                      tls_state :: atom(),
                                      tls_private_key :: binary() | undefined,
                                      tls_transcript :: binary(),
                                      handshake_secret :: binary() | undefined,
                                      master_secret :: binary() | undefined,
                                      server_hs_secret :: binary() | undefined,
                                      client_hs_secret :: binary() | undefined,
                                      crypto_buffer :: map(),
                                      crypto_offset :: map(),
                                      tls_buffer :: map(),
                                      alpn :: binary() | undefined,
                                      alpn_list :: [binary()],
                                      pn_initial ::
                                          #pn_space{next_pn :: non_neg_integer(),
                                                    largest_acked :: non_neg_integer() | undefined,
                                                    largest_recv :: non_neg_integer() | undefined,
                                                    recv_time :: non_neg_integer() | undefined,
                                                    ack_ranges ::
                                                        [{non_neg_integer(), non_neg_integer()}],
                                                    ack_eliciting_in_flight :: non_neg_integer(),
                                                    loss_time :: non_neg_integer() | undefined,
                                                    sent_packets ::
                                                        #{non_neg_integer() =>
                                                              #sent_packet{pn :: non_neg_integer(),
                                                                           time_sent ::
                                                                               non_neg_integer(),
                                                                           ack_eliciting :: boolean(),
                                                                           in_flight :: boolean(),
                                                                           size :: non_neg_integer(),
                                                                           frames :: [term()]}}},
                                      pn_handshake ::
                                          #pn_space{next_pn :: non_neg_integer(),
                                                    largest_acked :: non_neg_integer() | undefined,
                                                    largest_recv :: non_neg_integer() | undefined,
                                                    recv_time :: non_neg_integer() | undefined,
                                                    ack_ranges ::
                                                        [{non_neg_integer(), non_neg_integer()}],
                                                    ack_eliciting_in_flight :: non_neg_integer(),
                                                    loss_time :: non_neg_integer() | undefined,
                                                    sent_packets ::
                                                        #{non_neg_integer() =>
                                                              #sent_packet{pn :: non_neg_integer(),
                                                                           time_sent ::
                                                                               non_neg_integer(),
                                                                           ack_eliciting :: boolean(),
                                                                           in_flight :: boolean(),
                                                                           size :: non_neg_integer(),
                                                                           frames :: [term()]}}},
                                      pn_app ::
                                          #pn_space{next_pn :: non_neg_integer(),
                                                    largest_acked :: non_neg_integer() | undefined,
                                                    largest_recv :: non_neg_integer() | undefined,
                                                    recv_time :: non_neg_integer() | undefined,
                                                    ack_ranges ::
                                                        [{non_neg_integer(), non_neg_integer()}],
                                                    ack_eliciting_in_flight :: non_neg_integer(),
                                                    loss_time :: non_neg_integer() | undefined,
                                                    sent_packets ::
                                                        #{non_neg_integer() =>
                                                              #sent_packet{pn :: non_neg_integer(),
                                                                           time_sent ::
                                                                               non_neg_integer(),
                                                                           ack_eliciting :: boolean(),
                                                                           in_flight :: boolean(),
                                                                           size :: non_neg_integer(),
                                                                           frames :: [term()]}}},
                                      max_data_local :: non_neg_integer(),
                                      max_data_remote :: non_neg_integer(),
                                      data_sent :: non_neg_integer(),
                                      data_received :: non_neg_integer(),
                                      max_stream_data_bidi_local :: non_neg_integer(),
                                      max_stream_data_bidi_remote :: non_neg_integer(),
                                      max_stream_data_uni :: non_neg_integer(),
                                      fc_last_stream_update :: integer() | undefined,
                                      fc_last_conn_update :: integer() | undefined,
                                      fc_max_receive_window :: 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(),
                                                              recv_offset :: non_neg_integer(),
                                                              recv_max_data :: non_neg_integer(),
                                                              recv_fin :: boolean(),
                                                              recv_buffer :: map(),
                                                              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(),
                                                              reset_reliable_size ::
                                                                  non_neg_integer() | undefined,
                                                              reset_error ::
                                                                  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(),
                                      credited_peer_bidi :: sets:set(non_neg_integer()),
                                      credited_peer_uni :: sets:set(non_neg_integer()),
                                      max_datagram_frame_size_local :: non_neg_integer(),
                                      max_datagram_frame_size_remote :: non_neg_integer(),
                                      datagram_recv_queue_len :: non_neg_integer() | infinity,
                                      datagram_recv_queue :: queue:queue(binary()),
                                      datagram_recv_delivered :: non_neg_integer(),
                                      datagram_recv_dropped :: non_neg_integer(),
                                      datagram_sent :: non_neg_integer(),
                                      datagram_send_dropped :: non_neg_integer(),
                                      spin_outgoing :: 0 | 1,
                                      spin_recv :: 0 | 1,
                                      spin_recv_largest_pn :: integer(),
                                      spin_bit_enabled :: boolean(),
                                      stateless_reset_secret :: binary() | undefined,
                                      reset_stream_at_enabled :: boolean(),
                                      transport_params :: map(),
                                      idle_timeout :: non_neg_integer(),
                                      last_activity :: non_neg_integer(),
                                      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,
                                      idle_timer :: reference() | undefined,
                                      keep_alive_interval :: non_neg_integer() | disabled,
                                      keep_alive_timer :: reference() | undefined,
                                      pacing_timer :: reference() | undefined,
                                      pacing_enabled :: boolean(),
                                      send_queue :: tuple(),
                                      pending_data :: [{non_neg_integer(), iodata(), boolean()}],
                                      send_queue_bytes :: non_neg_integer(),
                                      send_queue_count :: non_neg_integer(),
                                      send_queue_version :: non_neg_integer(),
                                      recv_buffer_bytes :: non_neg_integer(),
                                      close_reason :: term(),
                                      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,
                                                      is_nat_rebinding :: boolean()} |
                                          undefined,
                                      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,
                                                       is_nat_rebinding :: 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,
                                                      is_nat_rebinding :: 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,
                                                      is_nat_rebinding :: boolean()} |
                                          undefined,
                                      path_validation_timer :: reference() | undefined,
                                      path_validation_token :: reference() | undefined,
                                      peer_disable_migration :: boolean(),
                                      current_packet_source ::
                                          {inet:ip_address(), inet:port_number()} | undefined,
                                      has_non_probing_frame :: boolean(),
                                      local_cid_pool ::
                                          [#cid_entry{seq_num :: non_neg_integer(),
                                                      cid :: binary(),
                                                      stateless_reset_token :: binary() | undefined,
                                                      status :: active | retired}],
                                      local_cid_seq :: non_neg_integer(),
                                      peer_cid_pool ::
                                          [#cid_entry{seq_num :: non_neg_integer(),
                                                      cid :: binary(),
                                                      stateless_reset_token :: binary() | undefined,
                                                      status :: active | retired}],
                                      local_active_cid_limit :: non_neg_integer(),
                                      peer_active_cid_limit :: non_neg_integer(),
                                      peer_cert :: binary() | undefined,
                                      peer_cert_chain :: [binary()],
                                      listener :: pid() | undefined,
                                      server_cert :: binary() | undefined,
                                      server_cert_chain :: [binary()],
                                      server_private_key :: 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,
                                      cert_request_received :: boolean(),
                                      resumption_secret :: binary() | undefined,
                                      max_early_data :: non_neg_integer(),
                                      ticket_store :: quic_ticket:ticket_store(),
                                      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_data_accepted :: boolean(),
                                      cid_config ::
                                          #cid_config{lb_config ::
                                                          #lb_config{config_rotation :: 0..6,
                                                                     algorithm ::
                                                                         plaintext | stream_cipher |
                                                                         block_cipher,
                                                                     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(),
                                      packets_received :: non_neg_integer(),
                                      packets_sent :: non_neg_integer(),
                                      ack_sent :: non_neg_integer(),
                                      retransmits :: non_neg_integer(),
                                      active_n :: pos_integer(),
                                      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,
                                      timer_dirty :: boolean(),
                                      pto_dirty :: boolean(),
                                      ack_elicited_count :: non_neg_integer(),
                                      ack_timer :: reference() | undefined,
                                      last_recv_trigger :: sequential | reordered,
                                      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}) ->
                                  #state{scid :: binary(),
                                         dcid :: binary(),
                                         original_dcid :: binary(),
                                         retry_token :: binary(),
                                         retry_received :: boolean(),
                                         address_validated :: boolean(),
                                         retry_scid_for_tp :: binary() | undefined,
                                         retry_scid :: binary() | undefined,
                                         role :: client | server,
                                         version :: non_neg_integer(),
                                         socket :: gen_udp:socket() | socket:socket() | undefined,
                                         send_socket :: gen_udp:socket() | undefined,
                                         socket_state :: quic_socket:socket_state() | undefined,
                                         client_socket_backend :: gen_udp | socket,
                                         client_receiver :: pid() | undefined,
                                         remote_addr :: {inet:ip_address(), inet:port_number()},
                                         local_addr ::
                                             {inet:ip_address(), inet:port_number()} | undefined,
                                         owner :: pid(),
                                         conn_ref :: reference(),
                                         server_name :: binary() | undefined,
                                         verify :: boolean(),
                                         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,
                                         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,
                                         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} |
                                             undefined,
                                         tls_state :: atom(),
                                         tls_private_key :: binary() | undefined,
                                         tls_transcript :: binary(),
                                         handshake_secret :: binary() | undefined,
                                         master_secret :: binary() | undefined,
                                         server_hs_secret :: binary() | undefined,
                                         client_hs_secret :: binary() | undefined,
                                         crypto_buffer :: map(),
                                         crypto_offset :: map(),
                                         tls_buffer :: map(),
                                         alpn :: binary() | undefined,
                                         alpn_list :: [binary()],
                                         pn_initial ::
                                             #pn_space{next_pn :: non_neg_integer(),
                                                       largest_acked :: non_neg_integer() | undefined,
                                                       largest_recv :: non_neg_integer() | undefined,
                                                       recv_time :: non_neg_integer() | undefined,
                                                       ack_ranges ::
                                                           [{non_neg_integer(), non_neg_integer()}],
                                                       ack_eliciting_in_flight :: non_neg_integer(),
                                                       loss_time :: non_neg_integer() | undefined,
                                                       sent_packets ::
                                                           #{non_neg_integer() =>
                                                                 #sent_packet{pn :: non_neg_integer(),
                                                                              time_sent ::
                                                                                  non_neg_integer(),
                                                                              ack_eliciting :: boolean(),
                                                                              in_flight :: boolean(),
                                                                              size :: non_neg_integer(),
                                                                              frames :: [term()]}}},
                                         pn_handshake ::
                                             #pn_space{next_pn :: non_neg_integer(),
                                                       largest_acked :: non_neg_integer() | undefined,
                                                       largest_recv :: non_neg_integer() | undefined,
                                                       recv_time :: non_neg_integer() | undefined,
                                                       ack_ranges ::
                                                           [{non_neg_integer(), non_neg_integer()}],
                                                       ack_eliciting_in_flight :: non_neg_integer(),
                                                       loss_time :: non_neg_integer() | undefined,
                                                       sent_packets ::
                                                           #{non_neg_integer() =>
                                                                 #sent_packet{pn :: non_neg_integer(),
                                                                              time_sent ::
                                                                                  non_neg_integer(),
                                                                              ack_eliciting :: boolean(),
                                                                              in_flight :: boolean(),
                                                                              size :: non_neg_integer(),
                                                                              frames :: [term()]}}},
                                         pn_app ::
                                             #pn_space{next_pn :: non_neg_integer(),
                                                       largest_acked :: non_neg_integer() | undefined,
                                                       largest_recv :: non_neg_integer() | undefined,
                                                       recv_time :: non_neg_integer() | undefined,
                                                       ack_ranges ::
                                                           [{non_neg_integer(), non_neg_integer()}],
                                                       ack_eliciting_in_flight :: non_neg_integer(),
                                                       loss_time :: non_neg_integer() | undefined,
                                                       sent_packets ::
                                                           #{non_neg_integer() =>
                                                                 #sent_packet{pn :: non_neg_integer(),
                                                                              time_sent ::
                                                                                  non_neg_integer(),
                                                                              ack_eliciting :: boolean(),
                                                                              in_flight :: boolean(),
                                                                              size :: non_neg_integer(),
                                                                              frames :: [term()]}}},
                                         max_data_local :: non_neg_integer(),
                                         max_data_remote :: non_neg_integer(),
                                         data_sent :: non_neg_integer(),
                                         data_received :: non_neg_integer(),
                                         max_stream_data_bidi_local :: non_neg_integer(),
                                         max_stream_data_bidi_remote :: non_neg_integer(),
                                         max_stream_data_uni :: non_neg_integer(),
                                         fc_last_stream_update :: integer() | undefined,
                                         fc_last_conn_update :: integer() | undefined,
                                         fc_max_receive_window :: 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(),
                                                                 recv_offset :: non_neg_integer(),
                                                                 recv_max_data :: non_neg_integer(),
                                                                 recv_fin :: boolean(),
                                                                 recv_buffer :: map(),
                                                                 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(),
                                                                 reset_reliable_size ::
                                                                     non_neg_integer() | undefined,
                                                                 reset_error ::
                                                                     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(),
                                         credited_peer_bidi :: sets:set(non_neg_integer()),
                                         credited_peer_uni :: sets:set(non_neg_integer()),
                                         max_datagram_frame_size_local :: non_neg_integer(),
                                         max_datagram_frame_size_remote :: non_neg_integer(),
                                         datagram_recv_queue_len :: non_neg_integer() | infinity,
                                         datagram_recv_queue :: queue:queue(binary()),
                                         datagram_recv_delivered :: non_neg_integer(),
                                         datagram_recv_dropped :: non_neg_integer(),
                                         datagram_sent :: non_neg_integer(),
                                         datagram_send_dropped :: non_neg_integer(),
                                         spin_outgoing :: 0 | 1,
                                         spin_recv :: 0 | 1,
                                         spin_recv_largest_pn :: integer(),
                                         spin_bit_enabled :: boolean(),
                                         stateless_reset_secret :: binary() | undefined,
                                         reset_stream_at_enabled :: boolean(),
                                         transport_params :: map(),
                                         idle_timeout :: non_neg_integer(),
                                         last_activity :: non_neg_integer(),
                                         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,
                                         idle_timer :: reference() | undefined,
                                         keep_alive_interval :: non_neg_integer() | disabled,
                                         keep_alive_timer :: reference() | undefined,
                                         pacing_timer :: reference() | undefined,
                                         pacing_enabled :: boolean(),
                                         send_queue :: tuple(),
                                         pending_data :: [{non_neg_integer(), iodata(), boolean()}],
                                         send_queue_bytes :: non_neg_integer(),
                                         send_queue_count :: non_neg_integer(),
                                         send_queue_version :: non_neg_integer(),
                                         recv_buffer_bytes :: non_neg_integer(),
                                         close_reason :: term(),
                                         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,
                                                         is_nat_rebinding :: boolean()} |
                                             undefined,
                                         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,
                                                          is_nat_rebinding :: 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,
                                                         is_nat_rebinding :: 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,
                                                         is_nat_rebinding :: boolean()} |
                                             undefined,
                                         path_validation_timer :: reference() | undefined,
                                         path_validation_token :: reference() | undefined,
                                         peer_disable_migration :: boolean(),
                                         current_packet_source ::
                                             {inet:ip_address(), inet:port_number()} | undefined,
                                         has_non_probing_frame :: boolean(),
                                         local_cid_pool ::
                                             [#cid_entry{seq_num :: non_neg_integer(),
                                                         cid :: binary(),
                                                         stateless_reset_token :: binary() | undefined,
                                                         status :: active | retired}],
                                         local_cid_seq :: non_neg_integer(),
                                         peer_cid_pool ::
                                             [#cid_entry{seq_num :: non_neg_integer(),
                                                         cid :: binary(),
                                                         stateless_reset_token :: binary() | undefined,
                                                         status :: active | retired}],
                                         local_active_cid_limit :: non_neg_integer(),
                                         peer_active_cid_limit :: non_neg_integer(),
                                         peer_cert :: binary() | undefined,
                                         peer_cert_chain :: [binary()],
                                         listener :: pid() | undefined,
                                         server_cert :: binary() | undefined,
                                         server_cert_chain :: [binary()],
                                         server_private_key :: 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,
                                         cert_request_received :: boolean(),
                                         resumption_secret :: binary() | undefined,
                                         max_early_data :: non_neg_integer(),
                                         ticket_store :: quic_ticket:ticket_store(),
                                         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_data_accepted :: boolean(),
                                         cid_config ::
                                             #cid_config{lb_config ::
                                                             #lb_config{config_rotation :: 0..6,
                                                                        algorithm ::
                                                                            plaintext | stream_cipher |
                                                                            block_cipher,
                                                                        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(),
                                         packets_received :: non_neg_integer(),
                                         packets_sent :: non_neg_integer(),
                                         ack_sent :: non_neg_integer(),
                                         retransmits :: non_neg_integer(),
                                         active_n :: pos_integer(),
                                         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,
                                         timer_dirty :: boolean(),
                                         pto_dirty :: boolean(),
                                         ack_elicited_count :: non_neg_integer(),
                                         ack_timer :: reference() | undefined,
                                         last_recv_trigger :: sequential | reordered,
                                         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}.

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_async(Conn, StreamId, Data, Fin)

-spec send_data_async(pid(), non_neg_integer(), iodata(), boolean()) -> ok.

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). PING frames bypass congestion control and are useful for liveness checks. 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.

Set new owner process (async).

set_owner_sync(Conn, NewOwner)

-spec set_owner_sync(pid(), pid()) -> ok.

Set new owner process (synchronous). Use this when you need to ensure ownership is transferred before continuing.

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)

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)