Add QuicSelfIssuedConnectionIdManager that handles RetireConnectionId Frame.

PiperOrigin-RevId: 353861078
Change-Id: I878c07e4a5430860e7f31551d5085f66efc94a96
diff --git a/quic/core/quic_connection_id_manager.cc b/quic/core/quic_connection_id_manager.cc
index d4380b7..c12ecbd 100644
--- a/quic/core/quic_connection_id_manager.cc
+++ b/quic/core/quic_connection_id_manager.cc
@@ -8,6 +8,7 @@
 #include "quic/core/quic_clock.h"
 #include "quic/core/quic_connection_id.h"
 #include "quic/core/quic_error_codes.h"
+#include "quic/core/quic_utils.h"
 #include "quic/platform/api/quic_uint128.h"
 
 namespace quic {
@@ -59,6 +60,7 @@
       clock_(clock),
       retire_connection_id_alarm_(alarm_factory->CreateAlarm(
           new RetirePeerIssuedConnectionIdAlarm(visitor))) {
+  DCHECK_GE(active_connection_id_limit_, 2u);
   DCHECK(!initial_peer_issued_connection_id.IsEmpty());
   active_connection_id_data_.emplace_back(initial_peer_issued_connection_id,
                                           /*sequence_number=*/0u,
@@ -204,4 +206,159 @@
   return result;
 }
 
+namespace {
+
+class RetireSelfIssuedConnectionIdAlarmDelegate : public QuicAlarm::Delegate {
+ public:
+  explicit RetireSelfIssuedConnectionIdAlarmDelegate(
+      QuicSelfIssuedConnectionIdManager* connection_id_manager)
+      : connection_id_manager_(connection_id_manager) {}
+  RetireSelfIssuedConnectionIdAlarmDelegate(
+      const RetireSelfIssuedConnectionIdAlarmDelegate&) = delete;
+  RetireSelfIssuedConnectionIdAlarmDelegate& operator=(
+      const RetireSelfIssuedConnectionIdAlarmDelegate&) = delete;
+
+  void OnAlarm() override { connection_id_manager_->RetireConnectionId(); }
+
+ private:
+  QuicSelfIssuedConnectionIdManager* connection_id_manager_;
+};
+
+}  // namespace
+
+QuicSelfIssuedConnectionIdManager::QuicSelfIssuedConnectionIdManager(
+    size_t active_connection_id_limit,
+    const QuicConnectionId& initial_connection_id,
+    const QuicClock* clock,
+    QuicAlarmFactory* alarm_factory,
+    QuicConnectionIdManagerVisitorInterface* visitor)
+    : active_connection_id_limit_(active_connection_id_limit),
+      clock_(clock),
+      visitor_(visitor),
+      retire_connection_id_alarm_(alarm_factory->CreateAlarm(
+          new RetireSelfIssuedConnectionIdAlarmDelegate(this))),
+      last_connection_id_(initial_connection_id),
+      next_connection_id_sequence_number_(1u) {
+  active_connection_ids_.emplace_back(initial_connection_id, 0u);
+}
+
+QuicSelfIssuedConnectionIdManager::~QuicSelfIssuedConnectionIdManager() {
+  retire_connection_id_alarm_->Cancel();
+}
+
+QuicConnectionId QuicSelfIssuedConnectionIdManager::GenerateNewConnectionId(
+    const QuicConnectionId& old_connection_id) const {
+  return QuicUtils::CreateReplacementConnectionId(old_connection_id);
+}
+
+QuicNewConnectionIdFrame
+QuicSelfIssuedConnectionIdManager::IssueNewConnectionId() {
+  QuicNewConnectionIdFrame frame;
+  frame.connection_id = GenerateNewConnectionId(last_connection_id_);
+  frame.sequence_number = next_connection_id_sequence_number_++;
+  frame.stateless_reset_token =
+      QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+  visitor_->OnNewConnectionIdIssued(frame.connection_id);
+  active_connection_ids_.emplace_back(frame.connection_id,
+                                      frame.sequence_number);
+  frame.retire_prior_to = active_connection_ids_.front().second;
+  last_connection_id_ = frame.connection_id;
+  return frame;
+}
+
+QuicNewConnectionIdFrame
+QuicSelfIssuedConnectionIdManager::IssueNewConnectionIdForPreferredAddress() {
+  QuicNewConnectionIdFrame frame = IssueNewConnectionId();
+  DCHECK_EQ(frame.sequence_number, 1u);
+  return frame;
+}
+
+QuicErrorCode QuicSelfIssuedConnectionIdManager::OnRetireConnectionIdFrame(
+    const QuicRetireConnectionIdFrame& frame,
+    QuicTime::Delta pto_delay,
+    std::string* error_detail) {
+  DCHECK(!active_connection_ids_.empty());
+  if (frame.sequence_number > active_connection_ids_.back().second) {
+    *error_detail = "To be retired connecton ID is never issued.";
+    return IETF_QUIC_PROTOCOL_VIOLATION;
+  }
+
+  auto it =
+      std::find_if(active_connection_ids_.begin(), active_connection_ids_.end(),
+                   [&frame](const std::pair<QuicConnectionId, uint64_t>& p) {
+                     return p.second == frame.sequence_number;
+                   });
+  // The corresponding connection ID has been retired. Ignore.
+  if (it == active_connection_ids_.end()) {
+    return QUIC_NO_ERROR;
+  }
+
+  if (to_be_retired_connection_ids_.size() + active_connection_ids_.size() >=
+      kMaxNumConnectonIdsInUse) {
+    // Close connection if the number of connection IDs in use will exeed the
+    // limit, i.e., peer retires connection ID too fast.
+    *error_detail = "There are too many connection IDs in use.";
+    return QUIC_TOO_MANY_CONNECTION_ID_WAITING_TO_RETIRE;
+  }
+
+  QuicTime retirement_time = clock_->ApproximateNow() + 3 * pto_delay;
+  if (!to_be_retired_connection_ids_.empty()) {
+    retirement_time =
+        std::max(retirement_time, to_be_retired_connection_ids_.back().second);
+  }
+
+  to_be_retired_connection_ids_.emplace_back(it->first, retirement_time);
+  if (!retire_connection_id_alarm_->IsSet()) {
+    retire_connection_id_alarm_->Set(retirement_time);
+  }
+
+  active_connection_ids_.erase(it);
+  MaybeSendNewConnectionIds();
+
+  return QUIC_NO_ERROR;
+}
+
+std::vector<QuicConnectionId>
+QuicSelfIssuedConnectionIdManager::GetUnretiredConnectionIds() const {
+  std::vector<QuicConnectionId> unretired_ids;
+  for (const auto& cid_pair : to_be_retired_connection_ids_) {
+    unretired_ids.push_back(cid_pair.first);
+  }
+  for (const auto& cid_pair : active_connection_ids_) {
+    unretired_ids.push_back(cid_pair.first);
+  }
+  return unretired_ids;
+}
+
+void QuicSelfIssuedConnectionIdManager::RetireConnectionId() {
+  if (to_be_retired_connection_ids_.empty()) {
+    QUIC_BUG
+        << "retire_connection_id_alarm fired but there is no connection ID "
+           "to be retired.";
+    return;
+  }
+  QuicTime now = clock_->ApproximateNow();
+  auto it = to_be_retired_connection_ids_.begin();
+  do {
+    visitor_->OnSelfIssuedConnectionIdRetired(it->first);
+    ++it;
+  } while (it != to_be_retired_connection_ids_.end() && it->second <= now);
+  to_be_retired_connection_ids_.erase(to_be_retired_connection_ids_.begin(),
+                                      it);
+  // Set the alarm again if there is another connection ID to be removed.
+  if (!to_be_retired_connection_ids_.empty()) {
+    retire_connection_id_alarm_->Set(
+        to_be_retired_connection_ids_.front().second);
+  }
+}
+
+void QuicSelfIssuedConnectionIdManager::MaybeSendNewConnectionIds() {
+  while (active_connection_ids_.size() < active_connection_id_limit_) {
+    QuicNewConnectionIdFrame frame = IssueNewConnectionId();
+    if (!visitor_->SendNewConnectionId(frame)) {
+      break;
+    }
+  }
+}
+
 }  // namespace quic
diff --git a/quic/core/quic_connection_id_manager.h b/quic/core/quic_connection_id_manager.h
index 1977740..bb31da9 100644
--- a/quic/core/quic_connection_id_manager.h
+++ b/quic/core/quic_connection_id_manager.h
@@ -4,17 +4,19 @@
 
 // QuicPeerIssuedConnectionIdManager handles the states associated with receving
 // and retiring peer issued connection Ids.
-// TODO(haoyuewang) Implements QuicSelfIssuedConnectionIdManager.
-// QuicConnectionIdManager handles the states associated with connection Ids
-// issued by the current end point.
+// QuicSelfIssuedConnectionIdManager handles the states associated with
+// connection Ids issued by the current end point.
 
 #ifndef QUICHE_QUIC_CORE_QUIC_CONNECTION_ID_MANAGER_H_
 #define QUICHE_QUIC_CORE_QUIC_CONNECTION_ID_MANAGER_H_
 
+#include <cstddef>
 #include <memory>
 #include "quic/core/frames/quic_new_connection_id_frame.h"
+#include "quic/core/frames/quic_retire_connection_id_frame.h"
 #include "quic/core/quic_alarm.h"
 #include "quic/core/quic_alarm_factory.h"
+#include "quic/core/quic_clock.h"
 #include "quic/core/quic_connection_id.h"
 #include "quic/core/quic_interval_set.h"
 #include "quic/platform/api/quic_uint128.h"
@@ -38,6 +40,11 @@
  public:
   virtual ~QuicConnectionIdManagerVisitorInterface() = default;
   virtual void OnPeerIssuedConnectionIdRetired() = 0;
+  virtual bool SendNewConnectionId(const QuicNewConnectionIdFrame& frame) = 0;
+  virtual void OnNewConnectionIdIssued(
+      const QuicConnectionId& connection_id) = 0;
+  virtual void OnSelfIssuedConnectionIdRetired(
+      const QuicConnectionId& connection_id) = 0;
 };
 
 class QUIC_EXPORT_PRIVATE QuicPeerIssuedConnectionIdManager {
@@ -91,6 +98,62 @@
   uint64_t max_new_connection_id_frame_retire_prior_to_ = 0u;
 };
 
