Send bandwidth estimate when the server network idles for half of idle_network_timeout for IETF QUIC guarded by connection option BWID.

Guarded with the same flag and connection option:
1. No longer sending bandwidth estimates when only bandwidth resumption is enabled.
2. When sending the bandwidth after idle long enough, the bandwidth from SendAlgorithm is used.
3. When the NEW_TOKEN frame is sent after the handshake, bandwidth from the previous session is sent along if there is no better measurement.

Protected by FLAGS_quic_reloadable_flag_quic_enable_sending_bandwidth_estimate_when_network_idle.

PiperOrigin-RevId: 443223110
diff --git a/quiche/quic/core/crypto/crypto_protocol.h b/quiche/quic/core/crypto/crypto_protocol.h
index 907857b..aef3ff1 100644
--- a/quiche/quic/core/crypto/crypto_protocol.h
+++ b/quiche/quic/core/crypto/crypto_protocol.h
@@ -300,6 +300,7 @@
 // Enable bandwidth resumption experiment.
 const QuicTag kBWRE = TAG('B', 'W', 'R', 'E');  // Bandwidth resumption.
 const QuicTag kBWMX = TAG('B', 'W', 'M', 'X');  // Max bandwidth resumption.
+const QuicTag kBWID = TAG('B', 'W', 'I', 'D');  // Send bandwidth when idle.
 const QuicTag kBWRS = TAG('B', 'W', 'R', 'S');  // Server bandwidth resumption.
 const QuicTag kBWS2 = TAG('B', 'W', 'S', '2');  // Server bw resumption v2.
 const QuicTag kBWS3 = TAG('B', 'W', 'S', '3');  // QUIC Initial CWND - Control.
diff --git a/quiche/quic/core/http/quic_server_session_base.cc b/quiche/quic/core/http/quic_server_session_base.cc
index fd38825..ade86ae 100644
--- a/quiche/quic/core/http/quic_server_session_base.cc
+++ b/quiche/quic/core/http/quic_server_session_base.cc
@@ -10,12 +10,14 @@
 #include "quiche/quic/core/quic_connection.h"
 #include "quiche/quic/core/quic_stream.h"
 #include "quiche/quic/core/quic_tag.h"
+#include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.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"
 
 namespace quic {
 
@@ -96,6 +98,12 @@
         << "Failed to disable resumption";
   }
 
+  enable_sending_bandwidth_estimate_when_network_idle_ =
+      GetQuicRestartFlag(
+          quic_enable_sending_bandwidth_estimate_when_network_idle) &&
+      version().HasIetfQuicFrames() &&
+      ContainsQuicTag(config()->ReceivedConnectionOptions(), kBWID);
+
   // Enable bandwidth resumption if peer sent correct connection options.
   const bool last_bandwidth_resumption =
       ContainsQuicTag(config()->ReceivedConnectionOptions(), kBWRE);
@@ -138,7 +146,31 @@
   }
 }
 
+void QuicServerSessionBase::OnBandwidthUpdateTimeout() {
+  if (!enable_sending_bandwidth_estimate_when_network_idle_) {
+    return;
+  }
+  QUIC_DVLOG(1) << "Bandwidth update timed out.";
+  const SendAlgorithmInterface* send_algorithm =
+      connection()->sent_packet_manager().GetSendAlgorithm();
+  if (send_algorithm != nullptr &&
+      send_algorithm->HasGoodBandwidthEstimateForResumption()) {
+    const bool success = MaybeSendAddressToken();
+    QUIC_BUG_IF(QUIC_BUG_25522, !success) << "Failed to send address token.";
+    QUIC_RESTART_FLAG_COUNT_N(
+        quic_enable_sending_bandwidth_estimate_when_network_idle, 2, 3);
+  }
+}
+
 void QuicServerSessionBase::OnCongestionWindowChange(QuicTime now) {
+  // Sending bandwidth is no longer conditioned on if session does bandwidth
+  // resumption.
+  if (GetQuicRestartFlag(
+          quic_enable_sending_bandwidth_estimate_when_network_idle)) {
+    QUIC_RESTART_FLAG_COUNT_N(
+        quic_enable_sending_bandwidth_estimate_when_network_idle, 3, 3);
+    return;
+  }
   if (!bandwidth_resumption_enabled_) {
     return;
   }
@@ -345,30 +377,55 @@
         sent_packet_manager.GetRttStats()->min_rtt().ToMilliseconds());
   }
 
