Add QuicPingManager to manage the PING alarm.

Protected by FLAGS_quic_reloadable_flag_quic_use_ping_manager.

PiperOrigin-RevId: 442893828
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 44a7830..82b07cc 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -128,6 +128,7 @@
 
   void OnAlarm() override {
     QUICHE_DCHECK(connection_->connected());
+    QUICHE_DCHECK(!GetQuicReloadableFlag(quic_use_ping_manager));
     connection_->OnPingTimeout();
   }
 };
@@ -327,7 +328,8 @@
                              alarm_factory_, &context_),
       path_validator_(alarm_factory_, &arena_, this, random_generator_,
                       &context_),
-      most_recent_frame_type_(NUM_FRAME_TYPES) {
+      most_recent_frame_type_(NUM_FRAME_TYPES),
+      ping_manager_(perspective, this, &arena_, alarm_factory_, &context_) {
   QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT ||
                 default_path_.self_address.IsInitialized());
 
@@ -1350,7 +1352,11 @@
   MaybeUpdateAckTimeout();
   visitor_->OnStreamFrame(frame);
   stats_.stream_bytes_received += frame.data_length;
-  consecutive_retransmittable_on_wire_ping_count_ = 0;
+  if (use_ping_manager_) {
+    ping_manager_.reset_consecutive_retransmittable_on_wire_count();
+  } else {
+    consecutive_retransmittable_on_wire_ping_count_ = 0;
+  }
   return connected_;
 }
 
@@ -4053,6 +4059,7 @@
 }
 
 void QuicConnection::OnPingTimeout() {
+  QUICHE_DCHECK(!use_ping_manager_);
   if (retransmission_alarm_->IsSet() ||
       !visitor_->ShouldKeepConnectionAlive()) {
     return;
@@ -4621,7 +4628,11 @@
   QUIC_DVLOG(1) << "Cancelling all QuicConnection alarms.";
 
   ack_alarm_->PermanentCancel();
-  ping_alarm_->PermanentCancel();
+  if (use_ping_manager_) {
+    ping_manager_.Stop();
+  } else {
+    ping_alarm_->PermanentCancel();
+  }
   retransmission_alarm_->PermanentCancel();
   send_alarm_->PermanentCancel();
   mtu_discovery_alarm_->PermanentCancel();
@@ -4665,6 +4676,13 @@
   if (!connected_) {
     return;
   }
+  if (use_ping_manager_) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_use_ping_manager);
+    ping_manager_.SetAlarm(clock_->ApproximateNow(),
+                           visitor_->ShouldKeepConnectionAlive(),
+                           sent_packet_manager_.HasInFlightPackets());
+    return;
+  }
   if (perspective_ == Perspective::IS_SERVER &&
       initial_retransmittable_on_wire_timeout_.IsInfinite()) {
     // The PING alarm exists to support two features:
@@ -6280,6 +6298,24 @@
                   idle_timeout_connection_close_behavior_);
 }
 
+void QuicConnection::OnKeepAliveTimeout() {
+  QUICHE_DCHECK(use_ping_manager_);
+  if (retransmission_alarm_->IsSet() ||
+      !visitor_->ShouldKeepConnectionAlive()) {
+    return;
+  }
+  SendPingAtLevel(framer().GetEncryptionLevelToSendApplicationData());
+}
+
+void QuicConnection::OnRetransmittableOnWireTimeout() {
+  QUICHE_DCHECK(use_ping_manager_);
+  if (retransmission_alarm_->IsSet() ||
+      !visitor_->ShouldKeepConnectionAlive()) {
+    return;
+  }
+  SendPingAtLevel(framer().GetEncryptionLevelToSendApplicationData());
+}
+
 void QuicConnection::OnPeerIssuedConnectionIdRetired() {
   QUICHE_DCHECK(peer_issued_cid_manager_ != nullptr);
   QuicConnectionId* default_path_cid =
@@ -7107,5 +7143,26 @@
   return old_send_algorithm;
 }
 
