Add experimental logic for RetransmittableOnWireTimeout

This change allows RetransmittableOnWireTimeout be set based on a multiple of PTO.

This new functionality is enabled only on client side and behind three tags (ROW1, ROW2, and ROW3), where:
- ROW1 will make the timeout value be equal to 1PTO
- ROW2 will make the timeout value be equal to 2PTO
- ROW3 will make the timeout value be equal to 3PTO.

This new logic is not enabled by default.

Protected by experimental quic connection options.

PiperOrigin-RevId: 775783151
diff --git a/quiche/quic/core/crypto/crypto_protocol.h b/quiche/quic/core/crypto/crypto_protocol.h
index 9056c47..9d1dbe2 100644
--- a/quiche/quic/core/crypto/crypto_protocol.h
+++ b/quiche/quic/core/crypto/crypto_protocol.h
@@ -445,6 +445,13 @@
                                // ROWP timeout.
 DEFINE_STATIC_QUIC_TAG(ROWR);  // Send random bytes on ROWP
                                // timeout.
+
+// Retransmittable on wire timeout experiment.
+// TODO: b/427246911 - Remove these tags once the experiment is complete.
+DEFINE_STATIC_QUIC_TAG(ROW1);  // Set retransmittable on wire timeout to 1*PTO.
+DEFINE_STATIC_QUIC_TAG(ROW2);  // Set retransmittable on wire timeout to 2*PTO.
+DEFINE_STATIC_QUIC_TAG(ROW3);  // Set retransmittable on wire timeout to 3*PTO.
+
 // Selective Resumption variants.
 DEFINE_STATIC_QUIC_TAG(GSR0);
 DEFINE_STATIC_QUIC_TAG(GSR1);
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 1e6279e..fe95424 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -549,6 +549,20 @@
       retransmittable_on_wire_behavior_ = SEND_RANDOM_BYTES;
     }
   }
+
+  // Set retransmittable-on-wire timeout to different PTO based values.
+  if (perspective_ == Perspective::IS_CLIENT && version().HasIetfQuicFrames()) {
+    if (config.HasClientRequestedIndependentOption(kROW1, perspective_)) {
+      ping_manager_.set_num_ptos_for_retransmittable_on_wire_timeout(1);
+    }
+    if (config.HasClientRequestedIndependentOption(kROW2, perspective_)) {
+      ping_manager_.set_num_ptos_for_retransmittable_on_wire_timeout(2);
+    }
+    if (config.HasClientRequestedIndependentOption(kROW3, perspective_)) {
+      ping_manager_.set_num_ptos_for_retransmittable_on_wire_timeout(3);
+    }
+  }
+
   if (config.HasClientRequestedIndependentOption(k3AFF, perspective_)) {
     anti_amplification_factor_ = 3;
   }
@@ -4892,7 +4906,8 @@
   }
   ping_manager_.SetAlarm(clock_->ApproximateNow(),
                          visitor_->ShouldKeepConnectionAlive(),
-                         sent_packet_manager_.HasInFlightPackets());
+                         sent_packet_manager_.HasInFlightPackets(),
+                         sent_packet_manager_.GetPtoDelay());
 }
 
 void QuicConnection::SetRetransmissionAlarm() {
@@ -6580,9 +6595,9 @@
     if (connected_) {
       // Always reset PING alarm with has_in_flight_packets=true. This is used
       // to avoid re-arming the alarm in retransmittable-on-wire mode.
-      ping_manager_.SetAlarm(clock_->ApproximateNow(),
-                             visitor_->ShouldKeepConnectionAlive(),
-                             /*has_in_flight_packets=*/true);
+      ping_manager_.SetAlarm(
+          clock_->ApproximateNow(), visitor_->ShouldKeepConnectionAlive(),
+          /*has_in_flight_packets=*/true, sent_packet_manager_.GetPtoDelay());
     }
     return;
   }
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index c0251ab..74060c0 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -8587,6 +8587,40 @@
             connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
 }
 