-  // Populate bandwidth estimates if any.
-  if (bandwidth_recorder != nullptr && bandwidth_recorder->HasEstimate()) {
-    const int32_t bw_estimate_bytes_per_second =
-        BandwidthToCachedParameterBytesPerSecond(
-            bandwidth_recorder->BandwidthEstimate());
-    const int32_t max_bw_estimate_bytes_per_second =
-        BandwidthToCachedParameterBytesPerSecond(
-            bandwidth_recorder->MaxBandwidthEstimate());
-    QUIC_BUG_IF(quic_bug_12513_1, max_bw_estimate_bytes_per_second < 0)
-        << max_bw_estimate_bytes_per_second;
-    QUIC_BUG_IF(quic_bug_10393_1, bw_estimate_bytes_per_second < 0)
-        << bw_estimate_bytes_per_second;
+  if (enable_sending_bandwidth_estimate_when_network_idle_) {
+    const SendAlgorithmInterface* send_algorithm =
+        sent_packet_manager.GetSendAlgorithm();
+    if (send_algorithm != nullptr &&
+        send_algorithm->HasGoodBandwidthEstimateForResumption()) {
+      cached_network_params.set_bandwidth_estimate_bytes_per_second(
+          BandwidthToCachedParameterBytesPerSecond(
+              send_algorithm->BandwidthEstimate()));
+      QUIC_CODE_COUNT(quic_send_measured_bandwidth_in_token);
+    } else {
+      const quic::CachedNetworkParameters* previous_cached_network_params =
+          crypto_stream()->PreviousCachedNetworkParams();
+      if (previous_cached_network_params != nullptr &&
+          previous_cached_network_params
+                  ->bandwidth_estimate_bytes_per_second() > 0) {
+        cached_network_params.set_bandwidth_estimate_bytes_per_second(
+            previous_cached_network_params
+                ->bandwidth_estimate_bytes_per_second());
+        QUIC_CODE_COUNT(quic_send_previous_bandwidth_in_token);
+      } else {
+        QUIC_CODE_COUNT(quic_not_send_bandwidth_in_token);
+      }
+    }
+  } else {
+    // Populate bandwidth estimates if any.
+    if (bandwidth_recorder != nullptr && bandwidth_recorder->HasEstimate()) {
+      const int32_t bw_estimate_bytes_per_second =
+          BandwidthToCachedParameterBytesPerSecond(
+              bandwidth_recorder->BandwidthEstimate());
+      const int32_t max_bw_estimate_bytes_per_second =
+          BandwidthToCachedParameterBytesPerSecond(
+              bandwidth_recorder->MaxBandwidthEstimate());
+      QUIC_BUG_IF(quic_bug_12513_1, max_bw_estimate_bytes_per_second < 0)
+          << max_bw_estimate_bytes_per_second;
+      QUIC_BUG_IF(quic_bug_10393_1, bw_estimate_bytes_per_second < 0)
+          << bw_estimate_bytes_per_second;
 
-    cached_network_params.set_bandwidth_estimate_bytes_per_second(
-        bw_estimate_bytes_per_second);
-    cached_network_params.set_max_bandwidth_estimate_bytes_per_second(
-        max_bw_estimate_bytes_per_second);
-    cached_network_params.set_max_bandwidth_timestamp_seconds(
-        bandwidth_recorder->MaxBandwidthTimestamp());
+      cached_network_params.set_bandwidth_estimate_bytes_per_second(
+          bw_estimate_bytes_per_second);
+      cached_network_params.set_max_bandwidth_estimate_bytes_per_second(
+          max_bw_estimate_bytes_per_second);
+      cached_network_params.set_max_bandwidth_timestamp_seconds(
+          bandwidth_recorder->MaxBandwidthTimestamp());
 
-    cached_network_params.set_previous_connection_state(
-        bandwidth_recorder->EstimateRecordedDuringSlowStart()
-            ? CachedNetworkParameters::SLOW_START
-            : CachedNetworkParameters::CONGESTION_AVOIDANCE);
+      cached_network_params.set_previous_connection_state(
+          bandwidth_recorder->EstimateRecordedDuringSlowStart()
+              ? CachedNetworkParameters::SLOW_START
+              : CachedNetworkParameters::CONGESTION_AVOIDANCE);
+    }
   }
 
   if (!serving_region_.empty()) {
diff --git a/quiche/quic/core/http/quic_server_session_base.h b/quiche/quic/core/http/quic_server_session_base.h
index d30b9dd..7fbbf8b 100644
--- a/quiche/quic/core/http/quic_server_session_base.h
+++ b/quiche/quic/core/http/quic_server_session_base.h
@@ -48,6 +48,9 @@
   void OnConnectionClosed(const QuicConnectionCloseFrame& frame,
                           ConnectionCloseSource source) override;
 
+  // Override to send bandwidth estimate.
+  void OnBandwidthUpdateTimeout() override;
+
   // Sends a server config update to the client, containing new bandwidth
   // estimate.
   void OnCongestionWindowChange(QuicTime now) override;
@@ -129,8 +132,9 @@
   // The most recent bandwidth estimate sent to the client.
   QuicBandwidth bandwidth_estimate_sent_to_client_;
 
-  // Text describing server location. Sent to the client as part of the bandwith
-  // estimate in the source-address token. Optional, can be left empty.
+  // Text describing server location. Sent to the client as part of the
+  // bandwidth estimate in the source-address token. Optional, can be left
+  // empty.
   std::string serving_region_;
 
   // Time at which we send the last SCUP to the client.
@@ -144,6 +148,8 @@
   // should go away once we fix http://b//27897982
   int32_t BandwidthToCachedParameterBytesPerSecond(
       const QuicBandwidth& bandwidth) const;
+
+  bool enable_sending_bandwidth_estimate_when_network_idle_ = false;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/http/quic_server_session_base_test.cc b/quiche/quic/core/http/quic_server_session_base_test.cc
index 56aa06a..df099a6 100644
--- a/quiche/quic/core/http/quic_server_session_base_test.cc
+++ b/quiche/quic/core/http/quic_server_session_base_test.cc
@@ -510,6 +510,7 @@
   // Client has sent kBWRE connection option to trigger bandwidth resumption.
   QuicTagVector copt;
   copt.push_back(kBWRE);
+  copt.push_back(kBWID);
   QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   session_->OnConfigNegotiated();
@@ -606,35 +607,40 @@
   sent_packet_manager->OnPacketSent(&packet, now, NOT_RETRANSMISSION,
                                     HAS_RETRANSMITTABLE_DATA, true);
 
-  // Verify that the proto has exactly the values we expect.
-  CachedNetworkParameters expected_network_params;
-  expected_network_params.set_bandwidth_estimate_bytes_per_second(
-      bandwidth_recorder.BandwidthEstimate().ToBytesPerSecond());
-  expected_network_params.set_max_bandwidth_estimate_bytes_per_second(
-      bandwidth_recorder.MaxBandwidthEstimate().ToBytesPerSecond());
-  expected_network_params.set_max_bandwidth_timestamp_seconds(
-      bandwidth_recorder.MaxBandwidthTimestamp());
-  expected_network_params.set_min_rtt_ms(session_->connection()
-                                             ->sent_packet_manager()
-                                             .GetRttStats()
-                                             ->min_rtt()
-                                             .ToMilliseconds());
-  expected_network_params.set_previous_connection_state(
-      CachedNetworkParameters::CONGESTION_AVOIDANCE);
-  expected_network_params.set_timestamp(
-      session_->connection()->clock()->WallNow().ToUNIXSeconds());
-  expected_network_params.set_serving_region(serving_region);
-
-  if (quic_crypto_stream) {
-    EXPECT_CALL(*quic_crypto_stream,
-                SendServerConfigUpdate(EqualsProto(expected_network_params)))
-        .Times(1);
+  if (GetQuicRestartFlag(
+          quic_enable_sending_bandwidth_estimate_when_network_idle)) {
+    EXPECT_CALL(*connection_, OnSendConnectionState(_)).Times(0);
   } else {
-    EXPECT_CALL(*tls_server_stream,
-                GetAddressToken(EqualsProto(expected_network_params)))
-        .WillOnce(testing::Return("Test address token"));
+    // Verify that the proto has exactly the values we expect.
+    CachedNetworkParameters expected_network_params;
+    expected_network_params.set_bandwidth_estimate_bytes_per_second(
+        bandwidth_recorder.BandwidthEstimate().ToBytesPerSecond());
+    expected_network_params.set_max_bandwidth_estimate_bytes_per_second(
+        bandwidth_recorder.MaxBandwidthEstimate().ToBytesPerSecond());
+    expected_network_params.set_max_bandwidth_timestamp_seconds(
+        bandwidth_recorder.MaxBandwidthTimestamp());
+    expected_network_params.set_min_rtt_ms(session_->connection()
+                                               ->sent_packet_manager()
+                                               .GetRttStats()
+                                               ->min_rtt()
+                                               .ToMilliseconds());
+    expected_network_params.set_previous_connection_state(
+        CachedNetworkParameters::CONGESTION_AVOIDANCE);
+    expected_network_params.set_timestamp(
+        session_->connection()->clock()->WallNow().ToUNIXSeconds());
+    expected_network_params.set_serving_region(serving_region);
+
+    if (quic_crypto_stream) {
+      EXPECT_CALL(*quic_crypto_stream,
+                  SendServerConfigUpdate(EqualsProto(expected_network_params)))
+          .Times(1);
+    } else {
+      EXPECT_CALL(*tls_server_stream,
+                  GetAddressToken(EqualsProto(expected_network_params)))
+          .WillOnce(testing::Return("Test address token"));
+    }
+    EXPECT_CALL(*connection_, OnSendConnectionState(_)).Times(1);
   }
-  EXPECT_CALL(*connection_, OnSendConnectionState(_)).Times(1);
   session_->OnCongestionWindowChange(now);
 }
 
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 51dfd24..db2597b 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -6259,6 +6259,10 @@
                   idle_timeout_connection_close_behavior_);
 }
 
