In QUIC, add a copt to extend idle timeout by PTO on sent packets. Client side only, not protected.

PiperOrigin-RevId: 327529464
Change-Id: I6ee548b29ce911da865e21539ad1e710394bc924
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index 9bd5f17..4ebdd2e 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -295,6 +295,10 @@
 const QuicTag kDTOS = TAG('D', 'T', 'O', 'S');  // Enable overshooting
                                                 // detection.
 
+const QuicTag kFIDT = TAG('F', 'I', 'D', 'T');  // Extend idle timer by PTO
+                                                // instead of the whole idle
+                                                // timeout.
+
 // Enable path MTU discovery experiment.
 const QuicTag kMTUH = TAG('M', 'T', 'U', 'H');  // High-target MTU discovery.
 const QuicTag kMTUL = TAG('M', 'T', 'U', 'L');  // Low-target MTU discovery.
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 11b5ad2..7aff953 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -578,6 +578,10 @@
     }
   }
 
+  if (config.HasClientRequestedIndependentOption(kFIDT, perspective_)) {
+    idle_network_detector_.enable_shorter_idle_timeout_on_sent_packet();
+  }
+
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnSetFromConfig(config);
   }
@@ -2858,7 +2862,8 @@
                                            GetNetworkBlackholeDeadline(),
                                            GetPathMtuReductionDeadline());
     }
-    idle_network_detector_.OnPacketSent(packet_send_time);
+    idle_network_detector_.OnPacketSent(packet_send_time,
+                                        sent_packet_manager_.GetPtoDelay());
   }
 
   MaybeSetMtuAlarm(packet_number);
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 8c12920..1b8c0c6 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -11766,6 +11766,52 @@
   EXPECT_FALSE(connection_.BlackholeDetectionInProgress());
 }
 
+TEST_P(QuicConnectionTest, ShorterIdleTimeoutOnSentPackets) {
+  EXPECT_TRUE(connection_.connected());
+  RttStats* rtt_stats = const_cast<RttStats*>(manager_->GetRttStats());
+  rtt_stats->UpdateRtt(QuicTime::Delta::FromMilliseconds(100),
+                       QuicTime::Delta::Zero(), QuicTime::Zero());
+
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kFIDT});
+  QuicConfigPeer::SetNegotiated(&config, true);
+  if (GetQuicReloadableFlag(quic_default_enable_5rto_blackhole_detection2)) {
+    EXPECT_CALL(visitor_, GetHandshakeState())
+        .WillRepeatedly(Return(HANDSHAKE_COMPLETE));
+  }
+  if (connection_.version().AuthenticatesHandshakeConnectionIds()) {
+    QuicConfigPeer::SetReceivedOriginalConnectionId(
+        &config, connection_.connection_id());
+    QuicConfigPeer::SetReceivedInitialSourceConnectionId(
+        &config, connection_.connection_id());
+  }
+  connection_.SetFromConfig(config);
+
+  ASSERT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
+  // Send a packet close to timeout.
+  QuicTime::Delta timeout =
+      connection_.GetTimeoutAlarm()->deadline() - clock_.Now();
+  clock_.AdvanceTime(timeout - QuicTime::Delta::FromSeconds(1));
+  // Send stream data.
+  SendStreamDataToPeer(
+      GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
+      0, FIN, nullptr);
+  // Verify this sent packet does not extend idle timeout since 1s is > PTO
+  // delay.
+  ASSERT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(1),
+            connection_.GetTimeoutAlarm()->deadline() - clock_.Now());
+
+  // Received an ACK 100ms later.
+  clock_.AdvanceTime(timeout - QuicTime::Delta::FromMilliseconds(100));
+  QuicAckFrame ack = InitAckFrame(1);
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+  ProcessAckPacket(1, &ack);
+  // Verify idle timeout gets extended.
+  EXPECT_EQ(clock_.Now() + timeout, connection_.GetTimeoutAlarm()->deadline());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_idle_network_detector.cc b/quic/core/quic_idle_network_detector.cc
index a033f7a..f9b37cc 100644
--- a/quic/core/quic_idle_network_detector.cc
+++ b/quic/core/quic_idle_network_detector.cc
@@ -71,13 +71,18 @@
   idle_network_timeout_ = QuicTime::Delta::Infinite();
 }
 