+// Make sure when enabled, the retransmittable on wire timeout is based on the
+// PTO.
+TEST_P(QuicConnectionTest, PtoBasedRetransmittableOnWireTimeout) {
+  if (!VersionHasIetfQuicFrames(connection_.version().transport_version)) {
+    return;
+  }
+
+  EXPECT_CALL(*send_algorithm_, EnableECT1()).WillRepeatedly(Return(false));
+  EXPECT_CALL(*send_algorithm_, EnableECT0()).WillRepeatedly(Return(false));
+
+  // Enable the retransmittable on wire timeout for 3 different PTOs.
+  struct TestCase {
+    QuicTag timeout_tag;
+    uint8_t expected_pto_count;
+  };
+  static constexpr TestCase kTestCases[] = {
+      {kROW1, 1},
+      {kROW2, 2},
+      {kROW3, 3},
+  };
+
+  for (const auto& test_case : kTestCases) {
+    QuicConfig config;
+    QuicTagVector connection_options;
+    EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+    connection_options.push_back(test_case.timeout_tag);
+    config.SetClientConnectionOptions(connection_options);
+    connection_.SetFromConfig(config);
+    EXPECT_EQ(QuicConnectionPeer::GetNumPtosForRetransmittableOnWireTimeout(
+                  &connection_),
+              test_case.expected_pto_count);
+  }
+}
+
 TEST_P(QuicConnectionTest, ValidStatelessResetToken) {
   const StatelessResetToken kTestToken{0, 1, 0, 1, 0, 1, 0, 1,
                                        0, 1, 0, 1, 0, 1, 0, 1};
diff --git a/quiche/quic/core/quic_ping_manager.cc b/quiche/quic/core/quic_ping_manager.cc
index 533dd10..52a5cd4 100644
--- a/quiche/quic/core/quic_ping_manager.cc
+++ b/quiche/quic/core/quic_ping_manager.cc
@@ -25,8 +25,9 @@
     : perspective_(perspective), delegate_(delegate), alarm_(alarm) {}
 
 void QuicPingManager::SetAlarm(QuicTime now, bool should_keep_alive,
-                               bool has_in_flight_packets) {
-  UpdateDeadlines(now, should_keep_alive, has_in_flight_packets);
+                               bool has_in_flight_packets,
+                               QuicTime::Delta pto_delay) {
+  UpdateDeadlines(now, should_keep_alive, has_in_flight_packets, pto_delay);
   const QuicTime earliest_deadline = GetEarliestDeadline();
   if (!earliest_deadline.IsInitialized()) {
     alarm_.Cancel();
@@ -72,7 +73,8 @@
 }
 
 void QuicPingManager::UpdateDeadlines(QuicTime now, bool should_keep_alive,
-                                      bool has_in_flight_packets) {
+                                      bool has_in_flight_packets,
+                                      QuicTime::Delta pto_delay) {
   // Reset keep-alive deadline given it will be set later (with left edge
   // |now|).
   keep_alive_deadline_ = QuicTime::Zero();
@@ -96,7 +98,8 @@
     // Clients send 15s PINGs to avoid NATs from timing out.
     keep_alive_deadline_ = now + keep_alive_timeout_;
   }
-  if (initial_retransmittable_on_wire_timeout_.IsInfinite() ||
+  if ((num_ptos_for_retransmittable_on_wire_timeout_ == 0 &&
+       initial_retransmittable_on_wire_timeout_.IsInfinite()) ||
       has_in_flight_packets ||
       retransmittable_on_wire_count_ >
           GetQuicFlag(quic_max_retransmittable_on_wire_ping_count)) {
@@ -105,10 +108,18 @@
     return;
   }
 
-  QUICHE_DCHECK_LT(initial_retransmittable_on_wire_timeout_,
-                   keep_alive_timeout_);
-  QuicTime::Delta retransmittable_on_wire_timeout =
-      initial_retransmittable_on_wire_timeout_;
+  QuicTime::Delta retransmittable_on_wire_timeout = QuicTime::Delta::Zero();
+  if (num_ptos_for_retransmittable_on_wire_timeout_ > 0) {
+    QUICHE_DCHECK_NE(pto_delay, QuicTime::Delta::Zero());
+    retransmittable_on_wire_timeout =
+        static_cast<int>(num_ptos_for_retransmittable_on_wire_timeout_) *
+        pto_delay;
+  } else {
+    QUICHE_DCHECK_LT(initial_retransmittable_on_wire_timeout_,
+                     keep_alive_timeout_);
+    retransmittable_on_wire_timeout = initial_retransmittable_on_wire_timeout_;
+  }
+
   const int max_aggressive_retransmittable_on_wire_count =
       GetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count);
   QUICHE_DCHECK_LE(0, max_aggressive_retransmittable_on_wire_count);
@@ -120,8 +131,9 @@
                              max_aggressive_retransmittable_on_wire_count,
                          kMaxRetransmittableOnWireDelayShift);
     retransmittable_on_wire_timeout =
-        initial_retransmittable_on_wire_timeout_ * (1 << shift);
+        retransmittable_on_wire_timeout * (1 << shift);
   }