+void QuicConnection::OnBandwidthUpdateTimeout() {
+  visitor_->OnBandwidthUpdateTimeout();
+}
+
 void QuicConnection::OnKeepAliveTimeout() {
   QUICHE_DCHECK(use_ping_manager_);
   if (retransmission_alarm_->IsSet() ||
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 5a4e655..b73f5c4 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -239,6 +239,9 @@
 
   // Whether the server address is known to the connection.
   virtual bool IsKnownServerAddress(const QuicSocketAddress& address) const = 0;
+
+  // When bandwidth update alarms.
+  virtual void OnBandwidthUpdateTimeout() = 0;
 };
 
 // Interface which gets callbacks from the QuicConnection at interesting
@@ -711,6 +714,7 @@
   // QuicIdleNetworkDetector::Delegate
   void OnHandshakeTimeout() override;
   void OnIdleNetworkDetected() override;
+  void OnBandwidthUpdateTimeout() override;
 
   // QuicPingManager::Delegate
   void OnKeepAliveTimeout() override;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 34c7ba0..932addc 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -5860,6 +5860,7 @@
         &config, connection_.connection_id());
   }
   connection_.SetFromConfig(config);
+  QuicConnectionPeer::DisableBandwidthUpdate(&connection_);
 
   const QuicTime::Delta default_idle_timeout =
       QuicTime::Delta::FromSeconds(kMaximumIdleTimeoutSecs - 1);