+class QUIC_EXPORT_PRIVATE QuicSelfIssuedConnectionIdManager {
+ public:
+  QuicSelfIssuedConnectionIdManager(
+      size_t active_connection_id_limit,
+      const QuicConnectionId& initial_connection_id,
+      const QuicClock* clock,
+      QuicAlarmFactory* alarm_factory,
+      QuicConnectionIdManagerVisitorInterface* visitor);
+
+  virtual ~QuicSelfIssuedConnectionIdManager();
+
+  QuicNewConnectionIdFrame IssueNewConnectionIdForPreferredAddress();
+
+  QuicErrorCode OnRetireConnectionIdFrame(
+      const QuicRetireConnectionIdFrame& frame,
+      QuicTime::Delta pto_delay,
+      std::string* error_detail);
+
+  std::vector<QuicConnectionId> GetUnretiredConnectionIds() const;
+
+  // Called when the retire_connection_id alarm_ fires. Removes the to be
+  // retired connection ID locally.
+  void RetireConnectionId();
+
+  // Sends new connection IDs if more can be sent.
+  void MaybeSendNewConnectionIds();
+
+  virtual QuicConnectionId GenerateNewConnectionId(
+      const QuicConnectionId& old_connection_id) const;
+
+ private:
+  friend class QuicConnectionIdManagerPeer;
+
+  QuicNewConnectionIdFrame IssueNewConnectionId();
+
+  // This should be set to the min of:
+  // (1) # of connection atcive IDs that peer can maintain.
+  // (2) maximum # of active connection IDs self plans to issue.
+  size_t active_connection_id_limit_;
+  const QuicClock* clock_;
+  QuicConnectionIdManagerVisitorInterface* visitor_;
+  // This tracks connection IDs issued to the peer but not retired by the peer.
+  // Each pair is a connection ID and its sequence number.
+  std::vector<std::pair<QuicConnectionId, uint64_t>> active_connection_ids_;
+  // This tracks connection IDs retired by the peer but has not been retired
+  // locally. Each pair is a connection ID and the time by which it should be
+  // retired.
+  std::vector<std::pair<QuicConnectionId, QuicTime>>
+      to_be_retired_connection_ids_;
+  // An alarm that fires when a connection ID should be retired.
+  std::unique_ptr<QuicAlarm> retire_connection_id_alarm_;
+  // State of the last issued connection Id.
+  QuicConnectionId last_connection_id_;
+  uint64_t next_connection_id_sequence_number_;
+};
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_QUIC_CONNECTION_ID_MANAGER_H_
diff --git a/quic/core/quic_connection_id_manager_test.cc b/quic/core/quic_connection_id_manager_test.cc
index 47d3dea..fe87f05 100644
--- a/quic/core/quic_connection_id_manager_test.cc
+++ b/quic/core/quic_connection_id_manager_test.cc
@@ -19,6 +19,11 @@
       QuicPeerIssuedConnectionIdManager* manager) {
     return manager->retire_connection_id_alarm_.get();
   }
