Add PeerIssuedConnectionIdManager that handles NewConnectionId Frame.
PiperOrigin-RevId: 353019787
Change-Id: I6b9e38c357165759503e17be03599d696b4e44f5
diff --git a/quic/core/quic_connection_id_manager.cc b/quic/core/quic_connection_id_manager.cc
new file mode 100644
index 0000000..d4380b7
--- /dev/null
+++ b/quic/core/quic_connection_id_manager.cc
@@ -0,0 +1,207 @@
+// Copyright (c) 2012 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 "quic/core/quic_connection_id_manager.h"
+#include <cstdio>
+
+#include "quic/core/quic_clock.h"
+#include "quic/core/quic_connection_id.h"
+#include "quic/core/quic_error_codes.h"
+#include "quic/platform/api/quic_uint128.h"
+
+namespace quic {
+
+QuicConnectionIdData::QuicConnectionIdData(
+ const QuicConnectionId& connection_id,
+ uint64_t sequence_number,
+ QuicUint128 stateless_reset_token)
+ : connection_id(connection_id),
+ sequence_number(sequence_number),
+ stateless_reset_token(stateless_reset_token) {}
+
+namespace {
+
+class RetirePeerIssuedConnectionIdAlarm : public QuicAlarm::Delegate {
+ public:
+ explicit RetirePeerIssuedConnectionIdAlarm(
+ QuicConnectionIdManagerVisitorInterface* visitor)
+ : visitor_(visitor) {}
+ RetirePeerIssuedConnectionIdAlarm(const RetirePeerIssuedConnectionIdAlarm&) =
+ delete;
+ RetirePeerIssuedConnectionIdAlarm& operator=(
+ const RetirePeerIssuedConnectionIdAlarm&) = delete;
+
+ void OnAlarm() override { visitor_->OnPeerIssuedConnectionIdRetired(); }
+
+ private:
+ QuicConnectionIdManagerVisitorInterface* visitor_;
+};
+
+std::vector<QuicConnectionIdData>::const_iterator FindConnectionIdData(
+ const std::vector<QuicConnectionIdData>& cid_data_vector,
+ const QuicConnectionId& cid) {
+ return std::find_if(cid_data_vector.begin(), cid_data_vector.end(),
+ [&cid](const QuicConnectionIdData& cid_data) {
+ return cid == cid_data.connection_id;
+ });
+}
+
+} // namespace
+
+QuicPeerIssuedConnectionIdManager::QuicPeerIssuedConnectionIdManager(
+ size_t active_connection_id_limit,
+ const QuicConnectionId& initial_peer_issued_connection_id,
+ const QuicClock* clock,
+ QuicAlarmFactory* alarm_factory,
+ QuicConnectionIdManagerVisitorInterface* visitor)
+ : active_connection_id_limit_(active_connection_id_limit),
+ clock_(clock),
+ retire_connection_id_alarm_(alarm_factory->CreateAlarm(
+ new RetirePeerIssuedConnectionIdAlarm(visitor))) {
+ DCHECK(!initial_peer_issued_connection_id.IsEmpty());
+ active_connection_id_data_.emplace_back(initial_peer_issued_connection_id,
+ /*sequence_number=*/0u,
+ QuicUint128());
+ recent_new_connection_id_sequence_numbers_.Add(0u, 1u);
+}
+
+QuicPeerIssuedConnectionIdManager::~QuicPeerIssuedConnectionIdManager() {
+ retire_connection_id_alarm_->Cancel();
+}
+
+bool QuicPeerIssuedConnectionIdManager::IsConnectionIdNew(
+ const QuicNewConnectionIdFrame& frame) {
+ auto is_old_connection_id = [&frame](const QuicConnectionIdData& cid_data) {
+ return cid_data.connection_id == frame.connection_id;
+ };
+ if (std::any_of(active_connection_id_data_.begin(),
+ active_connection_id_data_.end(), is_old_connection_id)) {
+ return false;
+ }
+ if (std::any_of(unused_connection_id_data_.begin(),
+ unused_connection_id_data_.end(), is_old_connection_id)) {
+ return false;
+ }
+ if (std::any_of(to_be_retired_connection_id_data_.begin(),
+ to_be_retired_connection_id_data_.end(),
+ is_old_connection_id)) {
+ return false;
+ }
+ return true;
+}
+
+void QuicPeerIssuedConnectionIdManager::PrepareToRetireConnectionIdPriorTo(
+ uint64_t retire_prior_to,
+ std::vector<QuicConnectionIdData>* cid_data_vector) {
+ auto it2 = cid_data_vector->begin();
+ for (auto it = cid_data_vector->begin(); it != cid_data_vector->end(); ++it) {
+ if (it->sequence_number >= retire_prior_to) {
+ *it2++ = *it;
+ } else {
+ to_be_retired_connection_id_data_.push_back(*it);
+ if (!retire_connection_id_alarm_->IsSet()) {
+ retire_connection_id_alarm_->Set(clock_->ApproximateNow());
+ }
+ }
+ }
+ cid_data_vector->erase(it2, cid_data_vector->end());
+}
+
+QuicErrorCode QuicPeerIssuedConnectionIdManager::OnNewConnectionIdFrame(
+ const QuicNewConnectionIdFrame& frame,
+ std::string* error_detail) {
+ if (recent_new_connection_id_sequence_numbers_.Contains(
+ frame.sequence_number)) {
+ // This frame has a recently seen sequence number. Ignore.
+ return QUIC_NO_ERROR;
+ }
+ if (!IsConnectionIdNew(frame)) {
+ *error_detail =
+ "Received a NEW_CONNECTION_ID frame that reuses a previously seen Id.";
+ return IETF_QUIC_PROTOCOL_VIOLATION;
+ }
+
+ recent_new_connection_id_sequence_numbers_.AddOptimizedForAppend(
+ frame.sequence_number, frame.sequence_number + 1);
+
+ if (recent_new_connection_id_sequence_numbers_.Size() >
+ kMaxNumConnectionIdSequenceNumberIntervals) {
+ *error_detail =
+ "Too many disjoint connection Id sequence number intervals.";
+ return IETF_QUIC_PROTOCOL_VIOLATION;
+ }
+
+ // QuicFramer::ProcessNewConnectionIdFrame guarantees that
+ // frame.sequence_number >= frame.retire_prior_to, and hence there is no need
+ // to check that.
+ if (frame.sequence_number < max_new_connection_id_frame_retire_prior_to_) {
+ // Later frames have asked for retirement of the current frame.
+ to_be_retired_connection_id_data_.emplace_back(frame.connection_id,
+ frame.sequence_number,
+ frame.stateless_reset_token);
+ if (!retire_connection_id_alarm_->IsSet()) {
+ retire_connection_id_alarm_->Set(clock_->ApproximateNow());
+ }
+ return QUIC_NO_ERROR;
+ }
+ if (frame.retire_prior_to > max_new_connection_id_frame_retire_prior_to_) {
+ max_new_connection_id_frame_retire_prior_to_ = frame.retire_prior_to;
+ PrepareToRetireConnectionIdPriorTo(frame.retire_prior_to,
+ &active_connection_id_data_);
+ PrepareToRetireConnectionIdPriorTo(frame.retire_prior_to,
+ &unused_connection_id_data_);
+ }
+
+ if (active_connection_id_data_.size() + unused_connection_id_data_.size() >=
+ active_connection_id_limit_) {
+ *error_detail = "Peer provides more connection IDs than the limit.";
+ return QUIC_CONNECTION_ID_LIMIT_ERROR;
+ }
+
+ unused_connection_id_data_.emplace_back(
+ frame.connection_id, frame.sequence_number, frame.stateless_reset_token);
+ return QUIC_NO_ERROR;
+}
+
+const QuicConnectionIdData*
+QuicPeerIssuedConnectionIdManager::ConsumeOneUnusedConnectionId() {
+ if (unused_connection_id_data_.empty()) {
+ return nullptr;
+ }
+ active_connection_id_data_.push_back(unused_connection_id_data_.back());
+ unused_connection_id_data_.pop_back();
+ return &active_connection_id_data_.back();
+}
+
+void QuicPeerIssuedConnectionIdManager::PrepareToRetireActiveConnectionId(
+ const QuicConnectionId& cid) {
+ auto it = FindConnectionIdData(active_connection_id_data_, cid);
+ if (it == active_connection_id_data_.end()) {
+ // The cid has already been retired.
+ return;
+ }
+ to_be_retired_connection_id_data_.push_back(*it);
+ active_connection_id_data_.erase(it);
+ if (!retire_connection_id_alarm_->IsSet()) {
+ retire_connection_id_alarm_->Set(clock_->ApproximateNow());
+ }
+}
+
+bool QuicPeerIssuedConnectionIdManager::IsConnectionIdActive(
+ const QuicConnectionId& cid) const {
+ return FindConnectionIdData(active_connection_id_data_, cid) !=
+ active_connection_id_data_.end();
+}
+
+std::vector<uint64_t> QuicPeerIssuedConnectionIdManager::
+ ConsumeToBeRetiredConnectionIdSequenceNumbers() {
+ std::vector<uint64_t> result;
+ for (auto const& cid_data : to_be_retired_connection_id_data_) {
+ result.push_back(cid_data.sequence_number);
+ }
+ to_be_retired_connection_id_data_.clear();
+ return result;
+}
+
+} // namespace quic
diff --git a/quic/core/quic_connection_id_manager.h b/quic/core/quic_connection_id_manager.h
new file mode 100644
index 0000000..1977740
--- /dev/null
+++ b/quic/core/quic_connection_id_manager.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 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.
+
+// 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.
+
+#ifndef QUICHE_QUIC_CORE_QUIC_CONNECTION_ID_MANAGER_H_
+#define QUICHE_QUIC_CORE_QUIC_CONNECTION_ID_MANAGER_H_
+
+#include <memory>
+#include "quic/core/frames/quic_new_connection_id_frame.h"
+#include "quic/core/quic_alarm.h"
+#include "quic/core/quic_alarm_factory.h"
+#include "quic/core/quic_connection_id.h"
+#include "quic/core/quic_interval_set.h"
+#include "quic/platform/api/quic_uint128.h"
+#include "net/quic/platform/impl/quic_export_impl.h"
+
+namespace quic {
+
+struct QUIC_EXPORT_PRIVATE QuicConnectionIdData {
+ QuicConnectionIdData(const QuicConnectionId& connection_id,
+ uint64_t sequence_number,
+ QuicUint128 stateless_reset_token);
+
+ QuicConnectionId connection_id;
+ uint64_t sequence_number;
+ QuicUint128 stateless_reset_token;
+};
+
+// Used by QuicSelfIssuedConnectionIdManager
+// and QuicPeerIssuedConnectionIdManager.
+class QUIC_EXPORT_PRIVATE QuicConnectionIdManagerVisitorInterface {
+ public:
+ virtual ~QuicConnectionIdManagerVisitorInterface() = default;
+ virtual void OnPeerIssuedConnectionIdRetired() = 0;
+};
+
+class QUIC_EXPORT_PRIVATE QuicPeerIssuedConnectionIdManager {
+ public:
+ // QuicPeerIssuedConnectionIdManager should be instantiated only when a peer
+ // issued-non empty connection ID is received.
+ QuicPeerIssuedConnectionIdManager(
+ size_t active_connection_id_limit,
+ const QuicConnectionId& initial_peer_issued_connection_id,
+ const QuicClock* clock,
+ QuicAlarmFactory* alarm_factory,
+ QuicConnectionIdManagerVisitorInterface* visitor);
+
+ ~QuicPeerIssuedConnectionIdManager();
+
+ QuicErrorCode OnNewConnectionIdFrame(const QuicNewConnectionIdFrame& frame,
+ std::string* error_detail);
+
+ // Returns the data associated with an unused connection Id. After the call,
+ // the Id is marked as used. Returns nullptr if there is no unused connection
+ // Id.
+ const QuicConnectionIdData* ConsumeOneUnusedConnectionId();
+
+ // Add the connection Id to the pending retirement connection Id list.
+ void PrepareToRetireActiveConnectionId(const QuicConnectionId& cid);
+
+ bool IsConnectionIdActive(const QuicConnectionId& cid) const;
+
+ // Get the sequence numbers of all the connection Ids pending retirement when
+ // it is safe to retires these Ids.
+ std::vector<uint64_t> ConsumeToBeRetiredConnectionIdSequenceNumbers();
+
+ private:
+ friend class QuicConnectionIdManagerPeer;
+
+ bool IsConnectionIdNew(const QuicNewConnectionIdFrame& frame);
+
+ void PrepareToRetireConnectionIdPriorTo(
+ uint64_t retire_prior_to,
+ std::vector<QuicConnectionIdData>* cid_data_vector);
+
+ size_t active_connection_id_limit_;
+ const QuicClock* clock_;
+ std::unique_ptr<QuicAlarm> retire_connection_id_alarm_;
+ std::vector<QuicConnectionIdData> active_connection_id_data_;
+ std::vector<QuicConnectionIdData> unused_connection_id_data_;
+ std::vector<QuicConnectionIdData> to_be_retired_connection_id_data_;
+ // Track sequence numbers of recent NEW_CONNECTION_ID frames received from
+ // the peer.
+ QuicIntervalSet<uint64_t> recent_new_connection_id_sequence_numbers_;
+ uint64_t max_new_connection_id_frame_retire_prior_to_ = 0u;
+};
+
+} // 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
new file mode 100644
index 0000000..47d3dea
--- /dev/null
+++ b/quic/core/quic_connection_id_manager_test.cc
@@ -0,0 +1,505 @@
+// Copyright 2013 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 "quic/core/quic_connection_id_manager.h"
+#include <cstddef>
+
+#include "quic/core/quic_connection_id.h"
+#include "quic/core/quic_error_codes.h"
+#include "quic/platform/api/quic_test.h"
+#include "quic/test_tools/mock_clock.h"
+#include "quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+
+class QuicConnectionIdManagerPeer {
+ public:
+ static QuicAlarm* GetRetirePeerIssuedConnectionIdAlarm(
+ QuicPeerIssuedConnectionIdManager* manager) {
+ return manager->retire_connection_id_alarm_.get();
+ }
+};
+
+namespace {
+
+using ::quic::test::IsError;
+using ::quic::test::IsQuicNoError;
+using ::quic::test::TestConnectionId;
+using ::testing::ElementsAre;
+using ::testing::IsNull;
+
+class TestPeerIssuedConnectionIdManagerVisitor
+ : public QuicConnectionIdManagerVisitorInterface {
+ public:
+ void SetPeerIssuedConnectionIdManager(
+ QuicPeerIssuedConnectionIdManager* peer_issued_connection_id_manager) {
+ peer_issued_connection_id_manager_ = peer_issued_connection_id_manager;
+ }
+
+ void OnPeerIssuedConnectionIdRetired() override {
+ // Replace current connection Id if it has been retired.
+ if (!peer_issued_connection_id_manager_->IsConnectionIdActive(
+ current_peer_issued_connection_id_)) {
+ current_peer_issued_connection_id_ =
+ peer_issued_connection_id_manager_->ConsumeOneUnusedConnectionId()
+ ->connection_id;
+ }
+ // Retire all the to-be-retired connection Ids.
+ most_recent_retired_connection_id_sequence_numbers_ =
+ peer_issued_connection_id_manager_
+ ->ConsumeToBeRetiredConnectionIdSequenceNumbers();
+ }
+
+ const std::vector<uint64_t>&
+ most_recent_retired_connection_id_sequence_numbers() {
+ return most_recent_retired_connection_id_sequence_numbers_;
+ }
+
+ void SetCurrentPeerConnectionId(QuicConnectionId cid) {
+ current_peer_issued_connection_id_ = cid;
+ }
+
+ const QuicConnectionId& GetCurrentPeerConnectionId() {
+ return current_peer_issued_connection_id_;
+ }
+
+ private:
+ QuicPeerIssuedConnectionIdManager* peer_issued_connection_id_manager_ =
+ nullptr;
+ QuicConnectionId current_peer_issued_connection_id_;
+ std::vector<uint64_t> most_recent_retired_connection_id_sequence_numbers_;
+};
+
+class QuicPeerIssuedConnectionIdManagerTest : public QuicTest {
+ public:
+ QuicPeerIssuedConnectionIdManagerTest()
+ : peer_issued_cid_manager_(/*active_connection_id_limit=*/2,
+ 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);
+ retire_peer_issued_cid_alarm_ =
+ QuicConnectionIdManagerPeer::GetRetirePeerIssuedConnectionIdAlarm(
+ &peer_issued_cid_manager_);
+ }
+
+ protected:
+ MockClock clock_;
+ test::MockAlarmFactory alarm_factory_;
+ TestPeerIssuedConnectionIdManagerVisitor cid_manager_visitor_;
+ QuicConnectionId initial_connection_id = TestConnectionId(0);
+ QuicPeerIssuedConnectionIdManager peer_issued_cid_manager_;
+ QuicAlarm* retire_peer_issued_cid_alarm_ = nullptr;
+ std::string error_details_;
+};
+
+TEST_F(QuicPeerIssuedConnectionIdManagerTest,
+ ConnectionIdSequenceWhenMigrationSucceed) {
+ {
+ // Receives CID #1 from peer.
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1);
+ frame.sequence_number = 1u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+
+ // Start to use CID #1 for alternative path.
+ const QuicConnectionIdData* aternative_connection_id_data =
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
+ ASSERT_THAT(aternative_connection_id_data, testing::NotNull());
+ EXPECT_EQ(aternative_connection_id_data->connection_id,
+ TestConnectionId(1));
+ EXPECT_EQ(aternative_connection_id_data->stateless_reset_token,
+ frame.stateless_reset_token);
+
+ // Connection migration succeed. Prepares to retire CID #0.
+ peer_issued_cid_manager_.PrepareToRetireActiveConnectionId(
+ TestConnectionId(0));
+ cid_manager_visitor_.SetCurrentPeerConnectionId(TestConnectionId(1));
+ ASSERT_TRUE(retire_peer_issued_cid_alarm_->IsSet());
+ alarm_factory_.FireAlarm(retire_peer_issued_cid_alarm_);
+ EXPECT_THAT(cid_manager_visitor_
+ .most_recent_retired_connection_id_sequence_numbers(),
+ ElementsAre(0u));
+ }
+
+ {
+ // Receives CID #2 from peer since CID #0 is retired.
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(2);
+ frame.sequence_number = 2u;
+ frame.retire_prior_to = 1u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ // Start to use CID #2 for alternative path.
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
+ // Connection migration succeed. Prepares to retire CID #1.
+ peer_issued_cid_manager_.PrepareToRetireActiveConnectionId(
+ TestConnectionId(1));
+ cid_manager_visitor_.SetCurrentPeerConnectionId(TestConnectionId(2));
+ ASSERT_TRUE(retire_peer_issued_cid_alarm_->IsSet());
+ alarm_factory_.FireAlarm(retire_peer_issued_cid_alarm_);
+ EXPECT_THAT(cid_manager_visitor_
+ .most_recent_retired_connection_id_sequence_numbers(),
+ ElementsAre(1u));
+ }
+
+ {
+ // Receives CID #3 from peer since CID #1 is retired.
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(3);
+ frame.sequence_number = 3u;
+ frame.retire_prior_to = 2u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ // Start to use CID #3 for alternative path.
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
+ // Connection migration succeed. Prepares to retire CID #2.
+ peer_issued_cid_manager_.PrepareToRetireActiveConnectionId(
+ TestConnectionId(2));
+ cid_manager_visitor_.SetCurrentPeerConnectionId(TestConnectionId(3));
+ ASSERT_TRUE(retire_peer_issued_cid_alarm_->IsSet());
+ alarm_factory_.FireAlarm(retire_peer_issued_cid_alarm_);
+ EXPECT_THAT(cid_manager_visitor_
+ .most_recent_retired_connection_id_sequence_numbers(),
+ ElementsAre(2u));
+ }
+
+ {
+ // Receives CID #4 from peer since CID #2 is retired.
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(4);
+ frame.sequence_number = 4u;
+ frame.retire_prior_to = 3u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ }
+}
+
+TEST_F(QuicPeerIssuedConnectionIdManagerTest,
+ ConnectionIdSequenceWhenMigrationFail) {
+ {
+ // Receives CID #1 from peer.
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1);
+ frame.sequence_number = 1u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ // Start to use CID #1 for alternative path.
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
+ // Connection migration fails. Prepares to retire CID #1.
+ peer_issued_cid_manager_.PrepareToRetireActiveConnectionId(
+ TestConnectionId(1));
+ // Actually retires CID #1.
+ ASSERT_TRUE(retire_peer_issued_cid_alarm_->IsSet());
+ alarm_factory_.FireAlarm(retire_peer_issued_cid_alarm_);
+ EXPECT_THAT(cid_manager_visitor_
+ .most_recent_retired_connection_id_sequence_numbers(),
+ ElementsAre(1u));
+ }
+
+ {
+ // Receives CID #2 from peer since CID #1 is retired.
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(2);
+ frame.sequence_number = 2u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ // Start to use CID #2 for alternative path.
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
+ // Connection migration fails again. Prepares to retire CID #2.
+ peer_issued_cid_manager_.PrepareToRetireActiveConnectionId(
+ TestConnectionId(2));
+ // Actually retires CID #2.
+ ASSERT_TRUE(retire_peer_issued_cid_alarm_->IsSet());
+ alarm_factory_.FireAlarm(retire_peer_issued_cid_alarm_);
+ EXPECT_THAT(cid_manager_visitor_
+ .most_recent_retired_connection_id_sequence_numbers(),
+ ElementsAre(2u));
+ }
+
+ {
+ // Receives CID #3 from peer since CID #2 is retired.
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(3);
+ frame.sequence_number = 3u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ // Start to use CID #3 for alternative path.
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
+ // Connection migration succeed. Prepares to retire CID #0.
+ peer_issued_cid_manager_.PrepareToRetireActiveConnectionId(
+ TestConnectionId(0));
+ // After CID #3 is default (i.e., when there is no pending frame to write
+ // associated with CID #0), #0 can actually be retired.
+ cid_manager_visitor_.SetCurrentPeerConnectionId(TestConnectionId(3));
+ ASSERT_TRUE(retire_peer_issued_cid_alarm_->IsSet());
+ alarm_factory_.FireAlarm(retire_peer_issued_cid_alarm_);
+ EXPECT_THAT(cid_manager_visitor_
+ .most_recent_retired_connection_id_sequence_numbers(),
+ ElementsAre(0u));
+ }
+
+ {
+ // Receives CID #4 from peer since CID #0 is retired.
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(4);
+ frame.sequence_number = 4u;
+ frame.retire_prior_to = 3u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ EXPECT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ EXPECT_FALSE(retire_peer_issued_cid_alarm_->IsSet());
+ }
+}
+
+TEST_F(QuicPeerIssuedConnectionIdManagerTest,
+ ReceivesNewConnectionIdOutOfOrder) {
+ {
+ // Receives new CID #1 that retires prior to #0.
+ // Outcome: (active: #0 unused: #1)
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1);
+ frame.sequence_number = 1u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ // Start to use CID #1 for alternative path.
+ // Outcome: (active: #0 #1 unused: None)
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
+ }
+
+ {
+ // Receives new CID #3 that retires prior to #2.
+ // Outcome: (active: None unused: #3)
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(3);
+ frame.sequence_number = 3u;
+ frame.retire_prior_to = 2u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ }
+
+ {
+ // Receives new CID #2 that retires prior to #1.
+ // Outcome: (active: None unused: #3, #2)
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(2);
+ frame.sequence_number = 2u;
+ frame.retire_prior_to = 1u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ }
+
+ {
+ EXPECT_FALSE(
+ peer_issued_cid_manager_.IsConnectionIdActive(TestConnectionId(0)));
+ EXPECT_FALSE(
+ peer_issued_cid_manager_.IsConnectionIdActive(TestConnectionId(1)));
+ // When there is no frame associated with #0 and #1 to write, replace the
+ // in-use CID with an unused CID (#2) and retires #0 & #1.
+ ASSERT_TRUE(retire_peer_issued_cid_alarm_->IsSet());
+ alarm_factory_.FireAlarm(retire_peer_issued_cid_alarm_);
+ EXPECT_THAT(cid_manager_visitor_
+ .most_recent_retired_connection_id_sequence_numbers(),
+ ElementsAre(0u, 1u));
+ EXPECT_EQ(cid_manager_visitor_.GetCurrentPeerConnectionId(),
+ TestConnectionId(2));
+ // Get another unused CID for path validation.
+ EXPECT_EQ(
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId()->connection_id,
+ TestConnectionId(3));
+ }
+}
+
+TEST_F(QuicPeerIssuedConnectionIdManagerTest,
+ VisitedNewConnectionIdFrameIsIgnored) {
+ // Receives new CID #1 that retires prior to #0.
+ // Outcome: (active: #0 unused: #1)
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1);
+ frame.sequence_number = 1u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ // Start to use CID #1 for alternative path.
+ // Outcome: (active: #0 #1 unused: None)
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
+ // Prepare to retire CID #1 as path validation fails.
+ peer_issued_cid_manager_.PrepareToRetireActiveConnectionId(
+ TestConnectionId(1));
+ // Actually retires CID #1.
+ ASSERT_TRUE(retire_peer_issued_cid_alarm_->IsSet());
+ alarm_factory_.FireAlarm(retire_peer_issued_cid_alarm_);
+ EXPECT_THAT(
+ cid_manager_visitor_.most_recent_retired_connection_id_sequence_numbers(),
+ ElementsAre(1u));
+ // Receives the same frame again. Should be a no-op.
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ EXPECT_THAT(peer_issued_cid_manager_.ConsumeOneUnusedConnectionId(),
+ testing::IsNull());
+}
+
+TEST_F(QuicPeerIssuedConnectionIdManagerTest,
+ ErrorWhenActiveConnectionIdLimitExceeded) {
+ {
+ // Receives new CID #1 that retires prior to #0.
+ // Outcome: (active: #0 unused: #1)
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1);
+ frame.sequence_number = 1u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ }
+
+ {
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(2);
+ frame.sequence_number = 2u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsError(QUIC_CONNECTION_ID_LIMIT_ERROR));
+ }
+}
+
+TEST_F(QuicPeerIssuedConnectionIdManagerTest,
+ ErrorWhenTheSameConnectionIdIsSeenWithDifferentSequenceNumbers) {
+ {
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1);
+ frame.sequence_number = 1u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ }
+
+ {
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1);
+ frame.sequence_number = 2u;
+ frame.retire_prior_to = 1u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(TestConnectionId(2));
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsError(IETF_QUIC_PROTOCOL_VIOLATION));
+ }
+}
+
+TEST_F(QuicPeerIssuedConnectionIdManagerTest,
+ NewConnectionIdFrameWithTheSameSequenceNumberIsIgnored) {
+ {
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1);
+ frame.sequence_number = 1u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ }
+
+ {
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(2);
+ frame.sequence_number = 1u;
+ frame.retire_prior_to = 0u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(TestConnectionId(2));
+ EXPECT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ EXPECT_EQ(
+ peer_issued_cid_manager_.ConsumeOneUnusedConnectionId()->connection_id,
+ TestConnectionId(1));
+ EXPECT_THAT(peer_issued_cid_manager_.ConsumeOneUnusedConnectionId(),
+ IsNull());
+ }
+}
+
+TEST_F(QuicPeerIssuedConnectionIdManagerTest,
+ ErrorWhenThereAreTooManyGapsInIssuedConnectionIdSequenceNumbers) {
+ // Add 20 intervals: [0, 1), [2, 3), ..., [38,39)
+ for (int i = 2; i <= 38; i += 2) {
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(i);
+ frame.sequence_number = i;
+ frame.retire_prior_to = i;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsQuicNoError());
+ }
+
+ // Interval [40, 41) goes over the limit.
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(40);
+ frame.sequence_number = 40u;
+ frame.retire_prior_to = 40u;
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ ASSERT_THAT(
+ peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
+ IsError(IETF_QUIC_PROTOCOL_VIOLATION));
+}
+
+} // namespace
+} // namespace quic
diff --git a/quic/core/quic_constants.h b/quic/core/quic_constants.h
index a2ead36..619ed32 100644
--- a/quic/core/quic_constants.h
+++ b/quic/core/quic_constants.h
@@ -209,6 +209,10 @@
// This will likely have to be tuned.
const QuicPacketCount kMaxPacketGap = 5000;
+// The max number of sequence number intervals that
+// QuicPeerIssuedConnetionIdManager can maintain.
+const size_t kMaxNumConnectionIdSequenceNumberIntervals = 20;
+
// The maximum number of random padding bytes to add.
const QuicByteCount kMaxNumRandomPaddingBytes = 256;
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index 8363dc1..9d7d610 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -167,6 +167,7 @@
RETURN_STRING_LITERAL(QUIC_STREAMS_BLOCKED_DATA);
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_INVALID_STOP_SENDING_FRAME_DATA);
RETURN_STRING_LITERAL(QUIC_INVALID_PATH_CHALLENGE_DATA);
RETURN_STRING_LITERAL(QUIC_INVALID_PATH_RESPONSE_DATA);
@@ -758,7 +759,8 @@
case QUIC_TLS_CERTIFICATE_REQUIRED:
return {true, static_cast<uint64_t>(CRYPTO_ERROR_FIRST +
SSL_AD_CERTIFICATE_REQUIRED)};
-
+ case QUIC_CONNECTION_ID_LIMIT_ERROR:
+ return {true, static_cast<uint64_t>(CONNECTION_ID_LIMIT_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 885e088..737c1ef 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -348,6 +348,8 @@
QUIC_INVALID_STREAM_BLOCKED_DATA = 106,
// NEW CONNECTION ID frame data is malformed.
QUIC_INVALID_NEW_CONNECTION_ID_DATA = 107,
+ // More connection IDs than allowed are issued.
+ QUIC_CONNECTION_ID_LIMIT_ERROR = 203,
// Received a MAX STREAM DATA frame with errors.
QUIC_INVALID_STOP_SENDING_FRAME_DATA = 108,
// Error deframing PATH CHALLENGE or PATH RESPONSE frames.
@@ -584,7 +586,7 @@
QUIC_TLS_CERTIFICATE_REQUIRED = 202,
// No error. Used as bound while iterating.
- QUIC_LAST_ERROR = 203,
+ QUIC_LAST_ERROR = 204,
};
// 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