Branch Bbr2Sender to create Bbr3Sender and no long have modes extend from Bbr2ModeBase. This code is not reachable in production yet. I will remove more code and possibly move some structs to bbr2_misc.h before it reaches production. PiperOrigin-RevId: 901611719
diff --git a/build/source_list.bzl b/build/source_list.bzl index f87c9b1..9a665df 100644 --- a/build/source_list.bzl +++ b/build/source_list.bzl
@@ -155,6 +155,7 @@ "quic/core/congestion_control/bbr2_probe_rtt.h", "quic/core/congestion_control/bbr2_sender.h", "quic/core/congestion_control/bbr2_startup.h", + "quic/core/congestion_control/bbr3_sender.h", "quic/core/congestion_control/bbr_sender.h", "quic/core/congestion_control/cubic_bytes.h", "quic/core/congestion_control/general_loss_algorithm.h", @@ -521,6 +522,7 @@ "quic/core/congestion_control/bbr2_probe_rtt.cc", "quic/core/congestion_control/bbr2_sender.cc", "quic/core/congestion_control/bbr2_startup.cc", + "quic/core/congestion_control/bbr3_sender.cc", "quic/core/congestion_control/bbr_sender.cc", "quic/core/congestion_control/cubic_bytes.cc", "quic/core/congestion_control/general_loss_algorithm.cc", @@ -1239,6 +1241,7 @@ "oblivious_http/oblivious_http_integration_test.cc", "quic/core/congestion_control/bandwidth_sampler_test.cc", "quic/core/congestion_control/bbr2_simulator_test.cc", + "quic/core/congestion_control/bbr3_simulator_test.cc", "quic/core/congestion_control/bbr_sender_test.cc", "quic/core/congestion_control/cubic_bytes_test.cc", "quic/core/congestion_control/general_loss_algorithm_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni index d1be9d0..392014b 100644 --- a/build/source_list.gni +++ b/build/source_list.gni
@@ -155,6 +155,7 @@ "src/quiche/quic/core/congestion_control/bbr2_probe_rtt.h", "src/quiche/quic/core/congestion_control/bbr2_sender.h", "src/quiche/quic/core/congestion_control/bbr2_startup.h", + "src/quiche/quic/core/congestion_control/bbr3_sender.h", "src/quiche/quic/core/congestion_control/bbr_sender.h", "src/quiche/quic/core/congestion_control/cubic_bytes.h", "src/quiche/quic/core/congestion_control/general_loss_algorithm.h", @@ -521,6 +522,7 @@ "src/quiche/quic/core/congestion_control/bbr2_probe_rtt.cc", "src/quiche/quic/core/congestion_control/bbr2_sender.cc", "src/quiche/quic/core/congestion_control/bbr2_startup.cc", + "src/quiche/quic/core/congestion_control/bbr3_sender.cc", "src/quiche/quic/core/congestion_control/bbr_sender.cc", "src/quiche/quic/core/congestion_control/cubic_bytes.cc", "src/quiche/quic/core/congestion_control/general_loss_algorithm.cc", @@ -1240,6 +1242,7 @@ "src/quiche/oblivious_http/oblivious_http_integration_test.cc", "src/quiche/quic/core/congestion_control/bandwidth_sampler_test.cc", "src/quiche/quic/core/congestion_control/bbr2_simulator_test.cc", + "src/quiche/quic/core/congestion_control/bbr3_simulator_test.cc", "src/quiche/quic/core/congestion_control/bbr_sender_test.cc", "src/quiche/quic/core/congestion_control/cubic_bytes_test.cc", "src/quiche/quic/core/congestion_control/general_loss_algorithm_test.cc",
diff --git a/build/source_list.json b/build/source_list.json index a953857..d692ed9 100644 --- a/build/source_list.json +++ b/build/source_list.json
@@ -154,6 +154,7 @@ "quiche/quic/core/congestion_control/bbr2_probe_rtt.h", "quiche/quic/core/congestion_control/bbr2_sender.h", "quiche/quic/core/congestion_control/bbr2_startup.h", + "quiche/quic/core/congestion_control/bbr3_sender.h", "quiche/quic/core/congestion_control/bbr_sender.h", "quiche/quic/core/congestion_control/cubic_bytes.h", "quiche/quic/core/congestion_control/general_loss_algorithm.h", @@ -520,6 +521,7 @@ "quiche/quic/core/congestion_control/bbr2_probe_rtt.cc", "quiche/quic/core/congestion_control/bbr2_sender.cc", "quiche/quic/core/congestion_control/bbr2_startup.cc", + "quiche/quic/core/congestion_control/bbr3_sender.cc", "quiche/quic/core/congestion_control/bbr_sender.cc", "quiche/quic/core/congestion_control/cubic_bytes.cc", "quiche/quic/core/congestion_control/general_loss_algorithm.cc", @@ -1239,6 +1241,7 @@ "quiche/oblivious_http/oblivious_http_integration_test.cc", "quiche/quic/core/congestion_control/bandwidth_sampler_test.cc", "quiche/quic/core/congestion_control/bbr2_simulator_test.cc", + "quiche/quic/core/congestion_control/bbr3_simulator_test.cc", "quiche/quic/core/congestion_control/bbr_sender_test.cc", "quiche/quic/core/congestion_control/cubic_bytes_test.cc", "quiche/quic/core/congestion_control/general_loss_algorithm_test.cc",
diff --git a/quiche/quic/core/congestion_control/bbr3_sender.cc b/quiche/quic/core/congestion_control/bbr3_sender.cc new file mode 100644 index 0000000..71f4352 --- /dev/null +++ b/quiche/quic/core/congestion_control/bbr3_sender.cc
@@ -0,0 +1,1316 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/congestion_control/bbr3_sender.h" + +#include <algorithm> +#include <cstddef> +#include <ostream> +#include <sstream> +#include <string> + +#include "quiche/quic/core/congestion_control/bandwidth_sampler.h" +#include "quiche/quic/core/congestion_control/bbr2_misc.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/quic_bandwidth.h" +#include "quiche/quic/core/quic_tag.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_flag_utils.h" +#include "quiche/quic/platform/api/quic_flags.h" +#include "quiche/quic/platform/api/quic_logging.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/print_elements.h" + +namespace quic { + +namespace { +// Constants based on TCP defaults. +// The minimum CWND to ensure delayed acks don't reduce bandwidth measurements. +// Does not inflate the pacing rate. +const QuicByteCount kDefaultMinimumCongestionWindow = 4 * kMaxSegmentSize; + +const float kInitialPacingGain = 2.885f; + +const int kMaxModeChangesPerCongestionEvent = 4; +} // namespace + +Bbr3Sender::Bbr3Sender(QuicTime now, const RttStats* rtt_stats, + const QuicUnackedPacketMap* unacked_packets, + QuicPacketCount initial_cwnd_in_packets, + QuicPacketCount max_cwnd_in_packets, QuicRandom* random, + QuicConnectionStats* stats, BbrSender* old_sender) + : mode_(Bbr2Mode::STARTUP), + rtt_stats_(rtt_stats), + unacked_packets_(unacked_packets), + random_(random), + connection_stats_(stats), + params_(kDefaultMinimumCongestionWindow, + max_cwnd_in_packets * kDefaultTCPMSS), + model_(¶ms_, rtt_stats->SmoothedOrInitialRtt(), + rtt_stats->last_update_time(), + /*cwnd_gain=*/1.0, + /*pacing_gain=*/kInitialPacingGain, + old_sender ? &old_sender->sampler_ : nullptr), + initial_cwnd_(params_.cwnd_limits.ApplyLimits( + (old_sender) ? old_sender->GetCongestionWindow() + : (initial_cwnd_in_packets * kDefaultTCPMSS))), + cwnd_(initial_cwnd_), + pacing_rate_(kInitialPacingGain * + QuicBandwidth::FromBytesAndTimeDelta( + cwnd_, rtt_stats->SmoothedOrInitialRtt())), + last_sample_is_app_limited_(false) { + // Increment, instead of reset startup stats, so we don't lose data recorded + // before QuicConnection switched send algorithm to BBRv2. + ++connection_stats_->slowstart_count; + if (!connection_stats_->slowstart_duration.IsRunning()) { + connection_stats_->slowstart_duration.Start(now); + } + // Enter() is never called for Startup, so the gains needs to be set here. + model_.set_pacing_gain(params_.startup_pacing_gain); + model_.set_cwnd_gain(params_.startup_cwnd_gain); + QUIC_DVLOG(2) << this << " Initializing Bbr3Sender. mode:" << mode_ + << ", PacingRate:" << pacing_rate_ << ", Cwnd:" << cwnd_ + << ", CwndLimits:" << params_.cwnd_limits << " @ " << now; + QUICHE_DCHECK_EQ(mode_, Bbr2Mode::STARTUP); +} + +void Bbr3Sender::SetFromConfig(const QuicConfig& config, + Perspective perspective) { + if (config.HasClientRequestedIndependentOption(kB2NA, perspective)) { + params_.add_ack_height_to_queueing_threshold = false; + } + if (config.HasClientRequestedIndependentOption(kB2RP, perspective)) { + params_.avoid_unnecessary_probe_rtt = false; + } + if (config.HasClientRequestedIndependentOption(k1RTT, perspective)) { + params_.startup_full_bw_rounds = 1; + } + if (config.HasClientRequestedIndependentOption(k2RTT, perspective)) { + params_.startup_full_bw_rounds = 2; + } + if (config.HasClientRequestedIndependentOption(kB2HR, perspective)) { + params_.inflight_hi_headroom = 0.15; + } + if (config.HasClientRequestedIndependentOption(kICW1, perspective)) { + max_cwnd_when_network_parameters_adjusted_ = 100 * kDefaultTCPMSS; + } + + ApplyConnectionOptions(config.ClientRequestedIndependentOptions(perspective)); +} + +void Bbr3Sender::ApplyConnectionOptions( + const QuicTagVector& connection_options) { + if (GetQuicReloadableFlag(quic_bbr2_extra_acked_window) && + ContainsQuicTag(connection_options, kBBR4)) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_extra_acked_window, 1, 2); + model_.SetMaxAckHeightTrackerWindowLength(20); + } + if (GetQuicReloadableFlag(quic_bbr2_extra_acked_window) && + ContainsQuicTag(connection_options, kBBR5)) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_extra_acked_window, 2, 2); + model_.SetMaxAckHeightTrackerWindowLength(40); + } + if (ContainsQuicTag(connection_options, kBBQ1)) { + params_.startup_pacing_gain = 2.773; + params_.drain_pacing_gain = 1.0 / params_.drain_cwnd_gain; + } + if (ContainsQuicTag(connection_options, kBBQ2)) { + params_.startup_cwnd_gain = 2.885; + params_.drain_cwnd_gain = 2.885; + model_.set_cwnd_gain(params_.startup_cwnd_gain); + } + if (ContainsQuicTag(connection_options, kB2LO)) { + params_.ignore_inflight_lo = true; + } + if (ContainsQuicTag(connection_options, kB2NE)) { + params_.always_exit_startup_on_excess_loss = true; + } + if (ContainsQuicTag(connection_options, kB2SL)) { + params_.startup_loss_exit_use_max_delivered_for_inflight_hi = false; + } + if (ContainsQuicTag(connection_options, kB2H2)) { + params_.limit_inflight_hi_by_max_delivered = true; + } + if (ContainsQuicTag(connection_options, kB2DL)) { + params_.use_bytes_delivered_for_inflight_hi = true; + } + if (ContainsQuicTag(connection_options, kB2RC)) { + params_.enable_reno_coexistence = false; + } + if (ContainsQuicTag(connection_options, kBSAO)) { + model_.EnableOverestimateAvoidance(); + } + if (ContainsQuicTag(connection_options, kBBQ6)) { + params_.decrease_startup_pacing_at_end_of_round = true; + } + if (ContainsQuicTag(connection_options, kBBQ7)) { + params_.bw_lo_mode_ = Bbr2Params::QuicBandwidthLoMode::MIN_RTT_REDUCTION; + } + if (ContainsQuicTag(connection_options, kBBQ8)) { + params_.bw_lo_mode_ = Bbr2Params::QuicBandwidthLoMode::INFLIGHT_REDUCTION; + } + if (ContainsQuicTag(connection_options, kBBQ9)) { + params_.bw_lo_mode_ = Bbr2Params::QuicBandwidthLoMode::CWND_REDUCTION; + } + if (ContainsQuicTag(connection_options, kB202)) { + params_.max_probe_up_queue_rounds = 1; + } + if (ContainsQuicTag(connection_options, kB203)) { + params_.probe_up_ignore_inflight_hi = false; + } + if (ContainsQuicTag(connection_options, kB204)) { + model_.SetReduceExtraAckedOnBandwidthIncrease(true); + } + if (ContainsQuicTag(connection_options, kB205)) { + params_.startup_include_extra_acked = true; + } + if (ContainsQuicTag(connection_options, kB207)) { + params_.max_startup_queue_rounds = 1; + } + if (ContainsQuicTag(connection_options, kBBRA)) { + model_.SetStartNewAggregationEpochAfterFullRound(true); + } + if (ContainsQuicTag(connection_options, kBBRB)) { + model_.SetLimitMaxAckHeightTrackerBySendRate(true); + } + if (ContainsQuicTag(connection_options, kADP0)) { + model_.SetEnableAppDrivenPacing(true); + } + if (ContainsQuicTag(connection_options, kB206)) { + params_.startup_full_loss_count = params_.probe_bw_full_loss_count; + } + if (ContainsQuicTag(connection_options, kBBHI)) { + params_.probe_up_simplify_inflight_hi = true; + // Simplify inflight_hi is intended as an alternative to ignoring it, + // so ensure we're not ignoring it. + params_.probe_up_ignore_inflight_hi = false; + } + if (ContainsQuicTag(connection_options, kBB2U)) { + params_.max_probe_up_queue_rounds = 2; + } + if (ContainsQuicTag(connection_options, kBB2S)) { + params_.max_startup_queue_rounds = 2; + } +} + +Limits<QuicByteCount> Bbr3Sender::GetCwndLimitsByMode() const { + switch (mode_) { + case Bbr2Mode::STARTUP: + // Inflight_lo is never set in STARTUP. + QUICHE_DCHECK_EQ(Bbr2NetworkModel::inflight_lo_default(), + model_.inflight_lo()); + return NoGreaterThan(model_.inflight_lo()); + case Bbr2Mode::PROBE_BW: { + if (probe_bw_.phase == ProbePhase::PROBE_CRUISE) { + return NoGreaterThan( + std::min(model_.inflight_lo(), model_.inflight_hi_with_headroom())); + } + if (params_.probe_up_ignore_inflight_hi && + probe_bw_.phase == ProbePhase::PROBE_UP) { + // Similar to STARTUP. + return NoGreaterThan(model_.inflight_lo()); + } + return NoGreaterThan( + std::min(model_.inflight_lo(), model_.inflight_hi())); + } + case Bbr2Mode::DRAIN: + return NoGreaterThan(model_.inflight_lo()); + case Bbr2Mode::PROBE_RTT: { + QuicByteCount inflight_upper_bound = + std::min(model_.inflight_lo(), model_.inflight_hi_with_headroom()); + return NoGreaterThan(std::min(inflight_upper_bound, InflightTarget())); + } + default: + QUICHE_NOTREACHED(); + return Unlimited<QuicByteCount>(); + } +} + +void Bbr3Sender::AdjustNetworkParameters(const NetworkParams& params) { + model_.UpdateNetworkParameters(params.rtt); + + if (mode_ == Bbr2Mode::STARTUP) { + const QuicByteCount prior_cwnd = cwnd_; + + QuicBandwidth effective_bandwidth = + std::max(params.bandwidth, model_.BandwidthEstimate()); + connection_stats_->cwnd_bootstrapping_rtt_us = + model_.MinRtt().ToMicroseconds(); + + if (params.max_initial_congestion_window > 0) { + max_cwnd_when_network_parameters_adjusted_ = + params.max_initial_congestion_window * kDefaultTCPMSS; + } + cwnd_ = params_.cwnd_limits.ApplyLimits( + std::min(max_cwnd_when_network_parameters_adjusted_, + model_.BDP(effective_bandwidth))); + + if (!params.allow_cwnd_to_decrease) { + cwnd_ = std::max(cwnd_, prior_cwnd); + } + + pacing_rate_ = std::max(pacing_rate_, QuicBandwidth::FromBytesAndTimeDelta( + cwnd_, model_.MinRtt())); + } +} + +void Bbr3Sender::SetInitialCongestionWindowInPackets( + QuicPacketCount congestion_window) { + if (mode_ == Bbr2Mode::STARTUP) { + // The cwnd limits is unchanged and still applies to the new cwnd. + cwnd_ = params_.cwnd_limits.ApplyLimits(congestion_window * kDefaultTCPMSS); + } +} + +void Bbr3Sender::SetApplicationDrivenPacingRate( + QuicBandwidth application_bandwidth_target) { + QUIC_CODE_COUNT(quic_bbr2_set_app_driven_pacing_rate); + model_.SetApplicationBandwidthTarget(application_bandwidth_target); +} + +void Bbr3Sender::OnCongestionEvent(bool /*rtt_updated*/, + QuicByteCount prior_in_flight, + QuicTime event_time, + const AckedPacketVector& acked_packets, + const LostPacketVector& lost_packets, + QuicPacketCount /*num_ect*/, + QuicPacketCount /*num_ce*/) { + QUIC_DVLOG(3) << this + << " OnCongestionEvent. prior_in_flight:" << prior_in_flight + << " prior_cwnd:" << cwnd_ << " @ " << event_time; + Bbr2CongestionEvent congestion_event; + congestion_event.prior_cwnd = cwnd_; + congestion_event.prior_bytes_in_flight = prior_in_flight; + bool is_probing_for_bandwidth = false; + if (mode_ == Bbr2Mode::STARTUP) { + is_probing_for_bandwidth = true; + } else if (mode_ == Bbr2Mode::PROBE_BW) { + is_probing_for_bandwidth = probe_bw_.phase == ProbePhase::PROBE_REFILL || + probe_bw_.phase == ProbePhase::PROBE_UP; + } + congestion_event.is_probing_for_bandwidth = is_probing_for_bandwidth; + + model_.OnCongestionEventStart(event_time, acked_packets, lost_packets, + &congestion_event); + + if (InSlowStart()) { + if (!lost_packets.empty()) { + connection_stats_->slowstart_packets_lost += lost_packets.size(); + connection_stats_->slowstart_bytes_lost += congestion_event.bytes_lost; + } + if (congestion_event.end_of_round_trip) { + ++connection_stats_->slowstart_num_rtts; + } + } + + // Number of mode changes allowed for this congestion event. + int mode_changes_allowed = kMaxModeChangesPerCongestionEvent; + while (true) { + Bbr2Mode prev_mode = mode_; + switch (mode_) { + case Bbr2Mode::STARTUP: + mode_ = OnCongestionEventStartup(congestion_event); + break; + case Bbr2Mode::DRAIN: + mode_ = OnCongestionEventDrain(congestion_event); + break; + case Bbr2Mode::PROBE_BW: + mode_ = OnCongestionEventProbeBw(prior_in_flight, event_time, + congestion_event); + break; + case Bbr2Mode::PROBE_RTT: + mode_ = OnCongestionEventProbeRtt(congestion_event); + break; + } + + if (mode_ == prev_mode) { + break; + } + + QUIC_DVLOG(2) << this << " Mode change: " << prev_mode << " ==> " << mode_ + << " @ " << event_time; + + if (prev_mode == Bbr2Mode::STARTUP) { + LeaveStartup(event_time); + } + + if (mode_ == Bbr2Mode::PROBE_BW) { + if (probe_bw_.phase == ProbePhase::PROBE_NOT_STARTED) { + EnterProbeDown(/*probed_too_high=*/false, /*stopped_risky_probe=*/false, + event_time); + } else { + QUICHE_DCHECK(probe_bw_.phase == ProbePhase::PROBE_CRUISE || + probe_bw_.phase == ProbePhase::PROBE_REFILL); + probe_bw_.cycle_start_time = event_time; + if (probe_bw_.phase == ProbePhase::PROBE_CRUISE) { + EnterProbeCruise(event_time); + } else if (probe_bw_.phase == ProbePhase::PROBE_REFILL) { + EnterProbeRefill(probe_bw_.probe_up_rounds, event_time); + } + } + } else if (mode_ == Bbr2Mode::PROBE_RTT) { + EnterProbeRtt(); + } + + --mode_changes_allowed; + if (mode_changes_allowed < 0) { + QUIC_BUG(quic_bug_10443_1) + << "Exceeded max number of mode changes per congestion event."; + break; + } + } + + UpdatePacingRate(congestion_event.bytes_acked); + QUIC_BUG_IF(quic_bug_10443_2, pacing_rate_.IsZero()) + << "Pacing rate must not be zero!"; + + UpdateCongestionWindow(congestion_event.bytes_acked); + QUIC_BUG_IF(quic_bug_10443_3, cwnd_ == 0u) + << "Congestion window must not be zero!"; + + model_.OnCongestionEventFinish(unacked_packets_->GetLeastUnacked(), + congestion_event); + last_sample_is_app_limited_ = + congestion_event.last_packet_send_state.is_app_limited; + if (!last_sample_is_app_limited_) { + has_non_app_limited_sample_ = true; + } + if (congestion_event.bytes_in_flight == 0 && + params_.avoid_unnecessary_probe_rtt) { + OnEnterQuiescence(event_time); + } + + QUIC_DVLOG(3) + << this + << " END CongestionEvent(acked:" << quiche::PrintElements(acked_packets) + << ", lost:" << lost_packets.size() << ") " + << ", Mode:" << mode_ << ", RttCount:" << model_.RoundTripCount() + << ", BytesInFlight:" << congestion_event.bytes_in_flight + << ", PacingRate:" << PacingRate(0) << ", CWND:" << GetCongestionWindow() + << ", PacingGain:" << model_.pacing_gain() + << ", CwndGain:" << model_.cwnd_gain() + << ", BandwidthEstimate(kbps):" << BandwidthEstimate().ToKBitsPerSecond() + << ", MinRTT(us):" << model_.MinRtt().ToMicroseconds() + << ", BDP:" << model_.BDP(BandwidthEstimate()) + << ", BandwidthLatest(kbps):" + << model_.bandwidth_latest().ToKBitsPerSecond() + << ", BandwidthLow(kbps):" << model_.bandwidth_lo().ToKBitsPerSecond() + << ", BandwidthHigh(kbps):" << model_.MaxBandwidth().ToKBitsPerSecond() + << ", InflightLatest:" << model_.inflight_latest() + << ", InflightLow:" << model_.inflight_lo() + << ", InflightHigh:" << model_.inflight_hi() + << ", TotalAcked:" << model_.total_bytes_acked() + << ", TotalLost:" << model_.total_bytes_lost() + << ", TotalSent:" << model_.total_bytes_sent() << " @ " << event_time; +} + +void Bbr3Sender::UpdatePacingRate(QuicByteCount bytes_acked) { + if (BandwidthEstimate().IsZero()) { + return; + } + + if (model_.total_bytes_acked() == bytes_acked) { + // After the first ACK, cwnd_ is still the initial congestion window. + pacing_rate_ = QuicBandwidth::FromBytesAndTimeDelta(cwnd_, model_.MinRtt()); + return; + } + + QuicBandwidth target_rate = model_.pacing_gain() * model_.BandwidthEstimate(); + if (model_.full_bandwidth_reached()) { + pacing_rate_ = target_rate; + return; + } + if (params_.decrease_startup_pacing_at_end_of_round && + model_.pacing_gain() < Params().startup_pacing_gain) { + pacing_rate_ = target_rate; + return; + } + if (params_.bw_lo_mode_ != Bbr2Params::DEFAULT && + model_.loss_events_in_round() > 0) { + pacing_rate_ = target_rate; + return; + } + + // By default, the pacing rate never decreases in STARTUP. + if (target_rate > pacing_rate_) { + pacing_rate_ = target_rate; + } +} + +void Bbr3Sender::UpdateCongestionWindow(QuicByteCount bytes_acked) { + QuicByteCount target_cwnd = GetTargetCongestionWindow(model_.cwnd_gain()); + + const QuicByteCount prior_cwnd = cwnd_; + if (model_.full_bandwidth_reached() || Params().startup_include_extra_acked) { + target_cwnd += model_.MaxAckHeight(); + cwnd_ = std::min(prior_cwnd + bytes_acked, target_cwnd); + } else if (prior_cwnd < target_cwnd || prior_cwnd < 2 * initial_cwnd_) { + cwnd_ = prior_cwnd + bytes_acked; + } + const QuicByteCount desired_cwnd = cwnd_; + + cwnd_ = GetCwndLimitsByMode().ApplyLimits(cwnd_); + const QuicByteCount model_limited_cwnd = cwnd_; + + cwnd_ = params_.cwnd_limits.ApplyLimits(cwnd_); + + QUIC_DVLOG(3) << this << " Updating CWND. target_cwnd:" << target_cwnd + << ", max_ack_height:" << model_.MaxAckHeight() + << ", full_bw:" << model_.full_bandwidth_reached() + << ", bytes_acked:" << bytes_acked + << ", inflight_lo:" << model_.inflight_lo() + << ", inflight_hi:" << model_.inflight_hi() << ". (prior_cwnd) " + << prior_cwnd << " => (desired_cwnd) " << desired_cwnd + << " => (model_limited_cwnd) " << model_limited_cwnd + << " => (final_cwnd) " << cwnd_; +} + +QuicByteCount Bbr3Sender::GetTargetCongestionWindow(float gain) const { + return std::max(model_.BDP(model_.BandwidthEstimate(), gain), + params_.cwnd_limits.Min()); +} + +void Bbr3Sender::OnPacketSent(QuicTime sent_time, QuicByteCount bytes_in_flight, + QuicPacketNumber packet_number, + QuicByteCount bytes, + HasRetransmittableData is_retransmittable) { + QUIC_DVLOG(3) << this << " OnPacketSent: pkn:" << packet_number + << ", bytes:" << bytes << ", cwnd:" << cwnd_ + << ", inflight:" << bytes_in_flight + bytes + << ", total_sent:" << model_.total_bytes_sent() + bytes + << ", total_acked:" << model_.total_bytes_acked() + << ", total_lost:" << model_.total_bytes_lost() << " @ " + << sent_time; + if (InSlowStart()) { + ++connection_stats_->slowstart_packets_sent; + connection_stats_->slowstart_bytes_sent += bytes; + } + if (bytes_in_flight == 0 && params_.avoid_unnecessary_probe_rtt) { + OnExitQuiescence(sent_time); + } + model_.OnPacketSent(sent_time, bytes_in_flight, packet_number, bytes, + is_retransmittable); +} + +void Bbr3Sender::OnPacketNeutered(QuicPacketNumber packet_number) { + model_.OnPacketNeutered(packet_number); +} + +bool Bbr3Sender::CanSend(QuicByteCount bytes_in_flight) { + const bool result = bytes_in_flight < GetCongestionWindow(); + return result; +} + +QuicByteCount Bbr3Sender::GetCongestionWindow() const { + // TODO(wub): Implement Recovery? + return cwnd_; +} + +QuicBandwidth Bbr3Sender::PacingRate(QuicByteCount /*bytes_in_flight*/) const { + return pacing_rate_; +} + +void Bbr3Sender::OnApplicationLimited(QuicByteCount bytes_in_flight) { + if (bytes_in_flight >= GetCongestionWindow()) { + return; + } + + model_.OnApplicationLimited(); + QUIC_DVLOG(2) << this << " Becoming application limited. Last sent packet: " + << model_.last_sent_packet() + << ", CWND: " << GetCongestionWindow(); +} + +QuicByteCount Bbr3Sender::GetTargetBytesInflight() const { + QuicByteCount bdp = model_.BDP(model_.BandwidthEstimate()); + return std::min(bdp, GetCongestionWindow()); +} + +void Bbr3Sender::PopulateConnectionStats(QuicConnectionStats* stats) const { + stats->num_ack_aggregation_epochs = model_.num_ack_aggregation_epochs(); +} + +void Bbr3Sender::OnEnterQuiescence(QuicTime now) { + last_quiescence_start_ = now; +} + +void Bbr3Sender::LeaveStartup(QuicTime now) { + connection_stats_->slowstart_duration.Stop(now); + // Clear bandwidth_lo if it's set during STARTUP. + model_.clear_bandwidth_lo(); +} + +void Bbr3Sender::OnExitQuiescence(QuicTime now) { + if (last_quiescence_start_ == QuicTime::Zero()) { + return; + } + + Bbr2Mode prev_mode = mode_; + switch (mode_) { + case Bbr2Mode::STARTUP: + case Bbr2Mode::DRAIN: + break; + case Bbr2Mode::PROBE_BW: + QUIC_DVLOG(3) << this << " Postponing min_rtt_timestamp(" + << model_.MinRttTimestamp() << ") by " + << now - last_quiescence_start_; + model_.PostponeMinRttTimestamp(now - last_quiescence_start_); + break; + case Bbr2Mode::PROBE_RTT: + if (now > probe_rtt_.exit_time) { + mode_ = Bbr2Mode::PROBE_BW; + } + break; + } + + if (mode_ != prev_mode) { + QUICHE_DCHECK_EQ(mode_, Bbr2Mode::PROBE_BW); + if (probe_bw_.phase == ProbePhase::PROBE_NOT_STARTED) { + EnterProbeDown(/*probed_too_high=*/false, /*stopped_risky_probe=*/false, + now); + } else { + QUICHE_DCHECK(probe_bw_.phase == ProbePhase::PROBE_CRUISE || + probe_bw_.phase == ProbePhase::PROBE_REFILL); + probe_bw_.cycle_start_time = now; + if (probe_bw_.phase == ProbePhase::PROBE_CRUISE) { + EnterProbeCruise(now); + } else if (probe_bw_.phase == ProbePhase::PROBE_REFILL) { + EnterProbeRefill(probe_bw_.probe_up_rounds, now); + } + } + } + last_quiescence_start_ = QuicTime::Zero(); +} + +std::string Bbr3Sender::GetDebugState() const { + std::ostringstream stream; + stream << ExportDebugState(); + return stream.str(); +} + +Bbr3Sender::DebugState Bbr3Sender::ExportDebugState() const { + DebugState s; + s.mode = mode_; + s.round_trip_count = model_.RoundTripCount(); + s.bandwidth_hi = model_.MaxBandwidth(); + s.bandwidth_lo = model_.bandwidth_lo(); + s.bandwidth_est = BandwidthEstimate(); + s.inflight_hi = model_.inflight_hi(); + s.inflight_lo = model_.inflight_lo(); + s.max_ack_height = model_.MaxAckHeight(); + s.min_rtt = model_.MinRtt(); + s.min_rtt_timestamp = model_.MinRttTimestamp(); + s.congestion_window = cwnd_; + s.pacing_rate = pacing_rate_; + s.last_sample_is_app_limited = last_sample_is_app_limited_; + s.end_of_app_limited_phase = model_.end_of_app_limited_phase(); + + s.startup.full_bandwidth_reached = model_.full_bandwidth_reached(); + s.startup.full_bandwidth_baseline = model_.full_bandwidth_baseline(); + s.startup.round_trips_without_bandwidth_growth = + model_.rounds_without_bandwidth_growth(); + + s.drain.drain_target = DrainTarget(); + + s.probe_bw.phase = probe_bw_.phase; + s.probe_bw.cycle_start_time = probe_bw_.cycle_start_time; + s.probe_bw.phase_start_time = probe_bw_.phase_start_time; + + s.probe_rtt.inflight_target = InflightTarget(); + s.probe_rtt.exit_time = probe_rtt_.exit_time; + + return s; +} + +std::ostream& operator<<(std::ostream& os, const Bbr3Sender::DebugState& s) { + os << "mode: " << s.mode << "\n"; + os << "round_trip_count: " << s.round_trip_count << "\n"; + os << "bandwidth_hi ~ lo ~ est: " << s.bandwidth_hi << " ~ " << s.bandwidth_lo + << " ~ " << s.bandwidth_est << "\n"; + os << "min_rtt: " << s.min_rtt << "\n"; + os << "min_rtt_timestamp: " << s.min_rtt_timestamp << "\n"; + os << "congestion_window: " << s.congestion_window << "\n"; + os << "pacing_rate: " << s.pacing_rate << "\n"; + os << "last_sample_is_app_limited: " << s.last_sample_is_app_limited << "\n"; + + os << "startup: {full_bw_reached: " << s.startup.full_bandwidth_reached + << ", full_bw_baseline: " << s.startup.full_bandwidth_baseline + << ", rounds_without_growth: " + << s.startup.round_trips_without_bandwidth_growth << "}\n"; + os << "drain: {drain_target: " << s.drain.drain_target << "}\n"; + os << "probe_bw: {phase: " << ProbePhaseToString(s.probe_bw.phase) + << ", cycle_start_time: " << s.probe_bw.cycle_start_time + << ", phase_start_time: " << s.probe_bw.phase_start_time << "}\n"; + os << "probe_rtt: {inflight_target: " << s.probe_rtt.inflight_target + << ", exit_time: " << s.probe_rtt.exit_time << "}\n"; + + return os; +} + +Bbr2Mode Bbr3Sender::OnCongestionEventStartup( + const Bbr2CongestionEvent& congestion_event) { + if (model_.full_bandwidth_reached()) { + QUIC_BUG(quic_bug_10463_2) + << "In STARTUP, but full_bandwidth_reached is true."; + return Bbr2Mode::DRAIN; + } + if (!congestion_event.end_of_round_trip) { + return Bbr2Mode::STARTUP; + } + bool has_bandwidth_growth = model_.HasBandwidthGrowth(congestion_event); + if (params_.max_startup_queue_rounds > 0 && !has_bandwidth_growth) { + // 1.75 is less than the 2x CWND gain, but substantially more than 1.25x, + // the minimum bandwidth increase expected during STARTUP. + model_.CheckPersistentQueue(congestion_event, 1.75); + } + // TCP BBR always exits upon excessive losses. QUIC BBRv1 does not exit + // upon excessive losses, if enough bandwidth growth is observed or if the + // sample was app limited. + if (params_.always_exit_startup_on_excess_loss || + (!congestion_event.last_packet_send_state.is_app_limited && + !has_bandwidth_growth)) { + CheckExcessiveLosses(congestion_event); + } + + if (params_.decrease_startup_pacing_at_end_of_round) { + QUICHE_DCHECK_GT(model_.pacing_gain(), 0); + if (!congestion_event.last_packet_send_state.is_app_limited) { + // Multiply by startup_pacing_gain, so if the bandwidth doubles, + // the pacing gain will be the full startup_pacing_gain. + if (startup_.max_bw_at_round_beginning > QuicBandwidth::Zero()) { + const float bandwidth_ratio = std::max( + 1., model_.MaxBandwidth().ToBitsPerSecond() / + static_cast<double>( + startup_.max_bw_at_round_beginning.ToBitsPerSecond())); + // Even when bandwidth isn't increasing, use a gain large enough to + // cause a full_bw_threshold increase. + const float new_gain = + ((bandwidth_ratio - 1) * + (params_.startup_pacing_gain - params_.full_bw_threshold)) + + params_.full_bw_threshold; + // Allow the pacing gain to decrease. + model_.set_pacing_gain(std::min(params_.startup_pacing_gain, new_gain)); + // Clear bandwidth_lo if it's less than the pacing rate. + // This avoids a constantly app-limited flow from having it's pacing + // gain effectively decreased below 1.25. + if (model_.bandwidth_lo() < + model_.MaxBandwidth() * model_.pacing_gain()) { + model_.clear_bandwidth_lo(); + } + } + startup_.max_bw_at_round_beginning = model_.MaxBandwidth(); + } + } + + // TODO(wub): Maybe implement STARTUP => PROBE_RTT. + return model_.full_bandwidth_reached() ? Bbr2Mode::DRAIN : Bbr2Mode::STARTUP; +} + +void Bbr3Sender::CheckExcessiveLosses( + const Bbr2CongestionEvent& congestion_event) { + QUICHE_DCHECK(congestion_event.end_of_round_trip); + + if (model_.full_bandwidth_reached()) { + return; + } + + // At the end of a round trip. Check if loss is too high in this round. + if (model_.IsInflightTooHigh(congestion_event, + params_.startup_full_loss_count)) { + QuicByteCount new_inflight_hi = model_.BDP(); + if (params_.startup_loss_exit_use_max_delivered_for_inflight_hi) { + if (new_inflight_hi < model_.max_bytes_delivered_in_round()) { + new_inflight_hi = model_.max_bytes_delivered_in_round(); + } + } + QUIC_DVLOG(3) << this << " Exiting STARTUP due to loss at round " + << model_.RoundTripCount() + << ". inflight_hi:" << new_inflight_hi; + // TODO(ianswett): Add a shared method to set inflight_hi in the model. + model_.set_inflight_hi(new_inflight_hi); + model_.set_full_bandwidth_reached(); + connection_stats_->bbr_exit_startup_due_to_loss = true; + } +} + +Bbr2Mode Bbr3Sender::OnCongestionEventDrain( + const Bbr2CongestionEvent& congestion_event) { + model_.set_pacing_gain(params_.drain_pacing_gain); + + // Only STARTUP can transition to DRAIN, both of them use the same cwnd gain. + QUICHE_DCHECK_EQ(model_.cwnd_gain(), params_.drain_cwnd_gain); + model_.set_cwnd_gain(params_.drain_cwnd_gain); + + QuicByteCount drain_target = DrainTarget(); + if (congestion_event.bytes_in_flight <= drain_target) { + QUIC_DVLOG(3) << this << " Exiting DRAIN. bytes_in_flight:" + << congestion_event.bytes_in_flight + << ", bdp:" << model_.BDP() + << ", drain_target:" << drain_target << " @ " + << congestion_event.event_time; + return Bbr2Mode::PROBE_BW; + } + + QUIC_DVLOG(3) << this << " Staying in DRAIN. bytes_in_flight:" + << congestion_event.bytes_in_flight << ", bdp:" << model_.BDP() + << ", drain_target:" << drain_target << " @ " + << congestion_event.event_time; + return Bbr2Mode::DRAIN; +} + +QuicByteCount Bbr3Sender::DrainTarget() const { + QuicByteCount bdp = model_.BDP(); + return std::max<QuicByteCount>(bdp, GetMinimumCongestionWindow()); +} + +Bbr2Mode Bbr3Sender::OnCongestionEventProbeBw( + QuicByteCount prior_in_flight, QuicTime event_time, + const Bbr2CongestionEvent& congestion_event) { + QUICHE_DCHECK_NE(probe_bw_.phase, ProbePhase::PROBE_NOT_STARTED); + + if (congestion_event.end_of_round_trip) { + if (probe_bw_.cycle_start_time != event_time) { + ++probe_bw_.rounds_since_probe; + } + if (probe_bw_.phase_start_time != event_time) { + ++probe_bw_.rounds_in_phase; + } + } + + bool switch_to_probe_rtt = false; + + if (probe_bw_.phase == ProbePhase::PROBE_UP) { + UpdateProbeUp(congestion_event); + } else if (probe_bw_.phase == ProbePhase::PROBE_DOWN) { + UpdateProbeDown(prior_in_flight, congestion_event); + // Maybe transition to PROBE_RTT at the end of this cycle. + if (probe_bw_.phase != ProbePhase::PROBE_DOWN && + model_.MaybeExpireMinRtt(congestion_event)) { + switch_to_probe_rtt = true; + } + } else if (probe_bw_.phase == ProbePhase::PROBE_CRUISE) { + UpdateProbeCruise(congestion_event); + } else if (probe_bw_.phase == ProbePhase::PROBE_REFILL) { + UpdateProbeRefill(congestion_event); + } + + // Do not need to set the gains if switching to PROBE_RTT, they will be set + // when Bbr2ProbeRttMode::Enter is called. + if (!switch_to_probe_rtt) { + model_.set_pacing_gain(PacingGainForPhase(probe_bw_.phase)); + model_.set_cwnd_gain(params_.probe_bw_cwnd_gain); + } + + return switch_to_probe_rtt ? Bbr2Mode::PROBE_RTT : Bbr2Mode::PROBE_BW; +} + +void Bbr3Sender::UpdateProbeDown(QuicByteCount prior_in_flight, + const Bbr2CongestionEvent& congestion_event) { + QUICHE_DCHECK_EQ(probe_bw_.phase, ProbePhase::PROBE_DOWN); + + if (probe_bw_.rounds_in_phase == 1 && congestion_event.end_of_round_trip) { + probe_bw_.is_sample_from_probing = false; + + if (!congestion_event.last_packet_send_state.is_app_limited) { + QUIC_DVLOG(2) + << this << " Advancing max bw filter after one round in PROBE_DOWN."; + model_.AdvanceMaxBandwidthFilter(); + probe_bw_.has_advanced_max_bw = true; + } + + if (probe_bw_.last_cycle_stopped_risky_probe && + !probe_bw_.last_cycle_probed_too_high) { + EnterProbeRefill(/*probe_up_rounds=*/0, congestion_event.event_time); + return; + } + } + + MaybeAdaptUpperBounds(congestion_event); + + if (IsTimeToProbeBandwidth(congestion_event)) { + EnterProbeRefill(/*probe_up_rounds=*/0, congestion_event.event_time); + return; + } + + if (HasStayedLongEnoughInProbeDown(congestion_event)) { + QUIC_DVLOG(3) << this << " Proportional time based PROBE_DOWN exit"; + EnterProbeCruise(congestion_event.event_time); + return; + } + + const QuicByteCount inflight_with_headroom = + model_.inflight_hi_with_headroom(); + QUIC_DVLOG(3) + << this << " Checking if have enough inflight headroom. prior_in_flight:" + << prior_in_flight << " congestion_event.bytes_in_flight:" + << congestion_event.bytes_in_flight + << ", inflight_with_headroom:" << inflight_with_headroom; + QuicByteCount bytes_in_flight = congestion_event.bytes_in_flight; + + if (bytes_in_flight > inflight_with_headroom) { + // Stay in PROBE_DOWN. + return; + } + + // Transition to PROBE_CRUISE iff we've drained to target. + QuicByteCount bdp = model_.BDP(); + QUIC_DVLOG(3) << this << " Checking if drained to target. bytes_in_flight:" + << bytes_in_flight << ", bdp:" << bdp; + if (bytes_in_flight < bdp) { + EnterProbeCruise(congestion_event.event_time); + } +} + +Bbr3Sender::AdaptUpperBoundsResult Bbr3Sender::MaybeAdaptUpperBounds( + const Bbr2CongestionEvent& congestion_event) { + const SendTimeState& send_state = congestion_event.last_packet_send_state; + if (!send_state.is_valid) { + QUIC_DVLOG(3) << this << " " << ProbePhaseToString(probe_bw_.phase) + << ": NOT_ADAPTED_INVALID_SAMPLE"; + return NOT_ADAPTED_INVALID_SAMPLE; + } + + QuicByteCount inflight_at_send = BytesInFlight(send_state); + if (params_.use_bytes_delivered_for_inflight_hi) { + if (congestion_event.last_packet_send_state.total_bytes_acked <= + model_.total_bytes_acked()) { + inflight_at_send = + model_.total_bytes_acked() - + congestion_event.last_packet_send_state.total_bytes_acked; + } else { + QUIC_BUG(quic_bug_10463_3) + << "Total_bytes_acked(" << model_.total_bytes_acked() + << ") < send_state.total_bytes_acked(" + << congestion_event.last_packet_send_state.total_bytes_acked << ")"; + } + } + if (model_.IsInflightTooHigh(congestion_event, + params_.probe_bw_full_loss_count)) { + if (probe_bw_.is_sample_from_probing) { + probe_bw_.is_sample_from_probing = false; + if (!send_state.is_app_limited || params_.max_probe_up_queue_rounds > 0) { + const QuicByteCount inflight_target = + GetTargetBytesInflight() * (1.0 - params_.beta); + if (params_.limit_inflight_hi_by_max_delivered) { + QuicByteCount new_inflight_hi = + std::max(inflight_at_send, inflight_target); + if (new_inflight_hi < model_.max_bytes_delivered_in_round()) { + new_inflight_hi = model_.max_bytes_delivered_in_round(); + } + QUIC_DVLOG(3) << this + << " Setting inflight_hi due to loss. new_inflight_hi:" + << new_inflight_hi + << ", inflight_at_send:" << inflight_at_send + << ", inflight_target:" << inflight_target + << ", max_bytes_delivered_in_round:" + << model_.max_bytes_delivered_in_round() << " @ " + << congestion_event.event_time; + model_.set_inflight_hi(new_inflight_hi); + } else { + model_.set_inflight_hi(std::max(inflight_at_send, inflight_target)); + } + } + + QUIC_DVLOG(3) << this << " " << ProbePhaseToString(probe_bw_.phase) + << ": ADAPTED_PROBED_TOO_HIGH"; + return ADAPTED_PROBED_TOO_HIGH; + } + return ADAPTED_OK; + } + + if (model_.inflight_hi() == model_.inflight_hi_default()) { + QUIC_DVLOG(3) << this << " " << ProbePhaseToString(probe_bw_.phase) + << ": NOT_ADAPTED_INFLIGHT_HIGH_NOT_SET"; + return NOT_ADAPTED_INFLIGHT_HIGH_NOT_SET; + } + + // Raise the upper bound for inflight. + if (inflight_at_send > model_.inflight_hi()) { + QUIC_DVLOG(3) + << this << " " << ProbePhaseToString(probe_bw_.phase) + << ": Adapting inflight_hi from inflight_at_send. inflight_at_send:" + << inflight_at_send << ", old inflight_hi:" << model_.inflight_hi(); + model_.set_inflight_hi(inflight_at_send); + } + + return ADAPTED_OK; +} + +bool Bbr3Sender::IsTimeToProbeBandwidth( + const Bbr2CongestionEvent& congestion_event) const { + if (HasCycleLasted(probe_bw_.probe_wait_time, congestion_event)) { + return true; + } + + if (IsTimeToProbeForRenoCoexistence(1.0, congestion_event)) { + ++connection_stats_->bbr_num_short_cycles_for_reno_coexistence; + return true; + } + return false; +} + +bool Bbr3Sender::HasStayedLongEnoughInProbeDown( + const Bbr2CongestionEvent& congestion_event) const { + return HasPhaseLasted(model_.MinRtt(), congestion_event); +} + +bool Bbr3Sender::HasCycleLasted( + QuicTime::Delta duration, + const Bbr2CongestionEvent& congestion_event) const { + bool result = + (congestion_event.event_time - probe_bw_.cycle_start_time) > duration; + QUIC_DVLOG(3) << this << " " << ProbePhaseToString(probe_bw_.phase) + << ": HasCycleLasted=" << result << ". elapsed:" + << (congestion_event.event_time - probe_bw_.cycle_start_time) + << ", duration:" << duration; + return result; +} + +bool Bbr3Sender::HasPhaseLasted( + QuicTime::Delta duration, + const Bbr2CongestionEvent& congestion_event) const { + bool result = + (congestion_event.event_time - probe_bw_.phase_start_time) > duration; + QUIC_DVLOG(3) << this << " " << ProbePhaseToString(probe_bw_.phase) + << ": HasPhaseLasted=" << result << ". elapsed:" + << (congestion_event.event_time - probe_bw_.phase_start_time) + << ", duration:" << duration; + return result; +} + +bool Bbr3Sender::IsTimeToProbeForRenoCoexistence( + double probe_wait_fraction, + const Bbr2CongestionEvent& /*congestion_event*/) const { + if (!params_.enable_reno_coexistence) { + return false; + } + + uint64_t rounds = params_.probe_bw_probe_max_rounds; + if (params_.probe_bw_probe_reno_gain > 0.0) { + QuicByteCount target_bytes_inflight = GetTargetBytesInflight(); + uint64_t reno_rounds = params_.probe_bw_probe_reno_gain * + target_bytes_inflight / kDefaultTCPMSS; + rounds = std::min(rounds, reno_rounds); + } + bool result = probe_bw_.rounds_since_probe >= (rounds * probe_wait_fraction); + QUIC_DVLOG(3) << this << " " << ProbePhaseToString(probe_bw_.phase) + << ": IsTimeToProbeForRenoCoexistence=" << result + << ". rounds_since_probe:" << probe_bw_.rounds_since_probe + << ", rounds:" << rounds + << ", probe_wait_fraction:" << probe_wait_fraction; + return result; +} + +void Bbr3Sender::RaiseInflightHighSlope() { + QUICHE_DCHECK_EQ(probe_bw_.phase, ProbePhase::PROBE_UP); + uint64_t growth_this_round = 1 << probe_bw_.probe_up_rounds; + // The number 30 below means |growth_this_round| is capped at 1G and the lower + // bound of |probe_up_bytes| is (practically) 1 mss, at this speed inflight_hi + // grows by approximately 1 packet per packet acked. + probe_bw_.probe_up_rounds = + std::min<uint64_t>(probe_bw_.probe_up_rounds + 1, 30); + uint64_t probe_up_bytes = GetCongestionWindow() / growth_this_round; + probe_bw_.probe_up_bytes = + std::max<QuicByteCount>(probe_up_bytes, kDefaultTCPMSS); + QUIC_DVLOG(3) << this << " Rasing inflight_hi slope. probe_up_rounds:" + << probe_bw_.probe_up_rounds + << ", probe_up_bytes:" << probe_bw_.probe_up_bytes; +} + +void Bbr3Sender::ProbeInflightHighUpward( + const Bbr2CongestionEvent& congestion_event) { + QUICHE_DCHECK_EQ(probe_bw_.phase, ProbePhase::PROBE_UP); + if (params_.probe_up_ignore_inflight_hi) { + return; + } + if (params_.probe_up_simplify_inflight_hi) { + // Raise inflight_hi exponentially if it was utilized this round. + probe_bw_.probe_up_acked += congestion_event.bytes_acked; + if (!congestion_event.end_of_round_trip) { + return; + } + if (!model_.inflight_hi_limited_in_round() || + model_.loss_events_in_round() > 0) { + probe_bw_.probe_up_acked = 0; + return; + } + } else { + if (congestion_event.prior_bytes_in_flight < congestion_event.prior_cwnd) { + QUIC_DVLOG(3) << this + << " Raising inflight_hi early return: Not cwnd limited."; + // Not fully utilizing cwnd, so can't safely grow. + return; + } + + if (congestion_event.prior_cwnd < model_.inflight_hi()) { + QUIC_DVLOG(3) + << this + << " Raising inflight_hi early return: inflight_hi not fully used."; + // Not fully using inflight_hi, so don't grow it. + return; + } + + // Increase inflight_hi by the number of probe_up_bytes within + // probe_up_acked. + probe_bw_.probe_up_acked += congestion_event.bytes_acked; + } + + if (probe_bw_.probe_up_acked >= probe_bw_.probe_up_bytes) { + uint64_t delta = probe_bw_.probe_up_acked / probe_bw_.probe_up_bytes; + probe_bw_.probe_up_acked -= delta * probe_bw_.probe_up_bytes; + QuicByteCount new_inflight_hi = + model_.inflight_hi() + delta * kDefaultTCPMSS; + if (new_inflight_hi > model_.inflight_hi()) { + QUIC_DVLOG(3) << this << " Raising inflight_hi from " + << model_.inflight_hi() << " to " << new_inflight_hi + << ". probe_up_bytes:" << probe_bw_.probe_up_bytes + << ", delta:" << delta + << ", (new)probe_up_acked:" << probe_bw_.probe_up_acked; + + model_.set_inflight_hi(new_inflight_hi); + } else { + QUIC_BUG(quic_bug_10463_4) + << "Not growing inflight_hi due to wrap around. Old value:" + << model_.inflight_hi() << ", new value:" << new_inflight_hi; + } + } + + if (congestion_event.end_of_round_trip) { + RaiseInflightHighSlope(); + } +} + +void Bbr3Sender::UpdateProbeCruise( + const Bbr2CongestionEvent& congestion_event) { + QUICHE_DCHECK_EQ(probe_bw_.phase, ProbePhase::PROBE_CRUISE); + MaybeAdaptUpperBounds(congestion_event); + QUICHE_DCHECK(!probe_bw_.is_sample_from_probing); + + if (IsTimeToProbeBandwidth(congestion_event)) { + EnterProbeRefill(/*probe_up_rounds=*/0, congestion_event.event_time); + return; + } +} + +void Bbr3Sender::UpdateProbeRefill( + const Bbr2CongestionEvent& congestion_event) { + QUICHE_DCHECK_EQ(probe_bw_.phase, ProbePhase::PROBE_REFILL); + MaybeAdaptUpperBounds(congestion_event); + QUICHE_DCHECK(!probe_bw_.is_sample_from_probing); + + if (probe_bw_.rounds_in_phase > 0 && congestion_event.end_of_round_trip) { + EnterProbeUp(congestion_event.event_time); + return; + } +} + +void Bbr3Sender::UpdateProbeUp(const Bbr2CongestionEvent& congestion_event) { + QUICHE_DCHECK_EQ(probe_bw_.phase, ProbePhase::PROBE_UP); + if (MaybeAdaptUpperBounds(congestion_event) == ADAPTED_PROBED_TOO_HIGH) { + EnterProbeDown(/*probed_too_high=*/true, /*stopped_risky_probe=*/false, + congestion_event.event_time); + return; + } + + ProbeInflightHighUpward(congestion_event); + + bool is_risky = false; + bool is_queuing = false; + if (probe_bw_.last_cycle_probed_too_high && + congestion_event.prior_bytes_in_flight >= model_.inflight_hi()) { + is_risky = true; + QUIC_DVLOG(3) << this << " Probe is too risky. last_cycle_probed_too_high:" + << probe_bw_.last_cycle_probed_too_high + << ", prior_in_flight:" + << congestion_event.prior_bytes_in_flight + << ", inflight_hi:" << model_.inflight_hi(); + } else if (probe_bw_.rounds_in_phase > 0) { + if (params_.max_probe_up_queue_rounds > 0) { + if (congestion_event.end_of_round_trip) { + model_.CheckPersistentQueue(congestion_event, + params_.full_bw_threshold); + if (model_.rounds_with_queueing() >= + params_.max_probe_up_queue_rounds) { + is_queuing = true; + } + } + } else { + QuicByteCount queuing_threshold_extra_bytes = + model_.QueueingThresholdExtraBytes(); + if (params_.add_ack_height_to_queueing_threshold) { + queuing_threshold_extra_bytes += model_.MaxAckHeight(); + } + QuicByteCount queuing_threshold = + (params_.full_bw_threshold * model_.BDP()) + + queuing_threshold_extra_bytes; + + is_queuing = congestion_event.bytes_in_flight >= queuing_threshold; + + QUIC_DVLOG(3) << this + << " Checking if building up a queue. prior_in_flight:" + << congestion_event.prior_bytes_in_flight + << ", post_in_flight:" << congestion_event.bytes_in_flight + << ", threshold:" << queuing_threshold + << ", is_queuing:" << is_queuing + << ", max_bw:" << model_.MaxBandwidth() + << ", min_rtt:" << model_.MinRtt(); + } + } + + if (is_risky || is_queuing) { + EnterProbeDown(/*probed_too_high=*/false, /*stopped_risky_probe=*/is_risky, + congestion_event.event_time); + } +} + +void Bbr3Sender::EnterProbeDown(bool probed_too_high, bool stopped_risky_probe, + QuicTime now) { + QUIC_DVLOG(2) << this + << " Phase change: " << ProbePhaseToString(probe_bw_.phase) + << " ==> " + << "PROBE_DOWN" << " after " << now - probe_bw_.phase_start_time + << ", or " << probe_bw_.rounds_in_phase + << " rounds. probed_too_high:" << probed_too_high + << ", stopped_risky_probe:" << stopped_risky_probe << " @ " + << now; + probe_bw_.last_cycle_probed_too_high = probed_too_high; + probe_bw_.last_cycle_stopped_risky_probe = stopped_risky_probe; + + probe_bw_.cycle_start_time = now; + probe_bw_.phase = ProbePhase::PROBE_DOWN; + probe_bw_.rounds_in_phase = 0; + probe_bw_.phase_start_time = now; + ++connection_stats_->bbr_num_cycles; + if (params_.bw_lo_mode_ != Bbr2Params::QuicBandwidthLoMode::DEFAULT) { + model_.clear_bandwidth_lo(); + } + + // Pick probe wait time. + probe_bw_.rounds_since_probe = + RandomUint64(params_.probe_bw_max_probe_rand_rounds); + probe_bw_.probe_wait_time = + params_.probe_bw_probe_base_duration + + QuicTime::Delta::FromMicroseconds(RandomUint64( + params_.probe_bw_probe_max_rand_duration.ToMicroseconds())); + + probe_bw_.probe_up_bytes = std::numeric_limits<QuicByteCount>::max(); + probe_bw_.probe_up_app_limited_since_inflight_hi_limited_ = false; + probe_bw_.has_advanced_max_bw = false; + model_.RestartRoundEarly(); +} + +void Bbr3Sender::EnterProbeCruise(QuicTime now) { + if (probe_bw_.phase == ProbePhase::PROBE_DOWN) { + ExitProbeDown(); + } + QUIC_DVLOG(2) << this + << " Phase change: " << ProbePhaseToString(probe_bw_.phase) + << " ==> " + << "PROBE_CRUISE" << " after " + << now - probe_bw_.phase_start_time << ", or " + << probe_bw_.rounds_in_phase << " rounds. @ " << now; + + model_.cap_inflight_lo(model_.inflight_hi()); + probe_bw_.phase = ProbePhase::PROBE_CRUISE; + probe_bw_.rounds_in_phase = 0; + probe_bw_.phase_start_time = now; + probe_bw_.is_sample_from_probing = false; +} + +void Bbr3Sender::EnterProbeRefill(uint64_t probe_up_rounds, QuicTime now) { + if (probe_bw_.phase == ProbePhase::PROBE_DOWN) { + ExitProbeDown(); + } + QUIC_DVLOG(2) << this + << " Phase change: " << ProbePhaseToString(probe_bw_.phase) + << " ==> " + << "PROBE_REFILL" << " after " + << now - probe_bw_.phase_start_time << ", or " + << probe_bw_.rounds_in_phase + << " rounds. probe_up_rounds:" << probe_up_rounds << " @ " + << now; + probe_bw_.phase = ProbePhase::PROBE_REFILL; + probe_bw_.rounds_in_phase = 0; + probe_bw_.phase_start_time = now; + probe_bw_.is_sample_from_probing = false; + probe_bw_.last_cycle_stopped_risky_probe = false; + + model_.clear_bandwidth_lo(); + model_.clear_inflight_lo(); + probe_bw_.probe_up_rounds = probe_up_rounds; + probe_bw_.probe_up_acked = 0; + model_.RestartRoundEarly(); +} + +void Bbr3Sender::EnterProbeUp(QuicTime now) { + QUICHE_DCHECK_EQ(probe_bw_.phase, ProbePhase::PROBE_REFILL); + QUIC_DVLOG(2) << this + << " Phase change: " << ProbePhaseToString(probe_bw_.phase) + << " ==> " + << "PROBE_UP" << " after " << now - probe_bw_.phase_start_time + << ", or " << probe_bw_.rounds_in_phase << " rounds. @ " + << now; + probe_bw_.phase = ProbePhase::PROBE_UP; + probe_bw_.rounds_in_phase = 0; + probe_bw_.phase_start_time = now; + probe_bw_.is_sample_from_probing = true; + RaiseInflightHighSlope(); + + model_.RestartRoundEarly(); +} + +void Bbr3Sender::ExitProbeDown() { + QUICHE_DCHECK_EQ(probe_bw_.phase, ProbePhase::PROBE_DOWN); + if (!probe_bw_.has_advanced_max_bw) { + QUIC_DVLOG(2) << this << " Advancing max bw filter at end of cycle."; + model_.AdvanceMaxBandwidthFilter(); + probe_bw_.has_advanced_max_bw = true; + } +} + +void Bbr3Sender::EnterProbeRtt() { + model_.set_pacing_gain(1.0); + model_.set_cwnd_gain(1.0); + probe_rtt_.exit_time = QuicTime::Zero(); +} + +Bbr2Mode Bbr3Sender::OnCongestionEventProbeRtt( + const Bbr2CongestionEvent& congestion_event) { + if (probe_rtt_.exit_time == QuicTime::Zero()) { + if (congestion_event.bytes_in_flight <= InflightTarget() || + congestion_event.bytes_in_flight <= GetMinimumCongestionWindow()) { + probe_rtt_.exit_time = + congestion_event.event_time + params_.probe_rtt_duration; + QUIC_DVLOG(2) << this << " PROBE_RTT exit time set to " + << probe_rtt_.exit_time + << ". bytes_inflight:" << congestion_event.bytes_in_flight + << ", inflight_target:" << InflightTarget() + << ", min_congestion_window:" + << GetMinimumCongestionWindow() << " @ " + << congestion_event.event_time; + } + return Bbr2Mode::PROBE_RTT; + } + + return congestion_event.event_time > probe_rtt_.exit_time + ? Bbr2Mode::PROBE_BW + : Bbr2Mode::PROBE_RTT; +} + +QuicByteCount Bbr3Sender::InflightTarget() const { + return model_.BDP(model_.MaxBandwidth(), + params_.probe_rtt_inflight_target_bdp_fraction); +} + +float Bbr3Sender::PacingGainForPhase(ProbePhase phase) const { + if (phase == ProbePhase::PROBE_UP) { + return params_.probe_bw_probe_up_pacing_gain; + } + if (phase == ProbePhase::PROBE_DOWN) { + return params_.probe_bw_probe_down_pacing_gain; + } + return params_.probe_bw_default_pacing_gain; +} + +} // namespace quic
diff --git a/quiche/quic/core/congestion_control/bbr3_sender.h b/quiche/quic/core/congestion_control/bbr3_sender.h new file mode 100644 index 0000000..6f61233 --- /dev/null +++ b/quiche/quic/core/congestion_control/bbr3_sender.h
@@ -0,0 +1,300 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CONGESTION_CONTROL_BBR3_SENDER_H_ +#define QUICHE_QUIC_CORE_CONGESTION_CONTROL_BBR3_SENDER_H_ + +#include <cstdint> + +#include "quiche/quic/core/congestion_control/bandwidth_sampler.h" +#include "quiche/quic/core/congestion_control/bbr2_misc.h" +#include "quiche/quic/core/congestion_control/bbr_sender.h" +#include "quiche/quic/core/congestion_control/rtt_stats.h" +#include "quiche/quic/core/congestion_control/send_algorithm_interface.h" +#include "quiche/quic/core/congestion_control/windowed_filter.h" +#include "quiche/quic/core/quic_bandwidth.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_export.h" +#include "quiche/quic/platform/api/quic_flags.h" + +namespace quic { + +class QUICHE_EXPORT Bbr3Sender final : public SendAlgorithmInterface { + public: + Bbr3Sender(QuicTime now, const RttStats* rtt_stats, + const QuicUnackedPacketMap* unacked_packets, + QuicPacketCount initial_cwnd_in_packets, + QuicPacketCount max_cwnd_in_packets, QuicRandom* random, + QuicConnectionStats* stats, BbrSender* old_sender); + + ~Bbr3Sender() override = default; + + // Start implementation of SendAlgorithmInterface. + bool InSlowStart() const override { return mode_ == Bbr2Mode::STARTUP; } + + bool InRecovery() const override { + // TODO(wub): Implement Recovery. + return false; + } + + void SetFromConfig(const QuicConfig& config, + Perspective perspective) override; + + void ApplyConnectionOptions(const QuicTagVector& connection_options) override; + + void AdjustNetworkParameters(const NetworkParams& params) override; + + void SetInitialCongestionWindowInPackets( + QuicPacketCount congestion_window) override; + + void SetApplicationDrivenPacingRate( + QuicBandwidth application_bandwidth_target) override; + + void OnCongestionEvent(bool rtt_updated, QuicByteCount prior_in_flight, + QuicTime event_time, + const AckedPacketVector& acked_packets, + const LostPacketVector& lost_packets, + QuicPacketCount num_ect, + QuicPacketCount num_ce) override; + + void OnPacketSent(QuicTime sent_time, QuicByteCount bytes_in_flight, + QuicPacketNumber packet_number, QuicByteCount bytes, + HasRetransmittableData is_retransmittable) override; + + void OnPacketNeutered(QuicPacketNumber packet_number) override; + + void OnRetransmissionTimeout(bool /*packets_retransmitted*/) override {} + + void OnConnectionMigration() override {} + + bool CanSend(QuicByteCount bytes_in_flight) override; + + QuicBandwidth PacingRate(QuicByteCount bytes_in_flight) const override; + + QuicBandwidth BandwidthEstimate() const override { + return model_.BandwidthEstimate(); + } + + bool HasGoodBandwidthEstimateForResumption() const override { + return has_non_app_limited_sample_; + } + + QuicByteCount GetCongestionWindow() const override; + + QuicByteCount GetSlowStartThreshold() const override { return 0; } + + CongestionControlType GetCongestionControlType() const override { + return kBBRv2; + } + + std::string GetDebugState() const override; + + void OnApplicationLimited(QuicByteCount bytes_in_flight) override; + + void PopulateConnectionStats(QuicConnectionStats* stats) const override; + + bool EnableECT0() override { return false; } + bool EnableECT1() override { return false; } + void ReduceMemoryUsage() override { model_.ReduceMemoryUsage(); } + // End implementation of SendAlgorithmInterface. + + const Bbr2Params& Params() const { return params_; } + + QuicByteCount GetMinimumCongestionWindow() const { + return params_.cwnd_limits.Min(); + } + + // Returns the min of BDP and congestion window. + QuicByteCount GetTargetBytesInflight() const; + + bool IsBandwidthOverestimateAvoidanceEnabled() const { + return model_.IsBandwidthOverestimateAvoidanceEnabled(); + } + + struct QUICHE_EXPORT DebugState { + Bbr2Mode mode; + + // Shared states. + QuicRoundTripCount round_trip_count; + QuicBandwidth bandwidth_hi = QuicBandwidth::Zero(); + QuicBandwidth bandwidth_lo = QuicBandwidth::Zero(); + QuicBandwidth bandwidth_est = QuicBandwidth::Zero(); + QuicByteCount inflight_hi; + QuicByteCount inflight_lo; + QuicByteCount max_ack_height; + QuicTime::Delta min_rtt = QuicTime::Delta::Zero(); + QuicTime min_rtt_timestamp = QuicTime::Zero(); + QuicByteCount congestion_window; + QuicBandwidth pacing_rate = QuicBandwidth::Zero(); + bool last_sample_is_app_limited; + QuicPacketNumber end_of_app_limited_phase; + + // Mode-specific states. + struct QUICHE_EXPORT Startup { + bool full_bandwidth_reached = false; + QuicBandwidth full_bandwidth_baseline = QuicBandwidth::Zero(); + QuicRoundTripCount round_trips_without_bandwidth_growth = 0; + } startup; + + struct QUICHE_EXPORT Drain { + QuicByteCount drain_target = 0; + } drain; + + struct QUICHE_EXPORT ProbeBw { + ProbePhase phase = ProbePhase::PROBE_NOT_STARTED; + QuicTime cycle_start_time = QuicTime::Zero(); + QuicTime phase_start_time = QuicTime::Zero(); + } probe_bw; + + struct QUICHE_EXPORT ProbeRtt { + QuicByteCount inflight_target = 0; + QuicTime exit_time = QuicTime::Zero(); + } probe_rtt; + }; + + DebugState ExportDebugState() const; + + const Bbr2NetworkModel& GetNetworkModel() const { return model_; } + + private: + void UpdatePacingRate(QuicByteCount bytes_acked); + void UpdateCongestionWindow(QuicByteCount bytes_acked); + QuicByteCount GetTargetCongestionWindow(float gain) const; + // Helper function for Bbr2Mode transitions. + void LeaveStartup(QuicTime now); + Bbr2Mode OnCongestionEventStartup( + const Bbr2CongestionEvent& congestion_event); + void CheckExcessiveLosses(const Bbr2CongestionEvent& congestion_event); + + Bbr2Mode OnCongestionEventDrain(const Bbr2CongestionEvent& congestion_event); + QuicByteCount DrainTarget() const; + + void OnEnterQuiescence(QuicTime now); + void OnExitQuiescence(QuicTime now); + + Bbr2Mode OnCongestionEventProbeBw( + QuicByteCount prior_in_flight, QuicTime event_time, + const Bbr2CongestionEvent& congestion_event); + + void UpdateProbeUp(const Bbr2CongestionEvent& congestion_event); + void UpdateProbeDown(QuicByteCount prior_in_flight, + const Bbr2CongestionEvent& congestion_event); + void UpdateProbeCruise(const Bbr2CongestionEvent& congestion_event); + void UpdateProbeRefill(const Bbr2CongestionEvent& congestion_event); + + enum AdaptUpperBoundsResult : uint8_t { + ADAPTED_OK, + ADAPTED_PROBED_TOO_HIGH, + NOT_ADAPTED_INFLIGHT_HIGH_NOT_SET, + NOT_ADAPTED_INVALID_SAMPLE, + }; + + AdaptUpperBoundsResult MaybeAdaptUpperBounds( + const Bbr2CongestionEvent& congestion_event); + + void EnterProbeDown(bool probed_too_high, bool stopped_risky_probe, + QuicTime now); + void EnterProbeCruise(QuicTime now); + void EnterProbeRefill(uint64_t probe_up_rounds, QuicTime now); + void EnterProbeUp(QuicTime now); + void ExitProbeDown(); + + bool IsTimeToProbeBandwidth( + const Bbr2CongestionEvent& congestion_event) const; + bool HasStayedLongEnoughInProbeDown( + const Bbr2CongestionEvent& congestion_event) const; + bool HasCycleLasted(QuicTime::Delta duration, + const Bbr2CongestionEvent& congestion_event) const; + bool HasPhaseLasted(QuicTime::Delta duration, + const Bbr2CongestionEvent& congestion_event) const; + bool IsTimeToProbeForRenoCoexistence( + double probe_wait_fraction, + const Bbr2CongestionEvent& congestion_event) const; + + void RaiseInflightHighSlope(); + void ProbeInflightHighUpward(const Bbr2CongestionEvent& congestion_event); + float PacingGainForPhase(ProbePhase phase) const; + + void EnterProbeRtt(); + Bbr2Mode OnCongestionEventProbeRtt( + const Bbr2CongestionEvent& congestion_event); + QuicByteCount InflightTarget() const; + + uint64_t RandomUint64(uint64_t max) const { + return random_->RandUint64() % max; + } + + // Cwnd limits imposed by the current Bbr2 mode. + Limits<QuicByteCount> GetCwndLimitsByMode() const; + + // Cwnd limits imposed by caller. + + Bbr2Mode mode_; + + const RttStats* const rtt_stats_; + const QuicUnackedPacketMap* const unacked_packets_; + QuicRandom* random_; + QuicConnectionStats* connection_stats_; + + // Don't use it directly outside of SetFromConfig and ApplyConnectionOptions. + // Instead, use params() to get read-only access. + Bbr2Params params_; + + Bbr2NetworkModel model_; + + const QuicByteCount initial_cwnd_; + + // Current cwnd and pacing rate. + QuicByteCount cwnd_; + QuicBandwidth pacing_rate_; + + QuicTime last_quiescence_start_ = QuicTime::Zero(); + + // Max congestion window when adjusting network parameters. + QuicByteCount max_cwnd_when_network_parameters_adjusted_ = + kMaxInitialCongestionWindow * kDefaultTCPMSS; + + // Startup state. + struct StartupState { + QuicBandwidth max_bw_at_round_beginning = QuicBandwidth::Zero(); + } startup_; + + // Probe BW state. + struct ProbeBWState { + QuicTime cycle_start_time = QuicTime::Zero(); + ProbePhase phase = ProbePhase::PROBE_NOT_STARTED; + uint64_t rounds_in_phase = 0; + QuicTime phase_start_time = QuicTime::Zero(); + QuicRoundTripCount rounds_since_probe = 0; + QuicTime::Delta probe_wait_time = QuicTime::Delta::Zero(); + uint64_t probe_up_rounds = 0; + QuicByteCount probe_up_bytes = std::numeric_limits<QuicByteCount>::max(); + QuicByteCount probe_up_acked = 0; + bool probe_up_app_limited_since_inflight_hi_limited_ = false; + // Whether max bandwidth filter window has advanced in this cycle. It is + // advanced once per cycle. + bool has_advanced_max_bw = false; + bool is_sample_from_probing = false; + + bool last_cycle_probed_too_high = false; + bool last_cycle_stopped_risky_probe = false; + } probe_bw_; + + // Probe RTT state. + struct ProbeRTTState { + QuicTime exit_time = QuicTime::Zero(); + } probe_rtt_; + + bool has_non_app_limited_sample_ = false; + + // Debug only. + bool last_sample_is_app_limited_; +}; + +QUICHE_EXPORT std::ostream& operator<<(std::ostream& os, + const Bbr3Sender::DebugState& state); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CONGESTION_CONTROL_BBR3_SENDER_H_
diff --git a/quiche/quic/core/congestion_control/bbr3_simulator_test.cc b/quiche/quic/core/congestion_control/bbr3_simulator_test.cc new file mode 100644 index 0000000..0adb9d1 --- /dev/null +++ b/quiche/quic/core/congestion_control/bbr3_simulator_test.cc
@@ -0,0 +1,2611 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <array> +#include <memory> +#include <optional> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/str_cat.h" +#include "quiche/quic/core/congestion_control/bbr2_misc.h" +#include "quiche/quic/core/congestion_control/bbr3_sender.h" +#include "quiche/quic/core/congestion_control/bbr_sender.h" +#include "quiche/quic/core/congestion_control/tcp_cubic_sender_bytes.h" +#include "quiche/quic/core/quic_bandwidth.h" +#include "quiche/quic/core/quic_packet_number.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/platform/api/quic_expect_bug.h" +#include "quiche/quic/platform/api/quic_flags.h" +#include "quiche/quic/platform/api/quic_logging.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_config_peer.h" +#include "quiche/quic/test_tools/quic_connection_peer.h" +#include "quiche/quic/test_tools/quic_sent_packet_manager_peer.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/quic/test_tools/send_algorithm_test_result.pb.h" +#include "quiche/quic/test_tools/send_algorithm_test_utils.h" +#include "quiche/quic/test_tools/simulator/link.h" +#include "quiche/quic/test_tools/simulator/quic_endpoint.h" +#include "quiche/quic/test_tools/simulator/simulator.h" +#include "quiche/quic/test_tools/simulator/switch.h" +#include "quiche/quic/test_tools/simulator/traffic_policer.h" +#include "quiche/common/platform/api/quiche_command_line_flags.h" + +using testing::AllOf; +using testing::Ge; +using testing::Le; + +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, quic_bbr3_test_regression_mode, "", + "One of a) 'record' to record test result (one file per test), or " + "b) 'regress' to regress against recorded results, or " + "c) <anything else> for non-regression mode."); + +namespace quic { + +namespace test { + +// Use the initial CWND of 10, as 32 is too much for the test network. +const uint32_t kDefaultInitialCwndPackets = 10; +const uint32_t kDefaultInitialCwndBytes = + kDefaultInitialCwndPackets * kDefaultTCPMSS; + +struct LinkParams { + LinkParams(int64_t kilo_bits_per_sec, int64_t delay_us) + : bandwidth(QuicBandwidth::FromKBitsPerSecond(kilo_bits_per_sec)), + delay(QuicTime::Delta::FromMicroseconds(delay_us)) {} + QuicBandwidth bandwidth; + QuicTime::Delta delay; +}; + +struct TrafficPolicerParams { + std::string name = "policer"; + QuicByteCount initial_burst_size; + QuicByteCount max_bucket_size; + QuicBandwidth target_bandwidth = QuicBandwidth::Zero(); +}; + +// All Bbr3DefaultTopologyTests uses the default network topology: +// +// Sender +// | +// | <-- local_link +// | +// Network switch +// * <-- the bottleneck queue in the direction +// | of the receiver +// | +// | <-- test_link +// | +// | +// Receiver +class DefaultTopologyParams { + public: + LinkParams local_link = {10000, 2000}; + LinkParams test_link = {4000, 30000}; + + const simulator::SwitchPortNumber switch_port_count = 2; + // Network switch queue capacity, in number of BDPs. + float switch_queue_capacity_in_bdp = 2; + + std::optional<TrafficPolicerParams> sender_policer_params; + + QuicBandwidth BottleneckBandwidth() const { + return std::min(local_link.bandwidth, test_link.bandwidth); + } + + // Round trip time of a single full size packet. + QuicTime::Delta RTT() const { + return 2 * (local_link.delay + test_link.delay + + local_link.bandwidth.TransferTime(kMaxOutgoingPacketSize) + + test_link.bandwidth.TransferTime(kMaxOutgoingPacketSize)); + } + + QuicByteCount BDP() const { return BottleneckBandwidth() * RTT(); } + + QuicByteCount SwitchQueueCapacity() const { + return switch_queue_capacity_in_bdp * BDP(); + } + + std::string ToString() const { + std::ostringstream os; + os << "{ BottleneckBandwidth: " << BottleneckBandwidth() + << " RTT: " << RTT() << " BDP: " << BDP() + << " BottleneckQueueSize: " << SwitchQueueCapacity() << "}"; + return os.str(); + } +}; + +class Bbr3SimulatorTest : public QuicTest { + protected: + Bbr3SimulatorTest() : simulator_(&random_) { + // Prevent the server(receiver), which only sends acks, from closing + // connection due to too many outstanding packets. + SetQuicFlag(quic_max_tracked_packet_count, 1000000); + } + + void SetUp() override { + if (quiche::GetQuicheCommandLineFlag( + FLAGS_quic_bbr3_test_regression_mode) == "regress") { + SendAlgorithmTestResult expected; + ASSERT_TRUE(LoadSendAlgorithmTestResult(&expected)); + random_seed_ = expected.random_seed(); + } else { + random_seed_ = QuicRandom::GetInstance()->RandUint64(); + } + random_.set_seed(random_seed_); + QUIC_LOG(INFO) << "Using random seed: " << random_seed_; + } + + ~Bbr3SimulatorTest() override { + const std::string regression_mode = + quiche::GetQuicheCommandLineFlag(FLAGS_quic_bbr3_test_regression_mode); + const QuicTime::Delta simulated_duration = + SimulatedNow() - QuicTime::Zero(); + if (regression_mode == "record") { + RecordSendAlgorithmTestResult(random_seed_, + simulated_duration.ToMicroseconds()); + } else if (regression_mode == "regress") { + CompareSendAlgorithmTestResult(simulated_duration.ToMicroseconds()); + } + } + + QuicTime SimulatedNow() const { return simulator_.GetClock()->Now(); } + + uint64_t random_seed_; + SimpleRandom random_; + simulator::Simulator simulator_; +}; + +class Bbr3DefaultTopologyTest : public Bbr3SimulatorTest { + protected: + Bbr3DefaultTopologyTest() + : sender_endpoint_(&simulator_, "Sender", "Receiver", + Perspective::IS_CLIENT, TestConnectionId(42)), + receiver_endpoint_(&simulator_, "Receiver", "Sender", + Perspective::IS_SERVER, TestConnectionId(42)) { + sender_ = SetupBbr3Sender(&sender_endpoint_, /*old_sender=*/nullptr); + } + + ~Bbr3DefaultTopologyTest() { + const auto* test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + const Bbr3Sender::DebugState& debug_state = sender_->ExportDebugState(); + QUIC_LOG(INFO) << "Bbr3DefaultTopologyTest." << test_info->name() + << " completed at simulated time: " + << SimulatedNow().ToDebuggingValue() / 1e6 + << " sec. packet loss:" + << sender_loss_rate_in_packets() * 100 + << "%, bw_hi:" << debug_state.bandwidth_hi; + } + + QuicUnackedPacketMap* GetUnackedMap(QuicConnection* connection) { + return QuicSentPacketManagerPeer::GetUnackedPacketMap( + QuicConnectionPeer::GetSentPacketManager(connection)); + } + + Bbr3Sender* SetupBbr3Sender(simulator::QuicEndpoint* endpoint, + BbrSender* old_sender) { + // Ownership of the sender will be overtaken by the endpoint. + Bbr3Sender* sender = new Bbr3Sender( + endpoint->connection()->clock()->Now(), + endpoint->connection()->sent_packet_manager().GetRttStats(), + GetUnackedMap(endpoint->connection()), kDefaultInitialCwndPackets, + GetQuicFlag(quic_max_congestion_window), &random_, + QuicConnectionPeer::GetStats(endpoint->connection()), old_sender); + QuicConnectionPeer::SetSendAlgorithm(endpoint->connection(), sender); + const int kTestMaxPacketSize = 1350; + endpoint->connection()->SetMaxPacketLength(kTestMaxPacketSize); + endpoint->RecordTrace(); + return sender; + } + + void CreateNetwork(const DefaultTopologyParams& params) { + QUIC_LOG(INFO) << "CreateNetwork with parameters: " << params.ToString(); + switch_ = std::make_unique<simulator::Switch>(&simulator_, "Switch", + params.switch_port_count, + params.SwitchQueueCapacity()); + + // WARNING: The order to add links to network_links_ matters, because some + // tests adjusts the link bandwidth on the fly. + + // Local link connects sender and port 1. + network_links_.push_back(std::make_unique<simulator::SymmetricLink>( + &sender_endpoint_, switch_->port(1), params.local_link.bandwidth, + params.local_link.delay)); + + // Test link connects receiver and port 2. + if (params.sender_policer_params.has_value()) { + const TrafficPolicerParams& policer_params = + params.sender_policer_params.value(); + sender_policer_ = std::make_unique<simulator::TrafficPolicer>( + &simulator_, policer_params.name, policer_params.initial_burst_size, + policer_params.max_bucket_size, policer_params.target_bandwidth, + switch_->port(2)); + network_links_.push_back(std::make_unique<simulator::SymmetricLink>( + &receiver_endpoint_, sender_policer_.get(), + params.test_link.bandwidth, params.test_link.delay)); + } else { + network_links_.push_back(std::make_unique<simulator::SymmetricLink>( + &receiver_endpoint_, switch_->port(2), params.test_link.bandwidth, + params.test_link.delay)); + } + } + + simulator::SymmetricLink* TestLink() { return network_links_[1].get(); } + + void DoSimpleTransfer(QuicByteCount transfer_size, QuicTime::Delta timeout) { + sender_endpoint_.AddBytesToTransfer(transfer_size); + // TODO(wub): consider rewriting this to run until the receiver actually + // receives the intended amount of bytes. + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + timeout); + EXPECT_TRUE(simulator_result) + << "Simple transfer failed. Bytes remaining: " + << sender_endpoint_.bytes_to_transfer(); + QUIC_LOG(INFO) << "Simple transfer state: " << sender_->ExportDebugState(); + } + + // Drive the simulator by sending enough data to enter PROBE_BW. + void DriveOutOfStartup(const DefaultTopologyParams& params) { + ASSERT_FALSE(sender_->ExportDebugState().startup.full_bandwidth_reached); + DoSimpleTransfer(1024 * 1024, QuicTime::Delta::FromSeconds(15)); + EXPECT_EQ(Bbr2Mode::PROBE_BW, sender_->ExportDebugState().mode); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.02f); + } + + // Send |bytes|-sized bursts of data |number_of_bursts| times, waiting for + // |wait_time| between each burst. + void SendBursts(const DefaultTopologyParams& params, size_t number_of_bursts, + QuicByteCount bytes, QuicTime::Delta wait_time) { + ASSERT_EQ(0u, sender_endpoint_.bytes_to_transfer()); + for (size_t i = 0; i < number_of_bursts; i++) { + sender_endpoint_.AddBytesToTransfer(bytes); + + // Transfer data and wait for three seconds between each transfer. + simulator_.RunFor(wait_time); + + // Ensure the connection did not time out. + ASSERT_TRUE(sender_endpoint_.connection()->connected()); + ASSERT_TRUE(receiver_endpoint_.connection()->connected()); + } + + simulator_.RunFor(wait_time + params.RTT()); + ASSERT_EQ(0u, sender_endpoint_.bytes_to_transfer()); + } + + template <class TerminationPredicate> + bool SendUntilOrTimeout(TerminationPredicate termination_predicate, + QuicTime::Delta timeout) { + EXPECT_EQ(0u, sender_endpoint_.bytes_to_transfer()); + const QuicTime deadline = SimulatedNow() + timeout; + do { + sender_endpoint_.AddBytesToTransfer(4 * kDefaultTCPMSS); + if (simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + deadline - SimulatedNow()) && + termination_predicate()) { + return true; + } + } while (SimulatedNow() < deadline); + return false; + } + + void EnableAggregation(QuicByteCount aggregation_bytes, + QuicTime::Delta aggregation_timeout) { + switch_->port_queue(1)->EnableAggregation(aggregation_bytes, + aggregation_timeout); + } + + void SetConnectionOption(QuicTag option) { + SetConnectionOption(std::move(option), sender_); + } + + void SetConnectionOption(QuicTag option, Bbr3Sender* sender) { + QuicConfig config; + QuicTagVector options; + options.push_back(option); + QuicConfigPeer::SetReceivedConnectionOptions(&config, options); + sender->SetFromConfig(config, Perspective::IS_SERVER); + } + + bool Bbr3ModeIsOneOf(const std::vector<Bbr2Mode>& expected_modes) const { + const Bbr2Mode mode = sender_->ExportDebugState().mode; + for (Bbr2Mode expected_mode : expected_modes) { + if (mode == expected_mode) { + return true; + } + } + return false; + } + + const RttStats* rtt_stats() { + return sender_endpoint_.connection()->sent_packet_manager().GetRttStats(); + } + + QuicConnection* sender_connection() { return sender_endpoint_.connection(); } + + Bbr3Sender::DebugState sender_debug_state() const { + return sender_->ExportDebugState(); + } + + const QuicConnectionStats& sender_connection_stats() { + return sender_connection()->GetStats(); + } + + QuicUnackedPacketMap* sender_unacked_map() { + return GetUnackedMap(sender_connection()); + } + + float sender_loss_rate_in_packets() { + return static_cast<float>(sender_connection_stats().packets_lost) / + sender_connection_stats().packets_sent; + } + + simulator::QuicEndpoint sender_endpoint_; + simulator::QuicEndpoint receiver_endpoint_; + Bbr3Sender* sender_; + + std::unique_ptr<simulator::Switch> switch_; + std::unique_ptr<simulator::TrafficPolicer> sender_policer_; + std::vector<std::unique_ptr<simulator::SymmetricLink>> network_links_; +}; + +TEST_F(Bbr3DefaultTopologyTest, NormalStartup) { + DefaultTopologyParams params; + CreateNetwork(params); + + // Run until the full bandwidth is reached and check how many rounds it was. + sender_endpoint_.AddBytesToTransfer(12 * 1024 * 1024); + QuicRoundTripCount max_bw_round = 0; + QuicBandwidth max_bw(QuicBandwidth::Zero()); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this, &max_bw, &max_bw_round]() { + if (max_bw * 1.001 < sender_->ExportDebugState().bandwidth_hi) { + max_bw = sender_->ExportDebugState().bandwidth_hi; + max_bw_round = sender_->ExportDebugState().round_trip_count; + } + return sender_->ExportDebugState().startup.full_bandwidth_reached; + }, + QuicTime::Delta::FromSeconds(5)); + ASSERT_TRUE(simulator_result); + EXPECT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode); + EXPECT_EQ(3u, sender_->ExportDebugState().round_trip_count - max_bw_round); + EXPECT_EQ( + 3u, + sender_->ExportDebugState().startup.round_trips_without_bandwidth_growth); + EXPECT_EQ(0u, sender_connection_stats().packets_lost); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); +} + +TEST_F(Bbr3DefaultTopologyTest, NormalStartupB207) { + SetConnectionOption(kB207); + DefaultTopologyParams params; + CreateNetwork(params); + + // Run until the full bandwidth is reached and check how many rounds it was. + sender_endpoint_.AddBytesToTransfer(12 * 1024 * 1024); + QuicRoundTripCount max_bw_round = 0; + QuicBandwidth max_bw(QuicBandwidth::Zero()); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this, &max_bw, &max_bw_round]() { + if (max_bw < sender_->ExportDebugState().bandwidth_hi) { + max_bw = sender_->ExportDebugState().bandwidth_hi; + max_bw_round = sender_->ExportDebugState().round_trip_count; + } + return sender_->ExportDebugState().startup.full_bandwidth_reached; + }, + QuicTime::Delta::FromSeconds(5)); + ASSERT_TRUE(simulator_result); + EXPECT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode); + EXPECT_EQ(1u, sender_->ExportDebugState().round_trip_count - max_bw_round); + EXPECT_EQ( + 1u, + sender_->ExportDebugState().startup.round_trips_without_bandwidth_growth); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + EXPECT_EQ(0u, sender_connection_stats().packets_lost); +} + +// Add extra_acked to CWND in STARTUP and exit STARTUP on a persistent queue. +TEST_F(Bbr3DefaultTopologyTest, NormalStartupB207andB205) { + SetConnectionOption(kB205); + SetConnectionOption(kB207); + DefaultTopologyParams params; + CreateNetwork(params); + + // Run until the full bandwidth is reached and check how many rounds it was. + sender_endpoint_.AddBytesToTransfer(12 * 1024 * 1024); + QuicRoundTripCount max_bw_round = 0; + QuicBandwidth max_bw(QuicBandwidth::Zero()); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this, &max_bw, &max_bw_round]() { + if (max_bw < sender_->ExportDebugState().bandwidth_hi) { + max_bw = sender_->ExportDebugState().bandwidth_hi; + max_bw_round = sender_->ExportDebugState().round_trip_count; + } + return sender_->ExportDebugState().startup.full_bandwidth_reached; + }, + QuicTime::Delta::FromSeconds(5)); + ASSERT_TRUE(simulator_result); + EXPECT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode); + EXPECT_EQ(1u, sender_->ExportDebugState().round_trip_count - max_bw_round); + EXPECT_EQ( + 2u, + sender_->ExportDebugState().startup.round_trips_without_bandwidth_growth); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + EXPECT_EQ(0u, sender_connection_stats().packets_lost); +} + +// Add extra_acked to CWND in STARTUP and exit STARTUP on a persistent queue. +TEST_F(Bbr3DefaultTopologyTest, NormalStartupBB2S) { + SetConnectionOption(kBB2S); + DefaultTopologyParams params; + CreateNetwork(params); + + // Run until the full bandwidth is reached and check how many rounds it was. + sender_endpoint_.AddBytesToTransfer(12 * 1024 * 1024); + QuicRoundTripCount max_bw_round = 0; + QuicBandwidth max_bw(QuicBandwidth::Zero()); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this, &max_bw, &max_bw_round]() { + if (max_bw * 1.001 < sender_->ExportDebugState().bandwidth_hi) { + max_bw = sender_->ExportDebugState().bandwidth_hi; + max_bw_round = sender_->ExportDebugState().round_trip_count; + } + return sender_->ExportDebugState().startup.full_bandwidth_reached; + }, + QuicTime::Delta::FromSeconds(5)); + ASSERT_TRUE(simulator_result); + EXPECT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode); + // BB2S reduces 3 rounds without bandwidth growth to 2. + EXPECT_EQ(2u, sender_->ExportDebugState().round_trip_count - max_bw_round); + EXPECT_EQ( + 2u, + sender_->ExportDebugState().startup.round_trips_without_bandwidth_growth); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + EXPECT_EQ(0u, sender_connection_stats().packets_lost); +} + +// Test a simple long data transfer in the default setup. +TEST_F(Bbr3DefaultTopologyTest, SimpleTransfer) { + DefaultTopologyParams params; + CreateNetwork(params); + + // At startup make sure we are at the default. + EXPECT_EQ(kDefaultInitialCwndBytes, sender_->GetCongestionWindow()); + // At startup make sure we can send. + EXPECT_TRUE(sender_->CanSend(0)); + // And that window is un-affected. + EXPECT_EQ(kDefaultInitialCwndBytes, sender_->GetCongestionWindow()); + + // Verify that Sender is in slow start. + EXPECT_TRUE(sender_->InSlowStart()); + + // Verify that pacing rate is based on the initial RTT. + QuicBandwidth expected_pacing_rate = QuicBandwidth::FromBytesAndTimeDelta( + 2.885 * kDefaultInitialCwndBytes, rtt_stats()->initial_rtt()); + EXPECT_APPROX_EQ(expected_pacing_rate.ToBitsPerSecond(), + sender_->PacingRate(0).ToBitsPerSecond(), 0.01f); + + ASSERT_GE(params.BDP(), kDefaultInitialCwndBytes + kDefaultTCPMSS); + + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(30)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + EXPECT_EQ(0u, sender_connection_stats().packets_lost); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); + + // The margin here is quite high, since there exists a possibility that the + // connection just exited high gain cycle. + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->smoothed_rtt(), 1.0f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferB2RC) { + SetConnectionOption(kB2RC); + DefaultTopologyParams params; + CreateNetwork(params); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + // The margin here is high, because the aggregation greatly increases + // smoothed rtt. + EXPECT_GE(params.RTT() * 4, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferB201) { + SetConnectionOption(kB201); + DefaultTopologyParams params; + CreateNetwork(params); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + // The margin here is high, because the aggregation greatly increases + // smoothed rtt. + EXPECT_GE(params.RTT() * 4, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferB206) { + SetConnectionOption(kB206); + DefaultTopologyParams params; + CreateNetwork(params); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + // The margin here is high, because the aggregation greatly increases + // smoothed rtt. + EXPECT_GE(params.RTT() * 4, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferB207) { + SetConnectionOption(kB207); + DefaultTopologyParams params; + CreateNetwork(params); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + // The margin here is high, because the aggregation greatly increases + // smoothed rtt. + EXPECT_GE(params.RTT() * 4, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferBBRB) { + SetConnectionOption(kBBRB); + DefaultTopologyParams params; + CreateNetwork(params); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + // The margin here is high, because the aggregation greatly increases + // smoothed rtt. + EXPECT_GE(params.RTT() * 4, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferBBR4) { + SetQuicReloadableFlag(quic_bbr2_extra_acked_window, true); + SetConnectionOption(kBBR4); + DefaultTopologyParams params; + CreateNetwork(params); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + // The margin here is high, because the aggregation greatly increases + // smoothed rtt. + EXPECT_GE(params.RTT() * 4, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferBBR5) { + SetQuicReloadableFlag(quic_bbr2_extra_acked_window, true); + SetConnectionOption(kBBR5); + DefaultTopologyParams params; + CreateNetwork(params); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + // The margin here is high, because the aggregation greatly increases + // smoothed rtt. + EXPECT_GE(params.RTT() * 4, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferBBQ1) { + SetConnectionOption(kBBQ1); + DefaultTopologyParams params; + CreateNetwork(params); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + // The margin here is high, because the aggregation greatly increases + // smoothed rtt. + EXPECT_GE(params.RTT() * 4, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferSmallBuffer) { + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(30)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.02f); + EXPECT_GE(sender_connection_stats().packets_lost, 0u); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferSmallBufferB2H2) { + SetConnectionOption(kB2H2); + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(30)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.02f); + EXPECT_GE(sender_connection_stats().packets_lost, 0u); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransfer2RTTAggregationBytes) { + SetConnectionOption(kBSAO); + DefaultTopologyParams params; + CreateNetwork(params); + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_EQ(sender_loss_rate_in_packets(), 0); + // The margin here is high, because both link level aggregation and ack + // decimation can greatly increase smoothed rtt. + EXPECT_GE(params.RTT() * 5, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransfer2RTTAggregationBytesB201) { + SetConnectionOption(kB201); + DefaultTopologyParams params; + CreateNetwork(params); + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + // TODO(wub): Tighten the error bound once BSAO is default enabled. + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.5f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.01); + // The margin here is high, because both link level aggregation and ack + // decimation can greatly increase smoothed rtt. + EXPECT_GE(params.RTT() * 5, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f); +} + +TEST_F(Bbr3DefaultTopologyTest, SimpleTransferAckDecimation) { + SetConnectionOption(kBSAO); + DefaultTopologyParams params; + CreateNetwork(params); + + // Transfer 12MB. + DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + + EXPECT_LE(sender_loss_rate_in_packets(), 0.001); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); + // The margin here is high, because the aggregation greatly increases + // smoothed rtt. + EXPECT_GE(params.RTT() * 3, rtt_stats()->smoothed_rtt()); + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.1f); +} + +// Test Bbr2's reaction to a 100x bandwidth decrease during a transfer. +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthDecrease)) { + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(20 * 1024 * 1024); + + // We can transfer ~12MB in the first 10 seconds. The rest ~8MB needs about + // 640 seconds. + simulator_.RunFor(QuicTime::Delta::FromSeconds(10)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth decreasing at time " << SimulatedNow(); + + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.1f); + EXPECT_EQ(0u, sender_connection_stats().packets_lost); + + // Now decrease the bottleneck bandwidth from 10Mbps to 100Kbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(800)); + EXPECT_TRUE(simulator_result); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with B203 +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncreaseB203)) { + SetConnectionOption(kB203); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(20 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.1f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.30); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.02f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with BBQ0 +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncreaseBBQ0)) { + SetConnectionOption(kBBQ0); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(10 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.1f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.30); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.02f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with BBQ0 +// in the presence of ACK aggregation. +TEST_F(Bbr3DefaultTopologyTest, + QUIC_SLOW_TEST(BandwidthIncreaseBBQ0Aggregation)) { + SetConnectionOption(kBBQ0); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Reduce the payload to 2MB because 10MB takes too long. + sender_endpoint_.AddBytesToTransfer(2 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + // This is much farther off when aggregation is present, + // Ideally BSAO or another option would fix this. + // TODO(ianswett) Make these bound tighter once overestimation is reduced. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.6f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.35); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure at least 10% of full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.90f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with B202 +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncreaseB202)) { + SetConnectionOption(kB202); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(10 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.1f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.30); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.1f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with B202 +// in the presence of ACK aggregation. +TEST_F(Bbr3DefaultTopologyTest, + QUIC_SLOW_TEST(BandwidthIncreaseB202Aggregation)) { + SetConnectionOption(kB202); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Reduce the payload to 2MB because 10MB takes too long. + sender_endpoint_.AddBytesToTransfer(2 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + // This is much farther off when aggregation is present, + // Ideally BSAO or another option would fix this. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.6f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.35); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure at least 10% of full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.92f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer. +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncrease)) { + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(10 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.1f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.30); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.02f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer in the +// presence of ACK aggregation. +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncreaseAggregation)) { + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Reduce the payload to 2MB because 10MB takes too long. + sender_endpoint_.AddBytesToTransfer(2 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + // This is much farther off when aggregation is present, + // Ideally BSAO or another option would fix this. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.60f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.35); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure at least 10% of full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.91f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with BBHI +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncreaseBBHI)) { + SetConnectionOption(kBBHI); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(10 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.1f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.30); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.02f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with BBHI +// in the presence of ACK aggregation. +TEST_F(Bbr3DefaultTopologyTest, + QUIC_SLOW_TEST(BandwidthIncreaseBBHIAggregation)) { + SetConnectionOption(kBBHI); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Reduce the payload to 2MB because 10MB takes too long. + sender_endpoint_.AddBytesToTransfer(2 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + // This is much farther off when aggregation is present, + // Ideally BSAO or another option would fix this. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.60f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.35); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.90f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with BBHI +// and B202, which changes the exit criteria to be based on +// min_bytes_in_flight_in_round, in the presence of ACK aggregation. +TEST_F(Bbr3DefaultTopologyTest, + QUIC_SLOW_TEST(BandwidthIncreaseBBHI_B202Aggregation)) { + SetConnectionOption(kBBHI); + SetConnectionOption(kB202); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Reduce the payload to 2MB because 10MB takes too long. + sender_endpoint_.AddBytesToTransfer(2 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + // This is much farther off when aggregation is present, + // Ideally BSAO or another option would fix this. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.60f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.35); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure at least 18% of the bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.85f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with B204 +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncreaseB204)) { + SetConnectionOption(kB204); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(10 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.1f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.25); + EXPECT_LE(sender_->ExportDebugState().max_ack_height, 2000u); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.02f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with B204 +// in the presence of ACK aggregation. +TEST_F(Bbr3DefaultTopologyTest, + QUIC_SLOW_TEST(BandwidthIncreaseB204Aggregation)) { + SetConnectionOption(kB204); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Reduce the payload to 2MB because 10MB takes too long. + sender_endpoint_.AddBytesToTransfer(2 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + // This is much farther off when aggregation is present, and B204 actually + // is increasing overestimation, which is surprising. + // Ideally BSAO or another option would fix this. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.60f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.35); + EXPECT_LE(sender_->ExportDebugState().max_ack_height, 10000u); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure at least 10% of full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.95f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with B205 +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncreaseB205)) { + SetConnectionOption(kB205); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(10 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.1f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.10); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.1f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with B205 +// in the presence of ACK aggregation. +TEST_F(Bbr3DefaultTopologyTest, + QUIC_SLOW_TEST(BandwidthIncreaseB205Aggregation)) { + SetConnectionOption(kB205); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Reduce the payload to 2MB because 10MB takes too long. + sender_endpoint_.AddBytesToTransfer(2 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + // This is much farther off when aggregation is present, + // Ideally BSAO or another option would fix this. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.45f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.15); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure at least 5% of full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.9f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with BB2U +TEST_F(Bbr3DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncreaseBB2U)) { + SetConnectionOption(kBB2U); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(10 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.1f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.25); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.1f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with BB2U +// in the presence of ACK aggregation. +TEST_F(Bbr3DefaultTopologyTest, + QUIC_SLOW_TEST(BandwidthIncreaseBB2UAggregation)) { + SetConnectionOption(kBB2U); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Reduce the payload to 5MB because 10MB takes too long. + sender_endpoint_.AddBytesToTransfer(5 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + // This is much farther off when aggregation is present, + // Ideally BSAO or another option would fix this. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.45f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.30); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure at least 15% of the full bandwidth is observed. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.85f); +} + +// Test Bbr2's reaction to a 100x bandwidth increase during a transfer with BB2U +// and BBHI in the presence of ACK aggregation. +TEST_F(Bbr3DefaultTopologyTest, + QUIC_SLOW_TEST(BandwidthIncreaseBB2UandBBHIAggregation)) { + SetConnectionOption(kBB2U); + SetConnectionOption(kBBHI); + DefaultTopologyParams params; + params.local_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(15000); + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(100); + CreateNetwork(params); + + // 2 RTTs of aggregation, with a max of 10kb. + EnableAggregation(10 * 1024, 2 * params.RTT()); + + // Reduce the payload to 5MB because 10MB takes too long. + sender_endpoint_.AddBytesToTransfer(5 * 1024 * 1024); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + QUIC_LOG(INFO) << "Bandwidth increasing at time " << SimulatedNow(); + + // This is much farther off when aggregation is present, + // Ideally BSAO or another option would fix this. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_est, 0.45f); + EXPECT_LE(sender_loss_rate_in_packets(), 0.30); + + // Now increase the bottleneck bandwidth from 100Kbps to 10Mbps. + params.test_link.bandwidth = QuicBandwidth::FromKBitsPerSecond(10000); + TestLink()->set_bandwidth(params.test_link.bandwidth); + + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, + QuicTime::Delta::FromSeconds(50)); + EXPECT_TRUE(simulator_result); + // Ensure at least 15% of the full bandwidth is observed. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.85f); +} + +// Test the number of losses incurred by the startup phase in a situation when +// the buffer is less than BDP. +TEST_F(Bbr3DefaultTopologyTest, PacketLossOnSmallBufferStartup) { + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + DriveOutOfStartup(params); + // Packet loss is smaller with a CWND gain of 2 than 2.889. + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); +} + +// Test the number of losses decreases with packet-conservation pacing. +TEST_F(Bbr3DefaultTopologyTest, PacketLossBBQ6SmallBufferStartup) { + SetConnectionOption(kBBQ2); // Increase CWND gain. + SetConnectionOption(kBBQ6); + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + DriveOutOfStartup(params); + EXPECT_LE(sender_loss_rate_in_packets(), 0.0575); + // bandwidth_lo is cleared exiting STARTUP. + EXPECT_EQ(sender_->ExportDebugState().bandwidth_lo, + QuicBandwidth::Infinite()); +} + +// Test the number of losses decreases with min_rtt packet-conservation pacing. +TEST_F(Bbr3DefaultTopologyTest, PacketLossBBQ7SmallBufferStartup) { + SetConnectionOption(kBBQ2); // Increase CWND gain. + SetConnectionOption(kBBQ7); + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + DriveOutOfStartup(params); + EXPECT_LE(sender_loss_rate_in_packets(), 0.06); + // bandwidth_lo is cleared exiting STARTUP. + EXPECT_EQ(sender_->ExportDebugState().bandwidth_lo, + QuicBandwidth::Infinite()); +} + +// Test the number of losses decreases with Inflight packet-conservation pacing. +TEST_F(Bbr3DefaultTopologyTest, PacketLossBBQ8SmallBufferStartup) { + SetConnectionOption(kBBQ2); // Increase CWND gain. + SetConnectionOption(kBBQ8); + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + DriveOutOfStartup(params); + EXPECT_LE(sender_loss_rate_in_packets(), 0.065); + // bandwidth_lo is cleared exiting STARTUP. + EXPECT_EQ(sender_->ExportDebugState().bandwidth_lo, + QuicBandwidth::Infinite()); +} + +// Test the number of losses decreases with CWND packet-conservation pacing. +TEST_F(Bbr3DefaultTopologyTest, PacketLossBBQ9SmallBufferStartup) { + SetConnectionOption(kBBQ2); // Increase CWND gain. + SetConnectionOption(kBBQ9); + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + DriveOutOfStartup(params); + EXPECT_LE(sender_loss_rate_in_packets(), 0.065); + // bandwidth_lo is cleared exiting STARTUP. + EXPECT_EQ(sender_->ExportDebugState().bandwidth_lo, + QuicBandwidth::Infinite()); +} + +// Verify the behavior of the algorithm in the case when the connection sends +// small bursts of data after sending continuously for a while. +TEST_F(Bbr3DefaultTopologyTest, ApplicationLimitedBursts) { + DefaultTopologyParams params; + CreateNetwork(params); + + EXPECT_FALSE(sender_->HasGoodBandwidthEstimateForResumption()); + DriveOutOfStartup(params); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); + EXPECT_TRUE(sender_->HasGoodBandwidthEstimateForResumption()); + + SendBursts(params, 20, 512, QuicTime::Delta::FromSeconds(3)); + EXPECT_TRUE(sender_->ExportDebugState().last_sample_is_app_limited); + EXPECT_TRUE(sender_->HasGoodBandwidthEstimateForResumption()); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); +} + +// Verify the behavior of the algorithm in the case when the connection sends +// small bursts of data and then starts sending continuously. +TEST_F(Bbr3DefaultTopologyTest, ApplicationLimitedBurstsWithoutPrior) { + DefaultTopologyParams params; + CreateNetwork(params); + + SendBursts(params, 40, 512, QuicTime::Delta::FromSeconds(3)); + EXPECT_TRUE(sender_->ExportDebugState().last_sample_is_app_limited); + + DriveOutOfStartup(params); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.01f); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); +} + +// Verify that the DRAIN phase works correctly. +TEST_F(Bbr3DefaultTopologyTest, Drain) { + DefaultTopologyParams params; + CreateNetwork(params); + + const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(10); + // Get the queue at the bottleneck, which is the outgoing queue at the port to + // which the receiver is connected. + const simulator::Queue* queue = switch_->port_queue(2); + bool simulator_result; + + // We have no intention of ever finishing this transfer. + sender_endpoint_.AddBytesToTransfer(100 * 1024 * 1024); + + // Run the startup, and verify that it fills up the queue. + ASSERT_EQ(Bbr2Mode::STARTUP, sender_->ExportDebugState().mode); + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return sender_->ExportDebugState().mode != Bbr2Mode::STARTUP; + }, + timeout); + ASSERT_TRUE(simulator_result); + ASSERT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode); + EXPECT_APPROX_EQ(sender_->BandwidthEstimate() * (1 / 2.885f), + sender_->PacingRate(0), 0.01f); + + // BBR uses CWND gain of 2 during STARTUP, hence it will fill the buffer with + // approximately 1 BDP. Here, we use 0.95 to give some margin for error. + EXPECT_GE(queue->bytes_queued(), 0.95 * params.BDP()); + + // Observe increased RTT due to bufferbloat. + const QuicTime::Delta queueing_delay = + params.test_link.bandwidth.TransferTime(queue->bytes_queued()); + EXPECT_APPROX_EQ(params.RTT() + queueing_delay, rtt_stats()->latest_rtt(), + 0.1f); + + // Transition to the drain phase and verify that it makes the queue + // have at most a BDP worth of packets. + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_->ExportDebugState().mode != Bbr2Mode::DRAIN; }, + timeout); + ASSERT_TRUE(simulator_result); + ASSERT_EQ(Bbr2Mode::PROBE_BW, sender_->ExportDebugState().mode); + EXPECT_LE(queue->bytes_queued(), params.BDP()); + + // Wait for a few round trips and ensure we're in appropriate phase of gain + // cycling before taking an RTT measurement. + const QuicRoundTripCount start_round_trip = + sender_->ExportDebugState().round_trip_count; + simulator_result = simulator_.RunUntilOrTimeout( + [this, start_round_trip]() { + const auto& debug_state = sender_->ExportDebugState(); + QuicRoundTripCount rounds_passed = + debug_state.round_trip_count - start_round_trip; + return rounds_passed >= 4 && debug_state.mode == Bbr2Mode::PROBE_BW && + debug_state.probe_bw.phase == ProbePhase::PROBE_REFILL; + }, + timeout); + ASSERT_TRUE(simulator_result); + + // Observe the bufferbloat go away. + EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->smoothed_rtt(), 0.1f); +} + +// Ensure that a connection that is app-limited and is at sufficiently low +// bandwidth will not exit high gain phase, and similarly ensure that the +// connection will exit low gain early if the number of bytes in flight is low. +TEST_F(Bbr3DefaultTopologyTest, InFlightAwareGainCycling) { + DefaultTopologyParams params; + CreateNetwork(params); + DriveOutOfStartup(params); + + const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); + bool simulator_result; + + // Start a few cycles prior to the high gain one. + simulator_result = SendUntilOrTimeout( + [this]() { + return sender_->ExportDebugState().probe_bw.phase == + ProbePhase::PROBE_REFILL; + }, + timeout); + ASSERT_TRUE(simulator_result); + + // Send at 10% of available rate. Run for 3 seconds, checking in the middle + // and at the end. The pacing gain should be high throughout. + QuicBandwidth target_bandwidth = 0.1f * params.BottleneckBandwidth(); + QuicTime::Delta burst_interval = QuicTime::Delta::FromMilliseconds(300); + for (int i = 0; i < 2; i++) { + SendBursts(params, 5, target_bandwidth * burst_interval, burst_interval); + EXPECT_EQ(Bbr2Mode::PROBE_BW, sender_->ExportDebugState().mode); + EXPECT_EQ(ProbePhase::PROBE_UP, sender_->ExportDebugState().probe_bw.phase); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), + sender_->ExportDebugState().bandwidth_hi, 0.02f); + } + + if (GetQuicReloadableFlag(quic_pacing_remove_non_initial_burst)) { + QuicSentPacketManagerPeer::GetPacingSender( + &sender_connection()->sent_packet_manager()) + ->SetBurstTokens(10); + } + + // Now that in-flight is almost zero and the pacing gain is still above 1, + // send approximately 1.4 BDPs worth of data. This should cause the PROBE_BW + // mode to enter low gain cycle(PROBE_DOWN), and exit it earlier than one + // min_rtt due to running out of data to send. + sender_endpoint_.AddBytesToTransfer(1.4 * params.BDP()); + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return sender_->ExportDebugState().probe_bw.phase == + ProbePhase::PROBE_DOWN; + }, + timeout); + ASSERT_TRUE(simulator_result); + simulator_.RunFor(0.75 * sender_->ExportDebugState().min_rtt); + EXPECT_EQ(Bbr2Mode::PROBE_BW, sender_->ExportDebugState().mode); + EXPECT_EQ(ProbePhase::PROBE_CRUISE, + sender_->ExportDebugState().probe_bw.phase); +} + +// Test exiting STARTUP earlier upon loss due to loss. +TEST_F(Bbr3DefaultTopologyTest, ExitStartupDueToLoss) { + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + // Run until the full bandwidth is reached and check how many rounds it was. + sender_endpoint_.AddBytesToTransfer(12 * 1024 * 1024); + QuicRoundTripCount max_bw_round = 0; + QuicBandwidth max_bw(QuicBandwidth::Zero()); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this, &max_bw, &max_bw_round]() { + if (max_bw < sender_->ExportDebugState().bandwidth_hi) { + max_bw = sender_->ExportDebugState().bandwidth_hi; + max_bw_round = sender_->ExportDebugState().round_trip_count; + } + return sender_->ExportDebugState().startup.full_bandwidth_reached; + }, + QuicTime::Delta::FromSeconds(5)); + ASSERT_TRUE(simulator_result); + EXPECT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode); + EXPECT_GE(2u, sender_->ExportDebugState().round_trip_count - max_bw_round); + EXPECT_EQ( + 1u, + sender_->ExportDebugState().startup.round_trips_without_bandwidth_growth); + EXPECT_NE(0u, sender_connection_stats().packets_lost); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); + + EXPECT_GT(sender_->ExportDebugState().inflight_hi, 1.2f * params.BDP()); +} + +// Test exiting STARTUP earlier upon loss due to loss when connection option +// B2SL is used. +TEST_F(Bbr3DefaultTopologyTest, ExitStartupDueToLossB2SL) { + SetConnectionOption(kB2SL); + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + // Run until the full bandwidth is reached and check how many rounds it was. + sender_endpoint_.AddBytesToTransfer(12 * 1024 * 1024); + QuicRoundTripCount max_bw_round = 0; + QuicBandwidth max_bw(QuicBandwidth::Zero()); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this, &max_bw, &max_bw_round]() { + if (max_bw < sender_->ExportDebugState().bandwidth_hi) { + max_bw = sender_->ExportDebugState().bandwidth_hi; + max_bw_round = sender_->ExportDebugState().round_trip_count; + } + return sender_->ExportDebugState().startup.full_bandwidth_reached; + }, + QuicTime::Delta::FromSeconds(5)); + ASSERT_TRUE(simulator_result); + EXPECT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode); + EXPECT_GE(2u, sender_->ExportDebugState().round_trip_count - max_bw_round); + EXPECT_EQ( + 1u, + sender_->ExportDebugState().startup.round_trips_without_bandwidth_growth); + EXPECT_NE(0u, sender_connection_stats().packets_lost); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); + + EXPECT_APPROX_EQ(sender_->ExportDebugState().inflight_hi, params.BDP(), 0.1f); +} + +// Verifies that in STARTUP, if we exceed loss threshold in a round, we exit +// STARTUP at the end of the round even if there's enough bandwidth growth. +TEST_F(Bbr3DefaultTopologyTest, ExitStartupDueToLossB2NE) { + // Set up flags such that any loss will be considered "too high". + SetQuicFlag(quic_bbr2_default_startup_full_loss_count, 0); + SetQuicFlag(quic_bbr2_default_loss_threshold, 0.0); + + sender_ = SetupBbr3Sender(&sender_endpoint_, /*old_sender=*/nullptr); + + SetConnectionOption(kB2NE); + DefaultTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.5; + CreateNetwork(params); + + // Run until the full bandwidth is reached and check how many rounds it was. + sender_endpoint_.AddBytesToTransfer(12 * 1024 * 1024); + QuicRoundTripCount max_bw_round = 0; + QuicBandwidth max_bw(QuicBandwidth::Zero()); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this, &max_bw, &max_bw_round]() { + if (max_bw < sender_->ExportDebugState().bandwidth_hi) { + max_bw = sender_->ExportDebugState().bandwidth_hi; + max_bw_round = sender_->ExportDebugState().round_trip_count; + } + return sender_->ExportDebugState().startup.full_bandwidth_reached; + }, + QuicTime::Delta::FromSeconds(5)); + ASSERT_TRUE(simulator_result); + EXPECT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode); + EXPECT_EQ(sender_->ExportDebugState().round_trip_count, max_bw_round); + EXPECT_EQ( + 0u, + sender_->ExportDebugState().startup.round_trips_without_bandwidth_growth); + EXPECT_NE(0u, sender_connection_stats().packets_lost); +} + +TEST_F(Bbr3DefaultTopologyTest, SenderPoliced) { + DefaultTopologyParams params; + params.sender_policer_params = TrafficPolicerParams(); + params.sender_policer_params->initial_burst_size = 1000 * 10; + params.sender_policer_params->max_bucket_size = 1000 * 100; + params.sender_policer_params->target_bandwidth = + params.BottleneckBandwidth() * 0.25; + + CreateNetwork(params); + + ASSERT_GE(params.BDP(), kDefaultInitialCwndBytes + kDefaultTCPMSS); + + DoSimpleTransfer(3 * 1024 * 1024, QuicTime::Delta::FromSeconds(30)); + EXPECT_TRUE(Bbr3ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT})); + // TODO(wub): Fix (long-term) bandwidth overestimation in policer mode, then + // reduce the loss rate upper bound. + EXPECT_LE(sender_loss_rate_in_packets(), 0.30); +} + +// TODO(wub): Add other slowstart stats to BBRv2. +TEST_F(Bbr3DefaultTopologyTest, StartupStats) { + DefaultTopologyParams params; + CreateNetwork(params); + + DriveOutOfStartup(params); + ASSERT_FALSE(sender_->InSlowStart()); + + const QuicConnectionStats& stats = sender_connection_stats(); + // The test explicitly replaces the default-created send algorithm with the + // one created by the test. slowstart_count increaments every time a BBR + // sender is created. + EXPECT_GE(stats.slowstart_count, 1u); + EXPECT_FALSE(stats.slowstart_duration.IsRunning()); + EXPECT_THAT(stats.slowstart_duration.GetTotalElapsedTime(), + AllOf(Ge(QuicTime::Delta::FromMilliseconds(500)), + Le(QuicTime::Delta::FromMilliseconds(1500)))); + EXPECT_EQ(stats.slowstart_duration.GetTotalElapsedTime(), + QuicConnectionPeer::GetSentPacketManager(sender_connection()) + ->GetSlowStartDuration()); +} + +TEST_F(Bbr3DefaultTopologyTest, ProbeUpAdaptInflightHiGradually) { + DefaultTopologyParams params; + CreateNetwork(params); + + DriveOutOfStartup(params); + + AckedPacketVector acked_packets; + QuicPacketNumber acked_packet_number = + sender_unacked_map()->GetLeastUnacked(); + for (auto& info : *sender_unacked_map()) { + acked_packets.emplace_back(acked_packet_number++, info.bytes_sent, + SimulatedNow()); + } + + // Advance time significantly so the OnCongestionEvent enters PROBE_REFILL. + QuicTime now = SimulatedNow() + QuicTime::Delta::FromSeconds(5); + auto next_packet_number = sender_unacked_map()->largest_sent_packet() + 1; + sender_->OnCongestionEvent( + /*rtt_updated=*/true, sender_unacked_map()->bytes_in_flight(), now, + acked_packets, {}, 0, 0); + ASSERT_EQ(ProbePhase::PROBE_REFILL, + sender_->ExportDebugState().probe_bw.phase); + + // Send and Ack one packet to exit app limited and enter PROBE_UP. + sender_->OnPacketSent(now, /*bytes_in_flight=*/0, next_packet_number++, + kDefaultMaxPacketSize, HAS_RETRANSMITTABLE_DATA); + now = now + params.RTT(); + sender_->OnCongestionEvent( + /*rtt_updated=*/true, kDefaultMaxPacketSize, now, + {AckedPacket(next_packet_number - 1, kDefaultMaxPacketSize, now)}, {}, 0, + 0); + ASSERT_EQ(ProbePhase::PROBE_UP, sender_->ExportDebugState().probe_bw.phase); + + // Send 2 packets and lose the first one(50% loss) to exit PROBE_UP. + for (uint64_t i = 0; i < 2; ++i) { + sender_->OnPacketSent(now, /*bytes_in_flight=*/i * kDefaultMaxPacketSize, + next_packet_number++, kDefaultMaxPacketSize, + HAS_RETRANSMITTABLE_DATA); + } + now = now + params.RTT(); + sender_->OnCongestionEvent( + /*rtt_updated=*/true, 2 * kDefaultMaxPacketSize, now, + {AckedPacket(next_packet_number - 1, kDefaultMaxPacketSize, now)}, + {LostPacket(next_packet_number - 2, kDefaultMaxPacketSize)}, 0, 0); + + QuicByteCount inflight_hi = sender_->ExportDebugState().inflight_hi; + EXPECT_LT(2 * kDefaultMaxPacketSize, inflight_hi); +} + +// Ensures bandwidth estimate does not change after a loss only event. +TEST_F(Bbr3DefaultTopologyTest, LossOnlyCongestionEvent) { + DefaultTopologyParams params; + CreateNetwork(params); + + DriveOutOfStartup(params); + EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); + + // Send some bursts, each burst increments round count by 1, since it only + // generates small, app-limited samples, the max_bandwidth_filter_ will not be + // updated. + SendBursts(params, 20, 512, QuicTime::Delta::FromSeconds(3)); + + // Run until we have something in flight. + sender_endpoint_.AddBytesToTransfer(50 * 1024 * 1024); + bool simulator_result = simulator_.RunUntilOrTimeout( + [&]() { return sender_unacked_map()->bytes_in_flight() > 0; }, + QuicTime::Delta::FromSeconds(5)); + ASSERT_TRUE(simulator_result); + + const QuicBandwidth prior_bandwidth_estimate = sender_->BandwidthEstimate(); + EXPECT_APPROX_EQ(params.BottleneckBandwidth(), prior_bandwidth_estimate, + 0.01f); + + // Lose the least unacked packet. + LostPacketVector lost_packets; + lost_packets.emplace_back( + sender_connection()->sent_packet_manager().GetLeastUnacked(), + kDefaultMaxPacketSize); + + QuicTime now = simulator_.GetClock()->Now() + params.RTT() * 0.25; + sender_->OnCongestionEvent(false, sender_unacked_map()->bytes_in_flight(), + now, {}, lost_packets, 0, 0); + + // Bandwidth estimate should not change for the loss only event. + EXPECT_EQ(prior_bandwidth_estimate, sender_->BandwidthEstimate()); +} + +// Simulate the case where a packet is considered lost but then acked. +TEST_F(Bbr3DefaultTopologyTest, SpuriousLossEvent) { + DefaultTopologyParams params; + CreateNetwork(params); + + DriveOutOfStartup(params); + + // Make sure we have something in flight. + if (sender_unacked_map()->bytes_in_flight() == 0) { + sender_endpoint_.AddBytesToTransfer(50 * 1024 * 1024); + bool simulator_result = simulator_.RunUntilOrTimeout( + [&]() { return sender_unacked_map()->bytes_in_flight() > 0; }, + QuicTime::Delta::FromSeconds(5)); + ASSERT_TRUE(simulator_result); + } + + // Lose all in flight packets. + QuicTime now = simulator_.GetClock()->Now() + params.RTT() * 0.25; + const QuicByteCount prior_inflight = sender_unacked_map()->bytes_in_flight(); + LostPacketVector lost_packets; + for (QuicPacketNumber packet_number = sender_unacked_map()->GetLeastUnacked(); + sender_unacked_map()->HasInFlightPackets(); packet_number++) { + const auto& info = sender_unacked_map()->GetTransmissionInfo(packet_number); + if (!info.in_flight) { + continue; + } + lost_packets.emplace_back(packet_number, info.bytes_sent); + sender_unacked_map()->RemoveFromInFlight(packet_number); + } + ASSERT_FALSE(lost_packets.empty()); + sender_->OnCongestionEvent(false, prior_inflight, now, {}, lost_packets, 0, + 0); + + // Pretend the first lost packet number is acked. + now = now + params.RTT() * 0.5; + AckedPacketVector acked_packets; + acked_packets.emplace_back(lost_packets[0].packet_number, 0, now); + acked_packets.back().spurious_loss = true; + EXPECT_EQ(sender_unacked_map()->bytes_in_flight(), 0); + sender_->OnCongestionEvent(false, sender_unacked_map()->bytes_in_flight(), + now, acked_packets, {}, 0, 0); + + EXPECT_EQ(sender_->GetNetworkModel().total_bytes_sent(), + sender_->GetNetworkModel().total_bytes_acked() + + sender_->GetNetworkModel().total_bytes_lost()); +} + +// After quiescence, if the sender is in PROBE_RTT, it should transition to +// PROBE_BW immediately on the first sent packet after quiescence. +TEST_F(Bbr3DefaultTopologyTest, ProbeRttAfterQuiescenceImmediatelyExits) { + DefaultTopologyParams params; + CreateNetwork(params); + + DriveOutOfStartup(params); + + const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(15); + bool simulator_result; + + // Keep sending until reach PROBE_RTT. + simulator_result = SendUntilOrTimeout( + [this]() { + return sender_->ExportDebugState().mode == Bbr2Mode::PROBE_RTT; + }, + timeout); + ASSERT_TRUE(simulator_result); + + // Wait for entering a quiescence of 5 seconds. + ASSERT_TRUE(simulator_.RunUntilOrTimeout( + [this]() { + return sender_unacked_map()->bytes_in_flight() == 0 && + sender_->ExportDebugState().mode == Bbr2Mode::PROBE_RTT; + }, + timeout)); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(5)); + + // Send one packet to exit quiescence. + EXPECT_EQ(sender_->ExportDebugState().mode, Bbr2Mode::PROBE_RTT); + sender_->OnPacketSent(SimulatedNow(), /*bytes_in_flight=*/0, + sender_unacked_map()->largest_sent_packet() + 1, + kDefaultMaxPacketSize, HAS_RETRANSMITTABLE_DATA); + + EXPECT_EQ(sender_->ExportDebugState().mode, Bbr2Mode::PROBE_BW); +} + +TEST_F(Bbr3DefaultTopologyTest, ProbeBwAfterQuiescencePostponeMinRttTimestamp) { + DefaultTopologyParams params; + CreateNetwork(params); + + DriveOutOfStartup(params); + + const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); + bool simulator_result; + + // Keep sending until reach PROBE_REFILL. + simulator_result = SendUntilOrTimeout( + [this]() { + return sender_->ExportDebugState().probe_bw.phase == + ProbePhase::PROBE_REFILL; + }, + timeout); + ASSERT_TRUE(simulator_result); + + const QuicTime min_rtt_timestamp_before_idle = + sender_->ExportDebugState().min_rtt_timestamp; + + // Wait for entering a quiescence of 15 seconds. + ASSERT_TRUE(simulator_.RunUntilOrTimeout( + [this]() { return sender_unacked_map()->bytes_in_flight() == 0; }, + params.RTT() + timeout)); + + simulator_.RunFor(QuicTime::Delta::FromSeconds(15)); + + // Send some data to exit quiescence. + SendBursts(params, 1, kDefaultTCPMSS, QuicTime::Delta::Zero()); + const QuicTime min_rtt_timestamp_after_idle = + sender_->ExportDebugState().min_rtt_timestamp; + + EXPECT_LT(min_rtt_timestamp_before_idle + QuicTime::Delta::FromSeconds(14), + min_rtt_timestamp_after_idle); +} + +TEST_F(Bbr3DefaultTopologyTest, SwitchToBbr2MidConnection) { + QuicTime now = QuicTime::Zero(); + BbrSender old_sender(sender_connection()->clock()->Now(), + sender_connection()->sent_packet_manager().GetRttStats(), + GetUnackedMap(sender_connection()), + kDefaultInitialCwndPackets + 1, + GetQuicFlag(quic_max_congestion_window), &random_, + QuicConnectionPeer::GetStats(sender_connection())); + + QuicPacketNumber next_packet_number(1); + + // Send packets 1-4. + while (next_packet_number < QuicPacketNumber(5)) { + now = now + QuicTime::Delta::FromMilliseconds(10); + + old_sender.OnPacketSent(now, /*bytes_in_flight=*/0, next_packet_number++, + /*bytes=*/1350, HAS_RETRANSMITTABLE_DATA); + } + + // Switch from |old_sender| to |sender_|. + const QuicByteCount old_sender_cwnd = old_sender.GetCongestionWindow(); + sender_ = SetupBbr3Sender(&sender_endpoint_, &old_sender); + EXPECT_EQ(old_sender_cwnd, sender_->GetCongestionWindow()); + + // Send packets 5-7. + now = now + QuicTime::Delta::FromMilliseconds(10); + sender_->OnPacketSent(now, /*bytes_in_flight=*/1350, next_packet_number++, + /*bytes=*/23, NO_RETRANSMITTABLE_DATA); + + now = now + QuicTime::Delta::FromMilliseconds(10); + sender_->OnPacketSent(now, /*bytes_in_flight=*/1350, next_packet_number++, + /*bytes=*/767, HAS_RETRANSMITTABLE_DATA); + + QuicByteCount bytes_in_flight = 767; + while (next_packet_number < QuicPacketNumber(30)) { + now = now + QuicTime::Delta::FromMilliseconds(10); + bytes_in_flight += 1350; + sender_->OnPacketSent(now, bytes_in_flight, next_packet_number++, + /*bytes=*/1350, HAS_RETRANSMITTABLE_DATA); + } + + // Ack 1 & 2. + AckedPacketVector acked = { + AckedPacket(QuicPacketNumber(1), /*bytes_acked=*/0, QuicTime::Zero()), + AckedPacket(QuicPacketNumber(2), /*bytes_acked=*/0, QuicTime::Zero()), + }; + now = now + QuicTime::Delta::FromMilliseconds(2000); + sender_->OnCongestionEvent(true, bytes_in_flight, now, acked, {}, 0, 0); + + // Send 30-41. + while (next_packet_number < QuicPacketNumber(42)) { + now = now + QuicTime::Delta::FromMilliseconds(10); + bytes_in_flight += 1350; + sender_->OnPacketSent(now, bytes_in_flight, next_packet_number++, + /*bytes=*/1350, HAS_RETRANSMITTABLE_DATA); + } + + // Ack 3. + acked = { + AckedPacket(QuicPacketNumber(3), /*bytes_acked=*/0, QuicTime::Zero()), + }; + now = now + QuicTime::Delta::FromMilliseconds(2000); + sender_->OnCongestionEvent(true, bytes_in_flight, now, acked, {}, 0, 0); + + // Send 42. + now = now + QuicTime::Delta::FromMilliseconds(10); + bytes_in_flight += 1350; + sender_->OnPacketSent(now, bytes_in_flight, next_packet_number++, + /*bytes=*/1350, HAS_RETRANSMITTABLE_DATA); + + // Ack 4-7. + acked = { + AckedPacket(QuicPacketNumber(4), /*bytes_acked=*/0, QuicTime::Zero()), + AckedPacket(QuicPacketNumber(5), /*bytes_acked=*/0, QuicTime::Zero()), + AckedPacket(QuicPacketNumber(6), /*bytes_acked=*/767, QuicTime::Zero()), + AckedPacket(QuicPacketNumber(7), /*bytes_acked=*/1350, QuicTime::Zero()), + }; + now = now + QuicTime::Delta::FromMilliseconds(2000); + sender_->OnCongestionEvent(true, bytes_in_flight, now, acked, {}, 0, 0); + EXPECT_FALSE(sender_->BandwidthEstimate().IsZero()); +} + +TEST_F(Bbr3DefaultTopologyTest, AdjustNetworkParameters) { + DefaultTopologyParams params; + CreateNetwork(params); + + QUIC_LOG(INFO) << "Initial cwnd: " << sender_debug_state().congestion_window + << "\nInitial pacing rate: " << sender_->PacingRate(0) + << "\nInitial bandwidth estimate: " + << sender_->BandwidthEstimate() + << "\nInitial rtt: " << sender_debug_state().min_rtt; + + sender_connection()->AdjustNetworkParameters( + SendAlgorithmInterface::NetworkParams(params.BottleneckBandwidth(), + params.RTT(), + /*allow_cwnd_to_decrease=*/false)); + + EXPECT_EQ(params.BDP(), sender_->ExportDebugState().congestion_window); + + EXPECT_EQ(params.BottleneckBandwidth(), + sender_->PacingRate(/*bytes_in_flight=*/0)); + EXPECT_NE(params.BottleneckBandwidth(), sender_->BandwidthEstimate()); + + EXPECT_APPROX_EQ(params.RTT(), sender_->ExportDebugState().min_rtt, 0.01f); + + DriveOutOfStartup(params); +} + +TEST_F(Bbr3DefaultTopologyTest, + 200InitialCongestionWindowWithNetworkParameterAdjusted) { + DefaultTopologyParams params; + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(1 * 1024 * 1024); + + // Wait until an ACK comes back. + const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return !sender_->ExportDebugState().min_rtt.IsZero(); }, + timeout); + ASSERT_TRUE(simulator_result); + + // Bootstrap cwnd by a overly large bandwidth sample. + sender_connection()->AdjustNetworkParameters( + SendAlgorithmInterface::NetworkParams(1024 * params.BottleneckBandwidth(), + QuicTime::Delta::Zero(), false)); + + // Verify cwnd is capped at 200. + EXPECT_EQ(200 * kDefaultTCPMSS, + sender_->ExportDebugState().congestion_window); + EXPECT_GT(1024 * params.BottleneckBandwidth(), sender_->PacingRate(0)); +} + +TEST_F(Bbr3DefaultTopologyTest, + 100InitialCongestionWindowFromNetworkParameter) { + DefaultTopologyParams params; + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(1 * 1024 * 1024); + // Wait until an ACK comes back. + const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return !sender_->ExportDebugState().min_rtt.IsZero(); }, + timeout); + ASSERT_TRUE(simulator_result); + + // Bootstrap cwnd by a overly large bandwidth sample. + SendAlgorithmInterface::NetworkParams network_params( + 1024 * params.BottleneckBandwidth(), QuicTime::Delta::Zero(), false); + network_params.max_initial_congestion_window = 100; + sender_connection()->AdjustNetworkParameters(network_params); + + // Verify cwnd is capped at 100. + EXPECT_EQ(100 * kDefaultTCPMSS, + sender_->ExportDebugState().congestion_window); + EXPECT_GT(1024 * params.BottleneckBandwidth(), sender_->PacingRate(0)); +} + +TEST_F(Bbr3DefaultTopologyTest, + 100InitialCongestionWindowWithNetworkParameterAdjusted) { + SetConnectionOption(kICW1); + DefaultTopologyParams params; + CreateNetwork(params); + + sender_endpoint_.AddBytesToTransfer(1 * 1024 * 1024); + // Wait until an ACK comes back. + const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return !sender_->ExportDebugState().min_rtt.IsZero(); }, + timeout); + ASSERT_TRUE(simulator_result); + + // Bootstrap cwnd by a overly large bandwidth sample. + sender_connection()->AdjustNetworkParameters( + SendAlgorithmInterface::NetworkParams(1024 * params.BottleneckBandwidth(), + QuicTime::Delta::Zero(), false)); + + // Verify cwnd is capped at 100. + EXPECT_EQ(100 * kDefaultTCPMSS, + sender_->ExportDebugState().congestion_window); + EXPECT_GT(1024 * params.BottleneckBandwidth(), sender_->PacingRate(0)); +} + +// All Bbr2MultiSenderTests uses the following network topology: +// +// Sender 0 (A Bbr3Sender) +// | +// | <-- local_links[0] +// | +// | Sender N (1 <= N < kNumLocalLinks) (May or may not be a Bbr3Sender) +// | | +// | | <-- local_links[N] +// | | +// Network switch +// * <-- the bottleneck queue in the direction +// | of the receiver +// | +// | <-- test_link +// | +// | +// Receiver +class MultiSenderTopologyParams { + public: + static constexpr size_t kNumLocalLinks = 8; + std::array<LinkParams, kNumLocalLinks> local_links = { + LinkParams(10000, 1987), LinkParams(10000, 1993), LinkParams(10000, 1997), + LinkParams(10000, 1999), LinkParams(10000, 2003), LinkParams(10000, 2011), + LinkParams(10000, 2017), LinkParams(10000, 2027), + }; + + LinkParams test_link = LinkParams(4000, 30000); + + const simulator::SwitchPortNumber switch_port_count = kNumLocalLinks + 1; + + // Network switch queue capacity, in number of BDPs. + float switch_queue_capacity_in_bdp = 2; + + QuicBandwidth BottleneckBandwidth() const { + // Make sure all local links have a higher bandwidth than the test link. + for (size_t i = 0; i < local_links.size(); ++i) { + QUICHE_CHECK_GT(local_links[i].bandwidth, test_link.bandwidth); + } + return test_link.bandwidth; + } + + // Sender n's round trip time of a single full size packet. + QuicTime::Delta Rtt(size_t n) const { + return 2 * (local_links[n].delay + test_link.delay + + local_links[n].bandwidth.TransferTime(kMaxOutgoingPacketSize) + + test_link.bandwidth.TransferTime(kMaxOutgoingPacketSize)); + } + + QuicByteCount Bdp(size_t n) const { return BottleneckBandwidth() * Rtt(n); } + + QuicByteCount SwitchQueueCapacity() const { + return switch_queue_capacity_in_bdp * Bdp(1); + } + + std::string ToString() const { + std::ostringstream os; + os << "{ BottleneckBandwidth: " << BottleneckBandwidth(); + for (size_t i = 0; i < local_links.size(); ++i) { + os << " RTT_" << i << ": " << Rtt(i) << " BDP_" << i << ": " << Bdp(i); + } + os << " BottleneckQueueSize: " << SwitchQueueCapacity() << "}"; + return os.str(); + } +}; + +class Bbr2MultiSenderTest : public Bbr3SimulatorTest { + protected: + Bbr2MultiSenderTest() { + uint64_t first_connection_id = 42; + std::vector<simulator::QuicEndpointBase*> receiver_endpoint_pointers; + for (size_t i = 0; i < MultiSenderTopologyParams::kNumLocalLinks; ++i) { + std::string sender_name = absl::StrCat("Sender", i + 1); + std::string receiver_name = absl::StrCat("Receiver", i + 1); + sender_endpoints_.push_back(std::make_unique<simulator::QuicEndpoint>( + &simulator_, sender_name, receiver_name, Perspective::IS_CLIENT, + TestConnectionId(first_connection_id + i))); + receiver_endpoints_.push_back(std::make_unique<simulator::QuicEndpoint>( + &simulator_, receiver_name, sender_name, Perspective::IS_SERVER, + TestConnectionId(first_connection_id + i))); + receiver_endpoint_pointers.push_back(receiver_endpoints_.back().get()); + } + receiver_multiplexer_ = + std::make_unique<simulator::QuicEndpointMultiplexer>( + "Receiver multiplexer", receiver_endpoint_pointers); + sender_0_ = SetupBbr3Sender(sender_endpoints_[0].get()); + } + + ~Bbr2MultiSenderTest() { + const auto* test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + QUIC_LOG(INFO) << "Bbr2MultiSenderTest." << test_info->name() + << " completed at simulated time: " + << SimulatedNow().ToDebuggingValue() / 1e6 + << " sec. Per sender stats:"; + for (size_t i = 0; i < sender_endpoints_.size(); ++i) { + QUIC_LOG(INFO) << "sender[" << i << "]: " + << sender_connection(i) + ->sent_packet_manager() + .GetSendAlgorithm() + ->GetCongestionControlType() + << ", packet_loss:" + << 100.0 * sender_loss_rate_in_packets(i) << "%"; + } + } + + Bbr3Sender* SetupBbr3Sender(simulator::QuicEndpoint* endpoint) { + // Ownership of the sender will be overtaken by the endpoint. + Bbr3Sender* sender = new Bbr3Sender( + endpoint->connection()->clock()->Now(), + endpoint->connection()->sent_packet_manager().GetRttStats(), + QuicSentPacketManagerPeer::GetUnackedPacketMap( + QuicConnectionPeer::GetSentPacketManager(endpoint->connection())), + kDefaultInitialCwndPackets, GetQuicFlag(quic_max_congestion_window), + &random_, QuicConnectionPeer::GetStats(endpoint->connection()), + nullptr); + // TODO(ianswett): Add dedicated tests for this option until it becomes + // the default behavior. + SetConnectionOption(sender, kBBRA); + + QuicConnectionPeer::SetSendAlgorithm(endpoint->connection(), sender); + endpoint->RecordTrace(); + return sender; + } + + BbrSender* SetupBbrSender(simulator::QuicEndpoint* endpoint) { + // Ownership of the sender will be overtaken by the endpoint. + BbrSender* sender = new BbrSender( + endpoint->connection()->clock()->Now(), + endpoint->connection()->sent_packet_manager().GetRttStats(), + QuicSentPacketManagerPeer::GetUnackedPacketMap( + QuicConnectionPeer::GetSentPacketManager(endpoint->connection())), + kDefaultInitialCwndPackets, GetQuicFlag(quic_max_congestion_window), + &random_, QuicConnectionPeer::GetStats(endpoint->connection())); + QuicConnectionPeer::SetSendAlgorithm(endpoint->connection(), sender); + endpoint->RecordTrace(); + return sender; + } + + // reno => Reno. !reno => Cubic. + TcpCubicSenderBytes* SetupTcpSender(simulator::QuicEndpoint* endpoint, + bool reno) { + // Ownership of the sender will be overtaken by the endpoint. + TcpCubicSenderBytes* sender = new TcpCubicSenderBytes( + endpoint->connection()->clock(), + endpoint->connection()->sent_packet_manager().GetRttStats(), reno, + kDefaultInitialCwndPackets, GetQuicFlag(quic_max_congestion_window), + QuicConnectionPeer::GetStats(endpoint->connection())); + QuicConnectionPeer::SetSendAlgorithm(endpoint->connection(), sender); + endpoint->RecordTrace(); + return sender; + } + + void SetConnectionOption(SendAlgorithmInterface* sender, QuicTag option) { + QuicConfig config; + QuicTagVector options; + options.push_back(option); + QuicConfigPeer::SetReceivedConnectionOptions(&config, options); + sender->SetFromConfig(config, Perspective::IS_SERVER); + } + + void CreateNetwork(const MultiSenderTopologyParams& params) { + QUIC_LOG(INFO) << "CreateNetwork with parameters: " << params.ToString(); + switch_ = std::make_unique<simulator::Switch>(&simulator_, "Switch", + params.switch_port_count, + params.SwitchQueueCapacity()); + + network_links_.push_back(std::make_unique<simulator::SymmetricLink>( + receiver_multiplexer_.get(), switch_->port(1), + params.test_link.bandwidth, params.test_link.delay)); + for (size_t i = 0; i < MultiSenderTopologyParams::kNumLocalLinks; ++i) { + simulator::SwitchPortNumber port_number = i + 2; + network_links_.push_back(std::make_unique<simulator::SymmetricLink>( + sender_endpoints_[i].get(), switch_->port(port_number), + params.local_links[i].bandwidth, params.local_links[i].delay)); + } + } + + QuicConnection* sender_connection(size_t which) { + return sender_endpoints_[which]->connection(); + } + + const QuicConnectionStats& sender_connection_stats(size_t which) { + return sender_connection(which)->GetStats(); + } + + float sender_loss_rate_in_packets(size_t which) { + return static_cast<float>(sender_connection_stats(which).packets_lost) / + sender_connection_stats(which).packets_sent; + } + + std::vector<std::unique_ptr<simulator::QuicEndpoint>> sender_endpoints_; + std::vector<std::unique_ptr<simulator::QuicEndpoint>> receiver_endpoints_; + std::unique_ptr<simulator::QuicEndpointMultiplexer> receiver_multiplexer_; + Bbr3Sender* sender_0_; + + std::unique_ptr<simulator::Switch> switch_; + std::vector<std::unique_ptr<simulator::SymmetricLink>> network_links_; +}; + +TEST_F(Bbr2MultiSenderTest, Bbr2VsBbr2) { + SetupBbr3Sender(sender_endpoints_[1].get()); + + MultiSenderTopologyParams params; + CreateNetwork(params); + + const QuicByteCount transfer_size = 10 * 1024 * 1024; + const QuicTime::Delta transfer_time = + params.BottleneckBandwidth().TransferTime(transfer_size); + QUIC_LOG(INFO) << "Single flow transfer time: " << transfer_time; + + // Transfer 10% of data in first transfer. + sender_endpoints_[0]->AddBytesToTransfer(transfer_size); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() >= 0.1 * transfer_size; + }, + transfer_time); + ASSERT_TRUE(simulator_result); + + // Start the second transfer and wait until both finish. + sender_endpoints_[1]->AddBytesToTransfer(transfer_size); + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() == transfer_size && + receiver_endpoints_[1]->bytes_received() == transfer_size; + }, + 3 * transfer_time); + ASSERT_TRUE(simulator_result); +} + +TEST_F(Bbr2MultiSenderTest, QUIC_SLOW_TEST(MultipleBbr2s)) { + const int kTotalNumSenders = 6; + for (int i = 1; i < kTotalNumSenders; ++i) { + SetupBbr3Sender(sender_endpoints_[i].get()); + } + + MultiSenderTopologyParams params; + CreateNetwork(params); + + const QuicByteCount transfer_size = 10 * 1024 * 1024; + const QuicTime::Delta transfer_time = + params.BottleneckBandwidth().TransferTime(transfer_size); + QUIC_LOG(INFO) << "Single flow transfer time: " << transfer_time + << ". Now: " << SimulatedNow(); + + // Start all transfers. + for (int i = 0; i < kTotalNumSenders; ++i) { + if (i != 0) { + const QuicTime sender_start_time = + SimulatedNow() + QuicTime::Delta::FromSeconds(2); + bool simulator_result = simulator_.RunUntilOrTimeout( + [&]() { return SimulatedNow() >= sender_start_time; }, transfer_time); + ASSERT_TRUE(simulator_result); + } + + sender_endpoints_[i]->AddBytesToTransfer(transfer_size); + } + + // Wait for all transfers to finish. + QuicTime::Delta expected_total_transfer_time_upper_bound = + QuicTime::Delta::FromMicroseconds(kTotalNumSenders * + transfer_time.ToMicroseconds() * 1.1); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + for (int i = 0; i < kTotalNumSenders; ++i) { + if (receiver_endpoints_[i]->bytes_received() < transfer_size) { + return false; + } + } + return true; + }, + expected_total_transfer_time_upper_bound); + ASSERT_TRUE(simulator_result) + << "Expected upper bound: " << expected_total_transfer_time_upper_bound; +} + +/* The first 11 packets are sent at the same time, but the duration between the + * acks of the 1st and the 11th packet is 49 milliseconds, causing very low bw + * samples. This happens for both large and small buffers. + */ +/* +TEST_F(Bbr2MultiSenderTest, Bbr2VsBbr2LargeRttTinyBuffer) { + SetupBbr3Sender(sender_endpoints_[1].get()); + + MultiSenderTopologyParams params; + params.switch_queue_capacity_in_bdp = 0.05; + params.test_link.delay = QuicTime::Delta::FromSeconds(1); + CreateNetwork(params); + + const QuicByteCount transfer_size = 10 * 1024 * 1024; + const QuicTime::Delta transfer_time = + params.BottleneckBandwidth().TransferTime(transfer_size); + QUIC_LOG(INFO) << "Single flow transfer time: " << transfer_time; + + // Transfer 10% of data in first transfer. + sender_endpoints_[0]->AddBytesToTransfer(transfer_size); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() >= 0.1 * transfer_size; + }, + transfer_time); + ASSERT_TRUE(simulator_result); + + // Start the second transfer and wait until both finish. + sender_endpoints_[1]->AddBytesToTransfer(transfer_size); + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() == transfer_size && + receiver_endpoints_[1]->bytes_received() == transfer_size; + }, + 3 * transfer_time); + ASSERT_TRUE(simulator_result); +} +*/ + +TEST_F(Bbr2MultiSenderTest, Bbr2VsBbr1) { + SetupBbrSender(sender_endpoints_[1].get()); + + MultiSenderTopologyParams params; + CreateNetwork(params); + + const QuicByteCount transfer_size = 10 * 1024 * 1024; + const QuicTime::Delta transfer_time = + params.BottleneckBandwidth().TransferTime(transfer_size); + QUIC_LOG(INFO) << "Single flow transfer time: " << transfer_time; + + // Transfer 10% of data in first transfer. + sender_endpoints_[0]->AddBytesToTransfer(transfer_size); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() >= 0.1 * transfer_size; + }, + transfer_time); + ASSERT_TRUE(simulator_result); + + // Start the second transfer and wait until both finish. + sender_endpoints_[1]->AddBytesToTransfer(transfer_size); + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() == transfer_size && + receiver_endpoints_[1]->bytes_received() == transfer_size; + }, + 3 * transfer_time); + ASSERT_TRUE(simulator_result); +} + +TEST_F(Bbr2MultiSenderTest, QUIC_SLOW_TEST(Bbr2VsReno)) { + SetupTcpSender(sender_endpoints_[1].get(), /*reno=*/true); + + MultiSenderTopologyParams params; + CreateNetwork(params); + + const QuicByteCount transfer_size = 10 * 1024 * 1024; + const QuicTime::Delta transfer_time = + params.BottleneckBandwidth().TransferTime(transfer_size); + QUIC_LOG(INFO) << "Single flow transfer time: " << transfer_time; + + // Transfer 10% of data in first transfer. + sender_endpoints_[0]->AddBytesToTransfer(transfer_size); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() >= 0.1 * transfer_size; + }, + transfer_time); + ASSERT_TRUE(simulator_result); + + // Start the second transfer and wait until both finish. + sender_endpoints_[1]->AddBytesToTransfer(transfer_size); + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() == transfer_size && + receiver_endpoints_[1]->bytes_received() == transfer_size; + }, + 3 * transfer_time); + ASSERT_TRUE(simulator_result); +} + +TEST_F(Bbr2MultiSenderTest, QUIC_SLOW_TEST(Bbr2VsRenoB2RC)) { + SetConnectionOption(sender_0_, kB2RC); + SetupTcpSender(sender_endpoints_[1].get(), /*reno=*/true); + + MultiSenderTopologyParams params; + CreateNetwork(params); + + const QuicByteCount transfer_size = 10 * 1024 * 1024; + const QuicTime::Delta transfer_time = + params.BottleneckBandwidth().TransferTime(transfer_size); + QUIC_LOG(INFO) << "Single flow transfer time: " << transfer_time; + + // Transfer 10% of data in first transfer. + sender_endpoints_[0]->AddBytesToTransfer(transfer_size); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() >= 0.1 * transfer_size; + }, + transfer_time); + ASSERT_TRUE(simulator_result); + + // Start the second transfer and wait until both finish. + sender_endpoints_[1]->AddBytesToTransfer(transfer_size); + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() == transfer_size && + receiver_endpoints_[1]->bytes_received() == transfer_size; + }, + 3 * transfer_time); + ASSERT_TRUE(simulator_result); +} + +TEST_F(Bbr2MultiSenderTest, QUIC_SLOW_TEST(Bbr2VsCubic)) { + SetupTcpSender(sender_endpoints_[1].get(), /*reno=*/false); + + MultiSenderTopologyParams params; + CreateNetwork(params); + + const QuicByteCount transfer_size = 50 * 1024 * 1024; + const QuicTime::Delta transfer_time = + params.BottleneckBandwidth().TransferTime(transfer_size); + QUIC_LOG(INFO) << "Single flow transfer time: " << transfer_time; + + // Transfer 10% of data in first transfer. + sender_endpoints_[0]->AddBytesToTransfer(transfer_size); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() >= 0.1 * transfer_size; + }, + transfer_time); + ASSERT_TRUE(simulator_result); + + // Start the second transfer and wait until both finish. + sender_endpoints_[1]->AddBytesToTransfer(transfer_size); + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return receiver_endpoints_[0]->bytes_received() == transfer_size && + receiver_endpoints_[1]->bytes_received() == transfer_size; + }, + 3 * transfer_time); + ASSERT_TRUE(simulator_result); +} + +TEST(MinRttFilter, BadRttSample) { + auto time_in_seconds = [](int64_t seconds) { + return QuicTime::Zero() + QuicTime::Delta::FromSeconds(seconds); + }; + + MinRttFilter filter(QuicTime::Delta::FromMilliseconds(10), + time_in_seconds(100)); + ASSERT_EQ(filter.Get(), QuicTime::Delta::FromMilliseconds(10)); + + filter.Update(QuicTime::Delta::FromMilliseconds(-1), time_in_seconds(150)); + + EXPECT_EQ(filter.Get(), QuicTime::Delta::FromMilliseconds(10)); + EXPECT_EQ(filter.GetTimestamp(), time_in_seconds(100)); + + filter.ForceUpdate(QuicTime::Delta::FromMilliseconds(-2), + time_in_seconds(200)); + + EXPECT_EQ(filter.Get(), QuicTime::Delta::FromMilliseconds(10)); + EXPECT_EQ(filter.GetTimestamp(), time_in_seconds(100)); +} + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/core/congestion_control/bbr_sender.h b/quiche/quic/core/congestion_control/bbr_sender.h index e8b819b..eb887eb 100644 --- a/quiche/quic/core/congestion_control/bbr_sender.h +++ b/quiche/quic/core/congestion_control/bbr_sender.h
@@ -1,4 +1,4 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. +// Copyright 2026 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -183,6 +183,7 @@ private: // For switching send algorithm mid connection. friend class Bbr2Sender; + friend class Bbr3Sender; using MaxBandwidthFilter = WindowedFilter<QuicBandwidth, MaxFilter<QuicBandwidth>,