+
+  static QuicAlarm* GetRetireSelfIssuedConnectionIdAlarm(
+      QuicSelfIssuedConnectionIdManager* manager) {
+    return manager->retire_connection_id_alarm_.get();
+  }
 };
 
 namespace {
@@ -26,8 +31,11 @@
 using ::quic::test::IsError;
 using ::quic::test::IsQuicNoError;
 using ::quic::test::TestConnectionId;
+using ::testing::_;
 using ::testing::ElementsAre;
 using ::testing::IsNull;
+using ::testing::Return;
+using ::testing::StrictMock;
 
 class TestPeerIssuedConnectionIdManagerVisitor
     : public QuicConnectionIdManagerVisitorInterface {
@@ -64,6 +72,14 @@
     return current_peer_issued_connection_id_;
   }
 
+  bool SendNewConnectionId(const QuicNewConnectionIdFrame& /*frame*/) override {
+    return false;
+  }
+  void OnNewConnectionIdIssued(
+      const QuicConnectionId& /*connection_id*/) override {}
+  void OnSelfIssuedConnectionIdRetired(
+      const QuicConnectionId& /*connection_id*/) override {}
+
  private:
   QuicPeerIssuedConnectionIdManager* peer_issued_connection_id_manager_ =
       nullptr;
@@ -75,14 +91,14 @@
  public:
   QuicPeerIssuedConnectionIdManagerTest()
       : peer_issued_cid_manager_(/*active_connection_id_limit=*/2,
-                                 initial_connection_id,
+                                 initial_connection_id_,
                                  &clock_,
                                  &alarm_factory_,
                                  &cid_manager_visitor_) {
     clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
     cid_manager_visitor_.SetPeerIssuedConnectionIdManager(
         &peer_issued_cid_manager_);
-    cid_manager_visitor_.SetCurrentPeerConnectionId(initial_connection_id);
+    cid_manager_visitor_.SetCurrentPeerConnectionId(initial_connection_id_);
     retire_peer_issued_cid_alarm_ =
         QuicConnectionIdManagerPeer::GetRetirePeerIssuedConnectionIdAlarm(
             &peer_issued_cid_manager_);
@@ -92,7 +108,7 @@
   MockClock clock_;
   test::MockAlarmFactory alarm_factory_;
   TestPeerIssuedConnectionIdManagerVisitor cid_manager_visitor_;
-  QuicConnectionId initial_connection_id = TestConnectionId(0);
+  QuicConnectionId initial_connection_id_ = TestConnectionId(0);
   QuicPeerIssuedConnectionIdManager peer_issued_cid_manager_;
   QuicAlarm* retire_peer_issued_cid_alarm_ = nullptr;
   std::string error_details_;
@@ -501,5 +517,434 @@
       IsError(IETF_QUIC_PROTOCOL_VIOLATION));
 }
 
