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