@@ -5935,6 +5936,7 @@
   EXPECT_THAT(error, IsQuicNoError());
 
   connection_.SetFromConfig(config);
+  QuicConnectionPeer::DisableBandwidthUpdate(&connection_);
 
   const QuicTime::Delta default_idle_timeout =
       QuicTime::Delta::FromSeconds(kMaximumIdleTimeoutSecs - 1);
@@ -5997,6 +5999,7 @@
         &config, connection_.connection_id());
   }
   connection_.SetFromConfig(config);
+  QuicConnectionPeer::DisableBandwidthUpdate(&connection_);
 
   const QuicTime::Delta default_idle_timeout =
       QuicTime::Delta::FromSeconds(kMaximumIdleTimeoutSecs - 1);
@@ -6092,6 +6095,7 @@
   connection_.SetNetworkTimeouts(
       QuicTime::Delta::Infinite(),
       initial_idle_timeout + QuicTime::Delta::FromSeconds(1));
+  QuicConnectionPeer::DisableBandwidthUpdate(&connection_);
   const QuicTime::Delta five_ms = QuicTime::Delta::FromMilliseconds(5);
   QuicTime default_timeout = clock_.ApproximateNow() + initial_idle_timeout;
 
@@ -11680,14 +11684,14 @@
   ASSERT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
   EXPECT_FALSE(connection_.MaybeTestLiveness());
 
