| // Copyright (c) 2022 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "quiche/quic/core/quic_ping_manager.h" | |
| #include "quiche/quic/core/quic_one_block_arena.h" | |
| #include "quiche/quic/platform/api/quic_test.h" | |
| #include "quiche/quic/test_tools/quic_test_utils.h" | |
| namespace quic { | |
| namespace test { | |
| class QuicPingManagerPeer { | |
| public: | |
| static QuicAlarm* GetAlarm(QuicPingManager* manager) { | |
| return manager->alarm_.get(); | |
| } | |
| static void SetPerspective(QuicPingManager* manager, | |
| Perspective perspective) { | |
| manager->perspective_ = perspective; | |
| } | |
| }; | |
| namespace { | |
| const bool kShouldKeepAlive = true; | |
| const bool kHasInflightPackets = true; | |
| class MockDelegate : public QuicPingManager::Delegate { | |
| public: | |
| MOCK_METHOD(void, OnKeepAliveTimeout, (), (override)); | |
| MOCK_METHOD(void, OnRetransmittableOnWireTimeout, (), (override)); | |
| }; | |
| class QuicPingManagerTest : public QuicTest { | |
| public: | |
| QuicPingManagerTest() | |
| : manager_(Perspective::IS_CLIENT, &delegate_, &arena_, &alarm_factory_, | |
| /*context=*/nullptr), | |
| alarm_(static_cast<MockAlarmFactory::TestAlarm*>( | |
| QuicPingManagerPeer::GetAlarm(&manager_))) { | |
| clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); | |
| } | |
| protected: | |
| testing::StrictMock<MockDelegate> delegate_; | |
| MockClock clock_; | |
| QuicConnectionArena arena_; | |
| MockAlarmFactory alarm_factory_; | |
| QuicPingManager manager_; | |
| MockAlarmFactory::TestAlarm* alarm_; | |
| }; | |
| TEST_F(QuicPingManagerTest, KeepAliveTimeout) { | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| // Set alarm with in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| // Reset alarm with no in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| // Verify the deadline is set slightly less than 15 seconds in the future, | |
| // because of the 1s alarm granularity. | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs) - | |
| QuicTime::Delta::FromMilliseconds(5), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kPingTimeoutSecs)); | |
| EXPECT_CALL(delegate_, OnKeepAliveTimeout()); | |
| alarm_->Fire(); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| // Reset alarm with in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| // Verify alarm is not armed if !kShouldKeepAlive. | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive, | |
| kHasInflightPackets); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| } | |
| TEST_F(QuicPingManagerTest, CustomizedKeepAliveTimeout) { | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| // Set customized keep-alive timeout. | |
| manager_.set_keep_alive_timeout(QuicTime::Delta::FromSeconds(10)); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| // Set alarm with in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(10), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| // Set alarm with no in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| // The deadline is set slightly less than 10 seconds in the future, because | |
| // of the 1s alarm granularity. | |
| EXPECT_EQ( | |
| QuicTime::Delta::FromSeconds(10) - QuicTime::Delta::FromMilliseconds(5), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10)); | |
| EXPECT_CALL(delegate_, OnKeepAliveTimeout()); | |
| alarm_->Fire(); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| // Reset alarm with in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| // Verify alarm is not armed if !kShouldKeepAlive. | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive, | |
| kHasInflightPackets); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| } | |
| TEST_F(QuicPingManagerTest, RetransmittableOnWireTimeout) { | |
| const QuicTime::Delta kRtransmittableOnWireTimeout = | |
| QuicTime::Delta::FromMilliseconds(50); | |
| manager_.set_initial_retransmittable_on_wire_timeout( | |
| kRtransmittableOnWireTimeout); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| // Set alarm with in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| // Verify alarm is in keep-alive mode. | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| // Set alarm with no in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| // Verify alarm is in retransmittable-on-wire mode. | |
| EXPECT_EQ(kRtransmittableOnWireTimeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(kRtransmittableOnWireTimeout); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| alarm_->Fire(); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| // Reset alarm with in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| // Verify the alarm is in keep-alive mode. | |
| ASSERT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| } | |
| TEST_F(QuicPingManagerTest, RetransmittableOnWireTimeoutExponentiallyBackOff) { | |
| const int kMaxAggressiveRetransmittableOnWireCount = 5; | |
| SetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count, | |
| kMaxAggressiveRetransmittableOnWireCount); | |
| const QuicTime::Delta initial_retransmittable_on_wire_timeout = | |
| QuicTime::Delta::FromMilliseconds(200); | |
| manager_.set_initial_retransmittable_on_wire_timeout( | |
| initial_retransmittable_on_wire_timeout); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| // Verify alarm is in keep-alive mode. | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| // Verify no exponential backoff on the first few retransmittable on wire | |
| // timeouts. | |
| for (int i = 0; i <= kMaxAggressiveRetransmittableOnWireCount; ++i) { | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| // Reset alarm with no in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| // Verify alarm is in retransmittable-on-wire mode. | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| alarm_->Fire(); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| // Reset alarm with in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| } | |
| QuicTime::Delta retransmittable_on_wire_timeout = | |
| initial_retransmittable_on_wire_timeout; | |
| // Verify subsequent retransmittable-on-wire timeout is exponentially backed | |
| // off. | |
| while (retransmittable_on_wire_timeout * 2 < | |
| QuicTime::Delta::FromSeconds(kPingTimeoutSecs)) { | |
| retransmittable_on_wire_timeout = retransmittable_on_wire_timeout * 2; | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(retransmittable_on_wire_timeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(retransmittable_on_wire_timeout); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| alarm_->Fire(); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| // Reset alarm with in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| } | |
| // Verify alarm is in keep-alive mode. | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| // Reset alarm with no in flight packets | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| // Verify alarm is in keep-alive mode because retransmittable-on-wire deadline | |
| // is later. | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs) - | |
| QuicTime::Delta::FromMilliseconds(5), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kPingTimeoutSecs) - | |
| QuicTime::Delta::FromMilliseconds(5)); | |
| EXPECT_CALL(delegate_, OnKeepAliveTimeout()); | |
| alarm_->Fire(); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| } | |
| TEST_F(QuicPingManagerTest, | |
| ResetRetransmitableOnWireTimeoutExponentiallyBackOff) { | |
| const int kMaxAggressiveRetransmittableOnWireCount = 3; | |
| SetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count, | |
| kMaxAggressiveRetransmittableOnWireCount); | |
| const QuicTime::Delta initial_retransmittable_on_wire_timeout = | |
| QuicTime::Delta::FromMilliseconds(200); | |
| manager_.set_initial_retransmittable_on_wire_timeout( | |
| initial_retransmittable_on_wire_timeout); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| // Verify alarm is in keep-alive mode. | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| // Verify alarm is in retransmittable-on-wire mode. | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); | |
| alarm_->Fire(); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| manager_.reset_consecutive_retransmittable_on_wire_count(); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); | |
| alarm_->Fire(); | |
| for (int i = 0; i < kMaxAggressiveRetransmittableOnWireCount; i++) { | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| alarm_->Fire(); | |
| // Reset alarm with in flight packets. | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| // Advance 5ms to receive next packet. | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| } | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout * 2, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(2 * initial_retransmittable_on_wire_timeout); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| alarm_->Fire(); | |
| clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); | |
| manager_.reset_consecutive_retransmittable_on_wire_count(); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| } | |
| TEST_F(QuicPingManagerTest, RetransmittableOnWireLimit) { | |
| static constexpr int kMaxRetransmittableOnWirePingCount = 3; | |
| SetQuicFlag(quic_max_retransmittable_on_wire_ping_count, | |
| kMaxRetransmittableOnWirePingCount); | |
| static constexpr QuicTime::Delta initial_retransmittable_on_wire_timeout = | |
| QuicTime::Delta::FromMilliseconds(200); | |
| static constexpr QuicTime::Delta kShortDelay = | |
| QuicTime::Delta::FromMilliseconds(5); | |
| ASSERT_LT(kShortDelay * 10, initial_retransmittable_on_wire_timeout); | |
| manager_.set_initial_retransmittable_on_wire_timeout( | |
| initial_retransmittable_on_wire_timeout); | |
| clock_.AdvanceTime(kShortDelay); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| for (int i = 0; i <= kMaxRetransmittableOnWirePingCount; i++) { | |
| clock_.AdvanceTime(kShortDelay); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| alarm_->Fire(); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| } | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| // Verify alarm is in keep-alive mode. | |
| EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kPingTimeoutSecs)); | |
| EXPECT_CALL(delegate_, OnKeepAliveTimeout()); | |
| alarm_->Fire(); | |
| EXPECT_FALSE(alarm_->IsSet()); | |
| } | |
| TEST_F(QuicPingManagerTest, MaxRetransmittableOnWireDelayShift) { | |
| QuicPingManagerPeer::SetPerspective(&manager_, Perspective::IS_SERVER); | |
| const int kMaxAggressiveRetransmittableOnWireCount = 3; | |
| SetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count, | |
| kMaxAggressiveRetransmittableOnWireCount); | |
| const QuicTime::Delta initial_retransmittable_on_wire_timeout = | |
| QuicTime::Delta::FromMilliseconds(200); | |
| manager_.set_initial_retransmittable_on_wire_timeout( | |
| initial_retransmittable_on_wire_timeout); | |
| for (int i = 0; i <= kMaxAggressiveRetransmittableOnWireCount; i++) { | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout, | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| alarm_->Fire(); | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| kHasInflightPackets); | |
| } | |
| for (int i = 1; i <= 20; ++i) { | |
| manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, | |
| !kHasInflightPackets); | |
| EXPECT_TRUE(alarm_->IsSet()); | |
| if (i <= 10) { | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout * (1 << i), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| } else { | |
| // Verify shift is capped. | |
| EXPECT_EQ(initial_retransmittable_on_wire_timeout * (1 << 10), | |
| alarm_->deadline() - clock_.ApproximateNow()); | |
| } | |
| clock_.AdvanceTime(alarm_->deadline() - clock_.ApproximateNow()); | |
| EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); | |
| alarm_->Fire(); | |
| } | |
| } | |
| } // namespace | |
| } // namespace test | |
| } // namespace quic |