+void QuicConnection::set_keep_alive_ping_timeout(
+    QuicTime::Delta keep_alive_ping_timeout) {
+  if (use_ping_manager_) {
+    ping_manager_.set_keep_alive_timeout(keep_alive_ping_timeout);
+    return;
+  }
+  QUICHE_DCHECK(!ping_alarm_->IsSet());
+  keep_alive_ping_timeout_ = keep_alive_ping_timeout;
+}
+
+void QuicConnection::set_initial_retransmittable_on_wire_timeout(
+    QuicTime::Delta retransmittable_on_wire_timeout) {
+  if (use_ping_manager_) {
+    ping_manager_.set_initial_retransmittable_on_wire_timeout(
+        retransmittable_on_wire_timeout);
+    return;
+  }
+  QUICHE_DCHECK(!ping_alarm_->IsSet());
+  initial_retransmittable_on_wire_timeout_ = retransmittable_on_wire_timeout;
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 6aca00b..9c93dd4 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -50,6 +50,7 @@
 #include "quiche/quic/core/quic_packet_writer.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_path_validator.h"
+#include "quiche/quic/core/quic_ping_manager.h"
 #include "quiche/quic/core/quic_sent_packet_manager.h"
 #include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/core/quic_types.h"
@@ -458,7 +459,8 @@
       public QuicNetworkBlackholeDetector::Delegate,
       public QuicIdleNetworkDetector::Delegate,
       public QuicPathValidator::SendDelegate,
-      public QuicConnectionIdManagerVisitorInterface {
+      public QuicConnectionIdManagerVisitorInterface,
+      public QuicPingManager::Delegate {
  public:
   // Constructs a new QuicConnection for |connection_id| and
   // |initial_peer_address| using |writer| to write packets. |owns_writer|
@@ -710,6 +712,10 @@
   void OnHandshakeTimeout() override;
   void OnIdleNetworkDetected() override;
 
+  // QuicPingManager::Delegate
+  void OnKeepAliveTimeout() override;
+  void OnRetransmittableOnWireTimeout() override;
+
   // QuicConnectionIdManagerVisitorInterface
   void OnPeerIssuedConnectionIdRetired() override;
   bool SendNewConnectionId(const QuicNewConnectionIdFrame& frame) override;
@@ -741,17 +747,11 @@
   }
   // Used in Chromium, but not internally.
   // Must only be called before ping_alarm_ is set.
-  void set_keep_alive_ping_timeout(QuicTime::Delta keep_alive_ping_timeout) {
-    QUICHE_DCHECK(!ping_alarm_->IsSet());
-    keep_alive_ping_timeout_ = keep_alive_ping_timeout;
-  }
+  void set_keep_alive_ping_timeout(QuicTime::Delta keep_alive_ping_timeout);
   // Sets an initial timeout for the ping alarm when there is no retransmittable
   // data in flight, allowing for a more aggressive ping alarm in that case.
   void set_initial_retransmittable_on_wire_timeout(
-      QuicTime::Delta retransmittable_on_wire_timeout) {
-    QUICHE_DCHECK(!ping_alarm_->IsSet());
-    initial_retransmittable_on_wire_timeout_ = retransmittable_on_wire_timeout;
-  }
+      QuicTime::Delta retransmittable_on_wire_timeout);
   // Used in Chromium, but not internally.
   void set_creator_debug_delegate(QuicPacketCreator::DebugDelegate* visitor) {
     packet_creator_.set_debug_delegate(visitor);
@@ -1976,6 +1976,8 @@
   // SendAlarm.
   bool defer_send_in_response_to_packets_;
 
+  // TODO(fayang): remove PING related fields below when deprecating
+  // quic_use_ping_manager.
   // The timeout for keep-alive PING.
   QuicTime::Delta keep_alive_ping_timeout_;
 
@@ -1999,6 +2001,7 @@
   // An alarm that is scheduled when the SentPacketManager requires a delay
   // before sending packets and fires when the packet may be sent.
   QuicArenaScopedPtr<QuicAlarm> send_alarm_;
+  // TODO(fayang): remove ping_alarm_ when deprecating quic_use_ping_manager.
   // An alarm that fires when a ping should be sent.
   QuicArenaScopedPtr<QuicAlarm> ping_alarm_;
   // An alarm that fires when an MTU probe should be sent.
@@ -2247,6 +2250,10 @@
   // If true, send connection close packet on INVALID_VERSION.
   bool send_connection_close_for_invalid_version_ = false;
 
+  const bool use_ping_manager_ = GetQuicReloadableFlag(quic_use_ping_manager);
+
+  QuicPingManager ping_manager_;
+
   // TODO(b/205023946) Debug-only fields, to be deprecated after the bug is
   // fixed.
   absl::optional<QuicWallTime> quic_bug_10511_43_timestamp_;
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 085f9d3..bfc2462 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -93,6 +93,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_update_ack_timeout_on_receipt_time, true)
 // If true, use BBRv2 as the default congestion controller. Takes precedence over --quic_default_to_bbr.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr_v2, false)
+// If true, use PING manager to manage the PING alarm.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_use_ping_manager, true)
 // If true, use new connection ID in connection migration.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_migration_use_new_cid_v2, true)
 // If true, uses conservative cwnd gain and pacing gain when cwnd gets bootstrapped.
diff --git a/quiche/quic/core/quic_one_block_arena.h b/quiche/quic/core/quic_one_block_arena.h
index 4bebebe..05d335b 100644
--- a/quiche/quic/core/quic_one_block_arena.h
+++ b/quiche/quic/core/quic_one_block_arena.h
@@ -69,7 +69,7 @@
 
 // QuicConnections currently use around 1KB of polymorphic types which would
 // ordinarily be on the heap. Instead, store them inline in an arena.
-using QuicConnectionArena = QuicOneBlockArena<1152>;
+using QuicConnectionArena = QuicOneBlockArena<1248>;
 
 }  // namespace quic
 