-  QuicTime deadline = connection_.GetTimeoutAlarm()->deadline();
+  QuicTime deadline = QuicConnectionPeer::GetIdleNetworkDeadline(&connection_);
   QuicTime::Delta timeout = deadline - clock_.ApproximateNow();
   // Advance time to near the idle timeout.
   clock_.AdvanceTime(timeout - QuicTime::Delta::FromMilliseconds(1));
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   EXPECT_TRUE(connection_.MaybeTestLiveness());
   // Verify idle deadline does not change.
-  EXPECT_EQ(deadline, connection_.GetTimeoutAlarm()->deadline());
+  EXPECT_EQ(deadline, QuicConnectionPeer::GetIdleNetworkDeadline(&connection_));
 }
 
 TEST_P(QuicConnectionTest, SilentIdleTimeout) {
@@ -11717,6 +11721,11 @@
   EXPECT_CALL(visitor_,
               OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF));
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
+  if (!QuicConnectionPeer::GetBandwidthUpdateTimeout(&connection_)
+           .IsInfinite()) {
+    // Fires the bandwidth update.
+    connection_.GetTimeoutAlarm()->Fire();
+  }
   connection_.GetTimeoutAlarm()->Fire();
   // Verify the connection close packets get serialized and added to
   // termination packets list.
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 179eda3..4408626 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -83,6 +83,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_require_handshake_confirmation, false)
 // If true, server proactively retires client issued connection ID on reverse path validation failure. 
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_retire_cid_on_reverse_path_validation_failure, true)
+// If true, server sends bandwidth eastimate when network is idle for a while.
+QUIC_FLAG(FLAGS_quic_restart_flag_quic_enable_sending_bandwidth_estimate_when_network_idle, false)
 // If true, servers drop received packets with changed server address.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_drop_packets_with_changed_server_address, true)
 // If true, set burst token to 2 in cwnd bootstrapping experiment.
diff --git a/quiche/quic/core/quic_idle_network_detector.cc b/quiche/quic/core/quic_idle_network_detector.cc
index edb5483..6371596 100644
--- a/quiche/quic/core/quic_idle_network_detector.cc
+++ b/quiche/quic/core/quic_idle_network_detector.cc
@@ -5,7 +5,10 @@
 #include "quiche/quic/core/quic_idle_network_detector.h"
 
 #include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/platform/api/quic_flag_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/common/platform/api/quiche_logging.h"
 
 namespace quic {
 
@@ -36,10 +39,18 @@
       time_of_last_received_packet_(now),
       time_of_first_packet_sent_after_receiving_(QuicTime::Zero()),
       idle_network_timeout_(QuicTime::Delta::Infinite()),
+      bandwidth_update_timeout_(QuicTime::Delta::Infinite()),
       alarm_(alarm_factory->CreateAlarm(
           arena->New<AlarmDelegate>(this, context), arena)) {}
 
 void QuicIdleNetworkDetector::OnAlarm() {
+  if (!bandwidth_update_timeout_.IsInfinite()) {
+    QUICHE_DCHECK(handshake_timeout_.IsInfinite());
+    bandwidth_update_timeout_ = QuicTime::Delta::Infinite();
+    SetAlarm();
+    delegate_->OnBandwidthUpdateTimeout();
+    return;
+  }
   if (handshake_timeout_.IsInfinite()) {
     delegate_->OnIdleNetworkDetected();
     return;
@@ -61,6 +72,14 @@
   handshake_timeout_ = handshake_timeout;
   idle_network_timeout_ = idle_network_timeout;
 
+  if (GetQuicRestartFlag(
+          quic_enable_sending_bandwidth_estimate_when_network_idle) &&
+      handshake_timeout_.IsInfinite()) {
+    QUIC_RESTART_FLAG_COUNT_N(
+        quic_enable_sending_bandwidth_estimate_when_network_idle, 1, 3);
+    bandwidth_update_timeout_ = idle_network_timeout_ * 0.5;
+  }
+
   SetAlarm();
 }
 
@@ -68,6 +87,7 @@
   alarm_->PermanentCancel();
   handshake_timeout_ = QuicTime::Delta::Infinite();
   idle_network_timeout_ = QuicTime::Delta::Infinite();
+  handshake_timeout_ = QuicTime::Delta::Infinite();
   stopped_ = true;
 }
 
@@ -115,6 +135,9 @@
       new_deadline = idle_network_deadline;
     }
   }
