quic_connection (quic v1.3.0)
View SourceQUIC 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.
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
-spec cancel_stream_deadline(pid(), non_neg_integer()) -> ok | {error, term()}.
Cancel a stream deadline.
Close the connection.
-spec close_stream(pid(), non_neg_integer(), non_neg_integer()) -> ok | {error, term()}.
Close a specific stream.
-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.
-spec datagram_max_size(pid()) -> non_neg_integer().
Get maximum datagram payload size. Returns 0 if peer doesn't support datagrams.
-spec datagram_stats(pid()) -> #{delivered := non_neg_integer(), dropped_recv := non_neg_integer(), sent := non_neg_integer(), dropped_send := non_neg_integer()}.
-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's transport parameters. Returns the transport parameters received from the peer during handshake. Useful for verifying peer capabilities (e.g., WebTransport support).
-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 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.
-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.
-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}.
-spec handle_timeout(pid()) -> ok.
Handle a timeout event.
-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.
-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.
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.
-spec open_stream(pid()) -> {ok, non_neg_integer()} | {error, term()}.
Open a new bidirectional stream.
-spec open_unidirectional_stream(pid()) -> {ok, non_neg_integer()} | {error, term()}.
Open a new unidirectional stream.
Get peer certificate (DER-encoded).
-spec peername(pid()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, term()}.
Get remote address.
-spec process(pid()) -> ok.
Process pending events (called when socket is ready).
-spec reset_stream(pid(), non_neg_integer(), non_neg_integer()) -> ok | {error, term()}.
Reset a stream.
-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.
-spec send_data(pid(), non_neg_integer(), iodata(), boolean()) -> ok | {error, term()}.
Send data on a stream.
-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 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.
-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 new owner process (async).
Set new owner process (synchronous). Use this when you need to ensure ownership is transferred before continuing.
-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()
-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 connection options.
-spec sockname(pid()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, term()}.
Get local address.
-spec start_link(binary() | inet:hostname() | inet:ip_address(), inet:port_number(), map(), pid()) -> {ok, pid()} | {error, term()}.
Start a QUIC connection process.
-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 a server-side QUIC connection. Called by quic_listener when a new connection is accepted.
-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).