+
   if (retransmittable_on_wire_deadline_.IsInitialized() &&
       retransmittable_on_wire_deadline_ <
           now + retransmittable_on_wire_timeout) {
diff --git a/quiche/quic/core/quic_ping_manager.h b/quiche/quic/core/quic_ping_manager.h
index 3250416..4cf9810 100644
--- a/quiche/quic/core/quic_ping_manager.h
+++ b/quiche/quic/core/quic_ping_manager.h
@@ -5,6 +5,8 @@
 #ifndef QUICHE_QUIC_CORE_QUIC_PING_MANAGER_H_
 #define QUICHE_QUIC_CORE_QUIC_PING_MANAGER_H_
 
+#include <cstdint>
+
 #include "quiche/quic/core/quic_alarm.h"
 #include "quiche/quic/core/quic_alarm_factory.h"
 #include "quiche/quic/core/quic_connection_alarms.h"
@@ -43,7 +45,7 @@
 
   // Called to set |alarm_|.
   void SetAlarm(QuicTime now, bool should_keep_alive,
-                bool has_in_flight_packets);
+                bool has_in_flight_packets, QuicTime::Delta pto_delay);
 
   // Called when |alarm_| fires.
   void OnAlarm();
@@ -66,13 +68,19 @@
     consecutive_retransmittable_on_wire_count_ = 0;
   }
 
+  void set_num_ptos_for_retransmittable_on_wire_timeout(
+      uint8_t num_ptos_for_retransmittable_on_wire_timeout) {
+    num_ptos_for_retransmittable_on_wire_timeout_ =
+        num_ptos_for_retransmittable_on_wire_timeout;
+  }
+
  private:
   friend class test::QuicConnectionPeer;
   friend class test::QuicPingManagerPeer;
 
   // Update |retransmittable_on_wire_deadline_| and |keep_alive_deadline_|.
   void UpdateDeadlines(QuicTime now, bool should_keep_alive,
-                       bool has_in_flight_packets);
+                       bool has_in_flight_packets, QuicTime::Delta pto_delay);
 
   // Get earliest deadline of |retransmittable_on_wire_deadline_| and
   // |keep_alive_deadline_|. Returns 0 if both deadlines are not initialized.
@@ -101,6 +109,8 @@
   QuicTime keep_alive_deadline_ = QuicTime::Zero();
 
   QuicAlarmProxy alarm_;
+
+  uint8_t num_ptos_for_retransmittable_on_wire_timeout_ = 0;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_ping_manager_test.cc b/quiche/quic/core/quic_ping_manager_test.cc
index e2a3155..0ab3331 100644
--- a/quiche/quic/core/quic_ping_manager_test.cc
+++ b/quiche/quic/core/quic_ping_manager_test.cc
@@ -26,6 +26,7 @@
 
 const bool kShouldKeepAlive = true;
 const bool kHasInflightPackets = true;
+const QuicTime::Delta kPtoDelay = QuicTime::Delta::FromMilliseconds(50);
 
 class MockDelegate : public QuicPingManager::Delegate {
  public:
@@ -62,7 +63,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   // Set alarm with in flight packets.
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
             alarm_->deadline() - clock_.ApproximateNow());
@@ -70,7 +71,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   // Reset alarm with no in flight packets.
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   // Verify the deadline is set slightly less than 15 seconds in the future,
   // because of the 1s alarm granularity.
@@ -84,13 +85,13 @@
   EXPECT_FALSE(alarm_->IsSet());
   // Reset alarm with in flight packets.
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
 
   // Verify alarm is not armed if !kShouldKeepAlive.
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   EXPECT_FALSE(alarm_->IsSet());
 }
 
@@ -103,7 +104,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   // Set alarm with in flight packets.
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   EXPECT_EQ(QuicTime::Delta::FromSeconds(10),
             alarm_->deadline() - clock_.ApproximateNow());