+  if (!bandwidth_update_timeout_.IsInfinite()) {
+    new_deadline = std::min(new_deadline, GetBandwidthUpdateDeadline());
+  }
   alarm_->Update(new_deadline, kAlarmGranularity);
 }
 
@@ -141,4 +164,9 @@
   return last_network_activity_time() + idle_network_timeout_;
 }
 
+QuicTime QuicIdleNetworkDetector::GetBandwidthUpdateDeadline() const {
+  QUICHE_DCHECK(!bandwidth_update_timeout_.IsInfinite());
+  return last_network_activity_time() + bandwidth_update_timeout_;
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/quic_idle_network_detector.h b/quiche/quic/core/quic_idle_network_detector.h
index 0b5a20e..ca43148 100644
--- a/quiche/quic/core/quic_idle_network_detector.h
+++ b/quiche/quic/core/quic_idle_network_detector.h
@@ -34,6 +34,9 @@
 
     // Called when idle network has been detected.
     virtual void OnIdleNetworkDetected() = 0;
+
+    // Called when bandwidth update alarms.
+    virtual void OnBandwidthUpdateTimeout() = 0;
   };
 
   QuicIdleNetworkDetector(Delegate* delegate, QuicTime now,
@@ -73,6 +76,10 @@
 
   QuicTime::Delta idle_network_timeout() const { return idle_network_timeout_; }
 
+  QuicTime::Delta bandwidth_update_timeout() const {
+    return bandwidth_update_timeout_;
+  }
+
   QuicTime GetIdleNetworkDeadline() const;
 
  private:
@@ -83,13 +90,15 @@
 
   void MaybeSetAlarmOnSentPacket(QuicTime::Delta pto_delay);
 
+  QuicTime GetBandwidthUpdateDeadline() const;
+
   Delegate* delegate_;  // Not owned.
 
   // Start time of the detector, handshake deadline = start_time_ +
   // handshake_timeout_.
   const QuicTime start_time_;
 
-  // Handshake timeout. Infinit means handshake has completed.
+  // Handshake timeout. Infinite means handshake has completed.
   QuicTime::Delta handshake_timeout_;
 
   // Time that last packet is received for this connection. Initialized to
@@ -102,9 +111,12 @@
   // Initialized to 0.
   QuicTime time_of_first_packet_sent_after_receiving_;
 
-  // Idle network timeout. Infinit means no idle network timeout.
+  // Idle network timeout. Infinite means no idle network timeout.
   QuicTime::Delta idle_network_timeout_;
 
+  // Bandwidth update timeout. Infinite means no bandwidth update timeout.
+  QuicTime::Delta bandwidth_update_timeout_;
+
   QuicArenaScopedPtr<QuicAlarm> alarm_;
 
   bool shorter_idle_timeout_on_sent_packet_ = false;
diff --git a/quiche/quic/core/quic_idle_network_detector_test.cc b/quiche/quic/core/quic_idle_network_detector_test.cc
index 31aa8b4..b838cbd 100644
--- a/quiche/quic/core/quic_idle_network_detector_test.cc
+++ b/quiche/quic/core/quic_idle_network_detector_test.cc
@@ -5,7 +5,9 @@
 #include "quiche/quic/core/quic_idle_network_detector.h"
 
 #include "quiche/quic/core/quic_one_block_arena.h"
+#include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/platform/api/quic_expect_bug.h"
+#include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
 
@@ -25,6 +27,7 @@
  public:
   MOCK_METHOD(void, OnHandshakeTimeout, (), (override));
   MOCK_METHOD(void, OnIdleNetworkDetected, (), (override));
+  MOCK_METHOD(void, OnBandwidthUpdateTimeout, (), (override));
 };
 
 class QuicIdleNetworkDetectorTest : public QuicTest {
@@ -90,6 +93,8 @@
       /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30),
       /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20));
   EXPECT_TRUE(alarm_->IsSet());
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(20),
+            alarm_->deadline());
 
   // Handshake completes in 200ms.
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
@@ -97,11 +102,30 @@
   detector_->SetTimeouts(
       /*handshake_timeout=*/QuicTime::Delta::Infinite(),
       /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(600));