+class TestSelfIssuedConnectionIdManagerVisitor
+    : public QuicConnectionIdManagerVisitorInterface {
+ public:
+  void OnPeerIssuedConnectionIdRetired() override {}
+
+  MOCK_METHOD(bool,
+              SendNewConnectionId,
+              (const QuicNewConnectionIdFrame& frame),
+              (override));
+  MOCK_METHOD(void,
+              OnNewConnectionIdIssued,
+              (const QuicConnectionId& connection_id),
+              (override));
+  MOCK_METHOD(void,
+              OnSelfIssuedConnectionIdRetired,
+              (const QuicConnectionId& connection_id),
+              (override));
+};
+
+class QuicSelfIssuedConnectionIdManagerTest : public QuicTest {
+ public:
+  QuicSelfIssuedConnectionIdManagerTest()
+      : cid_manager_(/*active_connection_id_limit*/ 2,
+                     initial_connection_id_,
+                     &clock_,
+                     &alarm_factory_,
+                     &cid_manager_visitor_) {
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
+    retire_self_issued_cid_alarm_ =
+        QuicConnectionIdManagerPeer::GetRetireSelfIssuedConnectionIdAlarm(
+            &cid_manager_);
+  }
+
+ protected:
+  MockClock clock_;
+  test::MockAlarmFactory alarm_factory_;
+  TestSelfIssuedConnectionIdManagerVisitor cid_manager_visitor_;
+  QuicConnectionId initial_connection_id_ = TestConnectionId(0);
+  StrictMock<QuicSelfIssuedConnectionIdManager> cid_manager_;
+  QuicAlarm* retire_self_issued_cid_alarm_ = nullptr;
+  std::string error_details_;
+  QuicTime::Delta pto_delay_ = QuicTime::Delta::FromMilliseconds(10);
+};
+
+MATCHER_P3(ExpectedNewConnectionIdFrame,
+           connection_id,
+           sequence_number,
+           retire_prior_to,
+           "") {
+  return (arg.connection_id == connection_id) &&
+         (arg.sequence_number == sequence_number) &&
+         (arg.retire_prior_to == retire_prior_to);
+}
+
+TEST_F(QuicSelfIssuedConnectionIdManagerTest,
+       RetireSelfIssuedConnectionIdInOrder) {
+  QuicConnectionId cid0 = initial_connection_id_;
+  QuicConnectionId cid1 = cid_manager_.GenerateNewConnectionId(cid0);
+  QuicConnectionId cid2 = cid_manager_.GenerateNewConnectionId(cid1);
+  QuicConnectionId cid3 = cid_manager_.GenerateNewConnectionId(cid2);
+  QuicConnectionId cid4 = cid_manager_.GenerateNewConnectionId(cid3);
+  QuicConnectionId cid5 = cid_manager_.GenerateNewConnectionId(cid4);
+
+  // Sends CID #1 to peer.
+  EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid1));
+  EXPECT_CALL(cid_manager_visitor_,
+              SendNewConnectionId(ExpectedNewConnectionIdFrame(cid1, 1u, 0u)))
+      .WillOnce(Return(true));
+  cid_manager_.MaybeSendNewConnectionIds();
+
+  {
+    // Peer retires CID #0;
+    // Sends CID #2 and asks peer to retire CIDs prior to #1.
+    // Outcome: (#1, #2) are active.
+    EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid2));
+    EXPECT_CALL(cid_manager_visitor_,
+                SendNewConnectionId(ExpectedNewConnectionIdFrame(cid2, 2u, 1u)))
+        .WillOnce(Return(true));
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = 0u;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+  }
+
+  {
+    // Peer retires CID #1;
+    // Sends CID #3 and asks peer to retire CIDs prior to #2.
+    // Outcome: (#2, #3) are active.
+    EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid3));
+    EXPECT_CALL(cid_manager_visitor_,
+                SendNewConnectionId(ExpectedNewConnectionIdFrame(cid3, 3u, 2u)))
+        .WillOnce(Return(true));
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = 1u;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+  }
+
+  {
+    // Peer retires CID #2;
+    // Sends CID #4 and asks peer to retire CIDs prior to #3.
+    // Outcome: (#3, #4) are active.
+    EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid4));
+    EXPECT_CALL(cid_manager_visitor_,
+                SendNewConnectionId(ExpectedNewConnectionIdFrame(cid4, 4u, 3u)))
+        .WillOnce(Return(true));
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = 2u;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+  }
+
+  {
+    // Peer retires CID #3;
+    // Sends CID #5 and asks peer to retire CIDs prior to #4.
+    // Outcome: (#4, #5) are active.
+    EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid5));
+    EXPECT_CALL(cid_manager_visitor_,
+                SendNewConnectionId(ExpectedNewConnectionIdFrame(cid5, 5u, 4u)))
+        .WillOnce(Return(true));
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = 3u;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+  }
+}
+
+TEST_F(QuicSelfIssuedConnectionIdManagerTest,
+       RetireSelfIssuedConnectionIdOutOfOrder) {
+  QuicConnectionId cid0 = initial_connection_id_;
+  QuicConnectionId cid1 = cid_manager_.GenerateNewConnectionId(cid0);
+  QuicConnectionId cid2 = cid_manager_.GenerateNewConnectionId(cid1);
+  QuicConnectionId cid3 = cid_manager_.GenerateNewConnectionId(cid2);
+  QuicConnectionId cid4 = cid_manager_.GenerateNewConnectionId(cid3);
+
+  // Sends CID #1 to peer.
+  EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid1));
+  EXPECT_CALL(cid_manager_visitor_,
+              SendNewConnectionId(ExpectedNewConnectionIdFrame(cid1, 1u, 0u)))
+      .WillOnce(Return(true));
+  cid_manager_.MaybeSendNewConnectionIds();
+
+  {
+    // Peer retires CID #1;
+    // Sends CID #2 and asks peer to retire CIDs prior to #0.
+    // Outcome: (#0, #2) are active.
+    EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid2));
+    EXPECT_CALL(cid_manager_visitor_,
+                SendNewConnectionId(ExpectedNewConnectionIdFrame(cid2, 2u, 0u)))
+        .WillOnce(Return(true));
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = 1u;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+  }
+
+  {
+    // Peer retires CID #1 again. This is a no-op.
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = 1u;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+  }
+
+  {
+    // Peer retires CID #0;
+    // Sends CID #3 and asks peer to retire CIDs prior to #2.
+    // Outcome: (#2, #3) are active.
+    EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid3));
+    EXPECT_CALL(cid_manager_visitor_,
+                SendNewConnectionId(ExpectedNewConnectionIdFrame(cid3, 3u, 2u)))
+        .WillOnce(Return(true));
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = 0u;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+  }
+
+  {
+    // Peer retires CID #3;
+    // Sends CID #4 and asks peer to retire CIDs prior to #2.
+    // Outcome: (#2, #4) are active.
+    EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid4));
+    EXPECT_CALL(cid_manager_visitor_,
+                SendNewConnectionId(ExpectedNewConnectionIdFrame(cid4, 4u, 2u)))
+        .WillOnce(Return(true));
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = 3u;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+  }
+
+  {
+    // Peer retires CID #0 again. This is a no-op.
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = 0u;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+  }
+}
+
+TEST_F(QuicSelfIssuedConnectionIdManagerTest,
+       ScheduleConnectionIdRetirementOneAtATime) {
+  QuicConnectionId cid0 = initial_connection_id_;
+  QuicConnectionId cid1 = cid_manager_.GenerateNewConnectionId(cid0);
+  QuicConnectionId cid2 = cid_manager_.GenerateNewConnectionId(cid1);
+  QuicConnectionId cid3 = cid_manager_.GenerateNewConnectionId(cid2);
+  EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(_)).Times(3);
+  EXPECT_CALL(cid_manager_visitor_, SendNewConnectionId(_))
+      .Times(3)
+      .WillRepeatedly(Return(true));
+  QuicTime::Delta connection_id_expire_timeout = 3 * pto_delay_;
+  QuicRetireConnectionIdFrame retire_cid_frame;
+
+  // CID #1 is sent to peer.
+  cid_manager_.MaybeSendNewConnectionIds();
+
+  // CID #0's retirement is scheduled and CID #2 is sent to peer.
+  retire_cid_frame.sequence_number = 0u;
+  ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                  retire_cid_frame, pto_delay_, &error_details_),
+              IsQuicNoError());
+  // While CID #0's retirement is scheduled, it is not retired yet.
+  EXPECT_THAT(cid_manager_.GetUnretiredConnectionIds(),
+              ElementsAre(cid0, cid1, cid2));
+  EXPECT_TRUE(retire_self_issued_cid_alarm_->IsSet());
+  EXPECT_EQ(retire_self_issued_cid_alarm_->deadline(),
+            clock_.ApproximateNow() + connection_id_expire_timeout);
+
+  // CID #0 is actually retired.
+  EXPECT_CALL(cid_manager_visitor_, OnSelfIssuedConnectionIdRetired(cid0));
+  clock_.AdvanceTime(connection_id_expire_timeout);
+  alarm_factory_.FireAlarm(retire_self_issued_cid_alarm_);
+  EXPECT_THAT(cid_manager_.GetUnretiredConnectionIds(),
+              ElementsAre(cid1, cid2));
+  EXPECT_FALSE(retire_self_issued_cid_alarm_->IsSet());
+
+  // CID #1's retirement is scheduled and CID #3 is sent to peer.
+  retire_cid_frame.sequence_number = 1u;
+  ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                  retire_cid_frame, pto_delay_, &error_details_),
+              IsQuicNoError());
+  // While CID #1's retirement is scheduled, it is not retired yet.
+  EXPECT_THAT(cid_manager_.GetUnretiredConnectionIds(),
+              ElementsAre(cid1, cid2, cid3));
+  EXPECT_TRUE(retire_self_issued_cid_alarm_->IsSet());
+  EXPECT_EQ(retire_self_issued_cid_alarm_->deadline(),
+            clock_.ApproximateNow() + connection_id_expire_timeout);
+
+  // CID #1 is actually retired.
+  EXPECT_CALL(cid_manager_visitor_, OnSelfIssuedConnectionIdRetired(cid1));
+  clock_.AdvanceTime(connection_id_expire_timeout);
+  alarm_factory_.FireAlarm(retire_self_issued_cid_alarm_);
+  EXPECT_THAT(cid_manager_.GetUnretiredConnectionIds(),
+              ElementsAre(cid2, cid3));
+  EXPECT_FALSE(retire_self_issued_cid_alarm_->IsSet());
+}
+
+TEST_F(QuicSelfIssuedConnectionIdManagerTest,
+       ScheduleMultipleConnectionIdRetirement) {
+  QuicConnectionId cid0 = initial_connection_id_;
+  QuicConnectionId cid1 = cid_manager_.GenerateNewConnectionId(cid0);
+  QuicConnectionId cid2 = cid_manager_.GenerateNewConnectionId(cid1);
+  QuicConnectionId cid3 = cid_manager_.GenerateNewConnectionId(cid2);
+  EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(_)).Times(3);
+  EXPECT_CALL(cid_manager_visitor_, SendNewConnectionId(_))
+      .Times(3)
+      .WillRepeatedly(Return(true));
+  QuicTime::Delta connection_id_expire_timeout = 3 * pto_delay_;
+  QuicRetireConnectionIdFrame retire_cid_frame;
+
+  // CID #1 is sent to peer.
+  cid_manager_.MaybeSendNewConnectionIds();
+
+  // CID #0's retirement is scheduled and CID #2 is sent to peer.
+  retire_cid_frame.sequence_number = 0u;
+  ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                  retire_cid_frame, pto_delay_, &error_details_),
+              IsQuicNoError());
+
+  clock_.AdvanceTime(connection_id_expire_timeout * 0.25);
+
+  // CID #1's retirement is scheduled and CID #3 is sent to peer.
+  retire_cid_frame.sequence_number = 1u;
+  ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                  retire_cid_frame, pto_delay_, &error_details_),
+              IsQuicNoError());
+
+  // While CID #0, #1s retirement is scheduled, they are not retired yet.
+  EXPECT_THAT(cid_manager_.GetUnretiredConnectionIds(),
+              ElementsAre(cid0, cid1, cid2, cid3));
+  EXPECT_TRUE(retire_self_issued_cid_alarm_->IsSet());
+  EXPECT_EQ(retire_self_issued_cid_alarm_->deadline(),
+            clock_.ApproximateNow() + connection_id_expire_timeout * 0.75);
+
+  // CID #0 is actually retired.
+  EXPECT_CALL(cid_manager_visitor_, OnSelfIssuedConnectionIdRetired(cid0));
+  clock_.AdvanceTime(connection_id_expire_timeout * 0.75);
+  alarm_factory_.FireAlarm(retire_self_issued_cid_alarm_);
+  EXPECT_THAT(cid_manager_.GetUnretiredConnectionIds(),
+              ElementsAre(cid1, cid2, cid3));
+  EXPECT_TRUE(retire_self_issued_cid_alarm_->IsSet());
+  EXPECT_EQ(retire_self_issued_cid_alarm_->deadline(),
+            clock_.ApproximateNow() + connection_id_expire_timeout * 0.25);
+
+  // CID #1 is actually retired.
+  EXPECT_CALL(cid_manager_visitor_, OnSelfIssuedConnectionIdRetired(cid1));
+  clock_.AdvanceTime(connection_id_expire_timeout * 0.25);
+  alarm_factory_.FireAlarm(retire_self_issued_cid_alarm_);
+  EXPECT_THAT(cid_manager_.GetUnretiredConnectionIds(),
+              ElementsAre(cid2, cid3));
+  EXPECT_FALSE(retire_self_issued_cid_alarm_->IsSet());
+}
+
+TEST_F(QuicSelfIssuedConnectionIdManagerTest,
+       AllExpiredConnectionIdsAreRetiredInOneBatch) {
+  QuicConnectionId cid0 = initial_connection_id_;
+  QuicConnectionId cid1 = cid_manager_.GenerateNewConnectionId(cid0);
+  QuicConnectionId cid2 = cid_manager_.GenerateNewConnectionId(cid1);
+  QuicConnectionId cid3 = cid_manager_.GenerateNewConnectionId(cid2);
+  EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(_)).Times(3);
+  EXPECT_CALL(cid_manager_visitor_, SendNewConnectionId(_))
+      .Times(3)
+      .WillRepeatedly(Return(true));
+  QuicTime::Delta connection_id_expire_timeout = 3 * pto_delay_;
+  QuicRetireConnectionIdFrame retire_cid_frame;
+
+  // CID #1 is sent to peer.
+  cid_manager_.MaybeSendNewConnectionIds();
+
+  // CID #0's retirement is scheduled and CID #2 is sent to peer.
+  retire_cid_frame.sequence_number = 0u;
+  cid_manager_.OnRetireConnectionIdFrame(retire_cid_frame, pto_delay_,
+                                         &error_details_);
+
+  clock_.AdvanceTime(connection_id_expire_timeout * 0.1);
+
+  // CID #1's retirement is scheduled and CID #3 is sent to peer.
+  retire_cid_frame.sequence_number = 1u;
+  cid_manager_.OnRetireConnectionIdFrame(retire_cid_frame, pto_delay_,
+                                         &error_details_);
+
+  {
+    // CID #0 & #1 are retired in a single alarm fire.
+    clock_.AdvanceTime(connection_id_expire_timeout);
+    testing::InSequence s;
+    EXPECT_CALL(cid_manager_visitor_, OnSelfIssuedConnectionIdRetired(cid0));
+    EXPECT_CALL(cid_manager_visitor_, OnSelfIssuedConnectionIdRetired(cid1));
+    alarm_factory_.FireAlarm(retire_self_issued_cid_alarm_);
+    EXPECT_THAT(cid_manager_.GetUnretiredConnectionIds(),
+                ElementsAre(cid2, cid3));
+    EXPECT_FALSE(retire_self_issued_cid_alarm_->IsSet());
+  }
+}
+
+TEST_F(QuicSelfIssuedConnectionIdManagerTest,
+       ErrorWhenRetireConnectionIdNeverIssued) {
+  QuicConnectionId cid0 = initial_connection_id_;
+  QuicConnectionId cid1 = cid_manager_.GenerateNewConnectionId(cid0);
+
+  // CID #1 is sent to peer.
+  EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(_)).Times(1);
+  EXPECT_CALL(cid_manager_visitor_, SendNewConnectionId(_))
+      .WillOnce(Return(true));
+  cid_manager_.MaybeSendNewConnectionIds();
+
+  // CID #2 is never issued.
+  QuicRetireConnectionIdFrame retire_cid_frame;
+  retire_cid_frame.sequence_number = 2u;
+  ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                  retire_cid_frame, pto_delay_, &error_details_),
+              IsError(IETF_QUIC_PROTOCOL_VIOLATION));
+}
+
+TEST_F(QuicSelfIssuedConnectionIdManagerTest,
+       ErrorWhenTooManyConnectionIdWaitingToBeRetired) {
+  // CID #0 & #1 are issued.
+  EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(_));
+  EXPECT_CALL(cid_manager_visitor_, SendNewConnectionId(_))
+      .WillOnce(Return(true));
+  cid_manager_.MaybeSendNewConnectionIds();
+
+  // Add 8 connection IDs to the to-be-retired list.
+  QuicConnectionId last_connection_id =
+      cid_manager_.GenerateNewConnectionId(initial_connection_id_);
+  for (int i = 0; i < 8; ++i) {
+    EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(_));
+    EXPECT_CALL(cid_manager_visitor_, SendNewConnectionId(_));
+    QuicRetireConnectionIdFrame retire_cid_frame;
+    retire_cid_frame.sequence_number = i;
+    ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                    retire_cid_frame, pto_delay_, &error_details_),
+                IsQuicNoError());
+    last_connection_id =
+        cid_manager_.GenerateNewConnectionId(last_connection_id);
+  }
+  QuicRetireConnectionIdFrame retire_cid_frame;
+  retire_cid_frame.sequence_number = 8u;
+  // This would have push the number of to-be-retired connection IDs over its
+  // limit.
+  ASSERT_THAT(cid_manager_.OnRetireConnectionIdFrame(
+                  retire_cid_frame, pto_delay_, &error_details_),
+              IsError(QUIC_TOO_MANY_CONNECTION_ID_WAITING_TO_RETIRE));
+}
+
+TEST_F(QuicSelfIssuedConnectionIdManagerTest,
+       DoNotIssueConnectionIdVoluntarilyIfOneHasIssuedForPerferredAddress) {
+  QuicConnectionId cid0 = initial_connection_id_;
+  QuicConnectionId cid1 = cid_manager_.GenerateNewConnectionId(cid0);
+  EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(cid1));
+  ASSERT_THAT(cid_manager_.IssueNewConnectionIdForPreferredAddress(),
+              ExpectedNewConnectionIdFrame(cid1, 1u, 0u));
+  EXPECT_THAT(cid_manager_.GetUnretiredConnectionIds(),
+              ElementsAre(cid0, cid1));
+
+  EXPECT_CALL(cid_manager_visitor_, OnNewConnectionIdIssued(_)).Times(0);
+  EXPECT_CALL(cid_manager_visitor_, SendNewConnectionId(_)).Times(0);
+  cid_manager_.MaybeSendNewConnectionIds();
+}
+
 }  // namespace
 }  // namespace quic
