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