-  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(600),
+  if (!GetQuicRestartFlag(
+          quic_enable_sending_bandwidth_estimate_when_network_idle)) {
+    EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(600),
+              alarm_->deadline());
+
+    // No network activity for 600s.
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(600));
+    EXPECT_CALL(delegate_, OnIdleNetworkDetected());
+    alarm_->Fire();
+    return;
+  }
+
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(300),
+            alarm_->deadline());
+
+  // No network activity for 300s.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(300));
+  EXPECT_CALL(delegate_, OnBandwidthUpdateTimeout());
+  alarm_->Fire();
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(300),
             alarm_->deadline());
 
   // No network activity for 600s.
-  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(600));
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(300));
   EXPECT_CALL(delegate_, OnIdleNetworkDetected());
   alarm_->Fire();
 }
@@ -115,11 +139,16 @@
   EXPECT_TRUE(alarm_->IsSet());
 
   // Handshake completes in 200ms.
+  const bool enable_sending_bandwidth_estimate_when_network_idle =
+      GetQuicRestartFlag(
+          quic_enable_sending_bandwidth_estimate_when_network_idle);
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
   detector_->OnPacketReceived(clock_.Now());
   detector_->SetTimeouts(
       /*handshake_timeout=*/QuicTime::Delta::Infinite(),
-      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(600));
+      enable_sending_bandwidth_estimate_when_network_idle
+          ? QuicTime::Delta::FromSeconds(1200)
+          : QuicTime::Delta::FromSeconds(600));
   EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(600),
             alarm_->deadline());
 
@@ -133,22 +162,46 @@
   // Sent another packet after 200ms
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
   detector_->OnPacketSent(clock_.Now(), QuicTime::Delta::Zero());
-  // Verify idle network deadline does not extend.
+  // Verify network deadline does not extend.
   EXPECT_EQ(packet_sent_time + QuicTime::Delta::FromSeconds(600),
             alarm_->deadline());
 
-  // No network activity for 600s.
+  if (!enable_sending_bandwidth_estimate_when_network_idle) {
+    // No network activity for 600s.
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(600) -
+                       QuicTime::Delta::FromMilliseconds(200));
+    EXPECT_CALL(delegate_, OnIdleNetworkDetected());
+    alarm_->Fire();
+    return;
+  }
+
+  // Bandwidth update times out after no network activity for 600s.
   clock_.AdvanceTime(QuicTime::Delta::FromSeconds(600) -
                      QuicTime::Delta::FromMilliseconds(200));
+  EXPECT_CALL(delegate_, OnBandwidthUpdateTimeout());
+  alarm_->Fire();
+  EXPECT_TRUE(alarm_->IsSet());
+  EXPECT_EQ(packet_sent_time + QuicTime::Delta::FromSeconds(1200),
+            alarm_->deadline());
+
+  // Network idle time out after no network activity for 1200s.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1200) -
+                     QuicTime::Delta::FromMilliseconds(600));
   EXPECT_CALL(delegate_, OnIdleNetworkDetected());
   alarm_->Fire();
 }
 
 TEST_F(QuicIdleNetworkDetectorTest, ShorterIdleTimeoutOnSentPacket) {
   detector_->enable_shorter_idle_timeout_on_sent_packet();
+  QuicTime::Delta idle_network_timeout = QuicTime::Delta::Zero();
+  if (GetQuicRestartFlag(
+          quic_enable_sending_bandwidth_estimate_when_network_idle)) {
+    idle_network_timeout = QuicTime::Delta::FromSeconds(60);
+  } else {
+    idle_network_timeout = QuicTime::Delta::FromSeconds(30);
+  }
   detector_->SetTimeouts(
-      /*handshake_timeout=*/QuicTime::Delta::Infinite(),
-      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(30));
+      /*handshake_timeout=*/QuicTime::Delta::Infinite(), idle_network_timeout);
   EXPECT_TRUE(alarm_->IsSet());
   const QuicTime deadline = alarm_->deadline();
   EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(30), deadline);