@@ -111,7 +112,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   // Set alarm with no in flight packets.
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   // The deadline is set slightly less than 10 seconds in the future, because
   // of the 1s alarm granularity.
@@ -125,13 +126,13 @@
   EXPECT_FALSE(alarm_->IsSet());
   // Reset alarm with in flight packets.
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
 
   // Verify alarm is not armed if !kShouldKeepAlive.
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   EXPECT_FALSE(alarm_->IsSet());
 }
 
@@ -145,7 +146,7 @@
 
   // Set alarm with in flight packets.
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   // Verify alarm is in keep-alive mode.
   EXPECT_TRUE(alarm_->IsSet());
   EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
@@ -154,7 +155,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   // Set alarm with no in flight packets.
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   // Verify alarm is in retransmittable-on-wire mode.
   EXPECT_EQ(kRtransmittableOnWireTimeout,
@@ -166,7 +167,7 @@
   EXPECT_FALSE(alarm_->IsSet());
   // Reset alarm with in flight packets.
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   // Verify the alarm is in keep-alive mode.
   ASSERT_TRUE(alarm_->IsSet());
   EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
@@ -185,7 +186,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   EXPECT_FALSE(alarm_->IsSet());
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   // Verify alarm is in keep-alive mode.
   EXPECT_TRUE(alarm_->IsSet());
   EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
@@ -197,7 +198,7 @@
     clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
     // Reset alarm with no in flight packets.
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      !kHasInflightPackets);
+                      !kHasInflightPackets, kPtoDelay);
     EXPECT_TRUE(alarm_->IsSet());
     // Verify alarm is in retransmittable-on-wire mode.
     EXPECT_EQ(initial_retransmittable_on_wire_timeout,
@@ -208,7 +209,7 @@
     EXPECT_FALSE(alarm_->IsSet());
     // Reset alarm with in flight packets.
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      kHasInflightPackets);
+                      kHasInflightPackets, kPtoDelay);
   }
 
   QuicTime::Delta retransmittable_on_wire_timeout =
@@ -221,7 +222,7 @@
     retransmittable_on_wire_timeout = retransmittable_on_wire_timeout * 2;
     clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      !kHasInflightPackets);
+                      !kHasInflightPackets, kPtoDelay);
     EXPECT_TRUE(alarm_->IsSet());
     EXPECT_EQ(retransmittable_on_wire_timeout,
               alarm_->deadline() - clock_.ApproximateNow());
@@ -232,7 +233,7 @@
     EXPECT_FALSE(alarm_->IsSet());
     // Reset alarm with in flight packets.
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      kHasInflightPackets);
+                      kHasInflightPackets, kPtoDelay);
   }
 
   // Verify alarm is in keep-alive mode.
@@ -243,7 +244,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   // Reset alarm with no in flight packets
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   // Verify alarm is in keep-alive mode because retransmittable-on-wire deadline
   // is later.
@@ -270,7 +271,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   EXPECT_FALSE(alarm_->IsSet());
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
   // Verify alarm is in keep-alive mode.
   EXPECT_TRUE(alarm_->IsSet());
   EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
@@ -278,7 +279,7 @@
 
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   // Verify alarm is in retransmittable-on-wire mode.
   EXPECT_EQ(initial_retransmittable_on_wire_timeout,
@@ -290,14 +291,14 @@
 
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   EXPECT_EQ(initial_retransmittable_on_wire_timeout,
             alarm_->deadline() - clock_.ApproximateNow());
 
   manager_.reset_consecutive_retransmittable_on_wire_count();
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_EQ(initial_retransmittable_on_wire_timeout,
             alarm_->deadline() - clock_.ApproximateNow());
   EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
@@ -306,7 +307,7 @@
 
   for (int i = 0; i < kMaxAggressiveRetransmittableOnWireCount; i++) {
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      !kHasInflightPackets);
+                      !kHasInflightPackets, kPtoDelay);
     EXPECT_TRUE(alarm_->IsSet());
     EXPECT_EQ(initial_retransmittable_on_wire_timeout,
               alarm_->deadline() - clock_.ApproximateNow());
@@ -315,13 +316,13 @@
     alarm_->Fire();
     // Reset alarm with in flight packets.
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      kHasInflightPackets);
+                      kHasInflightPackets, kPtoDelay);
     // Advance 5ms to receive next packet.
     clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   }
 
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   EXPECT_EQ(initial_retransmittable_on_wire_timeout * 2,
             alarm_->deadline() - clock_.ApproximateNow());