-void QuicIdleNetworkDetector::OnPacketSent(QuicTime now) {
+void QuicIdleNetworkDetector::OnPacketSent(QuicTime now,
+                                           QuicTime::Delta pto_delay) {
   if (time_of_first_packet_sent_after_receiving_ >
       time_of_last_received_packet_) {
     return;
   }
   time_of_first_packet_sent_after_receiving_ =
       std::max(time_of_first_packet_sent_after_receiving_, now);
+  if (shorter_idle_timeout_on_sent_packet_) {
+    MaybeSetAlarmOnSentPacket(pto_delay);
+    return;
+  }
 
   SetAlarm();
 }
@@ -105,6 +110,22 @@
   alarm_->Update(new_deadline, kAlarmGranularity);
 }
 
+void QuicIdleNetworkDetector::MaybeSetAlarmOnSentPacket(
+    QuicTime::Delta pto_delay) {
+  DCHECK(shorter_idle_timeout_on_sent_packet_);
+  if (!handshake_timeout_.IsInfinite() || !alarm_->IsSet()) {
+    SetAlarm();
+    return;
+  }
+  // Make sure connection will be alive for another PTO.
+  const QuicTime deadline = alarm_->deadline();
+  const QuicTime min_deadline = last_network_activity_time() + pto_delay;
+  if (deadline > min_deadline) {
+    return;
+  }
+  alarm_->Update(min_deadline, kAlarmGranularity);
+}
+
 QuicTime QuicIdleNetworkDetector::GetIdleNetworkDeadline() const {
   if (idle_network_timeout_.IsInfinite()) {
     return QuicTime::Zero();
diff --git a/quic/core/quic_idle_network_detector.h b/quic/core/quic_idle_network_detector.h
index 1537222..7d7797e 100644
--- a/quic/core/quic_idle_network_detector.h
+++ b/quic/core/quic_idle_network_detector.h
@@ -49,11 +49,15 @@
   void StopDetection();
 
   // Called when a packet gets sent.
-  void OnPacketSent(QuicTime now);
+  void OnPacketSent(QuicTime now, QuicTime::Delta pto_delay);
 
   // Called when a packet gets received.
   void OnPacketReceived(QuicTime now);
 
+  void enable_shorter_idle_timeout_on_sent_packet() {
+    shorter_idle_timeout_on_sent_packet_ = true;
+  }
+
   QuicTime::Delta handshake_timeout() const { return handshake_timeout_; }
 
   QuicTime time_of_last_received_packet() const {
@@ -75,6 +79,8 @@
 
   void SetAlarm();
 
+  void MaybeSetAlarmOnSentPacket(QuicTime::Delta pto_delay);
+
   Delegate* delegate_;  // Not owned.
 
   // Start time of the detector, handshake deadline = start_time_ +
@@ -98,6 +104,8 @@
   QuicTime::Delta idle_network_timeout_;
 
   QuicArenaScopedPtr<QuicAlarm> alarm_;
+
+  bool shorter_idle_timeout_on_sent_packet_ = false;
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_idle_network_detector_test.cc b/quic/core/quic_idle_network_detector_test.cc
index 0d40d64..3fa4ebe 100644
--- a/quic/core/quic_idle_network_detector_test.cc
+++ b/quic/core/quic_idle_network_detector_test.cc
@@ -123,14 +123,14 @@
 
   // Sent packets after 200ms.
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
-  detector_->OnPacketSent(clock_.Now());
+  detector_->OnPacketSent(clock_.Now(), QuicTime::Delta::Zero());
   const QuicTime packet_sent_time = clock_.Now();
   EXPECT_EQ(packet_sent_time + QuicTime::Delta::FromSeconds(600),
             alarm_->deadline());
 
   // Sent another packet after 200ms
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
-  detector_->OnPacketSent(clock_.Now());
+  detector_->OnPacketSent(clock_.Now(), QuicTime::Delta::Zero());
   // Verify idle network deadline does not extend.
   EXPECT_EQ(packet_sent_time + QuicTime::Delta::FromSeconds(600),
             alarm_->deadline());
@@ -142,6 +142,45 @@
   alarm_->Fire();
 }
 
+TEST_F(QuicIdleNetworkDetectorTest, ShorterIdleTimeoutOnSentPacket) {
+  detector_->enable_shorter_idle_timeout_on_sent_packet();
+  detector_->SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::Infinite(),
+      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(30));
+  EXPECT_TRUE(alarm_->IsSet());
+  const QuicTime deadline = alarm_->deadline();
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(30), deadline);
+
+  // Send a packet after 15s and 2s PTO delay.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(15));
+  detector_->OnPacketSent(clock_.Now(), QuicTime::Delta::FromSeconds(2));
+  EXPECT_TRUE(alarm_->IsSet());
+  // Verify alarm does not get extended because deadline is > PTO delay.
+  EXPECT_EQ(deadline, alarm_->deadline());
+
+  // Send another packet near timeout and 2 s PTO delay.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(14));
+  detector_->OnPacketSent(clock_.Now(), QuicTime::Delta::FromSeconds(2));
+  EXPECT_TRUE(alarm_->IsSet());
+  // Verify alarm does not get extended although it is shorter than PTO.
+  EXPECT_EQ(deadline, alarm_->deadline());
+
+  // Receive a packet after 1s.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  detector_->OnPacketReceived(clock_.Now());
+  EXPECT_TRUE(alarm_->IsSet());
+  // Verify idle timeout gets extended by 30s.
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(30),
+            alarm_->deadline());
+
+  // Send a packet near timeout..
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(29));
+  detector_->OnPacketSent(clock_.Now(), QuicTime::Delta::FromSeconds(2));
+  EXPECT_TRUE(alarm_->IsSet());
+  // Verify idle timeout gets extended by 1s.
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(2), alarm_->deadline());
+}
+
 }  // namespace
 
 }  // namespace test
