Explicit-time QUIC packet-number spaces and loss recovery state.
Packet numbers are allocated by reserve/4 and are never reused. A packet
moves through :reserved, :queued, :sent, then :acked, :lost, or
:failed. ACKs received before a local send receipt are retained as a bounded
pending acknowledgement and resolved when the receipt arrives.
Receive ACK history retains the newest max_ack_ranges (1..256) ranges per
space. A monotonic receive floor discards older packet numbers, including
previously unseen delayed packets; reliable data can be retransmitted under
a fresh packet number. Discarding an encryption space resets its receive epoch.
Packet-number spaces stay global across migration. Each reserved packet pins
its send path, and at most two independent RTT/congestion controllers remain
live. The selected controller occupies rtt/congestion for compatibility;
paths stores only its one inactive peer. Cross-path ACKs do not sample RTT.
Summary
Functions
Recompute the next deadline from actual sent packets, without advancing time.
Install connection-wide peer ACK parameters without copying RTT estimates.
Discard an encryption space's outstanding work while preserving allocated packet numbers.
Record an authenticated packet number received in a packet-number space.
An independent path controller, or nil after retirement.
Current probe timeout duration in microseconds, before PTO backoff.
Reserve one fresh packet for an original selected by the latest expired PTO.
Permanently retire Initial/Handshake recovery, preserving the next packet number.
The selected send path; packet-number spaces are shared by all paths.
Types
@type ack_result() :: %{ acked: [non_neg_integer()], lost: [{packet_space(), non_neg_integer()}], rtt_sample: pos_integer() | nil }
@type initial_state() :: %Quic.Recovery{ active_path: path_id(), congestion: %Quic.Congestion.NewReno{ acked_bytes: 0, bytes_in_flight: 0, cwnd: term(), max_datagram_size: term(), mss: number(), ssthresh: term() }, deadline: nil, max_ack_ranges: 1..256, max_sent_packets: term(), path_defaults: %{ rtt: Quic.Recovery.RTT.t(), congestion: Quic.Congestion.NewReno.initial_state() }, paths: %{}, pto_grants: %{}, rtt: %Quic.Recovery.RTT{ ack_delay_exponent: term(), latest: nil, max_ack_delay: term(), min: nil, pto_count: 0, smoothed: nil, variance: nil }, spaces: map(), timer_generation: 0 }
@type packet_space() :: :initial | :handshake | :application
@type path_controller() :: %{ rtt: Quic.Recovery.RTT.t(), congestion: Quic.Congestion.NewReno.t() }
@type path_id() :: term()
@type t() :: %Quic.Recovery{ active_path: term(), congestion: term(), deadline: term(), max_ack_ranges: term(), max_sent_packets: term(), path_defaults: path_controller(), paths: term(), pto_grants: %{optional({packet_space(), non_neg_integer()}) => path_id()}, rtt: term(), spaces: term(), timer_generation: term() }
Functions
Recompute the next deadline from actual sent packets, without advancing time.
@spec configure_peer_ack(t(), non_neg_integer(), 0..20) :: {:ok, t()} | {:error, :invalid_ack_parameters}
Install connection-wide peer ACK parameters without copying RTT estimates.
Discard an encryption space's outstanding work while preserving allocated packet numbers.
@spec new(keyword()) :: initial_state()
@spec note_received(t(), atom(), non_neg_integer()) :: {:ok, t()} | {:error, atom()}
Record an authenticated packet number received in a packet-number space.
@spec path(t(), path_id()) :: path_controller() | nil
An independent path controller, or nil after retirement.
@spec pto_duration(t()) :: pos_integer()
Current probe timeout duration in microseconds, before PTO backoff.
@spec pto_duration(t(), path_id()) :: pos_integer()
@spec receive_ack(t(), packet_space(), map(), integer()) :: {:ok, t(), ack_result()} | {:error, atom()}
@spec receive_ack(t(), packet_space(), map(), integer(), path_id()) :: {:ok, t(), ack_result()} | {:error, atom()}
@spec reserve_probe( t(), packet_space(), non_neg_integer(), map(), integer(), integer() ) :: {:ok, t(), Quic.Recovery.Packet.t()} | {:error, atom()}
Reserve one fresh packet for an original selected by the latest expired PTO.
The one-shot grant is tied to the original packet and its send path. Probe bytes count as additional flight without increasing the congestion window; ordinary reservations cannot use this allowance. The caller supplies the actual protected-packet size limit and only the selected retry may use it.
Permanently retire Initial/Handshake recovery, preserving the next packet number.
The selected send path; packet-number spaces are shared by all paths.
@spec timer_expired?(t(), non_neg_integer(), integer()) :: boolean()