nquic_cc_cubic (nquic v1.0.0)
View SourceCUBIC congestion control per RFC 8312.
CUBIC uses a cubic function for window growth during congestion avoidance, achieving better bandwidth utilization on high-BDP paths than NewReno while remaining TCP-friendly. Includes fast convergence (Section 4.6) and a TCP-friendly region (Section 4.2) where W_est tracks standard TCP growth.
Summary
Functions
Get the current congestion window size in bytes.
Get the current maximum datagram size in bytes.
Get the current slow start threshold.
Return the HyStart++ phase. standard means HyStart++ is disabled,
slow_start / css / done track the ladder. Used by tests and
diagnostic accessors; production code does not need to inspect it.
Initialize CUBIC state with default 1200-byte MSS.
Initialize CUBIC state with options. Recognises
Reduce the congestion window on a loss event.
Reset CUBIC state to the initial window after an idle period (RFC 9002 Section 7.8). All recovery/epoch bookkeeping is cleared so the next acked packet starts a fresh slow-start phase.
Grow the congestion window when a packet is acknowledged.
Handle a sent packet (no-op for CUBIC).
Collapse the congestion window to the minimum (2 * max_datagram_size)
on persistent congestion (RFC 9002 Section 7.6.2).
The CUBIC epoch is also reset (epoch_start, origin_point, tcp_cwnd,
w_max) so the next ACK after recovery starts a fresh growth phase from
the collapsed window. recovery_start_time is reset so subsequent ACKs
for newly sent packets are not filtered by the previous recovery period.
Any pending spurious-loss snapshot is discarded.
Roll back the most recent congestion-event reduction (RFC 9002 Appendix A.10). No-op when no rollback snapshot is available.
Set the maximum datagram size, recalculating cwnd if still at initial window.
Functions
-spec cubic_window(non_neg_integer(), non_neg_integer(), pos_integer()) -> non_neg_integer().
-spec get_cwnd(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}) -> non_neg_integer().
Get the current congestion window size in bytes.
-spec get_max_datagram_size(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}) -> pos_integer().
Get the current maximum datagram size in bytes.
-spec get_ssthresh(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}) -> non_neg_integer().
Get the current slow start threshold.
-spec hystart_phase(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}) -> standard | slow_start | css | done.
Return the HyStart++ phase. standard means HyStart++ is disabled,
slow_start / css / done track the ladder. Used by tests and
diagnostic accessors; production code does not need to inspect it.
-spec init() -> #state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}.
Initialize CUBIC state with default 1200-byte MSS.
-spec init(map()) -> #state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}.
Initialize CUBIC state with options. Recognises:
mss: pos_integer override of the 1200-byte default.slow_start:standard(classic Reno-style slow start, the default) orhystart_plus_plus(RFC 9406, exits slow start on RTT inflation rather than waiting for loss).
-spec on_congestion_event(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}, non_neg_integer(), non_neg_integer(), non_neg_integer()) -> #state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}.
Reduce the congestion window on a loss event.
-spec on_idle_reset(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}) -> #state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}.
Reset CUBIC state to the initial window after an idle period (RFC 9002 Section 7.8). All recovery/epoch bookkeeping is cleared so the next acked packet starts a fresh slow-start phase.
-spec on_packet_acked(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}, #sent_packet{packet_number :: nquic_packet_number:t(), time_sent :: non_neg_integer(), size :: non_neg_integer(), ack_eliciting :: boolean(), in_flight :: boolean(), frames :: [nquic_frame:t()]}, non_neg_integer(), map()) -> #state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}.
Grow the congestion window when a packet is acknowledged.
-spec on_packet_sent(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}, non_neg_integer(), non_neg_integer()) -> #state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}.
Handle a sent packet (no-op for CUBIC).
-spec on_persistent_congestion(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}) -> #state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}.
Collapse the congestion window to the minimum (2 * max_datagram_size)
on persistent congestion (RFC 9002 Section 7.6.2).
The CUBIC epoch is also reset (epoch_start, origin_point, tcp_cwnd,
w_max) so the next ACK after recovery starts a fresh growth phase from
the collapsed window. recovery_start_time is reset so subsequent ACKs
for newly sent packets are not filtered by the previous recovery period.
Any pending spurious-loss snapshot is discarded.
-spec on_spurious_congestion(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}) -> #state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}.
Roll back the most recent congestion-event reduction (RFC 9002 Appendix A.10). No-op when no rollback snapshot is available.
-spec set_max_datagram_size(#state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}, pos_integer()) -> #state{cwnd :: non_neg_integer(), ssthresh :: non_neg_integer(), max_datagram_size :: pos_integer(), recovery_start_time :: integer(), w_max :: non_neg_integer(), w_last_max :: non_neg_integer(), epoch_start :: non_neg_integer(), origin_point :: non_neg_integer(), tcp_cwnd :: non_neg_integer(), cubic_k :: undefined | float(), congestion_occurred :: boolean(), prev_state :: undefined | {non_neg_integer(), non_neg_integer(), integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), undefined | float(), boolean()}, hystart_phase :: standard | slow_start | css | done, last_round_min_rtt :: non_neg_integer(), current_round_min_rtt :: non_neg_integer(), rtt_sample_count :: non_neg_integer(), last_round_largest_pn :: non_neg_integer(), css_baseline_min_rtt :: non_neg_integer(), css_round_count :: non_neg_integer()}.
Set the maximum datagram size, recalculating cwnd if still at initial window.