diff --git a/quic/core/quic_constants.h b/quic/core/quic_constants.h
index 619ed32..2de827f 100644
--- a/quic/core/quic_constants.h
+++ b/quic/core/quic_constants.h
@@ -279,6 +279,9 @@
 // The default alarm granularity assumed by QUIC code.
 const QuicTime::Delta kAlarmGranularity = QuicTime::Delta::FromMilliseconds(1);
 
+// Maximum number of unretired connection IDs a connection can have.
+const size_t kMaxNumConnectonIdsInUse = 10u;
+
 // Packet number of first sending packet of a connection. Please note, this
 // cannot be used as first received packet because peer can choose its starting
 // packet number.
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index 9d7d610..cc050dd 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -168,6 +168,7 @@
     RETURN_STRING_LITERAL(QUIC_INVALID_NEW_CONNECTION_ID_DATA);
     RETURN_STRING_LITERAL(QUIC_INVALID_RETIRE_CONNECTION_ID_DATA);
     RETURN_STRING_LITERAL(QUIC_CONNECTION_ID_LIMIT_ERROR);
+    RETURN_STRING_LITERAL(QUIC_TOO_MANY_CONNECTION_ID_WAITING_TO_RETIRE);
     RETURN_STRING_LITERAL(QUIC_INVALID_STOP_SENDING_FRAME_DATA);
     RETURN_STRING_LITERAL(QUIC_INVALID_PATH_CHALLENGE_DATA);
     RETURN_STRING_LITERAL(QUIC_INVALID_PATH_RESPONSE_DATA);