@@ -333,7 +334,7 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   manager_.reset_consecutive_retransmittable_on_wire_count();
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   EXPECT_EQ(initial_retransmittable_on_wire_timeout,
             alarm_->deadline() - clock_.ApproximateNow());
@@ -354,7 +355,7 @@
   clock_.AdvanceTime(kShortDelay);
   EXPECT_FALSE(alarm_->IsSet());
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    kHasInflightPackets);
+                    kHasInflightPackets, kPtoDelay);
 
   EXPECT_TRUE(alarm_->IsSet());
   EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
@@ -363,7 +364,7 @@
   for (int i = 0; i <= kMaxRetransmittableOnWirePingCount; i++) {
     clock_.AdvanceTime(kShortDelay);
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      !kHasInflightPackets);
+                      !kHasInflightPackets, kPtoDelay);
     EXPECT_TRUE(alarm_->IsSet());
     EXPECT_EQ(initial_retransmittable_on_wire_timeout,
               alarm_->deadline() - clock_.ApproximateNow());
@@ -371,11 +372,11 @@
     EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
     alarm_->Fire();
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      kHasInflightPackets);
+                      kHasInflightPackets, kPtoDelay);
   }
 
   manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                    !kHasInflightPackets);
+                    !kHasInflightPackets, kPtoDelay);
   EXPECT_TRUE(alarm_->IsSet());
   // Verify alarm is in keep-alive mode.
   EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
@@ -398,7 +399,7 @@
 
   for (int i = 0; i <= kMaxAggressiveRetransmittableOnWireCount; i++) {
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      !kHasInflightPackets);
+                      !kHasInflightPackets, kPtoDelay);
     EXPECT_TRUE(alarm_->IsSet());
     EXPECT_EQ(initial_retransmittable_on_wire_timeout,
               alarm_->deadline() - clock_.ApproximateNow());
@@ -406,11 +407,11 @@
     EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
     alarm_->Fire();
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      kHasInflightPackets);
+                      kHasInflightPackets, kPtoDelay);
   }
   for (int i = 1; i <= 20; ++i) {
     manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
-                      !kHasInflightPackets);
+                      !kHasInflightPackets, kPtoDelay);
     EXPECT_TRUE(alarm_->IsSet());
     if (i <= 10) {
       EXPECT_EQ(initial_retransmittable_on_wire_timeout * (1 << i),
@@ -426,6 +427,113 @@
   }
 }
 