diff --git a/quic/core/quic_sent_packet_manager.cc b/quic/core/quic_sent_packet_manager.cc
index a9204d6..4746eae 100644
--- a/quic/core/quic_sent_packet_manager.cc
+++ b/quic/core/quic_sent_packet_manager.cc
@@ -1583,10 +1583,12 @@
 }
 
 bool QuicSentPacketManager::IsLessThanThreePTOs(QuicTime::Delta timeout) const {
-  const QuicTime::Delta retransmission_delay =
-      pto_enabled_ ? GetProbeTimeoutDelay(APPLICATION_DATA)
-                   : GetRetransmissionDelay();
-  return timeout < 3 * retransmission_delay;
+  return timeout < 3 * GetPtoDelay();
+}
+
+QuicTime::Delta QuicSentPacketManager::GetPtoDelay() const {
+  return pto_enabled_ ? GetProbeTimeoutDelay(APPLICATION_DATA)
+                      : GetRetransmissionDelay();
 }
 
 #undef ENDPOINT  // undef for jumbo builds
diff --git a/quic/core/quic_sent_packet_manager.h b/quic/core/quic_sent_packet_manager.h
index 584d0fe..9e5bd96 100644
--- a/quic/core/quic_sent_packet_manager.h
+++ b/quic/core/quic_sent_packet_manager.h
@@ -417,6 +417,9 @@
   // Returns true if |timeout| is less than 3 * RTO/PTO delay.
   bool IsLessThanThreePTOs(QuicTime::Delta timeout) const;
 
+  // Returns current PTO delay.
+  QuicTime::Delta GetPtoDelay() const;
+
   bool supports_multiple_packet_number_spaces() const {
     return unacked_packets_.supports_multiple_packet_number_spaces();
   }