@@ -761,6 +762,8 @@
                                           SSL_AD_CERTIFICATE_REQUIRED)};
     case QUIC_CONNECTION_ID_LIMIT_ERROR:
       return {true, static_cast<uint64_t>(CONNECTION_ID_LIMIT_ERROR)};
+    case QUIC_TOO_MANY_CONNECTION_ID_WAITING_TO_RETIRE:
+      return {true, static_cast<uint64_t>(INTERNAL_ERROR)};
     case QUIC_LAST_ERROR:
       return {false, static_cast<uint64_t>(QUIC_LAST_ERROR)};
   }
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 737c1ef..3d0e3f1 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -350,6 +350,8 @@
   QUIC_INVALID_NEW_CONNECTION_ID_DATA = 107,
   // More connection IDs than allowed are issued.
   QUIC_CONNECTION_ID_LIMIT_ERROR = 203,
+  // The peer retires connection IDs too quickly.
+  QUIC_TOO_MANY_CONNECTION_ID_WAITING_TO_RETIRE = 204,
   // Received a MAX STREAM DATA frame with errors.
   QUIC_INVALID_STOP_SENDING_FRAME_DATA = 108,
   // Error deframing PATH CHALLENGE or PATH RESPONSE frames.
@@ -586,7 +588,7 @@
   QUIC_TLS_CERTIFICATE_REQUIRED = 202,
 
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 204,
+  QUIC_LAST_ERROR = 205,
 };
 // QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
 // or a varint62 when doing IETF QUIC. Ensure that its value does not exceed