@@ -175,7 +228,7 @@
   EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(30),
             alarm_->deadline());
 
-  // Send a packet near timeout..
+  // Send a packet near timeout.
   clock_.AdvanceTime(QuicTime::Delta::FromSeconds(29));
   detector_->OnPacketSent(clock_.Now(), QuicTime::Delta::FromSeconds(2));
   EXPECT_TRUE(alarm_->IsSet());
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h
index 3051cf6..f43ef95 100644
--- a/quiche/quic/core/quic_session.h
+++ b/quiche/quic/core/quic_session.h
@@ -173,6 +173,7 @@
       const QuicSocketAddress& /*address*/) const override {
     return false;
   }
+  void OnBandwidthUpdateTimeout() override {}
 
   // QuicStreamFrameDataProducer
   WriteStreamDataResult WriteStreamData(QuicStreamId id,
diff --git a/quiche/quic/test_tools/quic_connection_peer.cc b/quiche/quic/test_tools/quic_connection_peer.cc
index 9eaa6fb..c00d12f 100644
--- a/quiche/quic/test_tools/quic_connection_peer.cc
+++ b/quiche/quic/test_tools/quic_connection_peer.cc
@@ -60,6 +60,23 @@
 }
 
 // static
+QuicTime::Delta QuicConnectionPeer::GetBandwidthUpdateTimeout(
+    QuicConnection* connection) {
+  return connection->idle_network_detector_.bandwidth_update_timeout_;
+}
+
+// static
+void QuicConnectionPeer::DisableBandwidthUpdate(QuicConnection* connection) {
+  if (connection->idle_network_detector_.bandwidth_update_timeout_
+          .IsInfinite()) {
+    return;
+  }
+  connection->idle_network_detector_.bandwidth_update_timeout_ =
+      QuicTime::Delta::Infinite();
+  connection->idle_network_detector_.SetAlarm();
+}
+
+// static
 void QuicConnectionPeer::SetPerspective(QuicConnection* connection,
                                         Perspective perspective) {
   connection->perspective_ = perspective;
diff --git a/quiche/quic/test_tools/quic_connection_peer.h b/quiche/quic/test_tools/quic_connection_peer.h
index ffe3b3c..1b85081 100644
--- a/quiche/quic/test_tools/quic_connection_peer.h
+++ b/quiche/quic/test_tools/quic_connection_peer.h
@@ -53,6 +53,10 @@
 
   static QuicTime::Delta GetHandshakeTimeout(QuicConnection* connection);
 
+  static QuicTime::Delta GetBandwidthUpdateTimeout(QuicConnection* connection);
+
+  static void DisableBandwidthUpdate(QuicConnection* connection);
+
   static void SetPerspective(QuicConnection* connection,
                              Perspective perspective);
 
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index 84aebc1..b7bda22 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -504,6 +504,8 @@
       const QuicSocketAddress& /*address*/) const override {
     return false;
   }
+
+  void OnBandwidthUpdateTimeout() override {}
 };
 
 class MockQuicConnectionHelper : public QuicConnectionHelperInterface {
diff --git a/quiche/quic/test_tools/simulator/quic_endpoint.h b/quiche/quic/test_tools/simulator/quic_endpoint.h
index e1a8c3e..853944a 100644
--- a/quiche/quic/test_tools/simulator/quic_endpoint.h
+++ b/quiche/quic/test_tools/simulator/quic_endpoint.h
@@ -108,6 +108,7 @@
       const QuicSocketAddress& /*address*/) const override {
     return false;
   }
+  void OnBandwidthUpdateTimeout() override {}
 
   // End QuicConnectionVisitorInterface implementation.