diff --git a/quiche/quic/core/quic_ping_manager.cc b/quiche/quic/core/quic_ping_manager.cc
new file mode 100644
index 0000000..7ce158d
--- /dev/null
+++ b/quiche/quic/core/quic_ping_manager.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2022 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/quic_ping_manager.h"
+
+namespace quic {
+
+namespace {
+
+class AlarmDelegate : public QuicAlarm::DelegateWithContext {
+ public:
+  explicit AlarmDelegate(QuicPingManager* manager,
+                         QuicConnectionContext* context)
+      : QuicAlarm::DelegateWithContext(context), manager_(manager) {}
+  AlarmDelegate(const AlarmDelegate&) = delete;
+  AlarmDelegate& operator=(const AlarmDelegate&) = delete;
+
+  void OnAlarm() override { manager_->OnAlarm(); }
+
+ private:
+  QuicPingManager* manager_;
+};
+
+}  // namespace
+
+QuicPingManager::QuicPingManager(Perspective perspective, Delegate* delegate,
+                                 QuicConnectionArena* arena,
+                                 QuicAlarmFactory* alarm_factory,
+                                 QuicConnectionContext* context)
+    : perspective_(perspective),
+      delegate_(delegate),
+      alarm_(alarm_factory->CreateAlarm(
+          arena->New<AlarmDelegate>(this, context), arena)) {}
+
+void QuicPingManager::SetAlarm(QuicTime now, bool should_keep_alive,
+                               bool has_in_flight_packets) {
+  UpdateDeadlines(now, should_keep_alive, has_in_flight_packets);
+  const QuicTime earliest_deadline = GetEarliestDeadline();
+  if (!earliest_deadline.IsInitialized()) {
+    alarm_->Cancel();
+    return;
+  }
+  if (earliest_deadline == keep_alive_deadline_) {
+    // Use 1s granularity for keep-alive time.
+    alarm_->Update(earliest_deadline, QuicTime::Delta::FromSeconds(1));
+    return;
+  }
+  alarm_->Update(earliest_deadline, kAlarmGranularity);
+  if (GetQuicFlag(
+          FLAGS_quic_max_aggressive_retransmittable_on_wire_ping_count) != 0) {
+    ++consecutive_retransmittable_on_wire_count_;
+  }
+  ++retransmittable_on_wire_count_;
+}
+
+void QuicPingManager::OnAlarm() {
+  const QuicTime earliest_deadline = GetEarliestDeadline();
+  if (!earliest_deadline.IsInitialized()) {
+    QUIC_BUG(quic_ping_manager_alarm_fires_unexpectedly)
+        << "QuicPingManager alarm fires unexpectedly.";
+    return;
+  }
+  // Please note, alarm does not get re-armed here, and we are relying on caller
+  // to SetAlarm later.
+  if (earliest_deadline == retransmittable_on_wire_deadline_) {
+    retransmittable_on_wire_deadline_ = QuicTime::Zero();
+    delegate_->OnRetransmittableOnWireTimeout();
+    return;
+  }
+  if (earliest_deadline == keep_alive_deadline_) {
+    keep_alive_deadline_ = QuicTime::Zero();
+    delegate_->OnKeepAliveTimeout();
+  }
+}
+
+void QuicPingManager::Stop() {
+  alarm_->PermanentCancel();
+  retransmittable_on_wire_deadline_ = QuicTime::Zero();
+  keep_alive_deadline_ = QuicTime::Zero();
+}
+
+void QuicPingManager::UpdateDeadlines(QuicTime now, bool should_keep_alive,
+                                      bool has_in_flight_packets) {
+  // Reset keep-alive deadline given it will be set later (with left edge
+  // |now|).
+  keep_alive_deadline_ = QuicTime::Zero();
+  if (perspective_ == Perspective::IS_SERVER &&
+      initial_retransmittable_on_wire_timeout_.IsInfinite()) {
+    // The PING alarm exists to support two features:
+    // 1) clients send PINGs every 15s to prevent NAT timeouts,
+    // 2) both clients and servers can send retransmittable on the wire PINGs
+    // (ROWP) while ShouldKeepConnectionAlive is true and there is no packets in
+    // flight.
+    QUICHE_DCHECK(!retransmittable_on_wire_deadline_.IsInitialized());
+    return;
+  }
+  if (!should_keep_alive) {
+    // Don't send a ping unless the application (ie: HTTP/3) says to, usually
+    // because it is expecting a response from the peer.
+    retransmittable_on_wire_deadline_ = QuicTime::Zero();
+    return;
+  }
+  if (perspective_ == Perspective::IS_CLIENT) {
+    // Clients send 15s PINGs to avoid NATs from timing out.
+    keep_alive_deadline_ = now + keep_alive_timeout_;
+  }
+  if (initial_retransmittable_on_wire_timeout_.IsInfinite() ||
+      has_in_flight_packets ||
+      retransmittable_on_wire_count_ >
+          GetQuicFlag(FLAGS_quic_max_retransmittable_on_wire_ping_count)) {
+    // No need to set retransmittable-on-wire timeout.
+    retransmittable_on_wire_deadline_ = QuicTime::Zero();
+    return;
+  }
+
+  QUICHE_DCHECK_LT(initial_retransmittable_on_wire_timeout_,
+                   keep_alive_timeout_);
+  QuicTime::Delta retransmittable_on_wire_timeout =
+      initial_retransmittable_on_wire_timeout_;
+  const int max_aggressive_retransmittable_on_wire_count =
+      GetQuicFlag(FLAGS_quic_max_aggressive_retransmittable_on_wire_ping_count);
+  QUICHE_DCHECK_LE(0, max_aggressive_retransmittable_on_wire_count);
+  if (consecutive_retransmittable_on_wire_count_ >
+      max_aggressive_retransmittable_on_wire_count) {
+    // Exponentially back off the timeout if the number of consecutive
+    // retransmittable on wire pings has exceeds the allowance.
+    int shift = consecutive_retransmittable_on_wire_count_ -
+                max_aggressive_retransmittable_on_wire_count;
+    retransmittable_on_wire_timeout =
+        initial_retransmittable_on_wire_timeout_ * (1 << shift);
+  }
+  if (retransmittable_on_wire_deadline_.IsInitialized() &&
+      retransmittable_on_wire_deadline_ <
+          now + retransmittable_on_wire_timeout) {
+    // Alarm is set to an earlier time. Do not postpone it.
+    QUIC_BUG_IF(quic_retransmittable_on_wire_deadline_is_in_past,
+                retransmittable_on_wire_deadline_ < now)
+        << "QUIC retransmittable PING deadline is in past";
+    return;
+  }
+  retransmittable_on_wire_deadline_ = now + retransmittable_on_wire_timeout;
+}
+
+QuicTime QuicPingManager::GetEarliestDeadline() const {
+  QuicTime earliest_deadline = QuicTime::Zero();
+  for (QuicTime t : {retransmittable_on_wire_deadline_, keep_alive_deadline_}) {
+    if (!t.IsInitialized()) {
+      continue;
+    }
+    if (!earliest_deadline.IsInitialized() || t < earliest_deadline) {
+      earliest_deadline = t;
+    }
+  }
+  return earliest_deadline;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/core/quic_ping_manager.h b/quiche/quic/core/quic_ping_manager.h
new file mode 100644
index 0000000..d88dac2
--- /dev/null
+++ b/quiche/quic/core/quic_ping_manager.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2022 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_QUIC_PING_MANAGER_H_
+#define QUICHE_QUIC_CORE_QUIC_PING_MANAGER_H_
+
+#include "quiche/quic/core/quic_alarm.h"
+#include "quiche/quic/core/quic_alarm_factory.h"
+#include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/core/quic_one_block_arena.h"
+#include "quiche/quic/core/quic_time.h"
+#include "quiche/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+namespace test {
+class QuicConnectionPeer;
+class QuicPingManagerPeer;
+}  // namespace test
+
+// QuicPingManager manages an alarm that has two modes:
+// 1) keep-alive. When alarm fires, send packet to extend idle timeout to keep
+// connection alive.
+// 2) retransmittable-on-wire. When alarm fires, send packets to detect path
+// degrading (used in IP/port migrations).
+class QUIC_EXPORT_PRIVATE QuicPingManager {
+ public:
+  // Interface that get notified when |alarm_| fires.
+  class QUIC_EXPORT_PRIVATE Delegate {
+   public:
+    virtual ~Delegate() {}
+
+    // Called when alarm fires in keep-alive mode.
+    virtual void OnKeepAliveTimeout() = 0;
+    // Called when alarm fires in retransmittable-on-wire mode.
+    virtual void OnRetransmittableOnWireTimeout() = 0;
+  };
+
+  QuicPingManager(Perspective perspective, Delegate* delegate,
+                  QuicConnectionArena* arena, QuicAlarmFactory* alarm_factory,
+                  QuicConnectionContext* context);
+
+  // Called to set |alarm_|.
+  void SetAlarm(QuicTime now, bool should_keep_alive,
+                bool has_in_flight_packets);
+
+  // Called when |alarm_| fires.
+  void OnAlarm();
+
+  // Called to stop |alarm_| permanently.
+  void Stop();
+
+  void set_keep_alive_timeout(QuicTime::Delta keep_alive_timeout) {
+    QUICHE_DCHECK(!alarm_->IsSet());
+    keep_alive_timeout_ = keep_alive_timeout;
+  }
+
+  void set_initial_retransmittable_on_wire_timeout(
+      QuicTime::Delta retransmittable_on_wire_timeout) {
+    QUICHE_DCHECK(!alarm_->IsSet());
+    initial_retransmittable_on_wire_timeout_ = retransmittable_on_wire_timeout;
+  }
+
+  void reset_consecutive_retransmittable_on_wire_count() {
+    consecutive_retransmittable_on_wire_count_ = 0;
+  }
+
+ 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);
+
+  // Get earliest deadline of |retransmittable_on_wire_deadline_| and
+  // |keep_alive_deadline_|. Returns 0 if both deadlines are not initialized.
+  QuicTime GetEarliestDeadline() const;
+
+  Perspective perspective_;
+
+  Delegate* delegate_;  // Not owned.
+
+  // Initial timeout for how long the wire can have no retransmittable packets.
+  QuicTime::Delta initial_retransmittable_on_wire_timeout_ =
+      QuicTime::Delta::Infinite();
+
+  // Indicates how many consecutive retransmittable-on-wire has been armed
+  // (since last reset).
+  int consecutive_retransmittable_on_wire_count_ = 0;
+
+  // Indicates how many retransmittable-on-wire has been armed in total.
+  int retransmittable_on_wire_count_ = 0;
+
+  QuicTime::Delta keep_alive_timeout_ =
+      QuicTime::Delta::FromSeconds(kPingTimeoutSecs);
+
+  QuicTime retransmittable_on_wire_deadline_ = QuicTime::Zero();
+
+  QuicTime keep_alive_deadline_ = QuicTime::Zero();
+
+  QuicArenaScopedPtr<QuicAlarm> alarm_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QUIC_PING_MANAGER_H_
diff --git a/quiche/quic/core/quic_ping_manager_test.cc b/quiche/quic/core/quic_ping_manager_test.cc
new file mode 100644
index 0000000..e7b4b78
--- /dev/null
+++ b/quiche/quic/core/quic_ping_manager_test.cc
@@ -0,0 +1,381 @@
+// Copyright (c) 2022 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/quic_ping_manager.h"
+
+#include "quiche/quic/core/quic_one_block_arena.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+namespace test {
+
+class QuicPingManagerPeer {
+ public:
+  static QuicAlarm* GetAlarm(QuicPingManager* manager) {
+    return manager->alarm_.get();
+  }
+};
+
+namespace {
+
+const bool kShouldKeepAlive = true;
+const bool kHasInflightPackets = true;
+
+class MockDelegate : public QuicPingManager::Delegate {
+ public:
+  MOCK_METHOD(void, OnKeepAliveTimeout, (), (override));
+  MOCK_METHOD(void, OnRetransmittableOnWireTimeout, (), (override));
+};
+
+class QuicPingManagerTest : public QuicTest {
+ public:
+  QuicPingManagerTest()
+      : manager_(Perspective::IS_CLIENT, &delegate_, &arena_, &alarm_factory_,
+                 /*context=*/nullptr),
+        alarm_(static_cast<MockAlarmFactory::TestAlarm*>(
+            QuicPingManagerPeer::GetAlarm(&manager_))) {
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  }
+
+ protected:
+  testing::StrictMock<MockDelegate> delegate_;
+  MockClock clock_;
+  QuicConnectionArena arena_;
+  MockAlarmFactory alarm_factory_;
+  QuicPingManager manager_;
+  MockAlarmFactory::TestAlarm* alarm_;
+};
+
+TEST_F(QuicPingManagerTest, KeepAliveTimeout) {
+  EXPECT_FALSE(alarm_->IsSet());
+
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  // Set alarm with in flight packets.
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    kHasInflightPackets);
+  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);
+  EXPECT_TRUE(alarm_->IsSet());
+  // Verify the deadline is set slightly less than 15 seconds in the future,
+  // because of the 1s alarm granularity.
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs) -
+                QuicTime::Delta::FromMilliseconds(5),
+            alarm_->deadline() - clock_.ApproximateNow());
+
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kPingTimeoutSecs));
+  EXPECT_CALL(delegate_, OnKeepAliveTimeout());
+  alarm_->Fire();
+  EXPECT_FALSE(alarm_->IsSet());
+  // Reset alarm with in flight packets.
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    kHasInflightPackets);
+  EXPECT_TRUE(alarm_->IsSet());
+
+  // Verify alarm is not armed if !kShouldKeepAlive.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive,
+                    kHasInflightPackets);
+  EXPECT_FALSE(alarm_->IsSet());
+}
+
+TEST_F(QuicPingManagerTest, CustomizedKeepAliveTimeout) {
+  EXPECT_FALSE(alarm_->IsSet());
+
+  // Set customized keep-alive timeout.
+  manager_.set_keep_alive_timeout(QuicTime::Delta::FromSeconds(10));
+
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  // Set alarm with in flight packets.
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    kHasInflightPackets);
+  EXPECT_TRUE(alarm_->IsSet());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(10),
+            alarm_->deadline() - clock_.ApproximateNow());
+
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  // Set alarm with no in flight packets.
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    !kHasInflightPackets);
+  EXPECT_TRUE(alarm_->IsSet());
+  // The deadline is set slightly less than 10 seconds in the future, because
+  // of the 1s alarm granularity.
+  EXPECT_EQ(
+      QuicTime::Delta::FromSeconds(10) - QuicTime::Delta::FromMilliseconds(5),
+      alarm_->deadline() - clock_.ApproximateNow());
+
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10));
+  EXPECT_CALL(delegate_, OnKeepAliveTimeout());
+  alarm_->Fire();
+  EXPECT_FALSE(alarm_->IsSet());
+  // Reset alarm with in flight packets.
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    kHasInflightPackets);
+  EXPECT_TRUE(alarm_->IsSet());
+
+  // Verify alarm is not armed if !kShouldKeepAlive.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive,
+                    kHasInflightPackets);
+  EXPECT_FALSE(alarm_->IsSet());
+}
+
+TEST_F(QuicPingManagerTest, RetransmittableOnWireTimeout) {
+  const QuicTime::Delta kRtransmittableOnWireTimeout =
+      QuicTime::Delta::FromMilliseconds(50);
+  manager_.set_initial_retransmittable_on_wire_timeout(
+      kRtransmittableOnWireTimeout);
+
+  EXPECT_FALSE(alarm_->IsSet());
+
+  // Set alarm with in flight packets.
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    kHasInflightPackets);
+  // 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));
+  // Set alarm with no in flight packets.
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    !kHasInflightPackets);
+  EXPECT_TRUE(alarm_->IsSet());
+  // Verify alarm is in retransmittable-on-wire mode.
+  EXPECT_EQ(kRtransmittableOnWireTimeout,
+            alarm_->deadline() - clock_.ApproximateNow());
+
+  clock_.AdvanceTime(kRtransmittableOnWireTimeout);
+  EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
+  alarm_->Fire();
+  EXPECT_FALSE(alarm_->IsSet());
+  // Reset alarm with in flight packets.
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    kHasInflightPackets);
+  // Verify the alarm is in keep-alive mode.
+  ASSERT_TRUE(alarm_->IsSet());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
+            alarm_->deadline() - clock_.ApproximateNow());
+}
+
+TEST_F(QuicPingManagerTest, RetransmittableOnWireTimeoutExponentiallyBackOff) {
+  const int kMaxAggressiveRetransmittableOnWireCount = 5;
+  SetQuicFlag(FLAGS_quic_max_aggressive_retransmittable_on_wire_ping_count,
+              kMaxAggressiveRetransmittableOnWireCount);
+  const QuicTime::Delta initial_retransmittable_on_wire_timeout =
+      QuicTime::Delta::FromMilliseconds(200);
+  manager_.set_initial_retransmittable_on_wire_timeout(
+      initial_retransmittable_on_wire_timeout);
+
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  EXPECT_FALSE(alarm_->IsSet());
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    kHasInflightPackets);
+  // 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);
+    EXPECT_TRUE(alarm_->IsSet());
+    // Verify alarm is in retransmittable-on-wire mode.
+    EXPECT_EQ(initial_retransmittable_on_wire_timeout,
+              alarm_->deadline() - clock_.ApproximateNow());
+    clock_.AdvanceTime(initial_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);
+  }
+
+  QuicTime::Delta retransmittable_on_wire_timeout =
+      initial_retransmittable_on_wire_timeout;
+
+  // 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);
+    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);
+  }
+
+  // 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);
+  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());
+}
+
+TEST_F(QuicPingManagerTest,
+       ResetRetransmitableOnWireTimeoutExponentiallyBackOff) {
+  const int kMaxAggressiveRetransmittableOnWireCount = 3;
+  SetQuicFlag(FLAGS_quic_max_aggressive_retransmittable_on_wire_ping_count,
+              kMaxAggressiveRetransmittableOnWireCount);
+  const QuicTime::Delta initial_retransmittable_on_wire_timeout =
+      QuicTime::Delta::FromMilliseconds(200);
+  manager_.set_initial_retransmittable_on_wire_timeout(
+      initial_retransmittable_on_wire_timeout);
+
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  EXPECT_FALSE(alarm_->IsSet());
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    kHasInflightPackets);
+  // 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));
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    !kHasInflightPackets);
+  EXPECT_TRUE(alarm_->IsSet());
+  // Verify alarm is in retransmittable-on-wire mode.
+  EXPECT_EQ(initial_retransmittable_on_wire_timeout,
+            alarm_->deadline() - clock_.ApproximateNow());
+
+  EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
+  clock_.AdvanceTime(initial_retransmittable_on_wire_timeout);
+  alarm_->Fire();
+
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    !kHasInflightPackets);
+  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);
+  EXPECT_EQ(initial_retransmittable_on_wire_timeout,
+            alarm_->deadline() - clock_.ApproximateNow());
+
+  for (int i = 0; i < kMaxAggressiveRetransmittableOnWireCount; i++) {
+    manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                      !kHasInflightPackets);
+    EXPECT_TRUE(alarm_->IsSet());
+    EXPECT_EQ(initial_retransmittable_on_wire_timeout,
+              alarm_->deadline() - clock_.ApproximateNow());
+    clock_.AdvanceTime(initial_retransmittable_on_wire_timeout);
+    EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
+    alarm_->Fire();
+    // Reset alarm with in flight packets.
+    manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                      kHasInflightPackets);
+    // Advance 5ms to receive next packet.
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  }
+
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    !kHasInflightPackets);
+  EXPECT_TRUE(alarm_->IsSet());
+  EXPECT_EQ(initial_retransmittable_on_wire_timeout * 2,
+            alarm_->deadline() - clock_.ApproximateNow());
+
+  clock_.AdvanceTime(2 * initial_retransmittable_on_wire_timeout);
+  EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
+  alarm_->Fire();
+
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  manager_.reset_consecutive_retransmittable_on_wire_count();
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    !kHasInflightPackets);
+  EXPECT_TRUE(alarm_->IsSet());
+  EXPECT_EQ(initial_retransmittable_on_wire_timeout,
+            alarm_->deadline() - clock_.ApproximateNow());
+}
+
+TEST_F(QuicPingManagerTest, RetransmittableOnWireLimit) {
+  static constexpr int kMaxRetransmittableOnWirePingCount = 3;
+  SetQuicFlag(FLAGS_quic_max_retransmittable_on_wire_ping_count,
+              kMaxRetransmittableOnWirePingCount);
+  static constexpr QuicTime::Delta initial_retransmittable_on_wire_timeout =
+      QuicTime::Delta::FromMilliseconds(200);
+  static constexpr QuicTime::Delta kShortDelay =
+      QuicTime::Delta::FromMilliseconds(5);
+  ASSERT_LT(kShortDelay * 10, initial_retransmittable_on_wire_timeout);
+  manager_.set_initial_retransmittable_on_wire_timeout(
+      initial_retransmittable_on_wire_timeout);
+
+  clock_.AdvanceTime(kShortDelay);
+  EXPECT_FALSE(alarm_->IsSet());
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    kHasInflightPackets);
+
+  EXPECT_TRUE(alarm_->IsSet());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
+            alarm_->deadline() - clock_.ApproximateNow());
+
+  for (int i = 0; i <= kMaxRetransmittableOnWirePingCount; i++) {
+    clock_.AdvanceTime(kShortDelay);
+    manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                      !kHasInflightPackets);
+    EXPECT_TRUE(alarm_->IsSet());
+    EXPECT_EQ(initial_retransmittable_on_wire_timeout,
+              alarm_->deadline() - clock_.ApproximateNow());
+    clock_.AdvanceTime(initial_retransmittable_on_wire_timeout);
+    EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout());
+    alarm_->Fire();
+    manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                      kHasInflightPackets);
+  }
+
+  manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive,
+                    !kHasInflightPackets);
+  EXPECT_TRUE(alarm_->IsSet());
+  // Verify alarm is in keep-alive mode.
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
+            alarm_->deadline() - clock_.ApproximateNow());
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kPingTimeoutSecs));
+  EXPECT_CALL(delegate_, OnKeepAliveTimeout());
+  alarm_->Fire();
+  EXPECT_FALSE(alarm_->IsSet());
+}
+
+}  // namespace
+
+}  // namespace test
+}  // namespace quic
diff --git a/quiche/quic/test_tools/quic_connection_peer.cc b/quiche/quic/test_tools/quic_connection_peer.cc
index e124580..9eaa6fb 100644
--- a/quiche/quic/test_tools/quic_connection_peer.cc
+++ b/quiche/quic/test_tools/quic_connection_peer.cc
@@ -64,6 +64,7 @@
                                         Perspective perspective) {
   connection->perspective_ = perspective;
   QuicFramerPeer::SetPerspective(&connection->framer_, perspective);
+  connection->ping_manager_.perspective_ = perspective;
 }
 
 // static
@@ -128,6 +129,9 @@
 
 // static
 QuicAlarm* QuicConnectionPeer::GetPingAlarm(QuicConnection* connection) {
+  if (GetQuicReloadableFlag(quic_use_ping_manager)) {
+    return connection->ping_manager_.alarm_.get();
+  }
   return connection->ping_alarm_.get();
 }