+TEST_F(QuicPingManagerTest, PtoBasedRetransmittableOnWireTimeout) {
+  // Set the initial retransmittable on wire timeout.
+  manager_.set_initial_retransmittable_on_wire_timeout(
+      QuicTime::Delta::FromMilliseconds(200));
+  // Verify the alarm is set based on different value based on PTO delay.
+  for (int num_times_pto : {1, 2, 3}) {
+    EXPECT_TRUE(!alarm_->IsSet());
+    manager_.set_num_ptos_for_retransmittable_on_wire_timeout(num_times_pto);
+    manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                      !kHasInflightPackets, kPtoDelay);
+    EXPECT_EQ(kPtoDelay * num_times_pto,
+              alarm_->deadline() - clock_.ApproximateNow());
+    EXPECT_TRUE(alarm_->IsSet());
+    clock_.AdvanceTime(kPtoDelay * num_times_pto);
+    EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
+    alarm_->Fire();
+  }
+}
+
+TEST_F(QuicPingManagerTest,
+       PtoBasedRetransmittableOnWireTimeoutExponentiallyBackOff) {
+  const int kMaxAggressiveRetransmittableOnWireCount = 5;
+  SetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count,
+              kMaxAggressiveRetransmittableOnWireCount);
+
+  for (int num_times_pto : {1, 2, 3}) {
+    manager_.set_num_ptos_for_retransmittable_on_wire_timeout(num_times_pto);
+
+    QuicTime::Delta pto_delay = static_cast<int>(num_times_pto) * kPtoDelay;
+
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+    EXPECT_FALSE(alarm_->IsSet());
+    manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                      kHasInflightPackets, kPtoDelay);
+    // Verify alarm is in keep-alive mode.
+    EXPECT_TRUE(alarm_->IsSet());
+    EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
+              alarm_->deadline() - clock_.ApproximateNow());
+
+    // Verify no exponential backoff on the first few retransmittable on wire
+    // timeouts.
+    for (int i = 0; i <= kMaxAggressiveRetransmittableOnWireCount; ++i) {
+      clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+      // Reset alarm with no in flight packets.
+      manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                        !kHasInflightPackets, kPtoDelay);
+      EXPECT_TRUE(alarm_->IsSet());
+      // Verify alarm is in retransmittable-on-wire mode.
+      EXPECT_EQ(pto_delay, alarm_->deadline() - clock_.ApproximateNow());
+      clock_.AdvanceTime(pto_delay);
+      EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
+      alarm_->Fire();
+      EXPECT_FALSE(alarm_->IsSet());
+      // Reset alarm with in flight packets.
+      manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                        kHasInflightPackets, kPtoDelay);
+    }
+
+    QuicTime::Delta retransmittable_on_wire_timeout = pto_delay;
+
+    // Verify subsequent retransmittable-on-wire timeout is exponentially
+    // backed off.
+    while (retransmittable_on_wire_timeout * 2 <
+           QuicTime::Delta::FromSeconds(kPingTimeoutSecs)) {
+      retransmittable_on_wire_timeout = retransmittable_on_wire_timeout * 2;
+      clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+      manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                        !kHasInflightPackets, kPtoDelay);
+      EXPECT_TRUE(alarm_->IsSet());
+      EXPECT_EQ(retransmittable_on_wire_timeout,
+                alarm_->deadline() - clock_.ApproximateNow());
+
+      clock_.AdvanceTime(retransmittable_on_wire_timeout);
+      EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
+      alarm_->Fire();
+      EXPECT_FALSE(alarm_->IsSet());
+      // Reset alarm with in flight packets.
+      manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                        kHasInflightPackets, kPtoDelay);
+    }
+
+    // Verify alarm is in keep-alive mode.
+    EXPECT_TRUE(alarm_->IsSet());
+    EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
+              alarm_->deadline() - clock_.ApproximateNow());
+
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+    // Reset alarm with no in flight packets
+    manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                      !kHasInflightPackets, kPtoDelay);
+    EXPECT_TRUE(alarm_->IsSet());
+    // Verify alarm is in keep-alive mode because retransmittable-on-wire
+    // deadline is later.
+    EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs) -
+                  QuicTime::Delta::FromMilliseconds(5),
+              alarm_->deadline() - clock_.ApproximateNow());
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kPingTimeoutSecs) -
+                       QuicTime::Delta::FromMilliseconds(5));
+    EXPECT_CALL(delegate_, OnKeepAliveTimeout());
+    alarm_->Fire();
+    EXPECT_FALSE(alarm_->IsSet());
+
+    // Reset the consecutive retransmittable on wire count for next iteration.
+    manager_.reset_consecutive_retransmittable_on_wire_count();
+  }
+}
+
 }  // namespace
 
 }  // namespace test
diff --git a/quiche/quic/test_tools/quic_connection_peer.cc b/quiche/quic/test_tools/quic_connection_peer.cc
index 001e690..c0d713a 100644
--- a/quiche/quic/test_tools/quic_connection_peer.cc
+++ b/quiche/quic/test_tools/quic_connection_peer.cc
@@ -607,5 +607,12 @@
   return connection->can_receive_ack_frequency_immediate_ack_;
 }
 
+// static
+uint8_t QuicConnectionPeer::GetNumPtosForRetransmittableOnWireTimeout(
+    const QuicConnection* connection) {
+  return connection->ping_manager_
+      .num_ptos_for_retransmittable_on_wire_timeout_;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/test_tools/quic_connection_peer.h b/quiche/quic/test_tools/quic_connection_peer.h
index 06d8c97..c1477a8 100644
--- a/quiche/quic/test_tools/quic_connection_peer.h
+++ b/quiche/quic/test_tools/quic_connection_peer.h
@@ -253,6 +253,9 @@
   static void OnForwardProgressMade(QuicConnection* connection);
 
   static bool CanReceiveAckFrequencyFrames(QuicConnection* connection);
+
+  static uint8_t GetNumPtosForRetransmittableOnWireTimeout(
+      const QuicConnection* connection);
 };
 
 }  // namespace test